CngxActionSelect
projects/forms/select/action-select/action-select.component.ts
Import#
import { CngxActionSelect } from '@cngx/forms/select'
Description#
Single-value autocomplete with inline quick-create. Mirrors
CngxTypeahead's input surface; adds the *cngxSelectAction
slot - when the consumer fires its commit() callback the bound
CngxSelectCreateAction materialises a new T through the
commit controller. On success: patches the local buffer, writes
value, announces 'created', optionally closes.
Pessimistic commit only: panel stays open while pending, isPending
flips on the slot context, error surfaces in the commit-error
banner. True optimistic create requires a consumer-supplied
tempValueFactory.
Dismiss-guard: Escape and click-outside are intercepted while
actionDirty() is true (consumer flips it via setDirty(true));
Escape fires cancel() to reset.
Metadata#
Host#
Providers#
CNGX_FORM_FIELD_CONTROL- useExisting
CngxActionSelect CNGX_STATEFUL- useFactory
(): { readonly state: CngxAsyncState} => { const self = inject(CngxActionSelect); return { state: self.commitState }; } CNGX_SELECT_PANEL_HOST- useExisting
CngxActionSelect CNGX_SELECT_PANEL_VIEW_HOST- useExisting
CngxActionSelect
Relationships
Depends on12
Index#
Methods
Inputs
Outputs
Derived State
HostBindings
Inputs#
"top" | "bottom" | "both" | "none"Position of the *cngxSelectAction slot in the panel.
Default 'bottom'.
this.actionConfig.actionPosition, TemplateRef | null nullTemplateRef | null nullWhether a successful create closes the panel. Default true.
Set false for confirmation / multi-step wizard flows.
this.actionConfig.closeOnCreate ?? trueCngxSelectCommitAction | null nullCngxCommitErrorAnnouncePolicyScalar-commit error-announce policy. Default { kind: 'soft' }
for action-select (matches typeahead's free-text flow). Override
to 'verbose' per-instance when destructive create warrants
a louder read.
this.config.commitErrorAnnouncePolicy ?? { kind: 'soft' }, CngxSelectCompareFn cngxSelectDefaultCompare as CngxSelectCompareFn<T>, (value: T) => stringStringNonNullableMobile enterkeyhint. Default 'go' - Enter routes to
quick-create when no AD item is active.
this.config.enterKeyHint ?? 'go', NonNullableMobile inputmode. Defaults from CngxSelectConfig.inputMode.
this.config.inputModeFall back to the raw <input> value when the debounced
searchTerm hasn't caught up. Disable when the consumer owns
its own search pipeline.
this.actionConfig.liveInputFallbackCngxSelectOptionsInput[] as CngxSelectOptionsInput<T>PopoverPlacementPopover placement relative to the trigger. Per-instance input wins
over CngxActionSelectConfig.popoverPlacement.
this.actionConfig.popoverPlacementCngxSelectCreateAction | null Quick-create handler fired by the slot's commit() callback.
Receives the live searchTerm and { label }, resolves to a new
T. null disables the create path - slot still renders, but
commit() becomes a silent no-op.
nullDebounce for the inline search (ms). Default 0 so the slot's
let-term reflects every keystroke; raise for large option lists.
this.config.typeaheadDebounceIntervalnullOutputs#
unknownCngxSelectOptionDefDedicated channel for successful creates. Fires after
selectionChange so consumers can bind (created) without
branching on action === 'create'.
CngxActionSelectChangeAsyncStatusInstance Properties#
unknowncomputed<CngxSelectOptionDef<T> | null>(
() => {
const v = this.value();
if (v === undefined || v === null) {
return null;
}
const map = this.core.valueToOptionMap();
if (map) {
return map.get(v as unknown) ?? null;
}
const eq = this.compareWith();
return this.flatOptions().find((o) => eq(o.value, v)) ?? null;
},
{
equal: (a, b) => {
if (a === b) {
return true;
}
if (a === null || b === null) {
return false;
}
return (this.compareWith() as CngxSelectCompareFn<unknown>)(a.value, b.value);
},
},
)Methods#
patchData(item: CngxSelectOptionDef<T>)Append a pre-built option to the local buffer. Used internally by the create-commit handler; exposed for consumer pre-seed (recent- items, history workflows).
HostBindings#
| Binding | Expression |
|---|---|
[id] | resolvedId() |
[attr.aria-readonly] | ariaReadonly() |
Structural styles shared across the entire CngxSelect family -
imported via styleUrls from every variant (CngxSelect,
CngxMultiSelect, CngxCombobox, CngxTypeahead, CngxTreeSelect,
CngxReorderableMultiSelect, CngxActionSelect,
CngxActionMultiSelect, CngxSelectShell, and the panel components).
All variants run encapsulation: None so the rules below are global;
the .cngx-select__* BEM-style class prefix is the namespace.
State modifiers
.cngx-option--highlightedon an option row - primary-tinted background marking the keyboard-focused row[aria-disabled='true']on an option - opacity 0.5 +cursor: not-allowed.cngx-select__option--pending-cursor: progresswhile a row's commit is in flight.cngx-select__chip-list--reordering- whole-stripcursor: grabbingwhile a drag is active.cngx-select__chip--draggingon a chip - Trello-style lift (shadow + scale + tilt + accent fill).cngx-select__chip--drag-overon a chip - vertical drop bar painted on the leading edge via::before
Variants
Chip-list overflow modes via data-overflow:
- default - flex-wrap chips across multiple rows
data-overflow='scroll-x'- single row + horizontal scrolldata-overflow='truncate'- single row + clipped overflow + the.cngx-select__chip-overflow-badgecount pill
Error surfaces with two layouts:
.cngx-select__error- column-stacked block (message + retry).cngx-select__error--inline- inline banner above the option list.cngx-select__commit-error- per-commit failure banner
Slots
.cngx-select__panel- the floating panel surface (anchor-positioned viaposition-try-fallbacks).cngx-select__group-header- uppercase optgroup label.cngx-select__option/.cngx-select__check- option row + the selected-state checkmark.cngx-select__loading/.cngx-select__empty- inline message rows.cngx-select__skeleton/.cngx-select__skeleton-row- first-load shimmer placeholders (static underprefers-reduced-motion).cngx-select__spinner-wrap/.cngx-select__spinner- first-load spinner ring.cngx-select__loading-bar- first-load top-edge progress bar.cngx-select__refreshing/.cngx-select__refreshing-spinner/.cngx-select__refreshing-dots- subsequent-load indicators.cngx-select__option-spinner/.cngx-select__option-error- per-row commit feedback.cngx-select__chip-list/.cngx-select__chip-overflow-badge- chip strip + overflow count.cngx-select__chip-wrap/.cngx-select__chip-handle- reorderable chip surface + optional drag-handle glyph.cngx-select__error/.cngx-select__error--inline/.cngx-select__error-message/.cngx-select__error-retry/.cngx-select__commit-error- error block, banner, and retry pieces
Inheritance
Tokens are registered top-level in @layer cngx.tokens via @property
so any consumer (cngx.theme layer, app layer) can override without
learning the type.
Panel surface, every primary-tinted glyph, and every danger-toned error surface delegate to the foundation so a dark-mode swap cascades through the floating panel even when anchor positioning detaches it from the trigger's DOM context:
--cngx-select-panel-border->--cngx-color-border--cngx-select-panel-bg->--cngx-color-surface--cngx-select-panel-color->--cngx-color-text--cngx-select-panel-shadow->--cngx-shadow-md--cngx-select-check-color->--cngx-color-primary--cngx-select-spinner-color->--cngx-color-primary--cngx-select-loading-bar-color->--cngx-color-primary--cngx-select-refreshing-color->--cngx-color-primary--cngx-select-option-spinner-color->--cngx-color-primary--cngx-select-option-error-color->--cngx-color-danger--cngx-select-error-color->--cngx-color-danger--cngx-select-chip-remove-hover-color->--cngx-color-danger--cngx-select-chip-drag-bg->--cngx-color-primary--cngx-select-chip-drop-bar-color->--cngx-color-primary--cngx-select-trigger-invalid-border-color->--cngx-color-danger--cngx-select-trigger-invalid-outline-color->--cngx-color-danger
--cngx-select-panel-min-width is intentionally NOT registered via
@property so the anchor-size(width) fallback survives when the
consumer doesn't override it; registering it with any initial-value
would shadow the var() fallback and break anchor positioning.
Dark mode
Three hooks swap alpha-on-black surfaces (panel shadow, option highlight, spinner track, chip-overflow tint, chip remove-hover tint, chip handle muted, chip drag shadow) plus the skeleton shimmer gradient and the option-spinner track:
prefers-color-scheme: dark[data-color-scheme="dark"].darkclass
Foundation-delegated tokens ride the foundation cascade unchanged.
Pair with
@cngx/themes/material/select-theme- Material 3 surface treatment across the entire family
Trigger skin for CngxActionSelect - a "type-to-create" scalar select
that pairs an inline <input> with an action handler (typically a
quick-create button). The host carries .cngx-action-select; the
trigger row matches CngxTypeahead 1:1 so a consumer with a typeahead
theme gets this variant for free. Panel frame, option rows, loading
variants, and error surfaces ride on shared/select-base.css.
State modifiers
:focus-withinon.cngx-action-select__trigger- 2px primary outline + 2px offset.cngx-action-select__input:disabled-cursor: not-allowed+ opacity dim:focus-visibleon.cngx-action-select__clear- 2px primary outline + rounded corner
Slots
.cngx-action-select__root- relative-positioned flex wrapper.cngx-action-select__trigger- the bordered row.cngx-action-select__input- inline text input.cngx-action-select__clear- reset button.cngx-action-select__caret- panel-state glyph
Inheritance
Trigger surface tokens delegate to the foundation so brand swaps on border / surface propagate without per-component theming:
--cngx-action-select-border->--cngx-color-border--cngx-action-select-bg->--cngx-color-surface
Pair with
@cngx/themes/material/select-theme- Material 3 surface treatment shared across the entire select family
Index#
Surface
Layout
State / Highlighted
State / Selected
State / Disabled
State / Loading
State / Refreshing
State / Commit
State / Remove
State / Dragging
State / Error
Surface
*1px solid oklch(0.85 0.01 250)Border shorthand of the dropdown panel. Falls back through
--cngx-color-border. inherits: true so the :root delegating
value reaches the floating panel host (anchor-positioned, may
sit outside the normal DOM flow).
See: [[--cngx-color-border]]
<color>oklch(1 0 0)Background of the dropdown panel. Falls back through
--cngx-color-surface.
See: [[--cngx-color-surface]]
*currentColorText color inside the panel. syntax: '*' + initial-value
currentColor lets the panel inherit text color from its
ancestor by default. Registering as <color> would forbid
currentColor since <color> requires a computationally-
independent value.
*0 4px 12px oklch(0 0 0 / 0.12)Drop-shadow shorthand. Falls back through --cngx-shadow-md.
inherits: true so the :root dark-mode override (deeper shadow
on dark) reaches the floating panel host.
<color>oklch(0 0 0 / 0.5)Color of the placeholder text shown when no value is selected.
<color>oklch(0 0 0 / 0.5)Color of the dropdown caret glyph shared across every variant.
Muted by default to mirror the placeholder token; the Material
bridge maps it to --mat-sys-on-surface-variant. inherits: true
so a trigger-level override reaches the glyph element.
<color>oklch(0 0 0 / 0.5)Color of the clear-button glyph shared across every variant. Tracks
the same muted default as the caret so both trigger affordances read
uniformly. inherits: true so a trigger-level override reaches the
glyph element.
*1px solid oklch(0.85 0.01 250)Border shorthand of the trigger.
<color>oklch(1 0 0)Background of the trigger. Falls back through --cngx-color-surface.
See: [[--cngx-color-surface]]
Layout
*16remMaximum height before vertical scrolling kicks in.
*autoMinimum height of an option row - defaults to auto so dense
rows stay compact.
*1.25emFont-size of the dropdown caret glyph shared across every variant.
*0.25remCorner radius of the reorderable chip wrap container.
*0.25remGap between the chip body and any projected drag handle.
State / Highlighted
<color>oklch(0.66 0.19 50 / 0.1)Background of the keyboard-highlighted option row. Defaults to a
low-alpha tint of --cngx-color-primary so the highlight reads
as brand-accented without competing with selection state.
See: [[--cngx-color-primary]]
State / Selected
<color>oklch(0.66 0.19 50)Color of the selected-option checkmark glyph. Falls back through
--cngx-color-primary.
See: [[--cngx-color-primary]]
State / Disabled
<number>0.5Opacity multiplier applied when the trigger is disabled. Shared family-wide so every variant dims its disabled trigger identically.
State / Loading
*0.125remCorner radius of each skeleton placeholder row.
*2px solid oklch(0 0 0 / 0.15)Track stroke of the first-load spinner ring. inherits: true
so the :root dark-mode override (alpha-on-white on dark)
reaches the spinner inside the floating panel.
<color>oklch(0.66 0.19 50)Indicator stroke of the first-load spinner ring. Falls back to
--cngx-color-primary.
<color>oklch(0.66 0.19 50)Color of the first-load loading bar. Falls back to
--cngx-color-primary.
State / Refreshing
<length>2pxHeight of the subsequent-load refreshing bar.
<color>oklch(0.66 0.19 50)Color of the refreshing bar gradient. Falls back to
--cngx-color-primary.
*0.25remPadding around the refreshing spinner wrapper.
*0.375remPadding around the refreshing dots block.
State / Commit
<color>oklch(0.66 0.19 50)Indicator stroke of the per-row commit spinner.
<color>oklch(0.6 0.18 25)Glyph color of the per-row commit error indicator. Falls back to
--cngx-color-danger.
*0.375rem 0.5remPadding of the commit error banner.
State / Overflow
<color>oklch(0 0 0 / 0.08)Background of the chip overflow badge shown in truncate overflow mode.
<color>oklch(0 0 0 / 0.6)Text color of the chip overflow badge.
State / Remove
*1.25remHit-target diameter of the chip remove button inside a reorderable chip wrap.
<color>oklch(0 0 0 / 0.12)Background tint of the chip remove button on hover.
<color>oklch(0.6 0.18 25)Foreground color of the chip remove button on hover. Falls back
to --cngx-color-danger.
State / Reorder
<color>oklch(0.5 0.01 250)Color of the optional projected drag-handle glyph.
*0.75remFont-size of the optional projected drag-handle glyph.
State / Dragging
*0 8px 20px oklch(0 0 0 / 0.28)Drop-shadow of the chip lifted into the dragging state.
inherits: true so the :root dark-mode override (deeper shadow
on dark) reaches the chip while it's being dragged.
<color>oklch(0.66 0.19 50)Background of the chip lifted into the dragging state. Falls back
to --cngx-color-primary.
See: [[--cngx-color-primary]]
<color>oklch(1 0 0)Text color of the chip lifted into the dragging state.
<number>1.06Scale multiplier of the dragging chip - Trello-style lift.
<angle>-1.5degRotation tilt applied to the dragging chip - Trello-style lift.
<length>3pxWidth of the drop-indicator bar between chips.
<color>oklch(0.66 0.19 50)Color of the drop-indicator bar between chips. Falls back to
--cngx-color-primary.
State / Error
<color>oklch(0.6 0.18 25)Text color of every error surface. Falls back to
--cngx-color-danger.
See: [[--cngx-color-danger]]
*0.375rem 0.5remPadding of the inline error banner shown above the option list.
*1px solid currentColorBorder shorthand of the error retry button.
State / Trigger invalid
<color>oklch(0.6 0.18 25)Border color painted on the trigger wrapper when aria-invalid="true".
Defaults track --cngx-color-danger via the delegating :root entry below.
<length>1pxBorder width painted on the trigger wrapper when aria-invalid="true".
<color>oklch(0.6 0.18 25)Outline color layered on the invalid trigger when focus is inside it.
Defaults track --cngx-color-danger via the delegating :root entry below.
*0 0 0 3px oklch(0.6 0.18 25 / 0.2)Soft halo layered behind the invalid trigger at focus time. Authored as a
box-shadow so it composes with any consumer-supplied focus outline.
Material theme
import { ChangeDetectionStrategy, Component, ViewEncapsulation, signal } from '@angular/core';
import { CngxActionSelect, type CngxSelectOptionDef } from '@cngx/forms/select';
/**
* `CngxActionSelect` rendered against a Material 3 palette via the published
* `@cngx/themes/material/select-theme` bridge.
*
* The stylesheet builds a real M3 theme: `mat.theme` emits the `--mat-sys-*`
* system tokens, then `select.theme($theme)` routes every `--cngx-select-*`
* token onto its Material counterpart, so the trigger, panel surface, and
* selected-option tones inherit the palette with no per-instance overrides.
* `ViewEncapsulation.None` lets the global `html` theme and the
* `:where(cngx-action-select)` bridge rules reach both the trigger and the
* top-layer panel.
*/
@Component({
selector: 'app-root',
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
imports: [CngxActionSelect],
styleUrl: './material-theme.component.scss',
template: `
<div class="demo">
<cngx-action-select
[label]="'Favorite color'"
[options]="colors"
[(value)]="value"
[clearable]="true"
[selectionIndicatorPosition]="'after'"
placeholder="Pick a color…"
/>
<p class="demo__readout">Selected: {{ value() ?? '—' }}</p>
</div>
`,
})
export class MaterialThemeExample {
protected readonly value = signal<string | undefined>(undefined);
protected readonly colors: CngxSelectOptionDef<string>[] = [
{ value: 'red', label: 'Red' },
{ value: 'green', label: 'Green' },
{ value: 'blue', label: 'Blue' },
{ value: 'amber', label: 'Amber' },
{ value: 'violet', label: 'Violet' },
];
}
export { MaterialThemeExample as AppComponent };