ngwr 12.1.0 → 13.0.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 (187) hide show
  1. package/README.md +11 -1
  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/border-glow/border-glow.scss +1 -1
  6. package/border-glow/styles/_index.scss +15 -233
  7. package/border-glow/styles/_rules.scss +228 -0
  8. package/breadcrumbs/breadcrumbs.scss +1 -1
  9. package/breadcrumbs/styles/_index.scss +15 -78
  10. package/breadcrumbs/styles/_rules.scss +84 -0
  11. package/card/card.scss +1 -1
  12. package/card/styles/_index.scss +15 -96
  13. package/card/styles/_rules.scss +96 -0
  14. package/cascader/cascader.scss +1 -1
  15. package/cascader/styles/_index.scss +15 -244
  16. package/cascader/styles/_rules.scss +261 -0
  17. package/checkbox/styles/_index.scss +7 -0
  18. package/circular-text/circular-text.scss +1 -1
  19. package/circular-text/styles/_index.scss +15 -63
  20. package/circular-text/styles/_rules.scss +58 -0
  21. package/color-picker/styles/_index.scss +8 -0
  22. package/descriptions/styles/_index.scss +13 -3
  23. package/drag-drop/sortable-list.scss +1 -1
  24. package/drag-drop/styles/_index.scss +14 -143
  25. package/drag-drop/styles/_rules.scss +151 -0
  26. package/drawer/styles/_index.scss +95 -1
  27. package/falling-text/falling-text.scss +1 -1
  28. package/falling-text/styles/_index.scss +15 -36
  29. package/falling-text/styles/_rules.scss +31 -0
  30. package/fesm2022/ngwr-border-glow.mjs +2 -2
  31. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  32. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  33. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  34. package/fesm2022/ngwr-card.mjs +2 -2
  35. package/fesm2022/ngwr-card.mjs.map +1 -1
  36. package/fesm2022/ngwr-cascader.mjs +27 -9
  37. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  38. package/fesm2022/ngwr-checkbox.mjs +86 -8
  39. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  40. package/fesm2022/ngwr-circular-text.mjs +2 -2
  41. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  42. package/fesm2022/ngwr-color-picker.mjs +57 -7
  43. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  44. package/fesm2022/ngwr-date-picker.mjs +8 -0
  45. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  46. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  47. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  48. package/fesm2022/ngwr-drawer.mjs +6 -0
  49. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  50. package/fesm2022/ngwr-falling-text.mjs +2 -2
  51. package/fesm2022/ngwr-falling-text.mjs.map +1 -1
  52. package/fesm2022/ngwr-file-upload.mjs +29 -8
  53. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  54. package/fesm2022/ngwr-form.mjs +48 -1
  55. package/fesm2022/ngwr-form.mjs.map +1 -1
  56. package/fesm2022/ngwr-glitch-text.mjs +2 -2
  57. package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
  58. package/fesm2022/ngwr-gradient-text.mjs +2 -2
  59. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  60. package/fesm2022/ngwr-input-otp.mjs +41 -3
  61. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  62. package/fesm2022/ngwr-knob.mjs +17 -2
  63. package/fesm2022/ngwr-knob.mjs.map +1 -1
  64. package/fesm2022/ngwr-lightbox.mjs +2 -2
  65. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  66. package/fesm2022/ngwr-list.mjs +2 -2
  67. package/fesm2022/ngwr-list.mjs.map +1 -1
  68. package/fesm2022/ngwr-loading-bar.mjs +2 -2
  69. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  70. package/fesm2022/ngwr-marquee.mjs +2 -2
  71. package/fesm2022/ngwr-marquee.mjs.map +1 -1
  72. package/fesm2022/ngwr-pagination.mjs +1 -1
  73. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  74. package/fesm2022/ngwr-radio.mjs +63 -8
  75. package/fesm2022/ngwr-radio.mjs.map +1 -1
  76. package/fesm2022/ngwr-rating.mjs +17 -2
  77. package/fesm2022/ngwr-rating.mjs.map +1 -1
  78. package/fesm2022/ngwr-segmented.mjs +22 -3
  79. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  80. package/fesm2022/ngwr-select.mjs +59 -15
  81. package/fesm2022/ngwr-select.mjs.map +1 -1
  82. package/fesm2022/ngwr-shiny-text.mjs +2 -2
  83. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  84. package/fesm2022/ngwr-slider.mjs +21 -6
  85. package/fesm2022/ngwr-slider.mjs.map +1 -1
  86. package/fesm2022/ngwr-spotlight-card.mjs +2 -2
  87. package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
  88. package/fesm2022/ngwr-star-border.mjs +2 -2
  89. package/fesm2022/ngwr-star-border.mjs.map +1 -1
  90. package/fesm2022/ngwr-switch.mjs +44 -4
  91. package/fesm2022/ngwr-switch.mjs.map +1 -1
  92. package/fesm2022/ngwr-table.mjs +1 -1
  93. package/fesm2022/ngwr-table.mjs.map +1 -1
  94. package/fesm2022/ngwr-textarea.mjs +5 -2
  95. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  96. package/fesm2022/ngwr-theme.mjs +41 -1
  97. package/fesm2022/ngwr-theme.mjs.map +1 -1
  98. package/fesm2022/ngwr-tilt-card.mjs +2 -2
  99. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  100. package/fesm2022/ngwr-transfer.mjs +38 -6
  101. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  102. package/fesm2022/ngwr-tree.mjs +33 -7
  103. package/fesm2022/ngwr-tree.mjs.map +1 -1
  104. package/fesm2022/ngwr-typewriter.mjs +2 -2
  105. package/fesm2022/ngwr-typewriter.mjs.map +1 -1
  106. package/fesm2022/ngwr-version.mjs +1 -1
  107. package/fesm2022/ngwr-version.mjs.map +1 -1
  108. package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
  109. package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
  110. package/fesm2022/ngwr-waves.mjs +2 -2
  111. package/fesm2022/ngwr-waves.mjs.map +1 -1
  112. package/file-upload/styles/_index.scss +6 -0
  113. package/glitch-text/glitch-text.scss +1 -1
  114. package/glitch-text/styles/_index.scss +14 -164
  115. package/glitch-text/styles/_rules.scss +160 -0
  116. package/gradient-text/gradient-text.scss +1 -1
  117. package/gradient-text/styles/_index.scss +15 -118
  118. package/gradient-text/styles/_rules.scss +113 -0
  119. package/input/styles/_index.scss +13 -0
  120. package/input-otp/styles/_index.scss +8 -0
  121. package/lightbox/lightbox.scss +1 -1
  122. package/lightbox/styles/_index.scss +15 -191
  123. package/lightbox/styles/_rules.scss +198 -0
  124. package/list/list.scss +1 -1
  125. package/list/styles/_index.scss +15 -101
  126. package/list/styles/_rules.scss +108 -0
  127. package/llms-full.txt +3 -3
  128. package/loading-bar/loading-bar.scss +1 -1
  129. package/loading-bar/styles/_index.scss +15 -33
  130. package/loading-bar/styles/_rules.scss +28 -0
  131. package/marquee/marquee.scss +1 -1
  132. package/marquee/styles/_index.scss +15 -155
  133. package/marquee/styles/_rules.scss +150 -0
  134. package/overlay/styles/_arrow.scss +243 -0
  135. package/package.json +1 -1
  136. package/popconfirm/styles/_index.scss +17 -0
  137. package/popover/styles/_index.scss +49 -207
  138. package/segmented/styles/_index.scss +8 -0
  139. package/select/styles/_index.scss +47 -5
  140. package/shiny-text/shiny-text.scss +1 -1
  141. package/shiny-text/styles/_index.scss +14 -105
  142. package/shiny-text/styles/_rules.scss +101 -0
  143. package/sidebar/styles/_index.scss +4 -1
  144. package/slider/styles/_index.scss +8 -0
  145. package/spotlight-card/spotlight-card.scss +1 -1
  146. package/spotlight-card/styles/_index.scss +15 -52
  147. package/spotlight-card/styles/_rules.scss +47 -0
  148. package/star-border/star-border.scss +1 -1
  149. package/star-border/styles/_index.scss +15 -125
  150. package/star-border/styles/_rules.scss +120 -0
  151. package/stepper/styles/_index.scss +44 -5
  152. package/switch/styles/_index.scss +7 -0
  153. package/theme/styles/_dark.scss +81 -4
  154. package/theme/styles/_index.scss +1 -0
  155. package/theme/styles/_mixins.scss +10 -0
  156. package/theme/styles/_motion.scss +94 -0
  157. package/tilt-card/styles/_index.scss +15 -45
  158. package/tilt-card/styles/_rules.scss +40 -0
  159. package/tilt-card/tilt-card.scss +1 -1
  160. package/tree/styles/_index.scss +25 -3
  161. package/types/ngwr-cascader.d.ts +17 -1
  162. package/types/ngwr-checkbox.d.ts +51 -3
  163. package/types/ngwr-color-picker.d.ts +19 -1
  164. package/types/ngwr-file-upload.d.ts +18 -1
  165. package/types/ngwr-form.d.ts +51 -2
  166. package/types/ngwr-input-otp.d.ts +23 -1
  167. package/types/ngwr-knob.d.ts +14 -0
  168. package/types/ngwr-radio.d.ts +37 -2
  169. package/types/ngwr-rating.d.ts +14 -0
  170. package/types/ngwr-segmented.d.ts +18 -1
  171. package/types/ngwr-select.d.ts +29 -3
  172. package/types/ngwr-slider.d.ts +14 -1
  173. package/types/ngwr-switch.d.ts +26 -2
  174. package/types/ngwr-textarea.d.ts +3 -0
  175. package/types/ngwr-theme.d.ts +24 -0
  176. package/types/ngwr-transfer.d.ts +17 -1
  177. package/types/ngwr-tree.d.ts +21 -1
  178. package/types/ngwr-version.d.ts +1 -1
  179. package/typewriter/styles/_index.scss +15 -38
  180. package/typewriter/styles/_rules.scss +33 -0
  181. package/typewriter/typewriter.scss +1 -1
  182. package/virtual-scroll/styles/_index.scss +15 -32
  183. package/virtual-scroll/styles/_rules.scss +27 -0
  184. package/virtual-scroll/virtual-scroll.scss +1 -1
  185. package/waves/styles/_index.scss +15 -50
  186. package/waves/styles/_rules.scss +45 -0
  187. package/waves/waves.scss +1 -1
@@ -3,6 +3,7 @@ import * as i0 from '@angular/core';
3
3
  import { input, model, output, signal, computed, ViewEncapsulation, Component } from '@angular/core';
4
4
  import { WrButton } from 'ngwr/button';
5
5
  import { WrCheckbox } from 'ngwr/checkbox';
6
+ import { useFormFieldAria, WR_FORM_FIELD } from 'ngwr/form';
6
7
  import { useI18nText, useI18nFormatter } from 'ngwr/i18n';
7
8
  import { WrInput } from 'ngwr/input';
8
9
 
@@ -55,6 +56,21 @@ class WrTransfer {
55
56
  touch = output();
56
57
  /** Disable the whole control. @default false */
57
58
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
59
+ /**
60
+ * Refuse changes to the value while both panes stay focusable and readable.
61
+ * Bound automatically from the field's readonly state when used with
62
+ * `[formField]`.
63
+ *
64
+ * The move buttons go inert and the row checkboxes go read-only; SEARCH keeps
65
+ * working, because filtering a pane changes what is shown and not what is
66
+ * chosen. No `aria-readonly` on the host: role `group` does not support the
67
+ * state, so each row's checkbox mirrors its own instead.
68
+ *
69
+ * @default false
70
+ */
71
+ readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
72
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
73
+ fieldAria = useFormFieldAria({ skipSelf: true });
58
74
  /** Show a filter box above each pane. @default false */
59
75
  searchable = input(false, { ...(ngDevMode ? { debugName: "searchable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
60
76
  /** Heading above the left pane. Falls back to `transfer.source`. */
@@ -120,14 +136,16 @@ class WrTransfer {
120
136
  .map(v => this.items().find(item => item.value === v))
121
137
  .filter((item) => item !== undefined), this.targetQuery(), this.targetChecked()), /* @ts-ignore */
122
138
  ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
123
- canMoveRight = computed(() => !this.disabled() && this.source().checkedCount > 0, /* @ts-ignore */
139
+ canMoveRight = computed(() => !this.disabled() && !this.readonly() && this.source().checkedCount > 0, /* @ts-ignore */
124
140
  ...(ngDevMode ? [{ debugName: "canMoveRight" }] : /* istanbul ignore next */ []));
125
- canMoveLeft = computed(() => !this.disabled() && this.target().checkedCount > 0, /* @ts-ignore */
141
+ canMoveLeft = computed(() => !this.disabled() && !this.readonly() && this.target().checkedCount > 0, /* @ts-ignore */
126
142
  ...(ngDevMode ? [{ debugName: "canMoveLeft" }] : /* istanbul ignore next */ []));
127
143
  classes = computed(() => {
128
144
  const parts = ['wr-transfer'];
129
145
  if (this.disabled())
130
146
  parts.push('wr-transfer--disabled');
147
+ else if (this.readonly())
148
+ parts.push('wr-transfer--readonly');
131
149
  if (this.searchable())
132
150
  parts.push('wr-transfer--searchable');
133
151
  return parts.join(' ');
@@ -137,7 +155,7 @@ class WrTransfer {
137
155
  return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);
138
156
  }
139
157
  toggle(pane, item, checked) {
140
- if (item.disabled)
158
+ if (item.disabled || this.readonly())
141
159
  return;
142
160
  const box = pane === 'source' ? this.sourceChecked : this.targetChecked;
143
161
  const current = box();
@@ -155,6 +173,8 @@ class WrTransfer {
155
173
  }
156
174
  /** Header checkbox — stages or clears every enabled row the filter shows. */
157
175
  toggleAll(pane, checked) {
176
+ if (this.readonly())
177
+ return;
158
178
  const state = pane === 'source' ? this.source() : this.target();
159
179
  const box = pane === 'source' ? this.sourceChecked : this.targetChecked;
160
180
  box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);
@@ -164,6 +184,8 @@ class WrTransfer {
164
184
  (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);
165
185
  }
166
186
  moveRight() {
187
+ if (this.disabled() || this.readonly())
188
+ return;
167
189
  // `source().checked`, not the raw staging box: rows a filter change hid are
168
190
  // neither counted nor ticked any more, so moving them would transfer rows the
169
191
  // pane never showed as chosen — and, since staging outlives an external write
@@ -176,6 +198,8 @@ class WrTransfer {
176
198
  this.touch.emit();
177
199
  }
178
200
  moveLeft() {
201
+ if (this.disabled() || this.readonly())
202
+ return;
179
203
  const moving = new Set(this.target().checked);
180
204
  if (moving.size === 0)
181
205
  return;
@@ -200,12 +224,20 @@ class WrTransfer {
200
224
  };
201
225
  }
202
226
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTransfer, deps: [], target: i0.ɵɵFactoryTarget.Component });
203
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTransfer, isStandalone: true, selector: "wr-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, sourceTitle: { classPropertyName: "sourceTitle", publicName: "sourceTitle", isSignal: true, isRequired: false, transformFunction: null }, targetTitle: { classPropertyName: "targetTitle", publicName: "targetTitle", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, toTargetLabel: { classPropertyName: "toTargetLabel", publicName: "toTargetLabel", isSignal: true, isRequired: false, transformFunction: null }, toSourceLabel: { classPropertyName: "toSourceLabel", publicName: "toSourceLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n", dependencies: [{ kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "isDisabledWhenLoading"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["wrSize", "rounded"] }], encapsulation: i0.ViewEncapsulation.None });
227
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTransfer, isStandalone: true, selector: "wr-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, sourceTitle: { classPropertyName: "sourceTitle", publicName: "sourceTitle", isSignal: true, isRequired: false, transformFunction: null }, targetTitle: { classPropertyName: "targetTitle", publicName: "targetTitle", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, toTargetLabel: { classPropertyName: "toTargetLabel", publicName: "toTargetLabel", isSignal: true, isRequired: false, transformFunction: null }, toSourceLabel: { classPropertyName: "toSourceLabel", publicName: "toSourceLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-invalid": "fieldAria.ariaInvalid()", "attr.aria-describedby": "fieldAria.describedBy()" } }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], ngImport: i0, template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n", dependencies: [{ kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "isDisabledWhenLoading"] }, { kind: "component", type: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["wrSize", "rounded"] }], encapsulation: i0.ViewEncapsulation.None });
204
228
  }
205
229
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTransfer, decorators: [{
206
230
  type: Component,
207
- args: [{ selector: 'wr-transfer', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [WrButton, WrCheckbox, WrInput], template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n" }]
208
- }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], 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 }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], sourceTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceTitle", required: false }] }], targetTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "targetTitle", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", required: false }] }], toTargetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toTargetLabel", required: false }] }], toSourceLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toSourceLabel", required: false }] }] } });
231
+ args: [{ selector: 'wr-transfer', encapsulation: ViewEncapsulation.None, host: {
232
+ '[class]': 'classes()',
233
+ // `role="group"` so the error state below has somewhere legible to live: the
234
+ // control is a composite with no single element carrying its semantics, and
235
+ // `aria-invalid` on a bare `generic` host is announced by nothing.
236
+ role: 'group',
237
+ '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',
238
+ '[attr.aria-describedby]': 'fieldAria.describedBy()',
239
+ }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], imports: [WrButton, WrCheckbox, WrInput], template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n" }]
240
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], 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 }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], sourceTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceTitle", required: false }] }], targetTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "targetTitle", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", required: false }] }], toTargetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toTargetLabel", required: false }] }], toSourceLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toSourceLabel", required: false }] }] } });
209
241
 
210
242
  /**
211
243
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-transfer.mjs","sources":["../../../projects/lib/transfer/transfer.ts","../../../projects/lib/transfer/transfer.html","../../../projects/lib/transfer/ngwr-transfer.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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, model, output, signal } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrInput } from 'ngwr/input';\n\nimport type { WrTransferItem } from './interfaces';\n\n/** A pane's rows plus the header state derived from them. */\ninterface PaneState {\n readonly rows: readonly WrTransferItem[];\n /**\n * The staged values this pane is actually SHOWING — visible and enabled. The\n * staging signals can hold more than that (a filter change hides rows without\n * unstaging them), and every consumer of the staged set has to agree on which\n * one it means: the count, the header checkbox and the move all read this.\n */\n readonly checked: readonly unknown[];\n readonly checkedCount: number;\n readonly enabledCount: number;\n readonly allChecked: boolean;\n readonly someChecked: boolean;\n}\n\n/**\n * Dual-listbox picker: two panes and the buttons that move rows between them.\n *\n * The full item set is `[items]` and the RIGHT pane is the value — `[(value)]`\n * holds the chosen `WrTransferItem['value']`s, everything else stays left. The\n * checked state inside each pane is transient staging, deliberately not part of\n * the value: what a form cares about is which rows ended up on the right.\n *\n * A signal-forms native control (`FormValueControl<readonly unknown[]>`), so\n * `[formField]` binds straight to `value`; `[(ngModel)]` and reactive forms keep\n * working through Angular's bridge, and `[(value)]` is the standalone shape.\n *\n * @example\n * ```html\n * <!-- Standalone -->\n * <wr-transfer [items]=\"permissions\" [(value)]=\"granted\" />\n *\n * <!-- Searchable, with pane titles -->\n * <wr-transfer\n * searchable\n * sourceTitle=\"Available\"\n * targetTitle=\"Granted\"\n * [items]=\"permissions\"\n * [formField]=\"form.granted\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/transfer\n */\n@Component({\n selector: 'wr-transfer',\n templateUrl: './transfer.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n imports: [WrButton, WrCheckbox, WrInput],\n})\nexport class WrTransfer implements FormValueControl<readonly unknown[]> {\n /** Every row, in either pane. Membership of the right pane is `value`. */\n readonly items = input<readonly WrTransferItem[]>([]);\n\n /**\n * Values currently in the RIGHT pane. Two-way bindable; bound automatically\n * by `[formField]` / `[(ngModel)]`.\n */\n readonly value = model<readonly unknown[]>([]);\n\n /** Emitted on blur / commit so a bound field marks itself touched. */\n readonly touch = output<void>();\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Show a filter box above each pane. @default false */\n readonly searchable = input(false, { transform: coerceBooleanProperty });\n\n /** Heading above the left pane. Falls back to `transfer.source`. */\n readonly sourceTitle = input<string | null>(null);\n\n /** Heading above the right pane. Falls back to `transfer.target`. */\n readonly targetTitle = input<string | null>(null);\n\n /** Placeholder in both filter boxes. Falls back to `transfer.search`. */\n readonly searchPlaceholder = input<string | null>(null);\n\n /** Shown in a pane with no rows. Falls back to `transfer.empty`. */\n readonly emptyText = input<string | null>(null);\n\n /**\n * Accessible name of each pane's select-all checkbox, composed with the pane\n * heading. Falls back to `transfer.selectAll`.\n */\n readonly selectAllLabel = input<string | null>(null);\n\n /** Accessible name of the move-right button. Falls back to `transfer.toTarget`. */\n readonly toTargetLabel = input<string | null>(null);\n\n /** Accessible name of the move-left button. Falls back to `transfer.toSource`. */\n readonly toSourceLabel = input<string | null>(null);\n\n protected readonly resolvedSourceTitle = useI18nText(this.sourceTitle, 'transfer.source', 'Available');\n protected readonly resolvedTargetTitle = useI18nText(this.targetTitle, 'transfer.target', 'Selected');\n protected readonly resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');\n protected readonly resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');\n protected readonly resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');\n protected readonly resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');\n protected readonly resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');\n\n /** `{{checked}} / {{total}}` under each heading. @internal */\n protected readonly countLabel = useI18nFormatter('transfer.count', '{{checked}} / {{total}}');\n\n /** Pane order — the template iterates it so both panes share one block. */\n protected readonly panes = ['source', 'target'] as const;\n\n private readonly sourceQuery = signal('');\n private readonly targetQuery = signal('');\n\n /** Staged rows, per pane. Cleared as soon as a move commits them. */\n private readonly sourceChecked = signal<readonly unknown[]>([]);\n private readonly targetChecked = signal<readonly unknown[]>([]);\n\n // The value as a guaranteed array — a classic-forms binding can write null even\n // though the type is `readonly unknown[]`, so normalise every read. Same hazard\n // `wr-checkbox-group` guards; here an unguarded `.map` threw outright.\n private readonly selected = computed<readonly unknown[]>(() => {\n const v = this.value();\n return Array.isArray(v) ? v : [];\n });\n\n private readonly valueSet = computed(() => new Set(this.selected()));\n\n protected readonly source = computed<PaneState>(() =>\n this.pane(\n this.items().filter(item => !this.valueSet().has(item.value)),\n this.sourceQuery(),\n this.sourceChecked()\n )\n );\n\n protected readonly target = computed<PaneState>(() =>\n this.pane(\n // Right-pane order follows `value`, not `items`: the order a user built is\n // the one they expect to read back.\n this.selected()\n .map(v => this.items().find(item => item.value === v))\n .filter((item): item is WrTransferItem => item !== undefined),\n this.targetQuery(),\n this.targetChecked()\n )\n );\n\n protected readonly canMoveRight = computed(() => !this.disabled() && this.source().checkedCount > 0);\n protected readonly canMoveLeft = computed(() => !this.disabled() && this.target().checkedCount > 0);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-transfer'];\n if (this.disabled()) parts.push('wr-transfer--disabled');\n if (this.searchable()) parts.push('wr-transfer--searchable');\n return parts.join(' ');\n });\n\n protected isChecked(pane: 'source' | 'target', item: WrTransferItem): boolean {\n return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);\n }\n\n protected toggle(pane: 'source' | 'target', item: WrTransferItem, checked: boolean): void {\n if (item.disabled) return;\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n const current = box();\n if (checked) {\n box.set([...current, item.value]);\n return;\n }\n // A `Set` compares with SameValueZero, which is what `WrTransferItem['value']`\n // is documented to use and what `isChecked`'s `includes` already does. A `!==`\n // filter cannot remove `NaN` from its own list, so such a row drew itself\n // unchecked while staying staged.\n const next = new Set(current);\n next.delete(item.value);\n box.set([...next]);\n }\n\n /** Header checkbox — stages or clears every enabled row the filter shows. */\n protected toggleAll(pane: 'source' | 'target', checked: boolean): void {\n const state = pane === 'source' ? this.source() : this.target();\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);\n }\n\n protected onSearch(pane: 'source' | 'target', event: Event): void {\n const query = (event.target as HTMLInputElement | null)?.value ?? '';\n (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);\n }\n\n protected moveRight(): void {\n // `source().checked`, not the raw staging box: rows a filter change hid are\n // neither counted nor ticked any more, so moving them would transfer rows the\n // pane never showed as chosen — and, since staging outlives an external write\n // to `value`, could land the same value on the right twice.\n const moving = this.source().checked;\n if (moving.length === 0) return;\n this.value.set([...this.selected(), ...moving]);\n this.sourceChecked.set([]);\n this.touch.emit();\n }\n\n protected moveLeft(): void {\n const moving = new Set(this.target().checked);\n if (moving.size === 0) return;\n this.value.set(this.selected().filter(v => !moving.has(v)));\n this.targetChecked.set([]);\n this.touch.emit();\n }\n\n /** Filter, then derive the header state from what survived. */\n private pane(rows: readonly WrTransferItem[], query: string, checked: readonly unknown[]): PaneState {\n const q = query.trim().toLowerCase();\n const visible = q ? rows.filter(item => item.label.toLowerCase().includes(q)) : rows;\n const enabled = visible.filter(item => !item.disabled);\n const staged = new Set(checked);\n const shown = enabled.filter(item => staged.has(item.value)).map(item => item.value);\n return {\n rows: visible,\n checked: shown,\n checkedCount: shown.length,\n enabledCount: enabled.length,\n allChecked: enabled.length > 0 && shown.length === enabled.length,\n someChecked: shown.length > 0 && shown.length < enabled.length,\n };\n }\n}\n","<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid — an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [ariaLabel]=\"`${title} — ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} — ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\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-transfer.mjs","sources":["../../../projects/lib/transfer/transfer.ts","../../../projects/lib/transfer/transfer.html","../../../projects/lib/transfer/ngwr-transfer.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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, model, output, signal } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrInput } from 'ngwr/input';\n\nimport type { WrTransferItem } from './interfaces';\n\n/** A pane's rows plus the header state derived from them. */\ninterface PaneState {\n readonly rows: readonly WrTransferItem[];\n /**\n * The staged values this pane is actually SHOWING — visible and enabled. The\n * staging signals can hold more than that (a filter change hides rows without\n * unstaging them), and every consumer of the staged set has to agree on which\n * one it means: the count, the header checkbox and the move all read this.\n */\n readonly checked: readonly unknown[];\n readonly checkedCount: number;\n readonly enabledCount: number;\n readonly allChecked: boolean;\n readonly someChecked: boolean;\n}\n\n/**\n * Dual-listbox picker: two panes and the buttons that move rows between them.\n *\n * The full item set is `[items]` and the RIGHT pane is the value — `[(value)]`\n * holds the chosen `WrTransferItem['value']`s, everything else stays left. The\n * checked state inside each pane is transient staging, deliberately not part of\n * the value: what a form cares about is which rows ended up on the right.\n *\n * A signal-forms native control (`FormValueControl<readonly unknown[]>`), so\n * `[formField]` binds straight to `value`; `[(ngModel)]` and reactive forms keep\n * working through Angular's bridge, and `[(value)]` is the standalone shape.\n *\n * @example\n * ```html\n * <!-- Standalone -->\n * <wr-transfer [items]=\"permissions\" [(value)]=\"granted\" />\n *\n * <!-- Searchable, with pane titles -->\n * <wr-transfer\n * searchable\n * sourceTitle=\"Available\"\n * targetTitle=\"Granted\"\n * [items]=\"permissions\"\n * [formField]=\"form.granted\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/transfer\n */\n@Component({\n selector: 'wr-transfer',\n templateUrl: './transfer.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n // `role=\"group\"` so the error state below has somewhere legible to live: the\n // control is a composite with no single element carrying its semantics, and\n // `aria-invalid` on a bare `generic` host is announced by nothing.\n role: 'group',\n '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',\n '[attr.aria-describedby]': 'fieldAria.describedBy()',\n },\n // The transfer is the control a `<wr-form-field>` wraps; the checkboxes and\n // the search inputs in its own template are parts, not controls. Without this\n // shield each of them found the field's token and announced the one error as\n // its own — which is why `fieldAria` below reads the field with `skipSelf`.\n providers: [{ provide: WR_FORM_FIELD, useValue: null }],\n imports: [WrButton, WrCheckbox, WrInput],\n})\nexport class WrTransfer implements FormValueControl<readonly unknown[]> {\n /** Every row, in either pane. Membership of the right pane is `value`. */\n readonly items = input<readonly WrTransferItem[]>([]);\n\n /**\n * Values currently in the RIGHT pane. Two-way bindable; bound automatically\n * by `[formField]` / `[(ngModel)]`.\n */\n readonly value = model<readonly unknown[]>([]);\n\n /** Emitted on blur / commit so a bound field marks itself touched. */\n readonly touch = output<void>();\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the value while both panes stay focusable and readable.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * The move buttons go inert and the row checkboxes go read-only; SEARCH keeps\n * working, because filtering a pane changes what is shown and not what is\n * chosen. No `aria-readonly` on the host: role `group` does not support the\n * state, so each row's checkbox mirrors its own instead.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria({ skipSelf: true });\n\n /** Show a filter box above each pane. @default false */\n readonly searchable = input(false, { transform: coerceBooleanProperty });\n\n /** Heading above the left pane. Falls back to `transfer.source`. */\n readonly sourceTitle = input<string | null>(null);\n\n /** Heading above the right pane. Falls back to `transfer.target`. */\n readonly targetTitle = input<string | null>(null);\n\n /** Placeholder in both filter boxes. Falls back to `transfer.search`. */\n readonly searchPlaceholder = input<string | null>(null);\n\n /** Shown in a pane with no rows. Falls back to `transfer.empty`. */\n readonly emptyText = input<string | null>(null);\n\n /**\n * Accessible name of each pane's select-all checkbox, composed with the pane\n * heading. Falls back to `transfer.selectAll`.\n */\n readonly selectAllLabel = input<string | null>(null);\n\n /** Accessible name of the move-right button. Falls back to `transfer.toTarget`. */\n readonly toTargetLabel = input<string | null>(null);\n\n /** Accessible name of the move-left button. Falls back to `transfer.toSource`. */\n readonly toSourceLabel = input<string | null>(null);\n\n protected readonly resolvedSourceTitle = useI18nText(this.sourceTitle, 'transfer.source', 'Available');\n protected readonly resolvedTargetTitle = useI18nText(this.targetTitle, 'transfer.target', 'Selected');\n protected readonly resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');\n protected readonly resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');\n protected readonly resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');\n protected readonly resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');\n protected readonly resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');\n\n /** `{{checked}} / {{total}}` under each heading. @internal */\n protected readonly countLabel = useI18nFormatter('transfer.count', '{{checked}} / {{total}}');\n\n /** Pane order — the template iterates it so both panes share one block. */\n protected readonly panes = ['source', 'target'] as const;\n\n private readonly sourceQuery = signal('');\n private readonly targetQuery = signal('');\n\n /** Staged rows, per pane. Cleared as soon as a move commits them. */\n private readonly sourceChecked = signal<readonly unknown[]>([]);\n private readonly targetChecked = signal<readonly unknown[]>([]);\n\n // The value as a guaranteed array — a classic-forms binding can write null even\n // though the type is `readonly unknown[]`, so normalise every read. Same hazard\n // `wr-checkbox-group` guards; here an unguarded `.map` threw outright.\n private readonly selected = computed<readonly unknown[]>(() => {\n const v = this.value();\n return Array.isArray(v) ? v : [];\n });\n\n private readonly valueSet = computed(() => new Set(this.selected()));\n\n protected readonly source = computed<PaneState>(() =>\n this.pane(\n this.items().filter(item => !this.valueSet().has(item.value)),\n this.sourceQuery(),\n this.sourceChecked()\n )\n );\n\n protected readonly target = computed<PaneState>(() =>\n this.pane(\n // Right-pane order follows `value`, not `items`: the order a user built is\n // the one they expect to read back.\n this.selected()\n .map(v => this.items().find(item => item.value === v))\n .filter((item): item is WrTransferItem => item !== undefined),\n this.targetQuery(),\n this.targetChecked()\n )\n );\n\n protected readonly canMoveRight = computed(\n () => !this.disabled() && !this.readonly() && this.source().checkedCount > 0\n );\n protected readonly canMoveLeft = computed(\n () => !this.disabled() && !this.readonly() && this.target().checkedCount > 0\n );\n\n protected readonly classes = computed(() => {\n const parts = ['wr-transfer'];\n if (this.disabled()) parts.push('wr-transfer--disabled');\n else if (this.readonly()) parts.push('wr-transfer--readonly');\n if (this.searchable()) parts.push('wr-transfer--searchable');\n return parts.join(' ');\n });\n\n protected isChecked(pane: 'source' | 'target', item: WrTransferItem): boolean {\n return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);\n }\n\n protected toggle(pane: 'source' | 'target', item: WrTransferItem, checked: boolean): void {\n if (item.disabled || this.readonly()) return;\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n const current = box();\n if (checked) {\n box.set([...current, item.value]);\n return;\n }\n // A `Set` compares with SameValueZero, which is what `WrTransferItem['value']`\n // is documented to use and what `isChecked`'s `includes` already does. A `!==`\n // filter cannot remove `NaN` from its own list, so such a row drew itself\n // unchecked while staying staged.\n const next = new Set(current);\n next.delete(item.value);\n box.set([...next]);\n }\n\n /** Header checkbox — stages or clears every enabled row the filter shows. */\n protected toggleAll(pane: 'source' | 'target', checked: boolean): void {\n if (this.readonly()) return;\n const state = pane === 'source' ? this.source() : this.target();\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);\n }\n\n protected onSearch(pane: 'source' | 'target', event: Event): void {\n const query = (event.target as HTMLInputElement | null)?.value ?? '';\n (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);\n }\n\n protected moveRight(): void {\n if (this.disabled() || this.readonly()) return;\n // `source().checked`, not the raw staging box: rows a filter change hid are\n // neither counted nor ticked any more, so moving them would transfer rows the\n // pane never showed as chosen — and, since staging outlives an external write\n // to `value`, could land the same value on the right twice.\n const moving = this.source().checked;\n if (moving.length === 0) return;\n this.value.set([...this.selected(), ...moving]);\n this.sourceChecked.set([]);\n this.touch.emit();\n }\n\n protected moveLeft(): void {\n if (this.disabled() || this.readonly()) return;\n const moving = new Set(this.target().checked);\n if (moving.size === 0) return;\n this.value.set(this.selected().filter(v => !moving.has(v)));\n this.targetChecked.set([]);\n this.touch.emit();\n }\n\n /** Filter, then derive the header state from what survived. */\n private pane(rows: readonly WrTransferItem[], query: string, checked: readonly unknown[]): PaneState {\n const q = query.trim().toLowerCase();\n const visible = q ? rows.filter(item => item.label.toLowerCase().includes(q)) : rows;\n const enabled = visible.filter(item => !item.disabled);\n const staged = new Set(checked);\n const shown = enabled.filter(item => staged.has(item.value)).map(item => item.value);\n return {\n rows: visible,\n checked: shown,\n checkedCount: shown.length,\n enabledCount: enabled.length,\n allChecked: enabled.length > 0 && shown.length === enabled.length,\n someChecked: shown.length > 0 && shown.length < enabled.length,\n };\n }\n}\n","<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid — an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} — ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} — ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
6
6
  import * as i0 from '@angular/core';
7
7
  import { input, model, computed, output, inject, ElementRef, ViewContainerRef, DestroyRef, PLATFORM_ID, Injector, signal, viewChild, TemplateRef, effect, untracked, afterNextRender, ViewEncapsulation, Component } from '@angular/core';
8
8
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
+ import { useFormFieldAria } from 'ngwr/form';
9
10
  import { useI18nText, readI18nText, useI18nFormatter } from 'ngwr/i18n';
10
11
  import { WR_OVERLAY, WrOutsideClick } from 'ngwr/overlay';
11
12
 
@@ -75,6 +76,25 @@ class WrTree {
75
76
  * @default false
76
77
  */
77
78
  disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
79
+ /**
80
+ * Refuse selection changes while the tree stays focusable and the value still
81
+ * submits. Bound automatically from the field's readonly state when used with
82
+ * `[formField]`.
83
+ *
84
+ * Expanding and collapsing keep working: a branch is NAVIGATION, not a value,
85
+ * so a read-only tree is still browsable — it is picking and un-picking that
86
+ * stops. In `overlay` mode the trigger additionally refuses to open, because
87
+ * there the panel exists only to choose from and it already shows what is
88
+ * chosen.
89
+ *
90
+ * `aria-readonly` rides on the combobox trigger only; role `tree` does not
91
+ * support the state, so the inline shape has nothing valid to mirror it onto.
92
+ *
93
+ * @default false
94
+ */
95
+ readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
96
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
97
+ fieldAria = useFormFieldAria();
78
98
  // Overlay-mode inputs (ignored when openOn='inline')
79
99
  /** Render shape. @default 'inline' */
80
100
  openOn = input('inline', /* @ts-ignore */
@@ -266,6 +286,8 @@ class WrTree {
266
286
  const parts = ['wr-tree'];
267
287
  if (this.disabled())
268
288
  parts.push('wr-tree--disabled');
289
+ else if (this.readonly())
290
+ parts.push('wr-tree--readonly');
269
291
  if (this.isOverlay()) {
270
292
  parts.push('wr-tree--combobox');
271
293
  if (this.open())
@@ -404,7 +426,7 @@ class WrTree {
404
426
  // Disabling the control closes any open overlay (mirrors the side effect
405
427
  // of the old CVA setDisabledState).
406
428
  effect(() => {
407
- if (this.disabled())
429
+ if (this.disabled() || this.readonly())
408
430
  this.open.set(false);
409
431
  });
410
432
  // Bridge an external `value` write back into the internal `selected`
@@ -590,7 +612,7 @@ class WrTree {
590
612
  }
591
613
  // Overlay-mode trigger handlers
592
614
  onTriggerClick() {
593
- if (this.disabled())
615
+ if (this.disabled() || this.readonly())
594
616
  return;
595
617
  this.open.update(v => !v);
596
618
  }
@@ -600,7 +622,7 @@ class WrTree {
600
622
  }
601
623
  removeChip(id, event) {
602
624
  event.stopPropagation();
603
- if (this.disabled())
625
+ if (this.disabled() || this.readonly())
604
626
  return;
605
627
  const next = this.selected().filter(x => x !== id);
606
628
  this.selected.set(next);
@@ -608,7 +630,7 @@ class WrTree {
608
630
  }
609
631
  clearSelection(event) {
610
632
  event.stopPropagation();
611
- if (this.disabled())
633
+ if (this.disabled() || this.readonly())
612
634
  return;
613
635
  this.selected.set([]);
614
636
  this.emit();
@@ -623,6 +645,10 @@ class WrTree {
623
645
  this.expanded.set([...set]);
624
646
  }
625
647
  select(id, additive) {
648
+ // Every selection path funnels through here — row click, Enter, Space — so
649
+ // one guard covers them all while expand / collapse stays reachable.
650
+ if (this.readonly())
651
+ return;
626
652
  const mode = this.selectionMode();
627
653
  if (mode === 'none')
628
654
  return;
@@ -773,12 +799,12 @@ class WrTree {
773
799
  this.host.nativeElement.querySelector('.wr-tree__trigger')?.focus();
774
800
  }
775
801
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
776
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTree, isStandalone: true, selector: "wr-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, openOn: { classPropertyName: "openOn", publicName: "openOn", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, defaultExpandAll: { classPropertyName: "defaultExpandAll", publicName: "defaultExpandAll", 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 }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", expanded: "expandedChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $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-tree__chip wr-tree__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($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-tree__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 <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
802
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTree, isStandalone: true, selector: "wr-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, openOn: { classPropertyName: "openOn", publicName: "openOn", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, defaultExpandAll: { classPropertyName: "defaultExpandAll", publicName: "defaultExpandAll", 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 }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", expanded: "expandedChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $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-tree__chip wr-tree__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled() && !readonly()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($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-tree__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 <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row.\n The field's error state is bound here for the INLINE shape only \u2014 in\n overlay mode the trigger carries it, and the panel would otherwise\n describe the same error a second time. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-invalid]=\"isOverlay() ? null : fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"isOverlay() ? null : fieldAria.describedBy()\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
777
803
  }
778
804
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTree, decorators: [{
779
805
  type: Component,
780
- args: [{ selector: 'wr-tree', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [NgTemplateOutlet], template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $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-tree__chip wr-tree__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($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-tree__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 <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n" }]
781
- }], ctorParameters: () => [], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], openOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOn", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], maxTagCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTagCount", required: false }] }], defaultExpandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultExpandAll", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { ...{ read: TemplateRef }, isSignal: true }] }] } });
806
+ args: [{ selector: 'wr-tree', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [NgTemplateOutlet], template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $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-tree__chip wr-tree__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled() && !readonly()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($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-tree__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 <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row.\n The field's error state is bound here for the INLINE shape only \u2014 in\n overlay mode the trigger carries it, and the panel would otherwise\n describe the same error a second time. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-invalid]=\"isOverlay() ? null : fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"isOverlay() ? null : fieldAria.describedBy()\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n" }]
807
+ }], ctorParameters: () => [], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], openOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOn", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], maxTagCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTagCount", required: false }] }], defaultExpandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultExpandAll", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { ...{ read: TemplateRef }, isSignal: true }] }] } });
782
808
 
783
809
  /**
784
810
  * Generated bundle index. Do not edit.