@wildmason/aegis 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -18,6 +18,51 @@ that 404s.
18
18
 
19
19
  ## [Unreleased]
20
20
 
21
+ ## [2.3.0] - 2026-10-07
22
+
23
+ Minor: `WmSelect` gains a compact chip trigger, a suffix, option descriptions,
24
+ a danger tone and panel sizing. Every addition is opt-in. A select that sets
25
+ none of them renders the same trigger and option markup as 2.2.1, so a
26
+ consumer that styles `.wm-select-trigger`, `.wm-select-value` or
27
+ `.wm-select-option-label` sees no change. One behaviour changes for every
28
+ select: the panel takes the trigger's width when it opens, not on every check
29
+ (see Fixed).
30
+
31
+ ### Added
32
+
33
+ - `WmSelect` `appearance`: `'field'` (the default, as before) or `'chip'`, a
34
+ filled trigger with no border, as wide as its words, for a row of choices
35
+ with no label beside each one. The host takes `.wm-select--chip`.
36
+ - `WmSelect` `suffix`: a muted word after the chosen label, in the trigger
37
+ only, so `suffix="model"` reads "Opus model". It is left out while the
38
+ placeholder shows, so an empty chip never reads "Model model". On a chip the
39
+ suffix turns secondary while the trigger is hovered or open: muted text falls
40
+ under 3:1 on `--wm-bg-hover` in the `wildmason` theme.
41
+ - `SelectOption.description`: a second line under the label, in the panel
42
+ only. The option is named by its label (`aria-labelledby`) and described by
43
+ the line (`aria-describedby`); the ids are unique per select instance. The
44
+ line is secondary text, not muted: muted measures 3.2:1 on `--wm-bg-float`
45
+ in the `wildmason` theme, under the 4.5:1 a 12px line needs.
46
+ - `WmSelect` `tone`: `'neutral'` (the default) or `'danger'`, for a choice that
47
+ is risky to keep. It uses the guarded status recipe: `--wm-color-danger-readable`
48
+ on `--wm-color-danger-bg`, inside `--wm-color-danger-border`. The host takes
49
+ `.wm-select--danger`.
50
+ - `WmSelect` `panelWidth`: `'trigger'`, `'content'` (from the trigger's width
51
+ up to 24rem, where a description wraps) or a width in pixels. Unset, a field
52
+ keeps the trigger's width and a chip fits its content.
53
+ - Types `WmSelectAppearance`, `WmSelectTone` and `WmSelectPanelWidth`.
54
+
55
+ ### Fixed
56
+
57
+ - `WmSelect` measures its trigger once, as the panel opens. It read
58
+ `offsetWidth` on every change-detection pass, so a trigger whose width moved
59
+ between Angular's update pass and its no-changes pass threw NG0100 in
60
+ development. A chip does that while its text renders.
61
+ - The docs app rendered the select page and the composition page from an old
62
+ copy of the component (`ui/src/app/components/select`), without the
63
+ scroll anchoring or the readable accent. Both pages now import the package
64
+ source, and the copy is deleted.
65
+
21
66
  ## [2.2.1] - 2026-10-07
22
67
 
23
68
  Patch: one fix and one theme colour. Nothing is added, renamed or removed.
@@ -244,6 +244,9 @@ class WmAnchoredScrollStrategy {
244
244
  * itself, including closing when its trigger is scrolled out of view.
245
245
  */
246
246
 
247
+ /** The widest a content-sized panel grows before its descriptions wrap. */
248
+ const CONTENT_MAX_WIDTH = '24rem';
249
+ let nextId$2 = 0;
247
250
  /**
248
251
  * WmSelect — Accessible dropdown select built on Angular CDK Overlay.
249
252
  *
@@ -261,20 +264,26 @@ class WmAnchoredScrollStrategy {
261
264
  * - Yes/No choices → use a Toggle or two radio buttons
262
265
  *
263
266
  * Options API:
264
- * options: SelectOption[] — Array of { value, label, disabled? } objects.
267
+ * options: SelectOption[] — Array of { value, label, disabled?, description? } objects.
265
268
  *
266
269
  * Two-way binding:
267
270
  * <wm-select [(value)]="selectedValue" [options]="myOptions" />
268
271
  *
272
+ * A compact choice in a row of others, which reads "Opus model":
273
+ * <wm-select appearance="chip" suffix="model" aria-label="Model" [options]="models" [(value)]="model" />
274
+ *
269
275
  * Accessibility:
270
276
  * - Trigger has role="combobox", aria-expanded, aria-haspopup="listbox"
271
277
  * - Panel has role="listbox"; each option has role="option" + aria-selected
272
278
  * - Disabled options have aria-disabled="true"
279
+ * - An option with a description is named by its label (aria-labelledby) and
280
+ * described by the second line (aria-describedby)
273
281
  * - Focus is trapped inside the open panel (cdkTrapFocus)
274
282
  * - Clicking the backdrop closes the panel and returns focus to the trigger
275
283
  * - Keyboard: Enter/Space on trigger opens; Escape closes; arrow keys
276
284
  * navigate options (relies on natural tab order within the panel)
277
- * - Always pair with a visible label via <wm-label> or aria-label on the host
285
+ * - Always pair with a visible label via <wm-label> or aria-label on the host.
286
+ * A chip has no visible label of its own, so it always needs the aria-label
278
287
  */
279
288
  class WmSelect {
280
289
  /**
@@ -287,6 +296,8 @@ class WmSelect {
287
296
  * keeps the coordinates it was born with while the trigger scrolls away.
288
297
  */
289
298
  scrollStrategy = new WmAnchoredScrollStrategy();
299
+ /** Prefix for the ids that tie an option to its label and description. */
300
+ uid = `wm-select-${++nextId$2}`;
290
301
  /** Array of options to display. */
291
302
  options = input([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
292
303
  /** The currently selected value. Two-way bindable. */
@@ -295,18 +306,58 @@ class WmSelect {
295
306
  placeholder = input('Select…', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
296
307
  /** Disables the trigger and prevents the panel from opening. */
297
308
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
309
+ /**
310
+ * `field` (the default) is a full-width form control with a border.
311
+ * `chip` is a compact, filled trigger as wide as its words, for a row of
312
+ * choices such as a composer's model and permissions.
313
+ */
314
+ appearance = input('field', ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
315
+ /**
316
+ * A muted word after the chosen label, in the trigger only: `suffix="model"`
317
+ * reads "Opus model". It names the choice where no label sits beside it. It
318
+ * is left out while the placeholder shows.
319
+ */
320
+ suffix = input(null, ...(ngDevMode ? [{ debugName: "suffix" }] : /* istanbul ignore next */ []));
321
+ /**
322
+ * `danger` draws the trigger in the danger status colours, for a choice that
323
+ * is risky to keep, such as running without permission prompts.
324
+ */
325
+ tone = input('neutral', ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
326
+ /**
327
+ * `trigger` holds the panel to the trigger's width. `content` lets it grow to
328
+ * its widest option, from the trigger's width up to 24rem, where a
329
+ * description wraps. A number is a fixed width in pixels. Left unset, a
330
+ * field's panel matches the trigger and a chip's panel fits its content.
331
+ */
332
+ panelWidth = input(null, ...(ngDevMode ? [{ debugName: "panelWidth" }] : /* istanbul ignore next */ []));
298
333
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
299
334
  selectedOption = computed(() => this.options().find((o) => o.value === this.value()) ?? null, ...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
335
+ sizing = computed(() => this.panelWidth() ?? (this.appearance() === 'chip' ? 'content' : 'trigger'), ...(ngDevMode ? [{ debugName: "sizing" }] : /* istanbul ignore next */ []));
300
336
  hostEl = inject((ElementRef));
301
- get overlayWidth() {
302
- return this.hostEl.nativeElement.offsetWidth || 200;
303
- }
337
+ /**
338
+ * The trigger's width, measured as the panel opens. A width read on every
339
+ * check would differ between Angular's update pass and its no-changes pass
340
+ * whenever the trigger is resizing, which a chip does while its text renders.
341
+ */
342
+ triggerWidth = signal(200, ...(ngDevMode ? [{ debugName: "triggerWidth" }] : /* istanbul ignore next */ []));
343
+ // The overlay leaves a size unset when it is given an empty string.
344
+ overlayWidth = computed(() => {
345
+ const sizing = this.sizing();
346
+ if (typeof sizing === 'number')
347
+ return sizing;
348
+ return sizing === 'trigger' ? this.triggerWidth() : '';
349
+ }, ...(ngDevMode ? [{ debugName: "overlayWidth" }] : /* istanbul ignore next */ []));
350
+ overlayMinWidth = computed(() => this.sizing() === 'content' ? this.triggerWidth() : '', ...(ngDevMode ? [{ debugName: "overlayMinWidth" }] : /* istanbul ignore next */ []));
351
+ /** The overlay has no max-width input, so the cap sits on the panel it holds. */
352
+ panelMaxWidth = computed(() => this.sizing() === 'content' ? CONTENT_MAX_WIDTH : '', ...(ngDevMode ? [{ debugName: "panelMaxWidth" }] : /* istanbul ignore next */ []));
304
353
  positions = [
305
354
  { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
306
355
  { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
307
356
  ];
308
357
  toggleOpen() {
309
358
  if (!this.disabled()) {
359
+ if (!this.isOpen())
360
+ this.triggerWidth.set(this.hostEl.nativeElement.offsetWidth || 200);
310
361
  this.isOpen.update((v) => !v);
311
362
  }
312
363
  }
@@ -317,12 +368,15 @@ class WmSelect {
317
368
  }
318
369
  }
319
370
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
320
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmSelect, isStandalone: true, selector: "wm-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: "<!-- Trigger button \u2014 anchors the CDK overlay -->\n<div\n class=\"wm-select-host\"\n cdkOverlayOrigin\n #origin=\"cdkOverlayOrigin\"\n>\n <button\n type=\"button\"\n role=\"combobox\"\n class=\"wm-select-trigger\"\n [class.is-open]=\"isOpen()\"\n [class.is-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-disabled]=\"disabled() ? true : null\"\n [disabled]=\"disabled() || null\"\n (click)=\"toggleOpen()\"\n >\n <span class=\"wm-select-value\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n <!-- Chevron icon \u2014 rotates when open -->\n <svg\n class=\"wm-select-chevron\"\n [class.is-open]=\"isOpen()\"\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" aria-hidden=\"true\"\n >\n <path d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n</div>\n\n<!-- Dropdown panel \u2014 rendered in CDK overlay container -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayWidth]=\"overlayWidth\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"wm-select-backdrop\"\n (backdropClick)=\"isOpen.set(false)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n class=\"wm-select-panel\"\n role=\"listbox\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n @for (opt of options(); track opt.value) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"wm-select-option\"\n [class.is-selected]=\"opt.value === value()\"\n [class.is-disabled]=\"!!opt.disabled\"\n [attr.aria-selected]=\"opt.value === value()\"\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\n [disabled]=\"opt.disabled || null\"\n (click)=\"selectOption(opt)\"\n >\n <span class=\"wm-select-option-label\">{{ opt.label }}</span>\n @if (opt.value === value()) {\n <!-- Checkmark for selected option -->\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"\n stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block}.wm-select-host{display:block}.wm-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:0 .75rem;height:36px;background:var(--wm-bg-base);border:1px solid var(--wm-border-input);border-radius:var(--wm-radius-md);color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;transition:border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast);outline:none;text-align:left}.wm-select-trigger:hover:not(.is-disabled){border-color:var(--wm-color-accent-readable)}.wm-select-trigger:focus-visible{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}.wm-select-trigger.is-open{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 20%,transparent)}.wm-select-trigger.is-disabled{opacity:.45;cursor:not-allowed}.wm-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-value.is-placeholder{color:var(--wm-text-placeholder)}.wm-select-chevron{flex-shrink:0;color:var(--wm-text-muted);transition:transform var(--wm-transition-fast)}.wm-select-chevron.is-open{transform:rotate(180deg)}.wm-select-backdrop{position:fixed;inset:0}.wm-select-panel{width:100%;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #0000001f;overflow:hidden;outline:none;padding:.25rem}.wm-select-option{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.4375rem .625rem;border:none;border-radius:var(--wm-radius-sm);background:transparent;color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;text-align:left;transition:background var(--wm-transition-fast)}.wm-select-option:hover:not(.is-disabled):not(.is-selected){background:var(--wm-bg-active)}.wm-select-option.is-selected{background:color-mix(in srgb,var(--wm-color-accent) 10%,transparent);color:var(--wm-color-accent-readable);font-weight:500}.wm-select-option.is-disabled{opacity:.4;cursor:not-allowed}.wm-select-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-option svg{flex-shrink:0;color:var(--wm-color-accent-readable)}\n"], dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }] });
371
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmSelect, isStandalone: true, selector: "wm-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, panelWidth: { classPropertyName: "panelWidth", publicName: "panelWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.wm-select--chip": "appearance() === 'chip'", "class.wm-select--danger": "tone() === 'danger'" } }, ngImport: i0, template: "<!-- Trigger button \u2014 anchors the CDK overlay -->\n<div\n class=\"wm-select-host\"\n cdkOverlayOrigin\n #origin=\"cdkOverlayOrigin\"\n>\n <button\n type=\"button\"\n role=\"combobox\"\n class=\"wm-select-trigger\"\n [class.is-open]=\"isOpen()\"\n [class.is-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-disabled]=\"disabled() ? true : null\"\n [disabled]=\"disabled() || null\"\n (click)=\"toggleOpen()\"\n >\n <span class=\"wm-select-value\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n @if (suffix() && selectedOption()) {\n <span class=\"wm-select-suffix\">{{ suffix() }}</span>\n }\n <!-- Chevron icon \u2014 rotates when open -->\n <svg\n class=\"wm-select-chevron\"\n [class.is-open]=\"isOpen()\"\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" aria-hidden=\"true\"\n >\n <path d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n</div>\n\n<!-- Dropdown panel \u2014 rendered in CDK overlay container -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayWidth]=\"overlayWidth()\"\n [cdkConnectedOverlayMinWidth]=\"overlayMinWidth()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"wm-select-backdrop\"\n (backdropClick)=\"isOpen.set(false)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n class=\"wm-select-panel\"\n role=\"listbox\"\n [style.max-width]=\"panelMaxWidth() || null\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n @for (opt of options(); track opt.value; let i = $index) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"wm-select-option\"\n [class.is-selected]=\"opt.value === value()\"\n [class.is-disabled]=\"!!opt.disabled\"\n [attr.aria-selected]=\"opt.value === value()\"\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\n [attr.aria-labelledby]=\"opt.description ? uid + '-label-' + i : null\"\n [attr.aria-describedby]=\"opt.description ? uid + '-description-' + i : null\"\n [disabled]=\"opt.disabled || null\"\n (click)=\"selectOption(opt)\"\n >\n @if (opt.description) {\n <span class=\"wm-select-option-text\">\n <span class=\"wm-select-option-label\" [id]=\"uid + '-label-' + i\">{{ opt.label }}</span>\n <span class=\"wm-select-option-description\" [id]=\"uid + '-description-' + i\">{{ opt.description }}</span>\n </span>\n } @else {\n <span class=\"wm-select-option-label\">{{ opt.label }}</span>\n }\n @if (opt.value === value()) {\n <!-- Checkmark for selected option -->\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"\n stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block}.wm-select-host{display:block}.wm-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:0 .75rem;height:36px;background:var(--wm-bg-base);border:1px solid var(--wm-border-input);border-radius:var(--wm-radius-md);color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;transition:border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast);outline:none;text-align:left}.wm-select-trigger:hover:not(.is-disabled){border-color:var(--wm-color-accent-readable)}.wm-select-trigger:focus-visible{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}.wm-select-trigger.is-open{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 20%,transparent)}.wm-select-trigger.is-disabled{opacity:.45;cursor:not-allowed}.wm-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-value.is-placeholder{color:var(--wm-text-placeholder)}.wm-select-chevron{flex-shrink:0;color:var(--wm-text-muted);transition:transform var(--wm-transition-fast)}.wm-select-chevron.is-open{transform:rotate(180deg)}.wm-select-backdrop{position:fixed;inset:0}.wm-select-panel{width:100%;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #0000001f;overflow:hidden;outline:none;padding:.25rem}.wm-select-option{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.4375rem .625rem;border:none;border-radius:var(--wm-radius-sm);background:transparent;color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;text-align:left;transition:background var(--wm-transition-fast)}.wm-select-option:hover:not(.is-disabled):not(.is-selected){background:var(--wm-bg-active)}.wm-select-option.is-selected{background:color-mix(in srgb,var(--wm-color-accent) 10%,transparent);color:var(--wm-color-accent-readable);font-weight:500}.wm-select-option.is-disabled{opacity:.4;cursor:not-allowed}.wm-select-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-option svg{flex-shrink:0;color:var(--wm-color-accent-readable)}.wm-select-suffix{flex-shrink:0;color:var(--wm-text-muted);font-size:.8125rem;font-weight:400}.wm-select-option-text{display:flex;flex:1;flex-direction:column;gap:.125rem;min-width:0}.wm-select-option:has(.wm-select-option-text){align-items:flex-start;padding-block:.5rem}.wm-select-option:has(.wm-select-option-text) svg{margin-top:.1875rem}.wm-select-option-description{color:var(--wm-text-secondary);font-size:.75rem;font-weight:400;line-height:1.4;white-space:normal}:host(.wm-select--chip){display:inline-block;max-width:100%}:host(.wm-select--chip) .wm-select-trigger{justify-content:flex-start;width:auto;max-width:100%;height:34px;padding:0 .625rem 0 .75rem;background:var(--wm-bg-base);border-color:transparent;font-weight:500;transition:background var(--wm-transition-fast),border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast)}:host(.wm-select--chip) .wm-select-value{flex:0 1 auto}:host(.wm-select--chip) .wm-select-chevron{width:14px;height:14px}:host(.wm-select--chip) .wm-select-trigger:hover:not(.is-disabled),:host(.wm-select--chip) .wm-select-trigger.is-open{background:var(--wm-bg-hover);border-color:transparent;box-shadow:none;animation:none}:host(.wm-select--chip) .wm-select-trigger:hover:not(.is-disabled) .wm-select-suffix,:host(.wm-select--chip) .wm-select-trigger.is-open .wm-select-suffix{color:var(--wm-text-secondary)}:host(.wm-select--chip) .wm-select-trigger:focus-visible{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}:host(.wm-select--danger) .wm-select-trigger,:host(.wm-select--danger) .wm-select-trigger:hover:not(.is-disabled),:host(.wm-select--danger) .wm-select-trigger.is-open{border-color:var(--wm-color-danger-border);color:var(--wm-color-danger-readable)}:host(.wm-select--danger) .wm-select-suffix,:host(.wm-select--danger) .wm-select-chevron{color:var(--wm-color-danger-readable)}:host(.wm-select--danger.wm-select--chip) .wm-select-trigger{background:linear-gradient(var(--wm-color-danger-bg),var(--wm-color-danger-bg)),var(--wm-bg-base)}:host(.wm-select--danger.wm-select--chip) .wm-select-trigger:hover:not(.is-disabled),:host(.wm-select--danger.wm-select--chip) .wm-select-trigger.is-open{background:linear-gradient(var(--wm-color-danger-bg),var(--wm-color-danger-bg)),var(--wm-bg-hover)}\n"], dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }] });
321
372
  }
322
373
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmSelect, decorators: [{
323
374
  type: Component,
324
- args: [{ selector: 'wm-select', imports: [CdkConnectedOverlay, CdkOverlayOrigin, CdkTrapFocus], template: "<!-- Trigger button \u2014 anchors the CDK overlay -->\n<div\n class=\"wm-select-host\"\n cdkOverlayOrigin\n #origin=\"cdkOverlayOrigin\"\n>\n <button\n type=\"button\"\n role=\"combobox\"\n class=\"wm-select-trigger\"\n [class.is-open]=\"isOpen()\"\n [class.is-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-disabled]=\"disabled() ? true : null\"\n [disabled]=\"disabled() || null\"\n (click)=\"toggleOpen()\"\n >\n <span class=\"wm-select-value\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n <!-- Chevron icon \u2014 rotates when open -->\n <svg\n class=\"wm-select-chevron\"\n [class.is-open]=\"isOpen()\"\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" aria-hidden=\"true\"\n >\n <path d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n</div>\n\n<!-- Dropdown panel \u2014 rendered in CDK overlay container -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayWidth]=\"overlayWidth\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"wm-select-backdrop\"\n (backdropClick)=\"isOpen.set(false)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n class=\"wm-select-panel\"\n role=\"listbox\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n @for (opt of options(); track opt.value) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"wm-select-option\"\n [class.is-selected]=\"opt.value === value()\"\n [class.is-disabled]=\"!!opt.disabled\"\n [attr.aria-selected]=\"opt.value === value()\"\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\n [disabled]=\"opt.disabled || null\"\n (click)=\"selectOption(opt)\"\n >\n <span class=\"wm-select-option-label\">{{ opt.label }}</span>\n @if (opt.value === value()) {\n <!-- Checkmark for selected option -->\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"\n stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block}.wm-select-host{display:block}.wm-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:0 .75rem;height:36px;background:var(--wm-bg-base);border:1px solid var(--wm-border-input);border-radius:var(--wm-radius-md);color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;transition:border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast);outline:none;text-align:left}.wm-select-trigger:hover:not(.is-disabled){border-color:var(--wm-color-accent-readable)}.wm-select-trigger:focus-visible{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}.wm-select-trigger.is-open{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 20%,transparent)}.wm-select-trigger.is-disabled{opacity:.45;cursor:not-allowed}.wm-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-value.is-placeholder{color:var(--wm-text-placeholder)}.wm-select-chevron{flex-shrink:0;color:var(--wm-text-muted);transition:transform var(--wm-transition-fast)}.wm-select-chevron.is-open{transform:rotate(180deg)}.wm-select-backdrop{position:fixed;inset:0}.wm-select-panel{width:100%;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #0000001f;overflow:hidden;outline:none;padding:.25rem}.wm-select-option{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.4375rem .625rem;border:none;border-radius:var(--wm-radius-sm);background:transparent;color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;text-align:left;transition:background var(--wm-transition-fast)}.wm-select-option:hover:not(.is-disabled):not(.is-selected){background:var(--wm-bg-active)}.wm-select-option.is-selected{background:color-mix(in srgb,var(--wm-color-accent) 10%,transparent);color:var(--wm-color-accent-readable);font-weight:500}.wm-select-option.is-disabled{opacity:.4;cursor:not-allowed}.wm-select-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-option svg{flex-shrink:0;color:var(--wm-color-accent-readable)}\n"] }]
325
- }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
375
+ args: [{ selector: 'wm-select', imports: [CdkConnectedOverlay, CdkOverlayOrigin, CdkTrapFocus], host: {
376
+ '[class.wm-select--chip]': "appearance() === 'chip'",
377
+ '[class.wm-select--danger]': "tone() === 'danger'",
378
+ }, template: "<!-- Trigger button \u2014 anchors the CDK overlay -->\n<div\n class=\"wm-select-host\"\n cdkOverlayOrigin\n #origin=\"cdkOverlayOrigin\"\n>\n <button\n type=\"button\"\n role=\"combobox\"\n class=\"wm-select-trigger\"\n [class.is-open]=\"isOpen()\"\n [class.is-disabled]=\"disabled()\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-disabled]=\"disabled() ? true : null\"\n [disabled]=\"disabled() || null\"\n (click)=\"toggleOpen()\"\n >\n <span class=\"wm-select-value\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption()?.label ?? placeholder() }}\n </span>\n @if (suffix() && selectedOption()) {\n <span class=\"wm-select-suffix\">{{ suffix() }}</span>\n }\n <!-- Chevron icon \u2014 rotates when open -->\n <svg\n class=\"wm-select-chevron\"\n [class.is-open]=\"isOpen()\"\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\n fill=\"none\" aria-hidden=\"true\"\n >\n <path d=\"M6 9l6 6 6-6\"\n stroke=\"currentColor\" stroke-width=\"2\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n</div>\n\n<!-- Dropdown panel \u2014 rendered in CDK overlay container -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayWidth]=\"overlayWidth()\"\n [cdkConnectedOverlayMinWidth]=\"overlayMinWidth()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"wm-select-backdrop\"\n (backdropClick)=\"isOpen.set(false)\"\n (detach)=\"isOpen.set(false)\"\n>\n <div\n class=\"wm-select-panel\"\n role=\"listbox\"\n [style.max-width]=\"panelMaxWidth() || null\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"true\"\n >\n @for (opt of options(); track opt.value; let i = $index) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"wm-select-option\"\n [class.is-selected]=\"opt.value === value()\"\n [class.is-disabled]=\"!!opt.disabled\"\n [attr.aria-selected]=\"opt.value === value()\"\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\n [attr.aria-labelledby]=\"opt.description ? uid + '-label-' + i : null\"\n [attr.aria-describedby]=\"opt.description ? uid + '-description-' + i : null\"\n [disabled]=\"opt.disabled || null\"\n (click)=\"selectOption(opt)\"\n >\n @if (opt.description) {\n <span class=\"wm-select-option-text\">\n <span class=\"wm-select-option-label\" [id]=\"uid + '-label-' + i\">{{ opt.label }}</span>\n <span class=\"wm-select-option-description\" [id]=\"uid + '-description-' + i\">{{ opt.description }}</span>\n </span>\n } @else {\n <span class=\"wm-select-option-label\">{{ opt.label }}</span>\n }\n @if (opt.value === value()) {\n <!-- Checkmark for selected option -->\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\"\n stroke=\"currentColor\" stroke-width=\"2.5\"\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n }\n </button>\n }\n </div>\n</ng-template>\n", styles: [":host{display:block}.wm-select-host{display:block}.wm-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:0 .75rem;height:36px;background:var(--wm-bg-base);border:1px solid var(--wm-border-input);border-radius:var(--wm-radius-md);color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;transition:border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast);outline:none;text-align:left}.wm-select-trigger:hover:not(.is-disabled){border-color:var(--wm-color-accent-readable)}.wm-select-trigger:focus-visible{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}.wm-select-trigger.is-open{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 20%,transparent)}.wm-select-trigger.is-disabled{opacity:.45;cursor:not-allowed}.wm-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-value.is-placeholder{color:var(--wm-text-placeholder)}.wm-select-chevron{flex-shrink:0;color:var(--wm-text-muted);transition:transform var(--wm-transition-fast)}.wm-select-chevron.is-open{transform:rotate(180deg)}.wm-select-backdrop{position:fixed;inset:0}.wm-select-panel{width:100%;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #0000001f;overflow:hidden;outline:none;padding:.25rem}.wm-select-option{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.4375rem .625rem;border:none;border-radius:var(--wm-radius-sm);background:transparent;color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;text-align:left;transition:background var(--wm-transition-fast)}.wm-select-option:hover:not(.is-disabled):not(.is-selected){background:var(--wm-bg-active)}.wm-select-option.is-selected{background:color-mix(in srgb,var(--wm-color-accent) 10%,transparent);color:var(--wm-color-accent-readable);font-weight:500}.wm-select-option.is-disabled{opacity:.4;cursor:not-allowed}.wm-select-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-option svg{flex-shrink:0;color:var(--wm-color-accent-readable)}.wm-select-suffix{flex-shrink:0;color:var(--wm-text-muted);font-size:.8125rem;font-weight:400}.wm-select-option-text{display:flex;flex:1;flex-direction:column;gap:.125rem;min-width:0}.wm-select-option:has(.wm-select-option-text){align-items:flex-start;padding-block:.5rem}.wm-select-option:has(.wm-select-option-text) svg{margin-top:.1875rem}.wm-select-option-description{color:var(--wm-text-secondary);font-size:.75rem;font-weight:400;line-height:1.4;white-space:normal}:host(.wm-select--chip){display:inline-block;max-width:100%}:host(.wm-select--chip) .wm-select-trigger{justify-content:flex-start;width:auto;max-width:100%;height:34px;padding:0 .625rem 0 .75rem;background:var(--wm-bg-base);border-color:transparent;font-weight:500;transition:background var(--wm-transition-fast),border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast)}:host(.wm-select--chip) .wm-select-value{flex:0 1 auto}:host(.wm-select--chip) .wm-select-chevron{width:14px;height:14px}:host(.wm-select--chip) .wm-select-trigger:hover:not(.is-disabled),:host(.wm-select--chip) .wm-select-trigger.is-open{background:var(--wm-bg-hover);border-color:transparent;box-shadow:none;animation:none}:host(.wm-select--chip) .wm-select-trigger:hover:not(.is-disabled) .wm-select-suffix,:host(.wm-select--chip) .wm-select-trigger.is-open .wm-select-suffix{color:var(--wm-text-secondary)}:host(.wm-select--chip) .wm-select-trigger:focus-visible{border-color:var(--wm-color-accent-readable);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}:host(.wm-select--danger) .wm-select-trigger,:host(.wm-select--danger) .wm-select-trigger:hover:not(.is-disabled),:host(.wm-select--danger) .wm-select-trigger.is-open{border-color:var(--wm-color-danger-border);color:var(--wm-color-danger-readable)}:host(.wm-select--danger) .wm-select-suffix,:host(.wm-select--danger) .wm-select-chevron{color:var(--wm-color-danger-readable)}:host(.wm-select--danger.wm-select--chip) .wm-select-trigger{background:linear-gradient(var(--wm-color-danger-bg),var(--wm-color-danger-bg)),var(--wm-bg-base)}:host(.wm-select--danger.wm-select--chip) .wm-select-trigger:hover:not(.is-disabled),:host(.wm-select--danger.wm-select--chip) .wm-select-trigger.is-open{background:linear-gradient(var(--wm-color-danger-bg),var(--wm-color-danger-bg)),var(--wm-bg-hover)}\n"] }]
379
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], panelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelWidth", required: false }] }] } });
326
380
 
327
381
  /**
328
382
  * WmPopover — general-purpose CDK-overlay floating panel.