CngxActionMultiSelect
projects/forms/select/action-multi-select/action-multi-select.component.ts
Import#
import { CngxActionMultiSelect } from '@cngx/forms/select'
Description#
Multi-value combobox with inline quick-create. Parallels
CngxCombobox and adds the action-slot protocol from
CngxActionSelect: *cngxSelectAction.commit() routes through the
CreateCommitHandler to materialise a new T, patches the
local buffer, appends to values, emits
selectionChange({ action: 'create', added: [newValue] }).
closeOnCreate default false - multi-pick UX keeps the panel open
after each create. Commit-controller supersede guard tears down
previous subscriptions on rapid consecutive creates.
Dismiss-guard: Escape and click-outside intercepted while
actionDirty() is true; Escape fires cancel().
Metadata#
Host#
Providers#
CNGX_FORM_FIELD_CONTROL- useExisting
CngxActionMultiSelect CNGX_STATEFUL- useFactory
(): { readonly state: CngxAsyncState} => { const self = inject(CngxActionMultiSelect); return { state: self.commitState }; } CNGX_SELECT_PANEL_HOST- useExisting
CngxActionMultiSelect CNGX_SELECT_PANEL_VIEW_HOST- useExisting
CngxActionMultiSelect
Relationships
Depends on13
Index#
Methods
Inputs
Outputs
HostBindings
Inputs#
"top" | "bottom" | "both" | "none"Position of *cngxSelectAction in the panel.
this.actionConfig.actionPosition, TemplateRef | null nullTemplateRef | null nullWhether a successful create closes the panel. Default false -
multi-pick UX keeps the panel open. Set true for confirm-to-
create workflows.
this.actionConfig.closeOnCreate ?? falseCngxSelectCommitAction | null nullCngxSelectCompareFn cngxSelectDefaultCompare as CngxSelectCompareFn<T>, NonNullableMobile enterkeyhint. Default 'enter' - Enter appends a chip
without closing the panel.
this.config.enterKeyHint ?? 'enter', NonNullableMobile inputmode. Defaults from CngxSelectConfig.inputMode.
this.config.inputModeFall back to raw <input>.value when debounced searchTerm
hasn't caught up.
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. null disables the create path - slot still
renders, commit() becomes a silent no-op.
nullDebounce for the inline search (ms). Default 0 for action-slot feedback.
this.config.typeaheadDebounceIntervalnullOutputs#
unknownCngxSelectOptionDefDedicated channel for successful creates. Fires after selectionChange.
CngxActionMultiSelectChangeAsyncStatusInstance Properties#
unknowncomputed<CngxSelectOptionDef<T>[]>(
() => {
const vals = this.values();
if (vals.length === 0) {
return [];
}
// Chip strip resolves against the UNFILTERED option merge so
// chips for previously-picked values stay visible when the
// search term hides the matching option from the listbox.
const eq = this.compareWith();
const out: CngxSelectOptionDef<T>[] = [];
const flat = this.core.unfilteredFlatOptions();
for (const v of vals) {
const match = flat.find((o) => eq(o.value, v));
if (match) {
out.push(match);
}
}
return out;
},
{
equal: (a, b) => {
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!Object.is(a[i], b[i])) {
return false;
}
}
return true;
},
},
)Methods#
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 CngxActionMultiSelect - a multi-select variant that
surfaces a per-row action handler (typically a quick-create button).
The host carries .cngx-action-multi-select; structural parts (panel
frame, option rows, chip strip, loading variants, error surfaces) ride
on shared/select-base.css. This file owns the trigger-specific bits:
the role="group" wrapper, the inline <input> as the focusable
element, the clear-all button, and the caret glyph.
State modifiers
:focus-withinon.cngx-action-multi-select__trigger- focus-ring outline + offset[aria-disabled='true']on the trigger - opacity dim +cursor: not-allowed:hoveron.cngx-action-multi-select__clear-all- opacity 1:focus-visibleon the clear-all button - 2px primary outline
Slots
.cngx-action-multi-select__root-display: contentshost shim.cngx-action-multi-select__trigger- the bordered row.cngx-action-multi-select__input- inlinerole="combobox"input.cngx-action-multi-select__trigger-label- text summary slot.cngx-action-multi-select__clear-all- reset-all button.cngx-action-multi-select__caret- panel-state glyph
Inheritance
Trigger tokens delegate up the family chain so consumer themes only need to override at the level they want to brand:
--cngx-action-multi-select-*->--cngx-combobox-*->--cngx-select-*-> hardcoded--cngx-action-multi-select-border->--cngx-color-border--cngx-action-multi-select-focus-outline->--cngx-color-primary
Once registered via @property the inner var() steps are
documentation-only; system tokens (--cngx-color-*) are the
brand-wide override path.
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 / Loading
State / Refreshing
State / Commit
State / Remove
State / Dragging
State / Error
State / Trigger invalid
State / Focus
State / Clear
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.
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.
*0.375remGap between the chip list, input, caret, and clear-all button.
*4remMinimum inline size of the inline input.
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.
<number>0.5Opacity multiplier applied when the trigger is disabled.
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.
State / Focus
*2px solid oklch(0.66 0.19 50)Focus-ring outline shorthand applied on :focus-within. Falls
back to --cngx-color-primary.
See: [[--cngx-color-primary]]
State / Clear
<number>0.6Resting opacity of the clear-all button.
Material theme
import { ChangeDetectionStrategy, Component, ViewEncapsulation, signal } from '@angular/core';
import { CngxActionMultiSelect, type CngxSelectOptionDef } from '@cngx/forms/select';
/**
* `CngxActionMultiSelect` 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, chip strip, 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-multi-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: [CngxActionMultiSelect],
styleUrl: './material-theme.component.scss',
template: `
<div class="demo">
<cngx-action-multi-select
[label]="'Favorite colors'"
[options]="colors"
[(values)]="values"
[clearable]="true"
[chipOverflow]="'truncate'"
placeholder="Pick colors…"
/>
<p class="demo__readout">Selected: {{ values().join(', ') || '—' }}</p>
</div>
`,
})
export class MaterialThemeExample {
protected readonly values = signal<string[]>([]);
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 };