ngwr 12.0.0 → 12.2.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.
Files changed (155) hide show
  1. package/README.md +79 -20
  2. package/alert/styles/_index.scss +11 -4
  3. package/badge/styles/_index.scss +30 -0
  4. package/badge/styles/_tag.scss +15 -1
  5. package/descriptions/styles/_index.scss +13 -3
  6. package/drawer/styles/_index.scss +95 -1
  7. package/fesm2022/ngwr-action-sheet.mjs +3 -3
  8. package/fesm2022/ngwr-affix.mjs +3 -3
  9. package/fesm2022/ngwr-alert.mjs +3 -3
  10. package/fesm2022/ngwr-anchor.mjs +3 -3
  11. package/fesm2022/ngwr-aurora.mjs +3 -3
  12. package/fesm2022/ngwr-avatar.mjs +3 -3
  13. package/fesm2022/ngwr-back-top.mjs +3 -3
  14. package/fesm2022/ngwr-badge.mjs +6 -6
  15. package/fesm2022/ngwr-bar-chart.mjs +3 -3
  16. package/fesm2022/ngwr-blur-text.mjs +3 -3
  17. package/fesm2022/ngwr-border-glow.mjs +3 -3
  18. package/fesm2022/ngwr-breadcrumbs.mjs +6 -6
  19. package/fesm2022/ngwr-burger.mjs +3 -3
  20. package/fesm2022/ngwr-button.mjs +6 -6
  21. package/fesm2022/ngwr-calendar-heatmap.mjs +3 -3
  22. package/fesm2022/ngwr-calendar.mjs +3 -3
  23. package/fesm2022/ngwr-card.mjs +9 -9
  24. package/fesm2022/ngwr-carousel.mjs +6 -6
  25. package/fesm2022/ngwr-cascader.mjs +3 -3
  26. package/fesm2022/ngwr-checkbox.mjs +6 -6
  27. package/fesm2022/ngwr-circular-text.mjs +3 -3
  28. package/fesm2022/ngwr-click-spark.mjs +3 -3
  29. package/fesm2022/ngwr-clipboard.mjs +3 -3
  30. package/fesm2022/ngwr-collapse.mjs +9 -9
  31. package/fesm2022/ngwr-color-picker.mjs +6 -6
  32. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  33. package/fesm2022/ngwr-command-palette.mjs +3 -3
  34. package/fesm2022/ngwr-compare.mjs +3 -3
  35. package/fesm2022/ngwr-confetti.mjs +3 -3
  36. package/fesm2022/ngwr-context-menu.mjs +12 -12
  37. package/fesm2022/ngwr-cookie.mjs +3 -3
  38. package/fesm2022/ngwr-counter.mjs +6 -6
  39. package/fesm2022/ngwr-date-adapters-fns.mjs +3 -3
  40. package/fesm2022/ngwr-date-adapters-luxon.mjs +3 -3
  41. package/fesm2022/ngwr-date-picker.mjs +18 -18
  42. package/fesm2022/ngwr-date.mjs +3 -3
  43. package/fesm2022/ngwr-decrypt-text.mjs +3 -3
  44. package/fesm2022/ngwr-density.mjs +6 -6
  45. package/fesm2022/ngwr-descriptions.mjs +6 -6
  46. package/fesm2022/ngwr-dialog.mjs +15 -15
  47. package/fesm2022/ngwr-directives.mjs +12 -12
  48. package/fesm2022/ngwr-divider.mjs +3 -3
  49. package/fesm2022/ngwr-donut-chart.mjs +3 -3
  50. package/fesm2022/ngwr-drag-drop.mjs +6 -6
  51. package/fesm2022/ngwr-drawer.mjs +24 -18
  52. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  53. package/fesm2022/ngwr-dropdown.mjs +9 -9
  54. package/fesm2022/ngwr-empty.mjs +3 -3
  55. package/fesm2022/ngwr-event-calendar.mjs +6 -6
  56. package/fesm2022/ngwr-falling-text.mjs +3 -3
  57. package/fesm2022/ngwr-file-upload.mjs +3 -3
  58. package/fesm2022/ngwr-form.mjs +9 -9
  59. package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
  60. package/fesm2022/ngwr-gauge.mjs +3 -3
  61. package/fesm2022/ngwr-glitch-text.mjs +3 -3
  62. package/fesm2022/ngwr-gradient-text.mjs +3 -3
  63. package/fesm2022/ngwr-hotkey.mjs +6 -6
  64. package/fesm2022/ngwr-i18n.mjs +9 -9
  65. package/fesm2022/ngwr-icon.mjs +6 -6
  66. package/fesm2022/ngwr-image-cropper.mjs +3 -3
  67. package/fesm2022/ngwr-input-number.mjs +3 -3
  68. package/fesm2022/ngwr-input-otp.mjs +3 -3
  69. package/fesm2022/ngwr-input.mjs +15 -15
  70. package/fesm2022/ngwr-keyboard.mjs +3 -3
  71. package/fesm2022/ngwr-knob.mjs +3 -3
  72. package/fesm2022/ngwr-layout.mjs +15 -15
  73. package/fesm2022/ngwr-lightbox.mjs +3 -3
  74. package/fesm2022/ngwr-line-chart.mjs +3 -3
  75. package/fesm2022/ngwr-list.mjs +6 -6
  76. package/fesm2022/ngwr-loading-bar.mjs +6 -6
  77. package/fesm2022/ngwr-markdown.mjs +6 -6
  78. package/fesm2022/ngwr-marquee.mjs +3 -3
  79. package/fesm2022/ngwr-media.mjs +3 -3
  80. package/fesm2022/ngwr-mention.mjs +6 -6
  81. package/fesm2022/ngwr-meta.mjs +6 -6
  82. package/fesm2022/ngwr-meter-group.mjs +3 -3
  83. package/fesm2022/ngwr-overlay.mjs +6 -6
  84. package/fesm2022/ngwr-page-header.mjs +3 -3
  85. package/fesm2022/ngwr-pagination.mjs +72 -9
  86. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  87. package/fesm2022/ngwr-pipes.mjs +21 -21
  88. package/fesm2022/ngwr-platform.mjs +9 -9
  89. package/fesm2022/ngwr-popconfirm.mjs +6 -6
  90. package/fesm2022/ngwr-popover.mjs +6 -6
  91. package/fesm2022/ngwr-progress.mjs +3 -3
  92. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  93. package/fesm2022/ngwr-qr.mjs +3 -3
  94. package/fesm2022/ngwr-radio.mjs +6 -6
  95. package/fesm2022/ngwr-rating.mjs +3 -3
  96. package/fesm2022/ngwr-result.mjs +12 -12
  97. package/fesm2022/ngwr-rotating-text.mjs +3 -3
  98. package/fesm2022/ngwr-scroll.mjs +3 -3
  99. package/fesm2022/ngwr-segmented-testing.mjs +71 -0
  100. package/fesm2022/ngwr-segmented-testing.mjs.map +1 -1
  101. package/fesm2022/ngwr-segmented.mjs +98 -8
  102. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  103. package/fesm2022/ngwr-select.mjs +22 -12
  104. package/fesm2022/ngwr-select.mjs.map +1 -1
  105. package/fesm2022/ngwr-shiny-text.mjs +6 -6
  106. package/fesm2022/ngwr-sidebar.mjs +3 -3
  107. package/fesm2022/ngwr-skeleton.mjs +3 -3
  108. package/fesm2022/ngwr-slider.mjs +3 -3
  109. package/fesm2022/ngwr-sparkline.mjs +3 -3
  110. package/fesm2022/ngwr-speed-dial.mjs +3 -3
  111. package/fesm2022/ngwr-spinner.mjs +3 -3
  112. package/fesm2022/ngwr-splash-cursor.mjs +3 -3
  113. package/fesm2022/ngwr-split-text.mjs +3 -3
  114. package/fesm2022/ngwr-splitter.mjs +3 -3
  115. package/fesm2022/ngwr-spotlight-card.mjs +6 -6
  116. package/fesm2022/ngwr-squircle.mjs +6 -6
  117. package/fesm2022/ngwr-star-border.mjs +3 -3
  118. package/fesm2022/ngwr-statistic.mjs +9 -9
  119. package/fesm2022/ngwr-stepper.mjs +6 -6
  120. package/fesm2022/ngwr-storage.mjs +3 -3
  121. package/fesm2022/ngwr-switch.mjs +3 -3
  122. package/fesm2022/ngwr-table.mjs +18 -18
  123. package/fesm2022/ngwr-tabs.mjs +6 -6
  124. package/fesm2022/ngwr-textarea.mjs +3 -3
  125. package/fesm2022/ngwr-theme.mjs +3 -3
  126. package/fesm2022/ngwr-tilt-card.mjs +6 -6
  127. package/fesm2022/ngwr-timeline.mjs +6 -6
  128. package/fesm2022/ngwr-toast.mjs +9 -9
  129. package/fesm2022/ngwr-toolbar.mjs +3 -3
  130. package/fesm2022/ngwr-tour.mjs +6 -6
  131. package/fesm2022/ngwr-transfer.mjs +3 -3
  132. package/fesm2022/ngwr-tree.mjs +3 -3
  133. package/fesm2022/ngwr-typewriter.mjs +3 -3
  134. package/fesm2022/ngwr-typography.mjs +3 -3
  135. package/fesm2022/ngwr-version.mjs +1 -1
  136. package/fesm2022/ngwr-version.mjs.map +1 -1
  137. package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
  138. package/fesm2022/ngwr-waves.mjs +3 -3
  139. package/fesm2022/ngwr-window.mjs +12 -12
  140. package/llms-full.txt +1 -1
  141. package/llms.txt +24 -2
  142. package/overlay/styles/_arrow.scss +243 -0
  143. package/package.json +1 -1
  144. package/popconfirm/styles/_index.scss +17 -0
  145. package/popover/styles/_index.scss +49 -207
  146. package/select/styles/_index.scss +30 -4
  147. package/sidebar/styles/_index.scss +4 -1
  148. package/stepper/styles/_index.scss +44 -5
  149. package/theme/styles/_mixins.scss +10 -0
  150. package/tree/styles/_index.scss +10 -2
  151. package/types/ngwr-pagination.d.ts +11 -0
  152. package/types/ngwr-segmented-testing.d.ts +44 -0
  153. package/types/ngwr-segmented.d.ts +86 -4
  154. package/types/ngwr-select.d.ts +12 -2
  155. package/types/ngwr-version.d.ts +1 -1
@@ -1,7 +1,8 @@
1
1
  import { Directionality } from '@angular/cdk/bidi';
2
2
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
3
3
  import * as i0 from '@angular/core';
4
- import { input, model, computed, signal, afterNextRender, inject, ViewEncapsulation, Component } from '@angular/core';
4
+ import { input, model, output, inject, ElementRef, computed, signal, afterNextRender, ViewEncapsulation, Component } from '@angular/core';
5
+ import { WR_FORM_FIELD } from 'ngwr/form';
5
6
  import { WrIcon } from 'ngwr/icon';
6
7
 
7
8
  /**
@@ -14,14 +15,81 @@ class WrSegmented {
14
15
  /** The segments to render. */
15
16
  options = input.required(/* @ts-ignore */
16
17
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
17
- /** Two-way bindable selected value. */
18
+ // The model keeps the component's own `T`, and the contract is satisfied as
19
+ // `FormValueControl<T | null>` — legal because `FormField<T>` infers its type from
20
+ // the FIELD it is handed and never from the host component, so the interface's
21
+ // parameter describes this model rather than constraining the binding.
22
+ // `wr-cascader` and `wr-tree` widen theirs to `unknown` because their value SHAPE
23
+ // changes with a mode input (`TId | null` in single mode, a `readonly TId[]` in
24
+ // multi) and no one type is true of both. A segmented control has one shape, so
25
+ // widening here would only throw away what `[options]` already proved: every
26
+ // `(valueChange)="pick($event)"` in a typed app would start receiving `unknown`.
27
+ // With the default type parameter this degenerates to exactly the catalog's
28
+ // `FormValueControl<unknown>`.
29
+ /**
30
+ * The picked segment's `value`, `null` when nothing is selected. Bound by
31
+ * `[formField]`, or two-way via `[(value)]`.
32
+ */
18
33
  value = model(null, /* @ts-ignore */
19
34
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
20
- /** Disable the whole control. @default false */
35
+ /** Emitted when focus leaves the strip, so a bound field can mark itself touched. */
36
+ touch = output();
37
+ /**
38
+ * Disable the whole control. Bound automatically from the field's disabled
39
+ * state when used with `[formField]`.
40
+ *
41
+ * @default false
42
+ */
21
43
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
22
44
  /** Control size — shares the `--wr-control-*` contract. @default 'md' */
23
45
  size = input('md', /* @ts-ignore */
24
46
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
47
+ host = inject(ElementRef);
48
+ field = inject(WR_FORM_FIELD, { optional: true });
49
+ /**
50
+ * Id the surrounding `<wr-form-field>`'s `<label for>` points at, stamped on the
51
+ * FIRST segment.
52
+ *
53
+ * The field renders its label before it can see what was projected into it, so the
54
+ * id travels the other way and the control adopts it, exactly as `[wrInput]`,
55
+ * `wr-slider` and `wr-select` do. Without this the `for` named an element that was
56
+ * nowhere in the document: clicking the label did nothing.
57
+ *
58
+ * It cannot go on the host — only a labelable element can be a label's target, and
59
+ * `<wr-segmented>` is not one — and there is no single control to put it on, so it
60
+ * goes where clicking the label should land a user: the first segment, which is
61
+ * also the strip's first tab stop. The same choice `wr-slider` makes with its low
62
+ * thumb.
63
+ *
64
+ * What this does NOT change is that segment's NAME: it is named by its own label,
65
+ * and a `<label for>` cannot outrank text content in the accname order anyway.
66
+ *
67
+ * The one shape this reads wrong is a strip NESTED inside another control that is
68
+ * itself the field — `<wr-color-picker>`'s own format tabs are the only case in
69
+ * the library — where the id lands on a tab instead of on the picker. That is the
70
+ * same exposure `[wrInput]`, `wr-slider` and `wr-select` all carry, and the fix
71
+ * belongs to the outer control (shield its subtree by providing `WR_FORM_FIELD`
72
+ * as `null`), not to a strip that cannot tell whose field it is in.
73
+ */
74
+ controlId = computed(() => this.field?.controlId() ?? null, /* @ts-ignore */
75
+ ...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
76
+ /**
77
+ * The field's error message, wired to the GROUP rather than to a segment.
78
+ *
79
+ * A segmented control is one field made of several buttons, so the description
80
+ * belongs to the thing the field describes — repeated on every segment it would be
81
+ * announced once per Tab. `role="group"` supports neither state as a role-specific
82
+ * attribute, but both are ARIA globals, so this is valid where an `aria-checked`
83
+ * would not be.
84
+ *
85
+ * `aria-invalid` is keyed on the message EXISTING rather than on `errorKeys()`,
86
+ * because the field only publishes an id once it is showing something — announcing
87
+ * "invalid" while pointing at nothing is worse than staying quiet.
88
+ */
89
+ describedBy = computed(() => this.field?.describedBy() ?? null, /* @ts-ignore */
90
+ ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
91
+ ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null), /* @ts-ignore */
92
+ ...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
25
93
  /** Index of the selected option, or `-1` when nothing is selected. */
26
94
  selectedIndex = computed(() => {
27
95
  const v = this.value();
@@ -91,13 +159,35 @@ class WrSegmented {
91
159
  trackByValue(_, option) {
92
160
  return option.value;
93
161
  }
94
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
95
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrSegmented, isStandalone: true, selector: "wr-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "style": "thumbStyle()" } }, ngImport: i0, template: "<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n", dependencies: [{ kind: "component", type: WrIcon, selector: "wr-icon", inputs: ["name"] }], encapsulation: i0.ViewEncapsulation.None });
162
+ /**
163
+ * A bound field learns it may show its validation copy from `touch`, and a click
164
+ * cannot be the source: a strip tabbed into and out of without a pick left the
165
+ * field untouched forever — which is exactly the case `touched` exists for.
166
+ *
167
+ * `wr-knob` and `wr-rating` bind `(blur)` on the one element they own; this
168
+ * component owns one button per option, so it listens for the bubbling `focusout`
169
+ * on its host instead — and ignores the one that only moves focus from one segment
170
+ * to the next, the way `wr-slider` ignores the hop between its two thumbs.
171
+ */
172
+ onFocusOut(event) {
173
+ if (this.host.nativeElement.contains(event.relatedTarget))
174
+ return;
175
+ this.touch.emit();
176
+ }
177
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrSegmented, deps: [], target: i0.ɵɵFactoryTarget.Component });
178
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrSegmented, isStandalone: true, selector: "wr-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, listeners: { "focusout": "onFocusOut($event)" }, properties: { "class": "classes()", "style": "thumbStyle()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "ariaInvalid()" } }, ngImport: i0, template: "<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n<!--\n `id` on the FIRST segment only: it is the id a surrounding `<wr-form-field>`\n already points its `<label for>` at, and a `for` may only name a labelable\n element \u2014 see `controlId` in the component. `null` without a field, so a bare\n strip stamps no document-global id on anything.\n-->\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [attr.id]=\"$first ? controlId() : null\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n", dependencies: [{ kind: "component", type: WrIcon, selector: "wr-icon", inputs: ["name"] }], encapsulation: i0.ViewEncapsulation.None });
96
179
  }
97
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSegmented, decorators: [{
180
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrSegmented, decorators: [{
98
181
  type: Component,
99
- args: [{ selector: 'wr-segmented', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()', '[style]': 'thumbStyle()', role: 'group' }, imports: [WrIcon], template: "<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n" }]
100
- }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], 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 }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
182
+ args: [{ selector: 'wr-segmented', encapsulation: ViewEncapsulation.None, host: {
183
+ '[class]': 'classes()',
184
+ '[style]': 'thumbStyle()',
185
+ role: 'group',
186
+ '[attr.aria-describedby]': 'describedBy()',
187
+ '[attr.aria-invalid]': 'ariaInvalid()',
188
+ '(focusout)': 'onFocusOut($event)',
189
+ }, imports: [WrIcon], template: "<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n<!--\n `id` on the FIRST segment only: it is the id a surrounding `<wr-form-field>`\n already points its `<label for>` at, and a `for` may only name a labelable\n element \u2014 see `controlId` in the component. `null` without a field, so a bare\n strip stamps no document-global id on anything.\n-->\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [attr.id]=\"$first ? controlId() : null\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n" }]
190
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
101
191
 
102
192
  /**
103
193
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-segmented.mjs","sources":["../../../projects/lib/segmented/segmented.ts","../../../projects/lib/segmented/segmented.html","../../../projects/lib/segmented/ngwr-segmented.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, afterNextRender, computed, inject, input, model, signal } from '@angular/core';\n\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSegmentedOption } from './interfaces';\n\n/**\n * Single-choice picker rendered as a segmented control. Two-way binds\n * the selected `value`.\n *\n * @example\n * ```html\n * <wr-segmented\n * [options]=\"[\n * { value: 'day', label: 'Day' },\n * { value: 'week', label: 'Week' },\n * { value: 'month', label: 'Month' },\n * ]\"\n * [(value)]=\"range\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/segmented\n */\nexport type WrSegmentedSize = 'sm' | 'md' | 'lg';\n\n@Component({\n selector: 'wr-segmented',\n templateUrl: './segmented.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()', '[style]': 'thumbStyle()', role: 'group' },\n imports: [WrIcon],\n})\nexport class WrSegmented<T = unknown> {\n /** The segments to render. */\n readonly options = input.required<readonly WrSegmentedOption<T>[]>();\n\n /** Two-way bindable selected value. */\n readonly value = model<T | null>(null);\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Control size — shares the `--wr-control-*` contract. @default 'md' */\n readonly size = input<WrSegmentedSize>('md');\n\n /** Index of the selected option, or `-1` when nothing is selected. */\n protected readonly selectedIndex = computed(() => {\n const v = this.value();\n return this.options().findIndex(o => o.value === v);\n });\n\n /** Flips true after the first paint so the thumb only animates user-driven changes, not the initial snap. */\n private readonly mounted = signal(false);\n\n constructor() {\n afterNextRender(() => this.mounted.set(true));\n }\n\n protected readonly classes = computed(() => {\n const parts = ['wr-segmented'];\n const size = this.size();\n if (size !== 'md') parts.push(`wr-segmented--${size}`);\n if (this.disabled()) parts.push('wr-segmented--disabled');\n if (this.selectedIndex() < 0) parts.push('wr-segmented--unselected');\n if (this.mounted()) parts.push('wr-segmented--mounted');\n return parts.join(' ');\n });\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. A subtree that overrides the direction does it with\n * the CDK's `Dir` directive, which writes `valueSignal`, so `thumbStyle`\n * re-renders instead of leaving the thumb parked on the old segment.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * Inline CSS vars driving the sliding thumb position.\n *\n * `--wr-segmented-thumb-index` is the SLOT the thumb parks in, counted from the\n * physical left, which is the selected index only in LTR. The stylesheet anchors\n * the thumb with a physical `left` and slides it with `translateX` — neither has\n * a logical form — while the options are a grid that mirrors, so under `dir=\"rtl\"`\n * the segment at logical index `i` occupies slot `count - 1 - i`. Left unsigned,\n * the pill sat under a different label than the one it marks (the whole strip's\n * worth of offset at the far end), which is the carousel's `trackStyle` problem\n * one component over.\n */\n protected readonly thumbStyle = computed<Record<string, string>>(() => {\n const i = Math.max(0, this.selectedIndex());\n const count = Math.max(1, this.options().length);\n const slot = this.isRtl() ? count - 1 - i : i;\n return {\n '--wr-segmented-thumb-index': `${slot}`,\n '--wr-segmented-thumb-count': `${count}`,\n };\n });\n\n protected isSelected(option: WrSegmentedOption<T>): boolean {\n return this.value() === option.value;\n }\n\n protected select(option: WrSegmentedOption<T>): void {\n if (this.disabled() || option.disabled || this.isSelected(option)) return;\n this.value.set(option.value);\n }\n\n protected trackByValue(_: number, option: WrSegmentedOption<T>): unknown {\n return option.value;\n }\n}\n","<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-segmented.mjs","sources":["../../../projects/lib/segmented/segmented.ts","../../../projects/lib/segmented/segmented.html","../../../projects/lib/segmented/ngwr-segmented.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WR_FORM_FIELD } from 'ngwr/form';\nimport { WrIcon } from 'ngwr/icon';\n\nimport type { WrSegmentedOption } from './interfaces';\n\n/**\n * Single-choice picker rendered as a segmented control. Two-way binds\n * the selected `value`.\n *\n * A signal-forms native control: it implements `FormValueControl<T | null>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone, and classic\n * `[(ngModel)]` / reactive forms keep working through Angular's bridge.\n *\n * It stays a `role=\"group\"` of `aria-pressed` toggle buttons rather than\n * becoming a `radiogroup`: being a form control changes what the value is bound\n * to, not what the widget is, and a radiogroup would owe a roving tab stop and\n * arrow-key selection — a different keyboard contract for every consumer who\n * already ships this. The group is what the field describes (`aria-describedby`\n * / `aria-invalid` land on the host), while the `<label for>` has to name a\n * labelable element, so the id goes on the first segment.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-segmented [options]=\"ranges\" [formField]=\"form.range\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-segmented\n * [options]=\"[\n * { value: 'day', label: 'Day' },\n * { value: 'week', label: 'Week' },\n * { value: 'month', label: 'Month' },\n * ]\"\n * [(value)]=\"range\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/segmented\n */\nexport type WrSegmentedSize = 'sm' | 'md' | 'lg';\n\n@Component({\n selector: 'wr-segmented',\n templateUrl: './segmented.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style]': 'thumbStyle()',\n role: 'group',\n '[attr.aria-describedby]': 'describedBy()',\n '[attr.aria-invalid]': 'ariaInvalid()',\n '(focusout)': 'onFocusOut($event)',\n },\n imports: [WrIcon],\n})\nexport class WrSegmented<T = unknown> implements FormValueControl<T | null> {\n /** The segments to render. */\n readonly options = input.required<readonly WrSegmentedOption<T>[]>();\n\n // The model keeps the component's own `T`, and the contract is satisfied as\n // `FormValueControl<T | null>` — legal because `FormField<T>` infers its type from\n // the FIELD it is handed and never from the host component, so the interface's\n // parameter describes this model rather than constraining the binding.\n // `wr-cascader` and `wr-tree` widen theirs to `unknown` because their value SHAPE\n // changes with a mode input (`TId | null` in single mode, a `readonly TId[]` in\n // multi) and no one type is true of both. A segmented control has one shape, so\n // widening here would only throw away what `[options]` already proved: every\n // `(valueChange)=\"pick($event)\"` in a typed app would start receiving `unknown`.\n // With the default type parameter this degenerates to exactly the catalog's\n // `FormValueControl<unknown>`.\n /**\n * The picked segment's `value`, `null` when nothing is selected. Bound by\n * `[formField]`, or two-way via `[(value)]`.\n */\n readonly value = model<T | null>(null);\n\n /** Emitted when focus leaves the strip, so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n /**\n * Disable the whole control. Bound automatically from the field's disabled\n * state when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Control size — shares the `--wr-control-*` contract. @default 'md' */\n readonly size = input<WrSegmentedSize>('md');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly field = inject(WR_FORM_FIELD, { optional: true });\n\n /**\n * Id the surrounding `<wr-form-field>`'s `<label for>` points at, stamped on the\n * FIRST segment.\n *\n * The field renders its label before it can see what was projected into it, so the\n * id travels the other way and the control adopts it, exactly as `[wrInput]`,\n * `wr-slider` and `wr-select` do. Without this the `for` named an element that was\n * nowhere in the document: clicking the label did nothing.\n *\n * It cannot go on the host — only a labelable element can be a label's target, and\n * `<wr-segmented>` is not one — and there is no single control to put it on, so it\n * goes where clicking the label should land a user: the first segment, which is\n * also the strip's first tab stop. The same choice `wr-slider` makes with its low\n * thumb.\n *\n * What this does NOT change is that segment's NAME: it is named by its own label,\n * and a `<label for>` cannot outrank text content in the accname order anyway.\n *\n * The one shape this reads wrong is a strip NESTED inside another control that is\n * itself the field — `<wr-color-picker>`'s own format tabs are the only case in\n * the library — where the id lands on a tab instead of on the picker. That is the\n * same exposure `[wrInput]`, `wr-slider` and `wr-select` all carry, and the fix\n * belongs to the outer control (shield its subtree by providing `WR_FORM_FIELD`\n * as `null`), not to a strip that cannot tell whose field it is in.\n */\n protected readonly controlId = computed(() => this.field?.controlId() ?? null);\n\n /**\n * The field's error message, wired to the GROUP rather than to a segment.\n *\n * A segmented control is one field made of several buttons, so the description\n * belongs to the thing the field describes — repeated on every segment it would be\n * announced once per Tab. `role=\"group\"` supports neither state as a role-specific\n * attribute, but both are ARIA globals, so this is valid where an `aria-checked`\n * would not be.\n *\n * `aria-invalid` is keyed on the message EXISTING rather than on `errorKeys()`,\n * because the field only publishes an id once it is showing something — announcing\n * \"invalid\" while pointing at nothing is worse than staying quiet.\n */\n protected readonly describedBy = computed(() => this.field?.describedBy() ?? null);\n protected readonly ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null));\n\n /** Index of the selected option, or `-1` when nothing is selected. */\n protected readonly selectedIndex = computed(() => {\n const v = this.value();\n return this.options().findIndex(o => o.value === v);\n });\n\n /** Flips true after the first paint so the thumb only animates user-driven changes, not the initial snap. */\n private readonly mounted = signal(false);\n\n constructor() {\n afterNextRender(() => this.mounted.set(true));\n }\n\n protected readonly classes = computed(() => {\n const parts = ['wr-segmented'];\n const size = this.size();\n if (size !== 'md') parts.push(`wr-segmented--${size}`);\n if (this.disabled()) parts.push('wr-segmented--disabled');\n if (this.selectedIndex() < 0) parts.push('wr-segmented--unselected');\n if (this.mounted()) parts.push('wr-segmented--mounted');\n return parts.join(' ');\n });\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. A subtree that overrides the direction does it with\n * the CDK's `Dir` directive, which writes `valueSignal`, so `thumbStyle`\n * re-renders instead of leaving the thumb parked on the old segment.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * Inline CSS vars driving the sliding thumb position.\n *\n * `--wr-segmented-thumb-index` is the SLOT the thumb parks in, counted from the\n * physical left, which is the selected index only in LTR. The stylesheet anchors\n * the thumb with a physical `left` and slides it with `translateX` — neither has\n * a logical form — while the options are a grid that mirrors, so under `dir=\"rtl\"`\n * the segment at logical index `i` occupies slot `count - 1 - i`. Left unsigned,\n * the pill sat under a different label than the one it marks (the whole strip's\n * worth of offset at the far end), which is the carousel's `trackStyle` problem\n * one component over.\n */\n protected readonly thumbStyle = computed<Record<string, string>>(() => {\n const i = Math.max(0, this.selectedIndex());\n const count = Math.max(1, this.options().length);\n const slot = this.isRtl() ? count - 1 - i : i;\n return {\n '--wr-segmented-thumb-index': `${slot}`,\n '--wr-segmented-thumb-count': `${count}`,\n };\n });\n\n protected isSelected(option: WrSegmentedOption<T>): boolean {\n return this.value() === option.value;\n }\n\n protected select(option: WrSegmentedOption<T>): void {\n if (this.disabled() || option.disabled || this.isSelected(option)) return;\n this.value.set(option.value);\n }\n\n protected trackByValue(_: number, option: WrSegmentedOption<T>): unknown {\n return option.value;\n }\n\n /**\n * A bound field learns it may show its validation copy from `touch`, and a click\n * cannot be the source: a strip tabbed into and out of without a pick left the\n * field untouched forever — which is exactly the case `touched` exists for.\n *\n * `wr-knob` and `wr-rating` bind `(blur)` on the one element they own; this\n * component owns one button per option, so it listens for the bubbling `focusout`\n * on its host instead — and ignores the one that only moves focus from one segment\n * to the next, the way `wr-slider` ignores the hop between its two thumbs.\n */\n protected onFocusOut(event: FocusEvent): void {\n if (this.host.nativeElement.contains(event.relatedTarget as Node | null)) return;\n this.touch.emit();\n }\n}\n","<span class=\"wr-segmented__thumb\" aria-hidden=\"true\"></span>\n\n<!--\n `id` on the FIRST segment only: it is the id a surrounding `<wr-form-field>`\n already points its `<label for>` at, and a `for` may only name a labelable\n element — see `controlId` in the component. `null` without a field, so a bare\n strip stamps no document-global id on anything.\n-->\n@for (option of options(); track option.value) {\n <button\n type=\"button\"\n class=\"wr-segmented__option\"\n [attr.id]=\"$first ? controlId() : null\"\n [class.wr-segmented__option--selected]=\"isSelected(option)\"\n [disabled]=\"disabled() || !!option.disabled\"\n [attr.aria-pressed]=\"isSelected(option)\"\n [attr.aria-label]=\"option.label ? null : (option.ariaLabel ?? option.value)\"\n (click)=\"select(option)\"\n >\n @if (option.icon) {\n <wr-icon class=\"wr-segmented__icon\" [name]=\"option.icon\" />\n }\n @if (option.label) {\n <span class=\"wr-segmented__label\">{{ option.label }}</span>\n }\n </button>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -123,10 +123,10 @@ class WrOption {
123
123
  return;
124
124
  this.parent.selectOption(this.value());
125
125
  }
126
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrOption, deps: [], target: i0.ɵɵFactoryTarget.Component });
127
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: WrOption, isStandalone: true, selector: "wr-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "click": "onClick()" }, properties: { "attr.id": "id", "class": "classes()", "attr.aria-selected": "selected()", "attr.aria-disabled": "disabled() ? true : null" } }, ngImport: i0, template: '<ng-content />', isInline: true, encapsulation: i0.ViewEncapsulation.None });
126
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOption, deps: [], target: i0.ɵɵFactoryTarget.Component });
127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: WrOption, isStandalone: true, selector: "wr-option", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "click": "onClick()" }, properties: { "attr.id": "id", "class": "classes()", "attr.aria-selected": "selected()", "attr.aria-disabled": "disabled() ? true : null" } }, ngImport: i0, template: '<ng-content />', isInline: true, encapsulation: i0.ViewEncapsulation.None });
128
128
  }
129
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrOption, decorators: [{
129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOption, decorators: [{
130
130
  type: Component,
131
131
  args: [{
132
132
  selector: 'wr-option',
@@ -518,10 +518,20 @@ class WrSelect {
518
518
  draft = signal('', /* @ts-ignore */
519
519
  ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
520
520
  /**
521
- * Show a clear-all (×) button at the end of the chip row when at
522
- * least one option is selected (multi mode only). @default true
521
+ * Show a clear (×) button once at least one option is selected.
522
+ *
523
+ * NOT multi-only, which this said for a long time and which the template
524
+ * refutes: only the button trigger gates on `isMulti()`, so the chips trigger
525
+ * AND a single `mode="search"` / `[searchable]` select carry it too. The
526
+ * wording mattered little while the default was `true`; it decides what a
527
+ * reader expects to lose now that it is not.
528
+ *
529
+ * The default changed from `true` to `false`: the affordance is opt-in, so a
530
+ * trigger that grew an × on its own no longer does — pass `clearable` to keep
531
+ * it. In `search` mode this also gates Backspace-to-clear on an empty field,
532
+ * since that key is the keyboard twin of this button. @default false
523
533
  */
524
- clearable = input(true, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
534
+ clearable = input(false, { ...(ngDevMode ? { debugName: "clearable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
525
535
  /**
526
536
  * Cap on selected items (multi mode). `0` = unlimited. Once reached,
527
537
  * additional clicks on unselected options are ignored. @default 0
@@ -1581,8 +1591,8 @@ class WrSelect {
1581
1591
  this.overlayRef.dispose();
1582
1592
  this.overlayRef = null;
1583
1593
  }
1584
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
1585
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: WrSelect, isStandalone: true, selector: "wr-select", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, loader: { classPropertyName: "loader", publicName: "loader", isSignal: true, isRequired: false, transformFunction: null }, serverSearch: { classPropertyName: "serverSearch", publicName: "serverSearch", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, freeText: { classPropertyName: "freeText", publicName: "freeText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxItems: { classPropertyName: "maxItems", publicName: "maxItems", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchQuery: "searchQueryChange", searchChange: "searchChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, providers: [
1594
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
1595
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrSelect, isStandalone: true, selector: "wr-select", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, clearLabel: { classPropertyName: "clearLabel", publicName: "clearLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, responsive: { classPropertyName: "responsive", publicName: "responsive", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null }, loader: { classPropertyName: "loader", publicName: "loader", isSignal: true, isRequired: false, transformFunction: null }, serverSearch: { classPropertyName: "serverSearch", publicName: "serverSearch", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, freeText: { classPropertyName: "freeText", publicName: "freeText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxItems: { classPropertyName: "maxItems", publicName: "maxItems", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { searchQuery: "searchQueryChange", searchChange: "searchChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, providers: [
1586
1596
  {
1587
1597
  provide: WR_SELECT,
1588
1598
  // eslint-disable-next-line @angular-eslint/no-forward-ref
@@ -1590,7 +1600,7 @@ class WrSelect {
1590
1600
  },
1591
1601
  ], queries: [{ propertyName: "projectedOptions", predicate: WrOption, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "tagInputEl", first: true, predicate: ["tagInput"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (isSearchTrigger()) {\n <!-- Search mode (or a searchable single): a real text input drives a filtered\n overlay panel. The wrapper isn't itself focusable \u2014 the input is the\n tabstop. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div class=\"wr-select__trigger wr-select__trigger--search\" (click)=\"focusSearchInput()\">\n <!-- `id` / `aria-describedby` / `aria-invalid` come from a surrounding\n `<wr-form-field>` and are null without one \u2014 see `controlId` in the\n component. They go on the tab stop, the only labelable element here. -->\n <input\n #searchInput\n type=\"text\"\n role=\"combobox\"\n class=\"wr-select__search-input\"\n autocomplete=\"off\"\n spellcheck=\"false\"\n [attr.id]=\"controlId()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n [placeholder]=\"resolvedPlaceholder()\"\n [value]=\"searchDisplay()\"\n (input)=\"onSearchInput($event)\"\n (focus)=\"onSearchFocus()\"\n (keydown)=\"onSearchKey($event)\"\n (blur)=\"onSearchBlur()\"\n />\n <!-- Pointer-only, and the `keydown.enter` / `keydown.space` pair this used to\n carry was dead code rather than the keyboard path it looked like: the \u00D7\n is `tabindex=\"-1\"` with no roving owner, so no key event ever originates\n here to bubble into a handler. It stays unfocusable because the same\n span sits inside the BUTTON trigger further down, where a focusable\n child is `nested-interactive` \u2014 the defect the window taskbar's close\n button already taught this repo. The keyboard reaches the function\n instead: Backspace on the empty field, which `onSearchKey` routes to\n `clearAll` for a single and to the last chip for a multi. -->\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n} @else if (isTag() || hasChipSearch()) {\n <!-- Chip trigger \u2014 tag mode, or a searchable multi. A focusable input lives\n inside, so the wrapper itself only exists to host chips and forward\n whitespace clicks to the input. Tag mode needs no role (users tab\n straight into the input); the searchable multi is a real combobox, so it\n carries the listbox wiring. The click handler is a mouse-only\n convenience. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n class=\"wr-select__trigger wr-select__trigger--tag\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n (click)=\"focusTagInput()\"\n >\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n <!-- At `maxItems` this input goes READONLY, never disabled: it is the only\n tab stop in the whole control (the chip \u00D7 and the clear button are\n `tabindex=\"-1\"` spans), so disabling it left a full tag select with\n nothing focusable at all \u2014 and Backspace on an empty draft, the one\n keyboard way back UNDER the cap, is bound to this very element. It was\n never needed as enforcement either: `tryAddTag` already refuses past\n capacity. -->\n <input\n #tagInput\n type=\"text\"\n class=\"wr-select__tag-input\"\n autocomplete=\"off\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n spellcheck=\"false\"\n [value]=\"hasChipSearch() ? searchQuery() : draft()\"\n [disabled]=\"isDisabled()\"\n [readonly]=\"isTag() && atCapacity()\"\n [placeholder]=\"hasSelection() ? '' : resolvedPlaceholder()\"\n [attr.role]=\"hasChipSearch() ? 'combobox' : null\"\n [attr.aria-haspopup]=\"hasChipSearch() ? 'listbox' : null\"\n [attr.aria-expanded]=\"hasChipSearch() ? open() : null\"\n [attr.aria-controls]=\"hasChipSearch() ? listboxId : null\"\n [attr.aria-activedescendant]=\"hasChipSearch() && open() ? activeOptionId() || null : null\"\n (input)=\"onChipsInput($event)\"\n (keydown)=\"onChipsKeydown($event)\"\n (paste)=\"onChipsPaste($event)\"\n (focus)=\"onChipsFocus()\"\n (blur)=\"onChipsBlur()\"\n />\n </span>\n\n @if (clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n </div>\n} @else {\n <button\n type=\"button\"\n class=\"wr-select__trigger\"\n role=\"combobox\"\n [attr.id]=\"controlId()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listboxId\"\n [attr.aria-activedescendant]=\"open() ? activeOptionId() || null : null\"\n (click)=\"onTriggerClick()\"\n (keydown)=\"onTriggerKey($event)\"\n (blur)=\"touch.emit()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-select__chips\">\n @for (chip of visibleChips(); track $index) {\n <span class=\"wr-select__chip\">\n <span class=\"wr-select__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-select__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip($index, $event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-select__chip wr-select__chip--more\">{{ moreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (selectedLabel(); as label) {\n <span class=\"wr-select__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-select__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (isMulti() && clearable() && hasSelection() && !isDisabled()) {\n <span\n class=\"wr-select__clear\"\n role=\"button\"\n [attr.aria-label]=\"resolvedClear()\"\n tabindex=\"-1\"\n (click)=\"clearAll($event)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-select__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n}\n\n<ng-template #panelTpl>\n <div\n class=\"wr-select-panel\"\n [class.wr-select-panel--sm]=\"resolvedSize() === 'sm'\"\n [class.wr-select-panel--lg]=\"resolvedSize() === 'lg'\"\n [class.wr-select-panel--virtual]=\"virtualActive()\"\n role=\"listbox\"\n [attr.id]=\"listboxId\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-busy]=\"isLoading() ? 'true' : null\"\n >\n <!-- `aria-busy` belongs on the listbox, not on this row: it is the container\n whose required `option` children are temporarily missing, and a global\n ARIA attribute on a role-less child makes that child an owned element the\n listbox may not own (axe `aria-required-children`, critical). Plain text\n inside the listbox is fine \u2014 only ELEMENTS are checked. -->\n @if (isLoading()) {\n <div class=\"wr-select-panel__loading\">{{ resolvedLoading() }}</div>\n }\n\n @if (virtualActive()) {\n <!-- Windowed data-array rows: plain role=\"option\" elements (not <wr-option>),\n navigated via the input's aria-activedescendant, selected by value. -->\n <div class=\"wr-select-panel__vlist\" [style.max-height]=\"resolvedViewportHeight()\">\n @if (virtualWindow().padTop > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padTop\"></div>\n }\n @for (entry of windowOptions(); track entry.index) {\n <!-- Keyboard handled by the search input via aria-activedescendant; the\n row is a pointer target only. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events,@angular-eslint/template/interactive-supports-focus -->\n <div\n role=\"option\"\n class=\"wr-option\"\n [id]=\"virtualRowId(entry.index)\"\n [class.wr-option--active]=\"entry.index === activeIndex()\"\n [class.wr-option--selected]=\"isSelected(entry.item)\"\n [attr.aria-selected]=\"isSelected(entry.item)\"\n [attr.aria-setsize]=\"filteredDynamicOptions().length\"\n [attr.aria-posinset]=\"entry.index + 1\"\n (click)=\"selectOption(entry.item)\"\n >\n {{ displayWith()(entry.item) }}\n </div>\n }\n @if (virtualWindow().padBottom > 0) {\n <div class=\"wr-select-panel__vspacer\" aria-hidden=\"true\" [style.height.px]=\"virtualWindow().padBottom\"></div>\n }\n </div>\n } @else {\n <!-- Dynamic options from [options] or async [loader] (search mode).\n Rendered as wr-option so the standard registration / filter\n pipeline applies uniformly. -->\n @for (item of dynamicOptions(); track $index) {\n <wr-option [value]=\"item\">{{ displayWith()(item) }}</wr-option>\n }\n }\n\n <!-- Projected static options \u2014 always present (a conditional `<ng-content>`\n would drop projected content). Empty whenever `virtualActive`. -->\n <ng-content />\n\n <!-- A listbox may only own `option` and `group` children, and as a bare `<div>`\n this row was the only thing in the tree when it showed \u2014 an\n `aria-required-children` critical. It cannot move out of the listbox the\n way the command palette's does: this element IS the listbox, and the id\n the trigger's `aria-controls` names is what gives it its accessible name.\n So it announces as a disabled option instead. Keyboard navigation skips\n it for free \u2014 arrow keys walk the registered `wr-option`s, and nothing\n registers this row. -->\n @if (hasNoResults()) {\n <div class=\"wr-select-panel__empty\" role=\"option\" aria-selected=\"false\" aria-disabled=\"true\">\n {{ resolvedNoResults() }}\n </div>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: WrOption, selector: "wr-option", inputs: ["value", "disabled"] }], encapsulation: i0.ViewEncapsulation.None });
1592
1602
  }
1593
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrSelect, decorators: [{
1603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrSelect, decorators: [{
1594
1604
  type: Component,
1595
1605
  args: [{ selector: 'wr-select', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrOption], providers: [
1596
1606
  {
@@ -1627,10 +1637,10 @@ class WrOptionGroup {
1627
1637
  * be wired to it for the name to reach a screen reader.
1628
1638
  */
1629
1639
  labelId = randomId('wr-option-group-label');
1630
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrOptionGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
1631
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: WrOptionGroup, isStandalone: true, selector: "wr-option-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId" }, classAttribute: "wr-option-group" }, ngImport: i0, template: "<div class=\"wr-option-group__label\" [id]=\"labelId\">{{ label() }}</div>\n<ng-content />\n", encapsulation: i0.ViewEncapsulation.None });
1640
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOptionGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
1641
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.4", type: WrOptionGroup, isStandalone: true, selector: "wr-option-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId" }, classAttribute: "wr-option-group" }, ngImport: i0, template: "<div class=\"wr-option-group__label\" [id]=\"labelId\">{{ label() }}</div>\n<ng-content />\n", encapsulation: i0.ViewEncapsulation.None });
1632
1642
  }
1633
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WrOptionGroup, decorators: [{
1643
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrOptionGroup, decorators: [{
1634
1644
  type: Component,
1635
1645
  args: [{ selector: 'wr-option-group', encapsulation: ViewEncapsulation.None, host: { class: 'wr-option-group', role: 'group', '[attr.aria-labelledby]': 'labelId' }, template: "<div class=\"wr-option-group__label\" [id]=\"labelId\">{{ label() }}</div>\n<ng-content />\n" }]
1636
1646
  }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }] } });