CngxReorderableMultiSelect
projects/forms/select/reorderable-multi-select/reorderable-multi-select.component.ts
Import#
import { CngxReorderableMultiSelect } from '@cngx/forms/select'
Description#
Multi-select with pointer-drag and keyboard reorder of selected values (modifier + arrow, Home/End). Non-reorder surface reuses createSelectCore and CngxMultiSelect's template slots.
Reorder commits bypass ArrayCommitHandler.beginToggle (its
sameArrayContents guard would skip same-membership reorders) and
drive the commit controller directly.
Metadata#
Host#
Providers#
CNGX_FORM_FIELD_CONTROL- useExisting
CngxReorderableMultiSelect CNGX_STATEFUL- useFactory
(): { readonly state: CngxAsyncState} => { const self = inject(CngxReorderableMultiSelect); return { state: self.commitState }; } CNGX_SELECT_PANEL_HOST- useExisting
CngxReorderableMultiSelect CNGX_SELECT_PANEL_VIEW_HOST- useExisting
CngxReorderableMultiSelect
Relationships
Depends on13
Index#
Inputs
Outputs
Derived State
HostBindings
Inputs#
TemplateRef | null nullTemplateRef | null Custom drag-handle glyph. Replaces the default ⋮⋮ grip before
each chip body. Handle span stays aria-hidden="true" - the
semantic move belongs to the chip wrapper's keyboard handler +
the directive.
this.reorderableConfig.dragHandleNonNullableChip-strip overflow. All chips must stay in the DOM for drag-
reorder, so 'truncate' is silently downgraded to 'scroll-x'
via effectiveChipOverflow. 'wrap' and 'scroll-x' pass
through.
this.config.chipOverflow, TemplateRef | null nullCngxSelectCommitAction | null nullCngxSelectCompareFn cngxSelectDefaultCompare as CngxSelectCompareFn<T>, CngxSelectOptionsInput[] as CngxSelectOptionsInput<T>PopoverPlacementPopover placement relative to the trigger. Per-instance input wins
over CngxSelectConfig.popoverPlacement.
this.config.popoverPlacementARIA label on the chip-strip region. Announced when the user tabs in so they understand they've entered a reorderable widget.
this.reorderableConfig.ariaLabelCngxReorderModifierModifier required for keyboard reorder moves. Plain arrow keys
keep roving-focus semantics; only modifier+arrow emits a reorder.
Forwarded to the inner CngxReorder. Default cascades
through provideReorderableSelectConfig(withReorderKeyboardModifier(...))
→ 'alt' (cross-platform; 'ctrl' is OS-bound to Spaces on macOS).
this.reorderableConfig.keyboardModifier, nullOutputs#
unknownCngxReorderableMultiSelectChangeDedicated channel for reorder events. Fires after
selectionChange with the same payload so consumers only caring
about positional changes can bind (reordered) directly.
CngxReorderableMultiSelectChangeAsyncStatusT[]Instance Properties#
unknowncomputed<CngxSelectOptionDef<T>[]>(
() => {
const vals = this.values();
if (vals.length === 0) {
return [];
}
const map = this.core.valueToOptionMap();
if (map) {
const out: CngxSelectOptionDef<T>[] = [];
for (const v of vals) {
const match = map.get(v as unknown);
if (match) {
out.push(match);
}
}
return out;
}
const eq = this.compareWith();
const out: CngxSelectOptionDef<T>[] = [];
const flat = this.flatOptions();
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;
},
},
)SignalRead-only values() view passed to CngxReorder.items. Directive
wants the Signal reference so it reads the freshest order at
drag-start and keyboard-move time without subscribing per tick.
this.values.asReadonly()Methods#
patchData(item: CngxSelectOptionDef<T>)Append a pre-built option to the local buffer. Renders in the next
panel emission and silently drops once the server includes a
matching value. Idempotent under compareWith.
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 CngxReorderableMultiSelect - chip-strip multi-select
with user-driven chip reordering via drag. The host carries
.cngx-reorderable-multi-select; chip-strip layout, drag ghost, drop
indicator, and handle cursor live in shared/select-base.css under
the .cngx-select__chip-* classes so they stay reusable by future
reorder-aware trigger variants (tag-input with user ordering, chip
toolbars). This file owns the trigger frame and placeholder - matched
1:1 with CngxMultiSelect's surface so a consumer with a multi-select
theme gets this variant for free.
State modifiers
:focus-visibleon.cngx-reorderable-multi-select__trigger- focus-ring outline + offset[aria-disabled='true']on the trigger - opacity dim +cursor: not-allowed[aria-invalid='true']on the trigger - danger border + box-shadow glow at focus-within time
Slots
.cngx-reorderable-multi-select__root-display: contentshost shim.cngx-reorderable-multi-select__trigger- the borderedrole="combobox"row (user-select: noneprevents drag-click text selection).cngx-reorderable-multi-select__placeholder- placeholder text when no chips selected.cngx-reorderable-multi-select__clear-all- reset-all button.cngx-reorderable-multi-select__caret- panel-state glyph
Inheritance
Trigger tokens delegate up the family chain so consumer themes can override at the level they want to brand:
--cngx-reorderable-multi-select-*->--cngx-multi-select-*->--cngx-select-*-> hardcoded--cngx-reorderable-multi-select-border->--cngx-color-border--cngx-reorderable-multi-select-focus-outline->--cngx-color-primary- invalid surfaces fall back to
--cngx-select-trigger-invalid-*->--cngx-color-danger
Registered @property values shadow the inner var() steps; 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.5remGap between the chip list, placeholder, caret, and clear button.
*0.375rem 0.5remPadding shorthand of the trigger.
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. 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 { CngxReorderableMultiSelect, type CngxSelectOptionDef } from '@cngx/forms/select';
/**
* `CngxReorderableMultiSelect` 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-reorderable-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: [CngxReorderableMultiSelect],
styleUrl: './material-theme.component.scss',
template: `
<div class="demo">
<cngx-reorderable-multi-select
[label]="'Workflow steps'"
[options]="steps"
[(values)]="values"
placeholder="Pick steps…"
/>
<p class="demo__readout">Order: {{ values().join(', ') || '—' }}</p>
</div>
`,
})
export class MaterialThemeExample {
protected readonly values = signal<string[]>(['build', 'test', 'deploy']);
protected readonly steps: CngxSelectOptionDef<string>[] = [
{ value: 'build', label: 'Build' },
{ value: 'test', label: 'Test' },
{ value: 'deploy', label: 'Deploy' },
{ value: 'lint', label: 'Lint' },
{ value: 'release', label: 'Release' },
];
}
export { MaterialThemeExample as AppComponent };