@uni-design-system/uni-angular 10.1.0 → 10.2.1

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
@@ -1,5 +1,94 @@
1
1
  # @uni-design-system/uni-angular
2
2
 
3
+ ## 10.2.1
4
+
5
+ ## 10.2.0
6
+
7
+ ### Minor Changes
8
+
9
+ - [`1cd0140`](https://github.com/uni-design-system/uni/commit/1cd0140d2e6a4c753f48fb46418ba08769979263) Thanks [@gaenglish](https://github.com/gaenglish)! - Checkbox, radio and toggle take their accent from the theme, not from the
10
+ variant's name.
11
+
12
+ **Twelve sites across the three controls resolved a variant name as a colour
13
+ token.** That held together only because every name in the closed union happened
14
+ to also be a colour. Under an open registry the coincidence ends by design:
15
+ `<uni-checkbox variant="destructive">` would look up `colors['destructive']`,
16
+ miss, and **silently render primary** — a wrong-coloured control with no error,
17
+ no warning, and nothing to grep for.
18
+
19
+ Checkbox was worse than it looked. Alongside five `getThemeColor` calls it had
20
+ two more through a second resolver that built `on-${variant}`, so an
21
+ unregistered intent also missed its paired content colour and fell back to
22
+ `on-primary` — the tick would have stayed light on a dark fill even after the
23
+ box was fixed.
24
+
25
+ The theme now says which colour draws each intent, through a new
26
+ `variantOptions` map on `ComponentTheme`:
27
+
28
+ ```ts
29
+ checkbox: {
30
+ variantOptions: {
31
+ primary: { accent: 'primary' },
32
+ warn: { accent: 'warn' },
33
+ },
34
+ }
35
+ ```
36
+
37
+ `variantOptions` is per-variant data a component **reads**, as against `variants`,
38
+ which is CSS that gets **applied**. The distinction earns its place here: a
39
+ checkbox's accent lands on the box outline, the checked and indeterminate fills,
40
+ the tick and the focus ring at once, and expressing that as CSS would have meant
41
+ the theme naming `.checkbox-check` and `.radio-inner` — promoting private DOM to
42
+ public theme contract.
43
+
44
+ All three controls gain a `checkedColor` input as the per-instance override,
45
+ matching the one `uni-toggle` already had; its resolution order is now input →
46
+ the variant's themed accent → theme option. The base theme defines the same
47
+ seven intents `button` and `iconButton` do, so the library is consistent about
48
+ which exist by default, and `getThemeColor` — triplicated byte-for-byte across
49
+ the three components, with a silent fallback to primary — is gone.
50
+
51
+ Rendering is unchanged for anything that does not set `variant`: the default
52
+ still resolves to the primary accent and its paired on-colour.
53
+
54
+ - [`1cd0140`](https://github.com/uni-design-system/uni/commit/1cd0140d2e6a4c753f48fb46418ba08769979263) Thanks [@gaenglish](https://github.com/gaenglish)! - `Variant` is an open registry: a design system can define its own intents.
55
+
56
+ A variant names _what an action means_, and it is the theme's job to describe
57
+ how that intent is drawn. So the set of names was never Uni's to fix — an app
58
+ whose actions are `destructive`, `subtle` and `info` had to translate them onto
59
+ twelve names chosen elsewhere. `Variant` is now `keyof UniVariantRegistry`,
60
+ extended by declaration merging:
61
+
62
+ ```ts
63
+ declare module '@uni-design-system/uni-core' {
64
+ interface UniVariantRegistry {
65
+ destructive: true;
66
+ }
67
+ }
68
+ ```
69
+
70
+ `variant="destructive"` then compiles wherever a variant is accepted, and
71
+ `variant="destructve"` still does not — which the library's other open-token
72
+ idiom, `Named | (string & {})`, cannot give you, and which would also have
73
+ collapsed the theme's `variants` map keys to `string`.
74
+
75
+ Only the type was ever closed: theme validation checks the _shape_ of a
76
+ `variants` block and never its key names, so a custom variant already reached
77
+ `componentStyle` untouched at runtime.
78
+
79
+ **The registry extends; it cannot replace.** Declaration merging has no way to
80
+ remove a member, so Uni's twelve names stay legal in a consuming app; enforcing
81
+ a house set is a lint concern rather than a type. Two names are reserved and
82
+ documented as always present: `primary`, which every component inherits as its
83
+ default, and `disabled`, which the disabled state resolves to.
84
+
85
+ **An unthemed variant now says so.** With a closed union this was nearly
86
+ impossible; with an open set it is the ordinary state of a work in progress —
87
+ a variant registered and used before its theme block exists. The theme service
88
+ warns once per component and variant in dev, naming what the theme does define,
89
+ mirroring what it already did for an unknown spacing token. Components that
90
+ theme no variants at all stay silent, since a missing key there is not a gap.
91
+
3
92
  ## 10.1.0
4
93
 
5
94
  ### Minor Changes
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="https://www.npmjs.com/package/@uni-design-system/uni-angular"><img src="https://img.shields.io/npm/v/%40uni-design-system%2Funi-angular" alt="npm version"></a>
13
- <a href="https://github.com/uni-design-system/uni/actions/workflows/ci.yml"><img src="https://github.com/uni-design-system/uni/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
+ <a href="https://github.com/uni-design-system/uni/actions/workflows/release.yml"><img src="https://github.com/uni-design-system/uni/actions/workflows/release.yml/badge.svg" alt="CI"></a>
14
14
  </p>
15
15
 
16
16
  <p align="center">
@@ -2193,10 +2193,53 @@ class ThemeService {
2193
2193
  componentStyle = (componentName, variant, size) => computed(() => {
2194
2194
  const component = this.component(componentName)();
2195
2195
  const { fixed, variants, sizes } = component;
2196
+ this.warnUnthemedVariant(componentName, variant, component);
2196
2197
  const variantStyle = variants && variants[variant];
2197
2198
  const sizeStyle = sizes && sizes[size];
2198
2199
  return { ...fixed, ...variantStyle, ...sizeStyle };
2199
2200
  });
2201
+ /**
2202
+ * The active variant's roles from a component's `variantOptions` map — the
2203
+ * per-variant data a component *reads* rather than CSS it applies. See
2204
+ * `ComponentTheme.variantOptions`.
2205
+ */
2206
+ variantRoles = (componentName, variant) => computed(() => {
2207
+ const component = this.component(componentName)();
2208
+ this.warnUnthemedVariant(componentName, variant, component);
2209
+ return component.variantOptions?.[variant];
2210
+ });
2211
+ warnedVariants = new Set();
2212
+ /**
2213
+ * Say something when a component is asked for a variant its theme does not
2214
+ * define, once per component/variant pair.
2215
+ *
2216
+ * `Variant` is an open registry, so a name the theme never styled cannot be a
2217
+ * compile error — and the miss is silent by construction, because an absent
2218
+ * style spreads to nothing and an absent role falls back. That is exactly the
2219
+ * ordinary state of a work in progress: a designer registers `destructive`,
2220
+ * uses it, and has not written its theme block yet. Same reasoning as
2221
+ * {@link resolveSpacing}, whose open scale has the same problem.
2222
+ *
2223
+ * A component that themes no variants at all is not missing anything — most
2224
+ * of the library never varies by intent — so silence there is correct.
2225
+ */
2226
+ warnUnthemedVariant(componentName, variant, component) {
2227
+ const { variants, variantOptions } = component;
2228
+ if (!variants && !variantOptions)
2229
+ return;
2230
+ if (variants?.[variant] !== undefined || variantOptions?.[variant] !== undefined)
2231
+ return;
2232
+ const key = `${componentName}/${variant}`;
2233
+ if (!(typeof ngDevMode === 'undefined' || ngDevMode) || this.warnedVariants.has(key))
2234
+ return;
2235
+ this.warnedVariants.add(key);
2236
+ const defined = [
2237
+ ...new Set([...Object.keys(variants ?? {}), ...Object.keys(variantOptions ?? {})]),
2238
+ ];
2239
+ console.warn(`[uni] Unknown variant "${variant}" on "${componentName}": the active theme does not ` +
2240
+ `define it, so the component falls back to its default appearance. Add it to the ` +
2241
+ `theme's \`components.${componentName}\` entry, or use one of: ${defined.join(', ')}.`);
2242
+ }
2200
2243
  /**
2201
2244
  * Resolve a spacing token to its CSS value. `'none'` resolves through the
2202
2245
  * scale like any other token — it is `0`, not the string `'none'`, which is
@@ -2439,12 +2482,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
2439
2482
  }], ctorParameters: () => [] });
2440
2483
 
2441
2484
  const COMPONENT_NAME = new InjectionToken('');
2485
+ // `V` defaults to `unknown` alongside `T`: passing the class as a value erases
2486
+ // its parameters to `unknown`, so an `object` default made
2487
+ // `TestBed.createComponent(BaseComponent)` disagree with `ComponentFixture<BaseComponent>`.
2442
2488
  class BaseComponent {
2443
2489
  componentName = inject(COMPONENT_NAME);
2444
2490
  theme = inject(ThemeService);
2445
2491
  variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ [])); // TODO: Make Variant support undefined
2446
2492
  size = input('lg', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
2447
2493
  componentTheme = computed(() => this.theme.getComponentTheme(this.componentName)(), ...(ngDevMode ? [{ debugName: "componentTheme" }] : /* istanbul ignore next */ []));
2494
+ /**
2495
+ * The active variant's roles from the theme's `variantOptions` map, for
2496
+ * components whose accent lands on several interior elements and so cannot
2497
+ * be expressed as a single applied style. Undefined when the theme does not
2498
+ * define this variant — which also raises a dev warning.
2499
+ */
2500
+ variantRoles = computed(() => this.theme.variantRoles(this.componentName, this.variant())(), ...(ngDevMode ? [{ debugName: "variantRoles" }] : /* istanbul ignore next */ []));
2448
2501
  componentOptions = computed(() => this.theme.getComponentOptions(this.componentName)(), ...(ngDevMode ? [{ debugName: "componentOptions" }] : /* istanbul ignore next */ []));
2449
2502
  style = computed(() => this.theme.componentStyle(this.componentName, this.variant(), this.size())(), ...(ngDevMode ? [{ debugName: "style" }] : /* istanbul ignore next */ []));
2450
2503
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
@@ -2558,6 +2611,13 @@ class UniCheckboxComponent extends BaseComponent {
2558
2611
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
2559
2612
  /** Synced from required() validators by the Signal Forms [formField] directive. */
2560
2613
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
2614
+ /**
2615
+ * Accent colour token, overriding the variant's themed accent for one
2616
+ * instance. Mirrors `uni-toggle`'s input of the same name — it exists because
2617
+ * `variant` defaults to `'primary'`, so the component cannot tell "set to
2618
+ * primary" from "not set".
2619
+ */
2620
+ checkedColor = input(...(ngDevMode ? [undefined, { debugName: "checkedColor" }] : /* istanbul ignore next */ []));
2561
2621
  /**
2562
2622
  * Id(s) of external element(s) describing this control — typically your
2563
2623
  * app-rendered error message — exposed as aria-describedby.
@@ -2601,8 +2661,8 @@ class UniCheckboxComponent extends BaseComponent {
2601
2661
  display: 'block',
2602
2662
  },
2603
2663
  '& .checkbox svg .checkbox-box': {
2604
- fill: this.getThemeColor(this.componentOptions().boxColor ?? 'surface'),
2605
- stroke: this.getThemeColor(this.variant()),
2664
+ fill: this.boxColor(),
2665
+ stroke: this.accent().fill,
2606
2666
  strokeWidth: 2,
2607
2667
  rx: this.componentOptions().borderRadius || 2,
2608
2668
  ry: this.componentOptions().borderRadius || 2,
@@ -2611,7 +2671,7 @@ class UniCheckboxComponent extends BaseComponent {
2611
2671
  // Check/dash draw on the variant-filled box, so they wear its on-color.
2612
2672
  '& .checkbox svg .checkbox-check': {
2613
2673
  fill: 'none',
2614
- stroke: this.getOnColor(this.variant()),
2674
+ stroke: this.accent().on,
2615
2675
  strokeWidth: 2,
2616
2676
  strokeLinecap: 'round',
2617
2677
  strokeLinejoin: 'round',
@@ -2620,7 +2680,7 @@ class UniCheckboxComponent extends BaseComponent {
2620
2680
  transition: 'all 0.5s ease',
2621
2681
  },
2622
2682
  '& .checkbox svg .checkbox-dash': {
2623
- stroke: this.getOnColor(this.variant()),
2683
+ stroke: this.accent().on,
2624
2684
  strokeWidth: 2,
2625
2685
  strokeLinecap: 'round',
2626
2686
  opacity: 0,
@@ -2634,16 +2694,16 @@ class UniCheckboxComponent extends BaseComponent {
2634
2694
  height: 0,
2635
2695
  opacity: 0,
2636
2696
  '&:checked + .checkbox': {
2637
- borderColor: this.getThemeColor(this.variant()),
2697
+ borderColor: this.accent().fill,
2638
2698
  },
2639
2699
  '&:checked + .checkbox svg .checkbox-box': {
2640
- fill: this.getThemeColor(this.variant()),
2700
+ fill: this.accent().fill,
2641
2701
  },
2642
2702
  '&:checked + .checkbox svg .checkbox-check': {
2643
2703
  strokeDashoffset: 0,
2644
2704
  },
2645
2705
  '&:indeterminate + .checkbox svg .checkbox-box': {
2646
- fill: this.getThemeColor(this.variant()),
2706
+ fill: this.accent().fill,
2647
2707
  },
2648
2708
  '&:indeterminate + .checkbox svg .checkbox-dash': {
2649
2709
  opacity: 1,
@@ -2656,26 +2716,37 @@ class UniCheckboxComponent extends BaseComponent {
2656
2716
  // 4px to round proportionally (as the original hand-drawn ring did) —
2657
2717
  // without it the corners gap away from the box.
2658
2718
  '&:focus + .checkbox': {
2659
- ...this.theme.focusRingStyle(this.getThemeColor(this.variant()), this.componentOptions().focusRingGap),
2719
+ ...this.theme.focusRingStyle(this.accent().fill, this.componentOptions().focusRingGap),
2660
2720
  borderRadius: `${(Number(this.componentOptions().borderRadius) || 2) + 2}px`,
2661
2721
  },
2662
2722
  }), ...(ngDevMode ? [{ debugName: "checkboxInput" }] : /* istanbul ignore next */ []));
2663
- getThemeColor(token) {
2664
- const colors = this.theme.colors();
2665
- return colors[token] ? colors[token] : colors['primary'];
2666
- }
2667
- /** The content color paired with a variant fill (on-primary, on-warn, …). */
2668
- getOnColor(variant) {
2723
+ /**
2724
+ * The accent and its paired content colour, from the theme's variant roles.
2725
+ *
2726
+ * Previously the variant *name* was looked up as a colour token, which held
2727
+ * together only because every variant happened to also be a colour. With the
2728
+ * registry open that coincidence ends by design: `variant="destructive"`
2729
+ * would have missed and silently rendered primary. The theme now says which
2730
+ * colour draws the intent, and an unthemed variant warns rather than lying.
2731
+ *
2732
+ * `primary` is the last resort because it is a reserved variant name — the
2733
+ * default every component inherits.
2734
+ */
2735
+ accent = computed(() => {
2669
2736
  const colors = this.theme.colors();
2670
- return colors[`on-${variant}`] ?? colors['on-primary'];
2671
- }
2737
+ const roles = this.variantRoles();
2738
+ const accent = this.checkedColor() ?? roles?.accent ?? 'primary';
2739
+ const onAccent = roles?.onAccent ?? `on-${accent}`;
2740
+ return { fill: colors[accent], on: colors[onAccent] };
2741
+ }, ...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
2742
+ boxColor = computed(() => this.theme.colors()[this.componentOptions().boxColor ?? 'surface'], ...(ngDevMode ? [{ debugName: "boxColor" }] : /* istanbul ignore next */ []));
2672
2743
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCheckboxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2673
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniCheckboxComponent, isStandalone: true, selector: "uni-checkbox", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange", indeterminate: "indeterminateChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'checkbox' }], usesInheritance: true, ngImport: i0, template: "<label [class]=\"checkboxLabel()\">\n <input\n type=\"checkbox\"\n [class]=\"checkboxInput()\"\n [checked]=\"checked()\"\n [indeterminate]=\"indeterminate()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"checkbox\">\n <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <rect class=\"checkbox-box\" x=\"1\" y=\"1\" width=\"18\" height=\"18\"></rect>\n <polyline class=\"checkbox-check\" points=\"4 11 8 15 16 6\"></polyline>\n <line class=\"checkbox-dash\" x1=\"5\" y1=\"10\" x2=\"15\" y2=\"10\"></line>\n </svg>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2744
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniCheckboxComponent, isStandalone: true, selector: "uni-checkbox", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, checkedColor: { classPropertyName: "checkedColor", publicName: "checkedColor", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange", indeterminate: "indeterminateChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'checkbox' }], usesInheritance: true, ngImport: i0, template: "<label [class]=\"checkboxLabel()\">\n <input\n type=\"checkbox\"\n [class]=\"checkboxInput()\"\n [checked]=\"checked()\"\n [indeterminate]=\"indeterminate()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"checkbox\">\n <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <rect class=\"checkbox-box\" x=\"1\" y=\"1\" width=\"18\" height=\"18\"></rect>\n <polyline class=\"checkbox-check\" points=\"4 11 8 15 16 6\"></polyline>\n <line class=\"checkbox-dash\" x1=\"5\" y1=\"10\" x2=\"15\" y2=\"10\"></line>\n </svg>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2674
2745
  }
2675
2746
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniCheckboxComponent, decorators: [{
2676
2747
  type: Component,
2677
2748
  args: [{ selector: 'uni-checkbox', imports: [UniTextDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'checkbox' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<label [class]=\"checkboxLabel()\">\n <input\n type=\"checkbox\"\n [class]=\"checkboxInput()\"\n [checked]=\"checked()\"\n [indeterminate]=\"indeterminate()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"checkbox\">\n <svg viewBox=\"0 0 20 20\" aria-hidden=\"true\">\n <rect class=\"checkbox-box\" x=\"1\" y=\"1\" width=\"18\" height=\"18\"></rect>\n <polyline class=\"checkbox-check\" points=\"4 11 8 15 16 6\"></polyline>\n <line class=\"checkbox-dash\" x1=\"5\" y1=\"10\" x2=\"15\" y2=\"10\"></line>\n </svg>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n" }]
2678
- }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }, { type: i0.Output, args: ["indeterminateChange"] }] } });
2749
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], checkedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkedColor", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }, { type: i0.Output, args: ["indeterminateChange"] }] } });
2679
2750
 
2680
2751
  /**
2681
2752
  * Whether the browser can keep a top-layer popup attached to its field.
@@ -5722,7 +5793,7 @@ class UniMultiSelectDropdownComponent extends BaseComponent {
5722
5793
  });
5723
5794
  }
5724
5795
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5725
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled() || !!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "containerColor"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "directive", type: UniStackDirective, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5796
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectDropdownComponent, isStandalone: true, selector: "uni-multi-select-dropdown", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { properties: { "class": "className" } }, providers: [{ provide: COMPONENT_NAME, useValue: 'multiSelectDropdown' }], viewQueries: [{ propertyName: "optionRefs", predicate: ["optionRow"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<button\n [class]=\"triggerClass\"\n #trigger\n [style.cursor]=\"disabled() ? 'default' : 'pointer'\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n (click)=\"!disabled() && touched.set(true)\"\n>\n @if (label(); as fieldLabel) {\n <span [class]=\"srOnly\">{{ fieldLabel }},</span>\n }\n <span [class]=\"srOnly\">{{ selectionSummary() }}</span>\n <uni-input-box [error]=\"showError()\" [disabled]=\"disabled()\">\n <div\n row-layout\n alignItems=\"center\"\n justifyContent=\"space-between\"\n [fullWidth]=\"true\"\n [minWidth]=\"0\"\n paddingLeft=\"sm\"\n >\n <span uni-text\n display=\"block\"\n [typeface]=\"componentOptions().textRole\"\n [color]=\"textColor()\"\n [ellipsis]=\"true\"\n [style.flex-grow]=\"1\"\n >{{ selectedLabelsText() }}</span>\n <uni-symbol name=\"keyboard_arrow_down\" [style.flex-shrink]=\"0\"></uni-symbol>\n </div>\n </uni-input-box>\n</button>\n<uni-dropdown\n #dropdown\n [trigger]=\"trigger\"\n ariaHasPopup=\"dialog\"\n (dropdownShowing)=\"$event && searchInput.focus()\"\n (dropdownHiding)=\"$event && trigger.focus()\"\n>\n <!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n <div (keydown)=\"onPanelKeydown($event)\">\n <div box-layout gap=\"xs\" padding=\"xs\">\n <input\n #searchInput\n type=\"text\"\n [class]=\"searchInputClass()\"\n [value]=\"query()\"\n (input)=\"handleQueryInput($event)\"\n placeholder=\"Search...\"\n aria-label=\"Filter options\"\n (click)=\"$event.stopPropagation()\"\n [disabled]=\"disabled()\"\n />\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div\n stack-layout\n gap=\"xs\"\n padding=\"xs\"\n role=\"group\"\n [attr.aria-label]=\"label() ? label() + ' options' : 'Options'\"\n >\n @for (option of filteredOptions(); track option.value; let i = $index) {\n <div #optionRow (focusin)=\"onOptionFocus(i)\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"isOptionSelected(option)()\"\n (checkedChange)=\"toggleOption(option, $event)\"\n variant=\"primary\"\n [disabled]=\"disabled() || !!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n @if (filteredOptions().length === 0) {\n <span uni-text=\"label\" role=\"status\">No options match.</span>\n }\n </div>\n\n <uni-divider [border]=\"componentOptions().dividerBorder\" />\n\n <div row-layout gap=\"xs\" justifyContent=\"space-around\" padding=\"xs\">\n <button text-button (click)=\"dropdown.hideDropdown()\" size=\"sm\" variant=\"ghost\">Done</button>\n </div>\n </div>\n</uni-dropdown>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "checkedColor", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }, { kind: "component", type: UniDropdownComponent, selector: "uni-dropdown", inputs: ["trigger", "placement", "offset", "ariaHasPopup", "paddingVertical", "paddingHorizontal", "border", "borderRadius", "shadow", "containerColor"], outputs: ["dropdownShowing", "dropdownHiding"] }, { kind: "directive", type: UniStackDirective, selector: "[uni-stack-layout], [stack-layout]", inputs: ["display", "flexDirection", "minHeight"] }, { kind: "component", type: UniDividerComponent, selector: "uni-divider", inputs: ["orientation", "border"] }, { kind: "component", type: UniButtonComponent, selector: "button[uni-text-button], button[text-button]", inputs: ["disable", "loading", "fullWidth", "symbolLeft", "symbolRight"] }, { kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }, { kind: "component", type: UniSymbolComponent, selector: "uni-symbol", inputs: ["name", "fill", "weight", "grade", "opticalSize"] }, { kind: "directive", type: UniRowDirective, selector: "[uni-row-layout], [row-layout]", inputs: ["display", "flexDirection", "minWidth"] }, { kind: "component", type: UniInputBoxComponent, selector: "uni-input-box", inputs: ["disabled", "error", "minWidth", "height", "width", "fullWidth", "grow", "managedInset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5726
5797
  }
5727
5798
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectDropdownComponent, decorators: [{
5728
5799
  type: Component,
@@ -5788,7 +5859,7 @@ class UniMultiSelectComponent {
5788
5859
  width: '100%',
5789
5860
  });
5790
5861
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5791
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectComponent, isStandalone: true, selector: "uni-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, checkboxGap: { classPropertyName: "checkboxGap", publicName: "checkboxGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { updates: "updates" }, host: { properties: { "class": "className" } }, ngImport: i0, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n [disabled]=\"!!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5862
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniMultiSelectComponent, isStandalone: true, selector: "uni-multi-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, selections: { classPropertyName: "selections", publicName: "selections", isSignal: true, isRequired: false, transformFunction: null }, flexDirection: { classPropertyName: "flexDirection", publicName: "flexDirection", isSignal: true, isRequired: false, transformFunction: null }, checkboxGap: { classPropertyName: "checkboxGap", publicName: "checkboxGap", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { updates: "updates" }, host: { properties: { "class": "className" } }, ngImport: i0, template: "<div box-layout display=\"flex\" [flexDirection]=\"flexDirection()\" [gap]=\"checkboxGap()\">\n <!-- track $index: option values may be objects and consumers rebuild the\n array, so identity tracking recreated every node (NG0956). -->\n @for (option of options(); track $index) {\n <div [class]=\"optionWrapper\">\n <uni-checkbox\n [label]=\"option.label\"\n [checked]=\"values().indexOf(option.value) > -1\"\n (checkedChange)=\"handleCheck($event, option.value)\"\n variant=\"primary\"\n [disabled]=\"!!option.disabled\"\n >\n </uni-checkbox>\n </div>\n }\n</div>\n", dependencies: [{ kind: "component", type: UniCheckboxComponent, selector: "uni-checkbox", inputs: ["checked", "disabled", "touched", "invalid", "dirty", "required", "checkedColor", "ariaDescribedBy", "label", "indeterminate"], outputs: ["checkedChange", "touchedChange", "indeterminateChange"] }, { kind: "directive", type: UniBoxDirective, selector: "[uni-box-layout], [box-layout]", inputs: ["containerColor", "backgroundColor", "borderRadius", "borderRadiusLeft", "borderRadiusRight", "borderRadiusTop", "borderRadiusBottom", "padding", "paddingHorizontal", "paddingVertical", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom", "border", "borderTop", "borderBottom", "borderLeft", "borderRight", "dashBorder", "alignSelf", "alignItems", "alignContent", "justifyContent", "grow", "flex", "shrink", "basis", "marginInline", "display", "position", "inset", "height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "ignoreDir", "gridArea", "gridColumn", "gridRow", "overflow", "shadow", "gap", "fullWidth", "fullHeight", "flexDirection", "textAlign", "wrapItems", "zIndex"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5792
5863
  }
5793
5864
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniMultiSelectComponent, decorators: [{
5794
5865
  type: Component,
@@ -5804,6 +5875,12 @@ class UniRadioComponent extends BaseComponent {
5804
5875
  dirty = input(false, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
5805
5876
  /** Synced from required() validators by the Signal Forms [formField] directive. */
5806
5877
  required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
5878
+ /**
5879
+ * Accent colour token, overriding the variant's themed accent for one
5880
+ * instance. Mirrors the input of the same name on `uni-checkbox` and
5881
+ * `uni-toggle`.
5882
+ */
5883
+ checkedColor = input(...(ngDevMode ? [undefined, { debugName: "checkedColor" }] : /* istanbul ignore next */ []));
5807
5884
  /**
5808
5885
  * Id(s) of external element(s) describing this control — typically your
5809
5886
  * app-rendered error message — exposed as aria-describedby.
@@ -5858,18 +5935,18 @@ class UniRadioComponent extends BaseComponent {
5858
5935
  height: outerCircleSize,
5859
5936
  borderRadius: '50%',
5860
5937
  border: `2px solid ${this.disabled()
5861
- ? this.getThemeColor('on-disabled')
5862
- : this.getThemeColor(this.componentOptions().ringColor ?? 'outline')}`,
5938
+ ? this.color('on-disabled')
5939
+ : this.color(this.componentOptions().ringColor ?? 'outline')}`,
5863
5940
  position: 'relative',
5864
5941
  transition: ringTransition,
5865
- backgroundColor: this.getThemeColor(this.componentOptions().fillColor ?? 'surface'),
5942
+ backgroundColor: this.color(this.componentOptions().fillColor ?? 'surface'),
5866
5943
  flexShrink: 0,
5867
5944
  },
5868
5945
  '& .radio-inner': {
5869
5946
  width: innerCircleSize,
5870
5947
  height: innerCircleSize,
5871
5948
  borderRadius: '50%',
5872
- backgroundColor: this.getThemeColor(this.variant()),
5949
+ backgroundColor: this.accent(),
5873
5950
  position: 'absolute',
5874
5951
  top: innerCircleOffset,
5875
5952
  left: innerCircleOffset,
@@ -5879,13 +5956,13 @@ class UniRadioComponent extends BaseComponent {
5879
5956
  '&:hover .radio-button': this.disabled()
5880
5957
  ? {}
5881
5958
  : {
5882
- borderColor: this.getThemeColor(this.variant()),
5959
+ borderColor: this.accent(),
5883
5960
  },
5884
5961
  '&.disabled': {
5885
5962
  cursor: 'not-allowed',
5886
5963
  opacity: 0.6,
5887
5964
  '& .radio-button': {
5888
- borderColor: this.getThemeColor('on-disabled'),
5965
+ borderColor: this.color('on-disabled'),
5889
5966
  },
5890
5967
  },
5891
5968
  });
@@ -5897,31 +5974,44 @@ class UniRadioComponent extends BaseComponent {
5897
5974
  height: 0,
5898
5975
  opacity: 0,
5899
5976
  '&:checked + .radio-button': {
5900
- borderColor: this.getThemeColor(this.variant()),
5977
+ borderColor: this.accent(),
5901
5978
  },
5902
5979
  '&:checked + .radio-button .radio-inner': {
5903
5980
  transform: 'scale(1)',
5904
5981
  },
5905
5982
  // The shared, themable focus indicator, keyed off the hidden input.
5906
5983
  '&:focus + .radio-button': {
5907
- ...this.theme.focusRingStyle(this.getThemeColor(this.variant())),
5984
+ ...this.theme.focusRingStyle(this.accent()),
5908
5985
  },
5909
5986
  }), ...(ngDevMode ? [{ debugName: "radioInputClass" }] : /* istanbul ignore next */ []));
5910
5987
  handleRadioChange(optionValue) {
5911
5988
  this.value.set(optionValue);
5912
5989
  this.markAsTouched();
5913
5990
  }
5914
- getThemeColor(token) {
5915
- const colors = this.theme.colors();
5916
- return colors[token] ? colors[token] : colors['primary'];
5991
+ /**
5992
+ * The accent colour, from the theme's variant roles rather than by treating
5993
+ * the variant name as a colour token see `uni-checkbox` for why that had
5994
+ * to stop. `primary` is the last resort: a reserved variant name.
5995
+ */
5996
+ /**
5997
+ * A chrome colour by token. Unlike the `getThemeColor` this replaces, there
5998
+ * is no silent fallback to primary: these are tokens the theme is required
5999
+ * to define, so a miss should be visible rather than disguised.
6000
+ */
6001
+ color(token) {
6002
+ return this.theme.colors()[token];
5917
6003
  }
6004
+ accent = computed(() => {
6005
+ const accent = this.checkedColor() ?? this.variantRoles()?.accent ?? 'primary';
6006
+ return this.theme.colors()[accent];
6007
+ }, ...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
5918
6008
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniRadioComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
5919
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniRadioComponent, isStandalone: true, selector: "uni-radio", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'radio' }], usesInheritance: true, ngImport: i0, template: "<div\n [class]=\"radioGroupClass\"\n role=\"radiogroup\"\n [attr.aria-labelledby]=\"label() ? groupLabelId : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n>\n @if (label()) {\n <span uni-text=\"label\" [attr.id]=\"groupLabelId\">{{ label() }}</span>\n }\n\n @for (option of options(); track option.value) {\n <label [class]=\"radioOptionClass()\" [class.disabled]=\"disabled() || option.disabled\">\n <input\n type=\"radio\"\n [class]=\"radioInputClass()\"\n [value]=\"option.value\"\n [name]=\"name()\"\n [checked]=\"value() === option.value\"\n [disabled]=\"disabled() || option.disabled\"\n (change)=\"handleRadioChange(option.value)\"\n />\n <div class=\"radio-button\">\n <div class=\"radio-inner\"></div>\n </div>\n <span uni-text=\"label\">{{ option.label }}</span>\n </label>\n }\n</div>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6009
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniRadioComponent, isStandalone: true, selector: "uni-radio", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, checkedColor: { classPropertyName: "checkedColor", publicName: "checkedColor", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'radio' }], usesInheritance: true, ngImport: i0, template: "<div\n [class]=\"radioGroupClass\"\n role=\"radiogroup\"\n [attr.aria-labelledby]=\"label() ? groupLabelId : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n>\n @if (label()) {\n <span uni-text=\"label\" [attr.id]=\"groupLabelId\">{{ label() }}</span>\n }\n\n @for (option of options(); track option.value) {\n <label [class]=\"radioOptionClass()\" [class.disabled]=\"disabled() || option.disabled\">\n <input\n type=\"radio\"\n [class]=\"radioInputClass()\"\n [value]=\"option.value\"\n [name]=\"name()\"\n [checked]=\"value() === option.value\"\n [disabled]=\"disabled() || option.disabled\"\n (change)=\"handleRadioChange(option.value)\"\n />\n <div class=\"radio-button\">\n <div class=\"radio-inner\"></div>\n </div>\n <span uni-text=\"label\">{{ option.label }}</span>\n </label>\n }\n</div>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5920
6010
  }
5921
6011
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniRadioComponent, decorators: [{
5922
6012
  type: Component,
5923
6013
  args: [{ selector: 'uni-radio', imports: [UniTextDirective], providers: [{ provide: COMPONENT_NAME, useValue: 'radio' }], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [class]=\"radioGroupClass\"\n role=\"radiogroup\"\n [attr.aria-labelledby]=\"label() ? groupLabelId : null\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n>\n @if (label()) {\n <span uni-text=\"label\" [attr.id]=\"groupLabelId\">{{ label() }}</span>\n }\n\n @for (option of options(); track option.value) {\n <label [class]=\"radioOptionClass()\" [class.disabled]=\"disabled() || option.disabled\">\n <input\n type=\"radio\"\n [class]=\"radioInputClass()\"\n [value]=\"option.value\"\n [name]=\"name()\"\n [checked]=\"value() === option.value\"\n [disabled]=\"disabled() || option.disabled\"\n (change)=\"handleRadioChange(option.value)\"\n />\n <div class=\"radio-button\">\n <div class=\"radio-inner\"></div>\n </div>\n <span uni-text=\"label\">{{ option.label }}</span>\n </label>\n }\n</div>\n" }]
5924
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
6014
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], checkedColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkedColor", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
5925
6015
 
5926
6016
  /**
5927
6017
  * Text input that emits `change` only after the user pauses typing. Wears the
@@ -8845,8 +8935,20 @@ class UniToggleComponent extends BaseComponent {
8845
8935
  const inset = Number(size['padding'] ?? (height - height * 0.8) / 2);
8846
8936
  return geometry(width, height, inset);
8847
8937
  }, ...(ngDevMode ? [{ debugName: "metrics" }] : /* istanbul ignore next */ []));
8848
- /** The resolved checked/accent color: input, then theme option, then variant. */
8849
- accent = computed(() => this.checkedColor() ?? this.componentOptions().checkedColor ?? this.variant(), ...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
8938
+ /**
8939
+ * The resolved checked/accent color: the per-instance input, then the
8940
+ * variant's themed accent, then the theme option.
8941
+ *
8942
+ * The last link used to be `this.variant()` — the variant *name* resolved as
8943
+ * a colour token. That only ever worked because every variant happened to
8944
+ * also be a colour; with the registry open, `variant="destructive"` would
8945
+ * have missed and silently rendered primary. `primary` is the last resort
8946
+ * because it is a reserved variant name.
8947
+ */
8948
+ accent = computed(() => this.checkedColor() ??
8949
+ this.variantRoles()?.accent ??
8950
+ this.componentOptions().checkedColor ??
8951
+ 'primary', ...(ngDevMode ? [{ debugName: "accent" }] : /* istanbul ignore next */ []));
8850
8952
  /** Knob slide and track color change, as a motion token — never `all`. */
8851
8953
  transitions = computed(() => {
8852
8954
  const motion = this.theme.motion(this.componentOptions().motion ?? 'control');
@@ -8872,8 +8974,8 @@ class UniToggleComponent extends BaseComponent {
8872
8974
  width,
8873
8975
  height,
8874
8976
  backgroundColor: this.disabled()
8875
- ? this.getThemeColor('disabled')
8876
- : this.getThemeColor(this.componentOptions().trackColor ?? 'surface-variant'),
8977
+ ? this.color('disabled')
8978
+ : this.color(this.componentOptions().trackColor ?? 'surface-variant'),
8877
8979
  borderRadius: radius,
8878
8980
  position: 'relative',
8879
8981
  transition: this.transitions().track,
@@ -8881,7 +8983,7 @@ class UniToggleComponent extends BaseComponent {
8881
8983
  '& .toggle-slider': {
8882
8984
  width: knob,
8883
8985
  height: knob,
8884
- backgroundColor: this.getThemeColor(this.componentOptions().knobColor ?? 'surface'),
8986
+ backgroundColor: this.color(this.componentOptions().knobColor ?? 'surface'),
8885
8987
  borderRadius: '50%',
8886
8988
  position: 'absolute',
8887
8989
  top: inset,
@@ -8899,7 +9001,7 @@ class UniToggleComponent extends BaseComponent {
8899
9001
  }, ...(ngDevMode ? [{ debugName: "toggleLabel" }] : /* istanbul ignore next */ []));
8900
9002
  toggleInput = computed(() => {
8901
9003
  const { travel } = this.metrics();
8902
- const accent = this.getThemeColor(this.accent());
9004
+ const accent = this.color(this.accent());
8903
9005
  return css({
8904
9006
  position: 'absolute',
8905
9007
  zIndex: -1,
@@ -8924,9 +9026,9 @@ class UniToggleComponent extends BaseComponent {
8924
9026
  },
8925
9027
  });
8926
9028
  }, ...(ngDevMode ? [{ debugName: "toggleInput" }] : /* istanbul ignore next */ []));
8927
- getThemeColor(token) {
8928
- const colors = this.theme.colors();
8929
- return colors[token] ? colors[token] : colors['primary'];
9029
+ /** A chrome colour by token — no silent fallback; see `uni-radio`. */
9030
+ color(token) {
9031
+ return this.theme.colors()[token];
8930
9032
  }
8931
9033
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: UniToggleComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
8932
9034
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: UniToggleComponent, isStandalone: true, selector: "uni-toggle", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, checkedColor: { classPropertyName: "checkedColor", publicName: "checkedColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", touched: "touchedChange" }, providers: [{ provide: COMPONENT_NAME, useValue: 'toggle' }], usesInheritance: true, ngImport: i0, template: "<label [class]=\"toggleLabel()\">\n <input\n type=\"checkbox\"\n role=\"switch\"\n [class]=\"toggleInput()\"\n [checked]=\"checked()\"\n (change)=\"handleChange($event)\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"showError() ? true : null\"\n [attr.aria-required]=\"required() ? true : null\"\n [attr.aria-describedby]=\"ariaDescribedBy() || null\"\n />\n <div class=\"toggle-switch\">\n <div class=\"toggle-slider\"></div>\n </div>\n @if (label()) {\n <span uni-text=\"label\">{{ label() }}</span>\n }\n</label>\n", dependencies: [{ kind: "directive", type: UniTextDirective, selector: "[uni-text]", inputs: ["uni-text", "typeface", "color", "display", "align", "nowrap", "maxWidth", "ellipsis"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });