ngwr 13.0.0 → 14.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 (200) hide show
  1. package/README.md +13 -6
  2. package/color-picker/styles/_index.scss +43 -5
  3. package/fesm2022/ngwr-alert.mjs +5 -5
  4. package/fesm2022/ngwr-alert.mjs.map +1 -1
  5. package/fesm2022/ngwr-back-top.mjs +11 -1
  6. package/fesm2022/ngwr-back-top.mjs.map +1 -1
  7. package/fesm2022/ngwr-blur-text.mjs +13 -2
  8. package/fesm2022/ngwr-blur-text.mjs.map +1 -1
  9. package/fesm2022/ngwr-border-glow.mjs +12 -3
  10. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  11. package/fesm2022/ngwr-breadcrumbs.mjs +14 -3
  12. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  13. package/fesm2022/ngwr-button.mjs +4 -4
  14. package/fesm2022/ngwr-button.mjs.map +1 -1
  15. package/fesm2022/ngwr-calendar-testing.mjs +15 -0
  16. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
  17. package/fesm2022/ngwr-calendar.mjs +51 -5
  18. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  19. package/fesm2022/ngwr-circular-text.mjs +20 -1
  20. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  21. package/fesm2022/ngwr-collapse.mjs.map +1 -1
  22. package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
  23. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
  24. package/fesm2022/ngwr-color-picker.mjs +89 -10
  25. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  26. package/fesm2022/ngwr-command-palette.mjs +27 -3
  27. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  28. package/fesm2022/ngwr-context-menu.mjs +13 -3
  29. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  30. package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
  31. package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
  32. package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
  33. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  34. package/fesm2022/ngwr-date-picker.mjs +103 -13
  35. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  36. package/fesm2022/ngwr-date.mjs +480 -79
  37. package/fesm2022/ngwr-date.mjs.map +1 -1
  38. package/fesm2022/ngwr-dialog.mjs +22 -16
  39. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  40. package/fesm2022/ngwr-directives.mjs +5 -2
  41. package/fesm2022/ngwr-directives.mjs.map +1 -1
  42. package/fesm2022/ngwr-drag-drop.mjs +30 -2
  43. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  44. package/fesm2022/ngwr-drawer.mjs +24 -18
  45. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  46. package/fesm2022/ngwr-dropdown.mjs +8 -3
  47. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  48. package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
  49. package/fesm2022/ngwr-event-calendar.mjs +1 -1
  50. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  51. package/fesm2022/ngwr-file-upload.mjs +52 -12
  52. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  53. package/fesm2022/ngwr-fuzzy-text.mjs +21 -4
  54. package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
  55. package/fesm2022/ngwr-gradient-text.mjs +9 -2
  56. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  57. package/fesm2022/ngwr-i18n-en.mjs +63 -1
  58. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  59. package/fesm2022/ngwr-i18n-ru.mjs +28 -1
  60. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  61. package/fesm2022/ngwr-i18n.mjs +150 -22
  62. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  63. package/fesm2022/ngwr-icon-adapters-feather.mjs +11 -0
  64. package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
  65. package/fesm2022/ngwr-icon-adapters-lucide.mjs +22 -2
  66. package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
  67. package/fesm2022/ngwr-icon.mjs +474 -37
  68. package/fesm2022/ngwr-icon.mjs.map +1 -1
  69. package/fesm2022/ngwr-input-number.mjs +3 -3
  70. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  71. package/fesm2022/ngwr-input-otp.mjs +21 -3
  72. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  73. package/fesm2022/ngwr-input.mjs +14 -7
  74. package/fesm2022/ngwr-input.mjs.map +1 -1
  75. package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
  76. package/fesm2022/ngwr-knob.mjs +20 -2
  77. package/fesm2022/ngwr-knob.mjs.map +1 -1
  78. package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
  79. package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
  80. package/fesm2022/ngwr-loading-bar.mjs +21 -32
  81. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  82. package/fesm2022/ngwr-markdown.mjs +63 -11
  83. package/fesm2022/ngwr-markdown.mjs.map +1 -1
  84. package/fesm2022/ngwr-mention.mjs +104 -14
  85. package/fesm2022/ngwr-mention.mjs.map +1 -1
  86. package/fesm2022/ngwr-overlay.mjs +36 -7
  87. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  88. package/fesm2022/ngwr-pagination-testing.mjs +11 -10
  89. package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
  90. package/fesm2022/ngwr-pagination.mjs +65 -30
  91. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  92. package/fesm2022/ngwr-popconfirm.mjs +3 -2
  93. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  94. package/fesm2022/ngwr-popover.mjs +2 -1
  95. package/fesm2022/ngwr-popover.mjs.map +1 -1
  96. package/fesm2022/ngwr-qr.mjs +38 -1
  97. package/fesm2022/ngwr-qr.mjs.map +1 -1
  98. package/fesm2022/ngwr-select.mjs +93 -34
  99. package/fesm2022/ngwr-select.mjs.map +1 -1
  100. package/fesm2022/ngwr-shiny-text.mjs +10 -3
  101. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  102. package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
  103. package/fesm2022/ngwr-squircle.mjs +2 -1
  104. package/fesm2022/ngwr-squircle.mjs.map +1 -1
  105. package/fesm2022/ngwr-statistic.mjs +35 -4
  106. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  107. package/fesm2022/ngwr-table.mjs +35 -12
  108. package/fesm2022/ngwr-table.mjs.map +1 -1
  109. package/fesm2022/ngwr-tabs-router.mjs +158 -0
  110. package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
  111. package/fesm2022/ngwr-tabs-testing.mjs +13 -11
  112. package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
  113. package/fesm2022/ngwr-tabs.mjs +104 -12
  114. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  115. package/fesm2022/ngwr-textarea.mjs +20 -1
  116. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  117. package/fesm2022/ngwr-tour.mjs +1 -1
  118. package/fesm2022/ngwr-tour.mjs.map +1 -1
  119. package/fesm2022/ngwr-transfer.mjs +2 -2
  120. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  121. package/fesm2022/ngwr-tree.mjs +18 -2
  122. package/fesm2022/ngwr-tree.mjs.map +1 -1
  123. package/fesm2022/ngwr-utils.mjs +143 -1
  124. package/fesm2022/ngwr-utils.mjs.map +1 -1
  125. package/fesm2022/ngwr-version.mjs +1 -1
  126. package/fesm2022/ngwr-version.mjs.map +1 -1
  127. package/fesm2022/ngwr-window-testing.mjs +10 -2
  128. package/fesm2022/ngwr-window-testing.mjs.map +1 -1
  129. package/fesm2022/ngwr-window.mjs +4 -4
  130. package/fesm2022/ngwr-window.mjs.map +1 -1
  131. package/i18n/en.json +33 -6
  132. package/i18n/ru.json +33 -6
  133. package/llms-full.txt +20 -11
  134. package/llms.txt +47 -16
  135. package/mcp/catalog.js +3 -3
  136. package/mcp/providers.js +11 -0
  137. package/package.json +11 -3
  138. package/schematics/migrations/v13/index.js +101 -0
  139. package/schematics/migrations/v14/index.js +422 -0
  140. package/schematics/migrations.json +10 -0
  141. package/schematics/page/templates.js +1 -1
  142. package/schematics/use/symbol-map.json +1 -0
  143. package/skills/ngwr/SKILL.md +4 -1
  144. package/skills/ngwr/references/catalog.md +2 -0
  145. package/skills/ngwr/references/setup.md +4 -0
  146. package/stepper/styles/_index.scss +19 -1
  147. package/textarea/styles/_index.scss +14 -6
  148. package/toast/styles/_index.scss +12 -4
  149. package/types/ngwr-alert.d.ts +3 -3
  150. package/types/ngwr-blur-text.d.ts +8 -0
  151. package/types/ngwr-breadcrumbs.d.ts +1 -0
  152. package/types/ngwr-button.d.ts +2 -2
  153. package/types/ngwr-calendar-testing.d.ts +21 -4
  154. package/types/ngwr-calendar.d.ts +37 -0
  155. package/types/ngwr-circular-text.d.ts +9 -0
  156. package/types/ngwr-collapse.d.ts +19 -15
  157. package/types/ngwr-color-picker-testing.d.ts +10 -4
  158. package/types/ngwr-color-picker.d.ts +70 -2
  159. package/types/ngwr-command-palette.d.ts +14 -0
  160. package/types/ngwr-context-menu.d.ts +1 -1
  161. package/types/ngwr-date-adapters-fns.d.ts +71 -29
  162. package/types/ngwr-date-picker-testing.d.ts +206 -185
  163. package/types/ngwr-date-picker.d.ts +7 -0
  164. package/types/ngwr-date.d.ts +143 -13
  165. package/types/ngwr-drag-drop.d.ts +19 -0
  166. package/types/ngwr-dropdown.d.ts +1 -1
  167. package/types/ngwr-event-calendar-testing.d.ts +12 -9
  168. package/types/ngwr-file-upload.d.ts +31 -3
  169. package/types/ngwr-fuzzy-text.d.ts +5 -0
  170. package/types/ngwr-i18n.d.ts +89 -7
  171. package/types/ngwr-icon-adapters-feather.d.ts +11 -0
  172. package/types/ngwr-icon.d.ts +42 -1
  173. package/types/ngwr-input-number.d.ts +1 -1
  174. package/types/ngwr-input-otp.d.ts +3 -0
  175. package/types/ngwr-input.d.ts +11 -4
  176. package/types/ngwr-knob-testing.d.ts +8 -4
  177. package/types/ngwr-knob.d.ts +18 -0
  178. package/types/ngwr-loading-bar-router.d.ts +35 -0
  179. package/types/ngwr-loading-bar.d.ts +19 -9
  180. package/types/ngwr-mention.d.ts +13 -0
  181. package/types/ngwr-pagination-testing.d.ts +11 -10
  182. package/types/ngwr-pagination.d.ts +39 -10
  183. package/types/ngwr-popconfirm.d.ts +1 -1
  184. package/types/ngwr-popover.d.ts +1 -1
  185. package/types/ngwr-select.d.ts +65 -10
  186. package/types/ngwr-shiny-text.d.ts +1 -0
  187. package/types/ngwr-splitter-testing.d.ts +9 -4
  188. package/types/ngwr-squircle.d.ts +1 -1
  189. package/types/ngwr-statistic.d.ts +22 -2
  190. package/types/ngwr-table.d.ts +24 -5
  191. package/types/ngwr-tabs-router.d.ts +67 -0
  192. package/types/ngwr-tabs-testing.d.ts +12 -10
  193. package/types/ngwr-tabs.d.ts +103 -6
  194. package/types/ngwr-textarea.d.ts +9 -0
  195. package/types/ngwr-tree.d.ts +7 -0
  196. package/types/ngwr-utils.d.ts +79 -1
  197. package/types/ngwr-version.d.ts +1 -1
  198. package/types/ngwr-window-testing.d.ts +11 -3
  199. package/types/ngwr-window.d.ts +11 -4
  200. package/window/styles/_index.scss +1 -1
@@ -2,7 +2,6 @@ import { Directionality } from '@angular/cdk/bidi';
2
2
  import { NgTemplateOutlet } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, input, viewChild, TemplateRef, inject, DestroyRef, computed, ViewEncapsulation, Component, model, contentChildren, signal, effect, afterNextRender, forwardRef } from '@angular/core';
5
- import { RouterLink, RouterLinkActive } from '@angular/router';
6
5
  import { coerceBooleanProperty } from '@angular/cdk/coercion';
7
6
  import { randomId } from 'ngwr/utils';
8
7
 
@@ -20,6 +19,19 @@ import { randomId } from 'ngwr/utils';
20
19
  */
21
20
  const WR_TABS = new InjectionToken('WR_TABS');
22
21
 
22
+ /**
23
+ * @license
24
+ *
25
+ * Use of this source code is governed by an MIT-style license that can be
26
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
27
+ */
28
+ /**
29
+ * Token a `<wr-tabs>` reads to find out whether router tabs are usable. Provided
30
+ * by the `WrTabsRouting` directive from `ngwr/tabs/router`; absent by default,
31
+ * which is what keeps `@angular/router` out of a content-only strip.
32
+ */
33
+ const WR_TABS_ROUTING = new InjectionToken('WR_TABS_ROUTING');
34
+
23
35
  /**
24
36
  * @license
25
37
  *
@@ -33,7 +45,9 @@ const WR_TABS = new InjectionToken('WR_TABS');
33
45
  * panel when this tab is active.
34
46
  * - **Router tab** — set `routerLink` and the tab becomes a link; the
35
47
  * parent skips its content panel and the consumer drops a
36
- * `<router-outlet>` below the tab strip.
48
+ * `<router-outlet>` below the tab strip. Router mode needs the
49
+ * `WrTabsRouting` directive from `ngwr/tabs/router` in the same
50
+ * `imports: []`; without it a strip carrying a `routerLink` throws.
37
51
  */
38
52
  class WrTab {
39
53
  /** Visible label on the tab strip. */
@@ -55,8 +69,48 @@ class WrTab {
55
69
  ...(ngDevMode ? [{ debugName: "contentTpl" }] : /* istanbul ignore next */ []));
56
70
  parent = inject(WR_TABS, { optional: true });
57
71
  destroyRef = inject(DestroyRef);
72
+ /**
73
+ * The routing adapter published by `WrTabsRouting` on the parent `<wr-tabs>`
74
+ * element. A `<wr-tab>` is DECLARED inside `<wr-tabs>` in the consumer's
75
+ * template — content projection moves where it renders, not where it injects
76
+ * — so the parent's element injector is this one's, the same walk that
77
+ * resolves `WR_TABS` above.
78
+ */
79
+ routing = inject(WR_TABS_ROUTING, { optional: true });
58
80
  isRouter = computed(() => this.routerLink() !== null, /* @ts-ignore */
59
81
  ...(ngDevMode ? [{ debugName: "isRouter" }] : /* istanbul ignore next */ []));
82
+ /**
83
+ * Resolved href for a router tab — `null` for a content tab, and for a
84
+ * disabled one, which is how the anchor stops navigating at all.
85
+ * @internal
86
+ */
87
+ routerHref = computed(() => {
88
+ const link = this.routerLink();
89
+ if (link === null || this.disabled())
90
+ return null;
91
+ return this.routing?.href(link) ?? null;
92
+ }, /* @ts-ignore */
93
+ ...(ngDevMode ? [{ debugName: "routerHref" }] : /* istanbul ignore next */ []));
94
+ /**
95
+ * Whether the current route selects this tab. The strip publishes it as BOTH
96
+ * `aria-selected` and the `--active` class, off this one read, so the
97
+ * announced selection and the painted one cannot drift apart.
98
+ * @internal
99
+ */
100
+ routerActive = computed(() => {
101
+ const link = this.routerLink();
102
+ if (link === null || this.disabled())
103
+ return false;
104
+ return this.routing?.isActive(link) ?? false;
105
+ }, /* @ts-ignore */
106
+ ...(ngDevMode ? [{ debugName: "routerActive" }] : /* istanbul ignore next */ []));
107
+ /** Hand a click on this tab's anchor to the routing adapter. @internal */
108
+ navigate(event) {
109
+ const link = this.routerLink();
110
+ if (link === null || this.disabled())
111
+ return;
112
+ this.routing?.navigate(link, event);
113
+ }
60
114
  constructor() {
61
115
  if (this.parent) {
62
116
  // Both calls run with the input signals still on their defaults — a
@@ -68,7 +122,7 @@ class WrTab {
68
122
  }
69
123
  }
70
124
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
71
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.4", type: WrTab, isStandalone: true, selector: "wr-tab", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { styleAttribute: "display:none" }, viewQueries: [{ propertyName: "contentTpl", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template><ng-content /></ng-template>`, isInline: true, encapsulation: i0.ViewEncapsulation.None });
125
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.4", type: WrTab, isStandalone: true, selector: "wr-tab", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.display": "'none'" } }, viewQueries: [{ propertyName: "contentTpl", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template><ng-content /></ng-template>`, isInline: true, encapsulation: i0.ViewEncapsulation.None });
72
126
  }
73
127
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTab, decorators: [{
74
128
  type: Component,
@@ -76,7 +130,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
76
130
  selector: 'wr-tab',
77
131
  template: `<ng-template><ng-content /></ng-template>`,
78
132
  encapsulation: ViewEncapsulation.None,
79
- host: { style: 'display:none' },
133
+ // A BOUND display rather than a static `style` attribute: Angular writes a
134
+ // binding through `style.setProperty`, which no CSP governs, while a real
135
+ // `style="…"` attribute is refused under `style-src 'self'` — and this host
136
+ // would then lay out an empty box in the middle of the content.
137
+ host: { '[style.display]': "'none'" },
80
138
  }]
81
139
  }], ctorParameters: () => [], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], key: [{ type: i0.Input, args: [{ isSignal: true, alias: "key", required: false }] }], routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], contentTpl: [{ type: i0.ViewChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
82
140
 
@@ -118,7 +176,10 @@ function idFragment(key) {
118
176
  * active-tab panel automatically.
119
177
  * - **Router** — any child with `routerLink` switches the whole strip
120
178
  * into router mode; the parent skips its panel so the consumer can
121
- * drop a `<router-outlet>` after it.
179
+ * drop a `<router-outlet>` after it. That mode needs the `WrTabsRouting`
180
+ * directive from `ngwr/tabs/router` alongside `WrTabs` in `imports: []`
181
+ * — the strip itself names no `@angular/router` symbol, which is what
182
+ * keeps a content-only strip free of the router.
122
183
  *
123
184
  * @example
124
185
  * ```html
@@ -128,7 +189,7 @@ function idFragment(key) {
128
189
  * <wr-tab key="two" title="Two">Second panel</wr-tab>
129
190
  * </wr-tabs>
130
191
  *
131
- * <!-- router tabs -->
192
+ * <!-- router tabs: imports: [WrTabs, WrTab, WrTabsRouting] -->
132
193
  * <wr-tabs>
133
194
  * <wr-tab title="Overview" routerLink="overview" />
134
195
  * <wr-tab title="Details" routerLink="details" />
@@ -157,8 +218,31 @@ class WrTabs {
157
218
  */
158
219
  isRtl = computed(() => this.dir?.valueSignal() === 'rtl', /* @ts-ignore */
159
220
  ...(ngDevMode ? [{ debugName: "isRtl" }] : /* istanbul ignore next */ []));
160
- /** When any child has a routerLink, the whole strip switches to router mode. */
161
- isRouter = computed(() => this.tabs().some(t => t.routerLink() !== null), /* @ts-ignore */
221
+ /**
222
+ * The routing adapter, when `WrTabsRouting` from `ngwr/tabs/router` is in the
223
+ * consumer's `imports`. `null` is the default and the whole point: the strip
224
+ * has no `@angular/router` import of its own, so a content-only tab strip
225
+ * costs no router bytes.
226
+ */
227
+ routing = inject(WR_TABS_ROUTING, { optional: true });
228
+ /**
229
+ * When any child has a routerLink, the whole strip switches to router mode.
230
+ *
231
+ * Throws rather than degrading when the adapter is missing. Falling back to
232
+ * content mode would render the tabs as `<button>`s that navigate nowhere and
233
+ * open an empty panel — a strip that looks right and does nothing, with the
234
+ * one missing import unnamed. The route is what selects a tab here, so there
235
+ * is no half of this that still works.
236
+ */
237
+ isRouter = computed(() => {
238
+ const isRouter = this.tabs().some(t => t.routerLink() !== null);
239
+ if (isRouter && !this.routing) {
240
+ throw new Error('<wr-tabs>: a <wr-tab> sets `routerLink`, but no routing adapter is provided. ' +
241
+ "Add `WrTabsRouting` from 'ngwr/tabs/router' to the `imports` of the component that " +
242
+ 'declares this strip.');
243
+ }
244
+ return isRouter;
245
+ }, /* @ts-ignore */
162
246
  ...(ngDevMode ? [{ debugName: "isRouter" }] : /* istanbul ignore next */ []));
163
247
  /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */
164
248
  activeTab = computed(() => {
@@ -242,6 +326,14 @@ class WrTabs {
242
326
  if (!this.isRouter())
243
327
  this.activate(tab.key());
244
328
  }
329
+ /**
330
+ * Click on a router tab's anchor. The anchor carries a real `href`, so a
331
+ * modified or middle click is left alone and the browser opens it the way it
332
+ * opens any link; only a plain left click is turned into a navigation.
333
+ */
334
+ onRouterTabClick(tab, event) {
335
+ tab.navigate(event);
336
+ }
245
337
  /** Per-instance prefix, so keys only have to be unique within one group. */
246
338
  uid = ++tabsUid;
247
339
  /** Header id for `aria-labelledby` on the panel. @internal */
@@ -314,7 +406,7 @@ class WrTabs {
314
406
  // eslint-disable-next-line @angular-eslint/no-forward-ref
315
407
  useExisting: forwardRef(() => WrTabs),
316
408
  },
317
- ], queries: [{ propertyName: "tabs", predicate: WrTab, isSignal: true }], viewQueries: [{ propertyName: "stripRef", first: true, predicate: ["strip"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink(); as link) {\n <!--\n `aria-selected` comes off the SAME `routerLinkActive` that paints `--active`,\n exported as `rla`, so the announced state and the painted one can never\n disagree. Without it a `role=\"tablist\"` shipped with no tab selected in any of\n them: the route decides here, and the class it paints is invisible to\n assistive tech.\n -->\n <a\n #rla=\"routerLinkActive\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n routerLinkActive=\"wr-tabs__tab--active\"\n [attr.id]=\"headerId(tab.key())\"\n [routerLink]=\"tab.disabled() ? null : link\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"rla.isActive\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], encapsulation: i0.ViewEncapsulation.None });
409
+ ], queries: [{ propertyName: "tabs", predicate: WrTab, isSignal: true }], viewQueries: [{ propertyName: "stripRef", first: true, predicate: ["strip"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink() !== null) {\n <!--\n `aria-selected` and `--active` come off the SAME `tab.routerActive()`, so the\n announced state and the painted one can never disagree. Without it a\n `role=\"tablist\"` shipped with no tab selected in any of them: the route decides\n here, and the class it paints is invisible to assistive tech.\n\n A real `href` rather than a `[routerLink]`: the directive would drag\n `@angular/router` into every content-only strip as well (see `WR_TABS_ROUTING`).\n The href, the active match and the click all come from the adapter the\n `ngwr/tabs/router` directive publishes, which is `RouterLink` doing the work.\n -->\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.href]=\"tab.routerHref()\"\n [class.wr-tabs__tab--active]=\"tab.routerActive()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"tab.routerActive()\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n (click)=\"onRouterTabClick(tab, $event)\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
318
410
  }
319
411
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTabs, decorators: [{
320
412
  type: Component,
@@ -324,18 +416,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
324
416
  '[class.wr-tabs--fade-end]': 'canScrollEnd()',
325
417
  '[class.wr-tabs--sm]': "size() === 'sm'",
326
418
  '[class.wr-tabs--lg]': "size() === 'lg'",
327
- }, imports: [NgTemplateOutlet, RouterLink, RouterLinkActive], providers: [
419
+ }, imports: [NgTemplateOutlet], providers: [
328
420
  {
329
421
  provide: WR_TABS,
330
422
  // eslint-disable-next-line @angular-eslint/no-forward-ref
331
423
  useExisting: forwardRef(() => WrTabs),
332
424
  },
333
- ], template: "<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink(); as link) {\n <!--\n `aria-selected` comes off the SAME `routerLinkActive` that paints `--active`,\n exported as `rla`, so the announced state and the painted one can never\n disagree. Without it a `role=\"tablist\"` shipped with no tab selected in any of\n them: the route decides here, and the class it paints is invisible to\n assistive tech.\n -->\n <a\n #rla=\"routerLinkActive\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n routerLinkActive=\"wr-tabs__tab--active\"\n [attr.id]=\"headerId(tab.key())\"\n [routerLink]=\"tab.disabled() ? null : link\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"rla.isActive\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n" }]
425
+ ], template: "<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink() !== null) {\n <!--\n `aria-selected` and `--active` come off the SAME `tab.routerActive()`, so the\n announced state and the painted one can never disagree. Without it a\n `role=\"tablist\"` shipped with no tab selected in any of them: the route decides\n here, and the class it paints is invisible to assistive tech.\n\n A real `href` rather than a `[routerLink]`: the directive would drag\n `@angular/router` into every content-only strip as well (see `WR_TABS_ROUTING`).\n The href, the active match and the click all come from the adapter the\n `ngwr/tabs/router` directive publishes, which is `RouterLink` doing the work.\n -->\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.href]=\"tab.routerHref()\"\n [class.wr-tabs__tab--active]=\"tab.routerActive()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"tab.routerActive()\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n (click)=\"onRouterTabClick(tab, $event)\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n" }]
334
426
  }], ctorParameters: () => [], propDecorators: { active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }, { type: i0.Output, args: ["activeChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WrTab), { isSignal: true }] }], stripRef: [{ type: i0.ViewChild, args: ['strip', { isSignal: true }] }] } });
335
427
 
336
428
  /**
337
429
  * Generated bundle index. Do not edit.
338
430
  */
339
431
 
340
- export { WR_TABS, WrTab, WrTabs };
432
+ export { WR_TABS, WR_TABS_ROUTING, WrTab, WrTabs };
341
433
  //# sourceMappingURL=ngwr-tabs.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-tabs.mjs","sources":["../../../projects/lib/tabs/tokens/tabs.token.ts","../../../projects/lib/tabs/tab.ts","../../../projects/lib/tabs/tabs.ts","../../../projects/lib/tabs/tabs.html","../../../projects/lib/tabs/ngwr-tabs.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 { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Contract a `<wr-tab>` uses to talk to its parent `<wr-tabs>`.\n *\n * @internal\n */\nexport interface WrTabsContext {\n /** Currently active tab key (component-managed only). */\n readonly active: Signal<string | null>;\n /** Activate a tab by key (component-managed only). */\n activate(key: string): void;\n /** Register a child tab so the parent can pick a default. */\n register(tab: { key: string; routerLink: unknown }): void;\n /** Unregister on destroy. */\n unregister(key: string): void;\n}\n\n/**\n * Token a `<wr-tab>` injects to register itself with — and read the active\n * key from — its parent `<wr-tabs>` host.\n *\n * @internal\n */\nexport const WR_TABS = new InjectionToken<WrTabsContext>('WR_TABS');\n","/**\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 {\n Component,\n DestroyRef,\n TemplateRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_TABS } from './tokens';\n\n/**\n * Single tab. Two modes, decided by inputs:\n *\n * - **Content tab** — projected children render in the parent's content\n * panel when this tab is active.\n * - **Router tab** — set `routerLink` and the tab becomes a link; the\n * parent skips its content panel and the consumer drops a\n * `<router-outlet>` below the tab strip.\n */\n@Component({\n selector: 'wr-tab',\n template: `<ng-template><ng-content /></ng-template>`,\n encapsulation: ViewEncapsulation.None,\n host: { style: 'display:none' },\n})\nexport class WrTab {\n /** Visible label on the tab strip. */\n readonly title = input.required<string>();\n\n /**\n * Stable identifier used by the parent to track the active tab.\n * Auto-generated if omitted.\n */\n readonly key = input<string>(randomId('wr-tab'));\n\n /** Router target — when set, the tab becomes a `[routerLink]` link. */\n readonly routerLink = input<string | readonly string[] | null>(null);\n\n /** Disable the tab. */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Tab content template; portaled into the parent's panel area. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n private readonly parent = inject(WR_TABS, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly isRouter = computed(() => this.routerLink() !== null);\n\n constructor() {\n if (this.parent) {\n // Both calls run with the input signals still on their defaults — a\n // constructor is too early for `key()` to be the consumer's value. The\n // parent tracks tabs through `contentChildren` and seeds `active` from\n // there; these stay as the declared lifecycle contract.\n this.parent.register({ key: this.key(), routerLink: this.routerLink() });\n this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));\n }\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { WrTab } from './tab';\nimport { WR_TABS, type WrTabsContext } from './tokens';\n\n/**\n * Instance counter for the header / panel ids. Deterministic under prerender,\n * unlike `randomId()` — the same reason `wr-sidebar` and `wr-mention` count.\n *\n * The ids used to be built from the tab key alone, so two `<wr-tabs>` on one\n * page that both name a tab `key=\"overview\"` emitted the same\n * `wr-tab-overview-header` twice, and the second group's `aria-labelledby`\n * resolved to the FIRST group's tab. A key is documented as parent-scoped, so\n * it is the parent that has to make it unique document-wide.\n */\nlet tabsUid = 0;\n\n/**\n * A tab key as an id fragment: anything outside `[A-Za-z0-9_-]` becomes `-`.\n * These ids land in `aria-controls` / `aria-labelledby`, which are\n * space-separated IDREF lists — `key=\"my tab\"` emitted\n * `aria-controls=\"wr-tab-my tab-panel\"`, two tokens, neither of which resolves.\n *\n * Two keys differing only outside that set collapse to one fragment; the\n * instance counter keeps that inside a single group, where the keys are the\n * consumer's own to keep apart.\n */\nfunction idFragment(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-');\n}\n\n/**\n * Tabbed container. Two modes depending on the child `<wr-tab>`\n * definitions:\n *\n * - **Content** — children project body content; the parent renders an\n * active-tab panel automatically.\n * - **Router** — any child with `routerLink` switches the whole strip\n * into router mode; the parent skips its panel so the consumer can\n * drop a `<router-outlet>` after it.\n *\n * @example\n * ```html\n * <!-- content tabs -->\n * <wr-tabs [(active)]=\"key\">\n * <wr-tab key=\"one\" title=\"One\">First panel</wr-tab>\n * <wr-tab key=\"two\" title=\"Two\">Second panel</wr-tab>\n * </wr-tabs>\n *\n * <!-- router tabs -->\n * <wr-tabs>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n */\n@Component({\n selector: 'wr-tabs',\n templateUrl: './tabs.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-tabs',\n '[class.wr-tabs--fade-start]': 'canScrollStart()',\n '[class.wr-tabs--fade-end]': 'canScrollEnd()',\n '[class.wr-tabs--sm]': \"size() === 'sm'\",\n '[class.wr-tabs--lg]': \"size() === 'lg'\",\n },\n imports: [NgTemplateOutlet, RouterLink, RouterLinkActive],\n providers: [\n {\n provide: WR_TABS,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabs),\n },\n ],\n})\nexport class WrTabs implements WrTabsContext {\n /** Two-way bindable active tab key (content mode). */\n readonly active = model<string | null>(null);\n\n /** Visual size variant. */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n protected readonly tabs = contentChildren(WrTab);\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. It reads the document once at construction; a\n * subtree that overrides the direction does it with the CDK's `Dir`\n * directive, which writes `valueSignal`, so the read stays reactive.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /** When any child has a routerLink, the whole strip switches to router mode. */\n protected readonly isRouter = computed(() => this.tabs().some(t => t.routerLink() !== null));\n\n /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */\n protected readonly activeTab = computed(() => {\n const key = this.active();\n const tabs = this.tabs();\n if (tabs.length === 0) return null;\n return tabs.find(t => t.key() === key) ?? tabs[0];\n });\n\n // WrTabsContext\n\n activate(key: string): void {\n this.active.set(key);\n }\n\n register(): void {\n // Deliberately a no-op. A `WrTab` calls this from its own constructor,\n // where its `key` signal input is not bound yet — so it would report the\n // generated default and this parent would write THAT into `active`. The\n // effect below seeds from `tabs()`, by which point the inputs are real.\n }\n\n unregister(): void {\n // No-op: contentChildren handles removal.\n }\n\n // Template handlers\n\n protected readonly stripRef = viewChild<ElementRef<HTMLElement>>('strip');\n\n /** Whether the strip is scrolled away from its start / end — drives the edge fades. */\n protected readonly canScrollStart = signal(false);\n protected readonly canScrollEnd = signal(false);\n\n constructor() {\n // Seed `active` with the first tab's key, once the content children exist\n // and their inputs are bound. Without this the model writes back a\n // generated id like `wr-tab-b1crta5aix0v`: the strip still highlighted the\n // right tab, because `activeTab()` falls back to `tabs[0]`, so the only\n // visible symptom was a two-way binding holding a key the consumer had\n // never heard of.\n effect(() => {\n const tabs = this.tabs();\n if (tabs.length > 0 && this.active() === null) this.active.set(tabs[0].key());\n });\n\n const destroyRef = inject(DestroyRef);\n afterNextRender(() => {\n this.updateFades();\n const el = this.stripRef()?.nativeElement;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Re-evaluate when the strip's width changes (e.g. the container narrows\n // and the tabs start overflowing).\n const ro = new ResizeObserver(() => this.updateFades());\n ro.observe(el);\n destroyRef.onDestroy(() => ro.disconnect());\n });\n }\n\n /** Recompute which edges can scroll, from the strip's scroll metrics. @internal */\n protected updateFades(): void {\n const el = this.stripRef()?.nativeElement;\n if (!el) return;\n const max = el.scrollWidth - el.clientWidth;\n // Distance from the INLINE start, which is what the two fades mean.\n // `scrollLeft` is 0 at the inline start in BOTH directions and counts away\n // from it — up in LTR, DOWN into negatives under `dir=\"rtl\"` (the CSSOM\n // model every engine Angular 22 supports now implements; the legacy\n // \"reversed\"/\"normal\" RTL conventions are gone). Read raw, the RTL value\n // said \"never scrolled from the start, always more to come\" at every\n // position: the end fade was pinned on and the start fade never appeared.\n //\n // Clamped rather than `Math.abs`, because an elastic overscroll reports a\n // value of the WRONG sign at the start edge — a rubber-band past the start\n // is still the start, and `abs` would light the start fade there in LTR too.\n const scrolled = this.isRtl() ? Math.max(-el.scrollLeft, 0) : Math.max(el.scrollLeft, 0);\n this.canScrollStart.set(scrolled > 1);\n this.canScrollEnd.set(max > 1 && scrolled < max - 1);\n }\n\n protected onTabClick(tab: WrTab): void {\n if (tab.disabled()) return;\n if (!this.isRouter()) this.activate(tab.key());\n }\n\n /** Per-instance prefix, so keys only have to be unique within one group. */\n private readonly uid = ++tabsUid;\n\n /** Header id for `aria-labelledby` on the panel. @internal */\n headerId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-header`;\n }\n\n /** Panel id for `aria-controls` on the tab header. @internal */\n panelId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-panel`;\n }\n\n /** ArrowLeft/Right/Home/End keyboard navigation on the tab strip. */\n protected onStripKeydown(event: KeyboardEvent): void {\n const tabs = this.tabs().filter(t => !t.disabled());\n if (tabs.length === 0) return;\n\n // Step from where FOCUS is, not from what is active.\n //\n // In router mode nothing ever moves `active` — `onTabClick` and this\n // handler both gate `activate()` on `!isRouter()`, because the route is\n // what selects a tab there. Reading the neighbour off `activeTab()`\n // therefore recomputed the same `idx` on every press, and ArrowRight\n // walked from the first tab to the second and stayed there for the rest of\n // the session. In manual mode focus and activation move together, so the\n // two readings agree and nothing changes.\n const strip = this.stripRef()?.nativeElement;\n const focused = strip?.ownerDocument.activeElement;\n const focusedIdx =\n focused instanceof HTMLElement && strip?.contains(focused)\n ? tabs.findIndex(t => this.headerId(t.key()) === focused.id)\n : -1;\n const active = this.activeTab();\n const idx = focusedIdx >= 0 ? focusedIdx : active ? tabs.indexOf(active) : -1;\n // The two neighbours in DOM order, wrapping at the ends.\n const forward = idx < tabs.length - 1 ? idx + 1 : 0;\n const backward = idx > 0 ? idx - 1 : tabs.length - 1;\n const rtl = this.isRtl();\n let nextIdx: number;\n\n switch (event.key) {\n // Arrow keys follow VISUAL order (WAI-ARIA APG). The strip is mirrored\n // under `dir=\"rtl\"`, so ArrowRight moves toward the visual right, which is\n // the PREVIOUS tab there.\n case 'ArrowRight':\n nextIdx = rtl ? backward : forward;\n break;\n case 'ArrowLeft':\n nextIdx = rtl ? forward : backward;\n break;\n // Home / End name a position in the tab list — first and last — not a\n // physical edge, so they read the same in both directions.\n case 'Home':\n nextIdx = 0;\n break;\n case 'End':\n nextIdx = tabs.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const next = tabs[nextIdx];\n if (!this.isRouter()) this.activate(next.key());\n // Move focus to the corresponding header element.\n const headerEl = this.stripRef()?.nativeElement.querySelector<HTMLElement>(\n `#${CSS.escape(this.headerId(next.key()))}`\n );\n headerEl?.focus();\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink(); as link) {\n <!--\n `aria-selected` comes off the SAME `routerLinkActive` that paints `--active`,\n exported as `rla`, so the announced state and the painted one can never\n disagree. Without it a `role=\"tablist\"` shipped with no tab selected in any of\n them: the route decides here, and the class it paints is invisible to\n assistive tech.\n -->\n <a\n #rla=\"routerLinkActive\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n routerLinkActive=\"wr-tabs__tab--active\"\n [attr.id]=\"headerId(tab.key())\"\n [routerLink]=\"tab.disabled() ? null : link\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"rla.isActive\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\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-tabs.mjs","sources":["../../../projects/lib/tabs/tokens/tabs.token.ts","../../../projects/lib/tabs/tokens/tabs-routing.token.ts","../../../projects/lib/tabs/tab.ts","../../../projects/lib/tabs/tabs.ts","../../../projects/lib/tabs/tabs.html","../../../projects/lib/tabs/ngwr-tabs.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 { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Contract a `<wr-tab>` uses to talk to its parent `<wr-tabs>`.\n *\n * @internal\n */\nexport interface WrTabsContext {\n /** Currently active tab key (component-managed only). */\n readonly active: Signal<string | null>;\n /** Activate a tab by key (component-managed only). */\n activate(key: string): void;\n /** Register a child tab so the parent can pick a default. */\n register(tab: { key: string; routerLink: unknown }): void;\n /** Unregister on destroy. */\n unregister(key: string): void;\n}\n\n/**\n * Token a `<wr-tab>` injects to register itself with — and read the active\n * key from — its parent `<wr-tabs>` host.\n *\n * @internal\n */\nexport const WR_TABS = new InjectionToken<WrTabsContext>('WR_TABS');\n","/**\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 { InjectionToken } from '@angular/core';\n\n/**\n * What a `<wr-tabs>` needs from a router in order to render a router tab, and\n * nothing more. Three questions — the href to put on the anchor, whether the\n * current URL selects it, and what a click should do.\n *\n * It exists so that `ngwr/tabs` can name the capability without naming\n * `@angular/router`: a directive listed in `imports: []` lands in the compiled\n * component's `dependencies` whether or not its branch of the template ever\n * runs, so `RouterLink` + `RouterLinkActive` on the strip charged every content\n * tab strip the whole router — 73.9 kB of a 144.8 kB component. The\n * implementation lives at `ngwr/tabs/router`, which a consumer imports only\n * when they use router tabs.\n *\n * `href` and `isActive` are read from computeds and must both be reactive to\n * navigation: a RELATIVE `routerLink` resolves against the active route, so its\n * href changes when the route does, exactly as `RouterLink` recomputes its own.\n */\nexport interface WrTabsRoutingAdapter {\n /**\n * Serialized href for a tab's `routerLink`, or `null` when the adapter cannot\n * build one.\n */\n href(commands: string | readonly string[]): string | null;\n\n /**\n * Whether the current URL selects this tab. Matched the way `routerLinkActive`\n * matches without `[routerLinkActiveOptions]`: a path prefix, not an exact URL.\n */\n isActive(commands: string | readonly string[]): boolean;\n\n /**\n * Handle a click on a tab anchor. A modified or non-primary click is left to\n * the browser, so ctrl/cmd-click still opens the tab in a new window.\n */\n navigate(commands: string | readonly string[], event: MouseEvent): void;\n}\n\n/**\n * Token a `<wr-tabs>` reads to find out whether router tabs are usable. Provided\n * by the `WrTabsRouting` directive from `ngwr/tabs/router`; absent by default,\n * which is what keeps `@angular/router` out of a content-only strip.\n */\nexport const WR_TABS_ROUTING = new InjectionToken<WrTabsRoutingAdapter>('WR_TABS_ROUTING');\n","/**\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 {\n Component,\n DestroyRef,\n TemplateRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_TABS, WR_TABS_ROUTING } from './tokens';\n\n/**\n * Single tab. Two modes, decided by inputs:\n *\n * - **Content tab** — projected children render in the parent's content\n * panel when this tab is active.\n * - **Router tab** — set `routerLink` and the tab becomes a link; the\n * parent skips its content panel and the consumer drops a\n * `<router-outlet>` below the tab strip. Router mode needs the\n * `WrTabsRouting` directive from `ngwr/tabs/router` in the same\n * `imports: []`; without it a strip carrying a `routerLink` throws.\n */\n@Component({\n selector: 'wr-tab',\n template: `<ng-template><ng-content /></ng-template>`,\n encapsulation: ViewEncapsulation.None,\n // A BOUND display rather than a static `style` attribute: Angular writes a\n // binding through `style.setProperty`, which no CSP governs, while a real\n // `style=\"…\"` attribute is refused under `style-src 'self'` — and this host\n // would then lay out an empty box in the middle of the content.\n host: { '[style.display]': \"'none'\" },\n})\nexport class WrTab {\n /** Visible label on the tab strip. */\n readonly title = input.required<string>();\n\n /**\n * Stable identifier used by the parent to track the active tab.\n * Auto-generated if omitted.\n */\n readonly key = input<string>(randomId('wr-tab'));\n\n /** Router target — when set, the tab becomes a `[routerLink]` link. */\n readonly routerLink = input<string | readonly string[] | null>(null);\n\n /** Disable the tab. */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Tab content template; portaled into the parent's panel area. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n private readonly parent = inject(WR_TABS, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n /**\n * The routing adapter published by `WrTabsRouting` on the parent `<wr-tabs>`\n * element. A `<wr-tab>` is DECLARED inside `<wr-tabs>` in the consumer's\n * template — content projection moves where it renders, not where it injects\n * — so the parent's element injector is this one's, the same walk that\n * resolves `WR_TABS` above.\n */\n private readonly routing = inject(WR_TABS_ROUTING, { optional: true });\n\n protected readonly isRouter = computed(() => this.routerLink() !== null);\n\n /**\n * Resolved href for a router tab — `null` for a content tab, and for a\n * disabled one, which is how the anchor stops navigating at all.\n * @internal\n */\n readonly routerHref = computed(() => {\n const link = this.routerLink();\n if (link === null || this.disabled()) return null;\n return this.routing?.href(link) ?? null;\n });\n\n /**\n * Whether the current route selects this tab. The strip publishes it as BOTH\n * `aria-selected` and the `--active` class, off this one read, so the\n * announced selection and the painted one cannot drift apart.\n * @internal\n */\n readonly routerActive = computed(() => {\n const link = this.routerLink();\n if (link === null || this.disabled()) return false;\n return this.routing?.isActive(link) ?? false;\n });\n\n /** Hand a click on this tab's anchor to the routing adapter. @internal */\n navigate(event: MouseEvent): void {\n const link = this.routerLink();\n if (link === null || this.disabled()) return;\n this.routing?.navigate(link, event);\n }\n\n constructor() {\n if (this.parent) {\n // Both calls run with the input signals still on their defaults — a\n // constructor is too early for `key()` to be the consumer's value. The\n // parent tracks tabs through `contentChildren` and seeds `active` from\n // there; these stay as the declared lifecycle contract.\n this.parent.register({ key: this.key(), routerLink: this.routerLink() });\n this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));\n }\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { WrTab } from './tab';\nimport { WR_TABS, WR_TABS_ROUTING, type WrTabsContext } from './tokens';\n\n/**\n * Instance counter for the header / panel ids. Deterministic under prerender,\n * unlike `randomId()` — the same reason `wr-sidebar` and `wr-mention` count.\n *\n * The ids used to be built from the tab key alone, so two `<wr-tabs>` on one\n * page that both name a tab `key=\"overview\"` emitted the same\n * `wr-tab-overview-header` twice, and the second group's `aria-labelledby`\n * resolved to the FIRST group's tab. A key is documented as parent-scoped, so\n * it is the parent that has to make it unique document-wide.\n */\nlet tabsUid = 0;\n\n/**\n * A tab key as an id fragment: anything outside `[A-Za-z0-9_-]` becomes `-`.\n * These ids land in `aria-controls` / `aria-labelledby`, which are\n * space-separated IDREF lists — `key=\"my tab\"` emitted\n * `aria-controls=\"wr-tab-my tab-panel\"`, two tokens, neither of which resolves.\n *\n * Two keys differing only outside that set collapse to one fragment; the\n * instance counter keeps that inside a single group, where the keys are the\n * consumer's own to keep apart.\n */\nfunction idFragment(key: string): string {\n return key.replace(/[^A-Za-z0-9_-]/g, '-');\n}\n\n/**\n * Tabbed container. Two modes depending on the child `<wr-tab>`\n * definitions:\n *\n * - **Content** — children project body content; the parent renders an\n * active-tab panel automatically.\n * - **Router** — any child with `routerLink` switches the whole strip\n * into router mode; the parent skips its panel so the consumer can\n * drop a `<router-outlet>` after it. That mode needs the `WrTabsRouting`\n * directive from `ngwr/tabs/router` alongside `WrTabs` in `imports: []`\n * — the strip itself names no `@angular/router` symbol, which is what\n * keeps a content-only strip free of the router.\n *\n * @example\n * ```html\n * <!-- content tabs -->\n * <wr-tabs [(active)]=\"key\">\n * <wr-tab key=\"one\" title=\"One\">First panel</wr-tab>\n * <wr-tab key=\"two\" title=\"Two\">Second panel</wr-tab>\n * </wr-tabs>\n *\n * <!-- router tabs: imports: [WrTabs, WrTab, WrTabsRouting] -->\n * <wr-tabs>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n */\n@Component({\n selector: 'wr-tabs',\n templateUrl: './tabs.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-tabs',\n '[class.wr-tabs--fade-start]': 'canScrollStart()',\n '[class.wr-tabs--fade-end]': 'canScrollEnd()',\n '[class.wr-tabs--sm]': \"size() === 'sm'\",\n '[class.wr-tabs--lg]': \"size() === 'lg'\",\n },\n imports: [NgTemplateOutlet],\n providers: [\n {\n provide: WR_TABS,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabs),\n },\n ],\n})\nexport class WrTabs implements WrTabsContext {\n /** Two-way bindable active tab key (content mode). */\n readonly active = model<string | null>(null);\n\n /** Visual size variant. */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n protected readonly tabs = contentChildren(WrTab);\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. It reads the document once at construction; a\n * subtree that overrides the direction does it with the CDK's `Dir`\n * directive, which writes `valueSignal`, so the read stays reactive.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /**\n * The routing adapter, when `WrTabsRouting` from `ngwr/tabs/router` is in the\n * consumer's `imports`. `null` is the default and the whole point: the strip\n * has no `@angular/router` import of its own, so a content-only tab strip\n * costs no router bytes.\n */\n private readonly routing = inject(WR_TABS_ROUTING, { optional: true });\n\n /**\n * When any child has a routerLink, the whole strip switches to router mode.\n *\n * Throws rather than degrading when the adapter is missing. Falling back to\n * content mode would render the tabs as `<button>`s that navigate nowhere and\n * open an empty panel — a strip that looks right and does nothing, with the\n * one missing import unnamed. The route is what selects a tab here, so there\n * is no half of this that still works.\n */\n protected readonly isRouter = computed(() => {\n const isRouter = this.tabs().some(t => t.routerLink() !== null);\n if (isRouter && !this.routing) {\n throw new Error(\n '<wr-tabs>: a <wr-tab> sets `routerLink`, but no routing adapter is provided. ' +\n \"Add `WrTabsRouting` from 'ngwr/tabs/router' to the `imports` of the component that \" +\n 'declares this strip.'\n );\n }\n return isRouter;\n });\n\n /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */\n protected readonly activeTab = computed(() => {\n const key = this.active();\n const tabs = this.tabs();\n if (tabs.length === 0) return null;\n return tabs.find(t => t.key() === key) ?? tabs[0];\n });\n\n // WrTabsContext\n\n activate(key: string): void {\n this.active.set(key);\n }\n\n register(): void {\n // Deliberately a no-op. A `WrTab` calls this from its own constructor,\n // where its `key` signal input is not bound yet — so it would report the\n // generated default and this parent would write THAT into `active`. The\n // effect below seeds from `tabs()`, by which point the inputs are real.\n }\n\n unregister(): void {\n // No-op: contentChildren handles removal.\n }\n\n // Template handlers\n\n protected readonly stripRef = viewChild<ElementRef<HTMLElement>>('strip');\n\n /** Whether the strip is scrolled away from its start / end — drives the edge fades. */\n protected readonly canScrollStart = signal(false);\n protected readonly canScrollEnd = signal(false);\n\n constructor() {\n // Seed `active` with the first tab's key, once the content children exist\n // and their inputs are bound. Without this the model writes back a\n // generated id like `wr-tab-b1crta5aix0v`: the strip still highlighted the\n // right tab, because `activeTab()` falls back to `tabs[0]`, so the only\n // visible symptom was a two-way binding holding a key the consumer had\n // never heard of.\n effect(() => {\n const tabs = this.tabs();\n if (tabs.length > 0 && this.active() === null) this.active.set(tabs[0].key());\n });\n\n const destroyRef = inject(DestroyRef);\n afterNextRender(() => {\n this.updateFades();\n const el = this.stripRef()?.nativeElement;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Re-evaluate when the strip's width changes (e.g. the container narrows\n // and the tabs start overflowing).\n const ro = new ResizeObserver(() => this.updateFades());\n ro.observe(el);\n destroyRef.onDestroy(() => ro.disconnect());\n });\n }\n\n /** Recompute which edges can scroll, from the strip's scroll metrics. @internal */\n protected updateFades(): void {\n const el = this.stripRef()?.nativeElement;\n if (!el) return;\n const max = el.scrollWidth - el.clientWidth;\n // Distance from the INLINE start, which is what the two fades mean.\n // `scrollLeft` is 0 at the inline start in BOTH directions and counts away\n // from it — up in LTR, DOWN into negatives under `dir=\"rtl\"` (the CSSOM\n // model every engine Angular 22 supports now implements; the legacy\n // \"reversed\"/\"normal\" RTL conventions are gone). Read raw, the RTL value\n // said \"never scrolled from the start, always more to come\" at every\n // position: the end fade was pinned on and the start fade never appeared.\n //\n // Clamped rather than `Math.abs`, because an elastic overscroll reports a\n // value of the WRONG sign at the start edge — a rubber-band past the start\n // is still the start, and `abs` would light the start fade there in LTR too.\n const scrolled = this.isRtl() ? Math.max(-el.scrollLeft, 0) : Math.max(el.scrollLeft, 0);\n this.canScrollStart.set(scrolled > 1);\n this.canScrollEnd.set(max > 1 && scrolled < max - 1);\n }\n\n protected onTabClick(tab: WrTab): void {\n if (tab.disabled()) return;\n if (!this.isRouter()) this.activate(tab.key());\n }\n\n /**\n * Click on a router tab's anchor. The anchor carries a real `href`, so a\n * modified or middle click is left alone and the browser opens it the way it\n * opens any link; only a plain left click is turned into a navigation.\n */\n protected onRouterTabClick(tab: WrTab, event: MouseEvent): void {\n tab.navigate(event);\n }\n\n /** Per-instance prefix, so keys only have to be unique within one group. */\n private readonly uid = ++tabsUid;\n\n /** Header id for `aria-labelledby` on the panel. @internal */\n headerId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-header`;\n }\n\n /** Panel id for `aria-controls` on the tab header. @internal */\n panelId(key: string): string {\n return `wr-tabs-${this.uid}-tab-${idFragment(key)}-panel`;\n }\n\n /** ArrowLeft/Right/Home/End keyboard navigation on the tab strip. */\n protected onStripKeydown(event: KeyboardEvent): void {\n const tabs = this.tabs().filter(t => !t.disabled());\n if (tabs.length === 0) return;\n\n // Step from where FOCUS is, not from what is active.\n //\n // In router mode nothing ever moves `active` — `onTabClick` and this\n // handler both gate `activate()` on `!isRouter()`, because the route is\n // what selects a tab there. Reading the neighbour off `activeTab()`\n // therefore recomputed the same `idx` on every press, and ArrowRight\n // walked from the first tab to the second and stayed there for the rest of\n // the session. In manual mode focus and activation move together, so the\n // two readings agree and nothing changes.\n const strip = this.stripRef()?.nativeElement;\n const focused = strip?.ownerDocument.activeElement;\n const focusedIdx =\n focused instanceof HTMLElement && strip?.contains(focused)\n ? tabs.findIndex(t => this.headerId(t.key()) === focused.id)\n : -1;\n const active = this.activeTab();\n const idx = focusedIdx >= 0 ? focusedIdx : active ? tabs.indexOf(active) : -1;\n // The two neighbours in DOM order, wrapping at the ends.\n const forward = idx < tabs.length - 1 ? idx + 1 : 0;\n const backward = idx > 0 ? idx - 1 : tabs.length - 1;\n const rtl = this.isRtl();\n let nextIdx: number;\n\n switch (event.key) {\n // Arrow keys follow VISUAL order (WAI-ARIA APG). The strip is mirrored\n // under `dir=\"rtl\"`, so ArrowRight moves toward the visual right, which is\n // the PREVIOUS tab there.\n case 'ArrowRight':\n nextIdx = rtl ? backward : forward;\n break;\n case 'ArrowLeft':\n nextIdx = rtl ? forward : backward;\n break;\n // Home / End name a position in the tab list — first and last — not a\n // physical edge, so they read the same in both directions.\n case 'Home':\n nextIdx = 0;\n break;\n case 'End':\n nextIdx = tabs.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const next = tabs[nextIdx];\n if (!this.isRouter()) this.activate(next.key());\n // Move focus to the corresponding header element.\n const headerEl = this.stripRef()?.nativeElement.querySelector<HTMLElement>(\n `#${CSS.escape(this.headerId(next.key()))}`\n );\n headerEl?.focus();\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink() !== null) {\n <!--\n `aria-selected` and `--active` come off the SAME `tab.routerActive()`, so the\n announced state and the painted one can never disagree. Without it a\n `role=\"tablist\"` shipped with no tab selected in any of them: the route decides\n here, and the class it paints is invisible to assistive tech.\n\n A real `href` rather than a `[routerLink]`: the directive would drag\n `@angular/router` into every content-only strip as well (see `WR_TABS_ROUTING`).\n The href, the active match and the click all come from the adapter the\n `ngwr/tabs/router` directive publishes, which is `RouterLink` doing the work.\n -->\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.href]=\"tab.routerHref()\"\n [class.wr-tabs__tab--active]=\"tab.routerActive()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.aria-selected]=\"tab.routerActive()\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n (click)=\"onRouterTabClick(tab, $event)\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { Directionality } from '@angular/cdk/bidi';
1
2
  import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
2
3
  import { isPlatformBrowser } from '@angular/common';
3
4
  import * as i0 from '@angular/core';
@@ -100,6 +101,15 @@ class WrTextarea {
100
101
  platformId = inject(PLATFORM_ID);
101
102
  isBrowser = isPlatformBrowser(this.platformId);
102
103
  hostEl = inject(ElementRef);
104
+ /**
105
+ * Ambient reading direction, read by the horizontal half of the resize drag.
106
+ *
107
+ * Optional so a bare `TestBed` — or any consumer that never set a direction —
108
+ * needs no provider; `Directionality` is root-provided, so `null` only ever
109
+ * means "nobody asked", which is LTR. Read inside the handler rather than
110
+ * cached, so a runtime flip needs no subscription to `Directionality.change`.
111
+ */
112
+ dir = inject(Directionality, { optional: true });
103
113
  // Custom resize-grip drag state (replaces the native corner handle).
104
114
  resizing = false;
105
115
  resizeStartY = 0;
@@ -169,7 +179,16 @@ class WrTextarea {
169
179
  el.style.height = `${h}px`;
170
180
  }
171
181
  if (dir === 'horizontal' || dir === 'both') {
172
- const w = Math.min(this.resizeMaxWidth, Math.max(64, this.resizeStartWidth + (event.clientX - this.resizeStartX)));
182
+ // The grip sits at the field's INLINE END — physically bottom-right in LTR,
183
+ // bottom-left in RTL, where a block-level box grows because it is anchored
184
+ // at the right edge of its containing block. So the pointer's physical
185
+ // delta means "wider" in one direction and "narrower" in the other, and the
186
+ // sign has to follow the reading direction. It did not, so under `dir="rtl"`
187
+ // the grip sat on the corner the box does NOT extend toward and dragging it
188
+ // outward shrank the field.
189
+ const dx = event.clientX - this.resizeStartX;
190
+ const inline = this.dir?.value === 'rtl' ? -dx : dx;
191
+ const w = Math.min(this.resizeMaxWidth, Math.max(64, this.resizeStartWidth + inline));
173
192
  this.hostEl.nativeElement.style.width = `${w}px`;
174
193
  }
175
194
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-textarea.mjs","sources":["../../../projects/lib/textarea/textarea.ts","../../../projects/lib/textarea/textarea.html","../../../projects/lib/textarea/ngwr-textarea.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, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { useFormFieldAria } from 'ngwr/form';\n\n/**\n * Multi-line text input.\n *\n * A signal-forms native control: it implements `FormValueControl<string>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone.\n *\n * Optional `autosize` grows the textarea with its content, capped at\n * `maxRows` if provided.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-textarea placeholder=\"Notes\" [formField]=\"form.notes\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-textarea autosize [maxRows]=\"6\" [(value)]=\"text\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/textarea\n */\nexport type WrTextareaSize = 'sm' | 'md' | 'lg';\n\nexport type WrTextareaResize = 'none' | 'vertical' | 'horizontal' | 'both';\n\n@Component({\n selector: 'wr-textarea',\n templateUrl: './textarea.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrTextarea implements FormValueControl<string> {\n /** Native placeholder text. @default '' */\n readonly placeholder = input<string>('');\n\n /**\n * Accessible name for the field. Falls back to the placeholder — the native\n * `<textarea>` lives inside the component, so a `<label for>` outside cannot\n * reach it.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = computed(() => {\n // Not `??`: an empty placeholder is no name at all, so it must fall through.\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const placeholder = this.placeholder();\n return placeholder ? placeholder : null;\n });\n\n /**\n * Control size — shares the `--wr-control-*` contract. Unset, it falls back to\n * `provideWrConfig({ textarea: { size } })`. @default 'md'\n */\n readonly size = input<WrTextareaSize | null>(null);\n\n // `null` as the input's own default, not `'md'`: with a literal sitting there,\n // \"the template said nothing\" and \"the template said md\" are the same value, so no\n // app-wide default could ever apply. The effective default is the last argument\n // below, which is where the old inline one moved to.\n protected readonly resolvedSize = useConfigValue(this.size, c => c.textarea?.size, 'md');\n\n /** Visible row count. @default 3 */\n readonly rows = input(3, { transform: (v: unknown): number => coerceNumberProperty(v, 3) });\n\n /** Allow user resize via the corner grip. @default true */\n readonly resizable = input(true, { transform: coerceBooleanProperty });\n\n /** Resize direction of the corner grip (the grip icon adapts). @default 'vertical' */\n readonly resize = input<WrTextareaResize>('vertical');\n\n /** Read-only state. @default false */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Grow the textarea to fit its content. @default false */\n readonly autosize = input(false, { transform: coerceBooleanProperty });\n\n /** Cap autosize at this many rows. Ignored when `autosize` is false. */\n readonly maxRows = input<number | null>(null);\n\n /**\n * Disable the textarea. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** The edited text. Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<string>('');\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly focused = signal(false);\n\n protected readonly native = viewChild.required<ElementRef<HTMLTextAreaElement>>('native');\n\n /** Resize direction, gated by `resizable` (false → 'none'). */\n protected readonly effectiveResize = computed<WrTextareaResize>(() => (this.resizable() ? this.resize() : 'none'));\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria();\n\n protected readonly classes = computed(() => {\n const parts = ['wr-textarea'];\n const size = this.resolvedSize();\n if (size !== 'md') parts.push(`wr-textarea--${size}`);\n if (this.autosize()) parts.push('wr-textarea--no-resize');\n const resize = this.effectiveResize();\n if (resize === 'horizontal' || resize === 'both') parts.push('wr-textarea--resize-x');\n if (this.focused()) parts.push('wr-textarea--focused');\n if (this.disabled()) parts.push('wr-textarea--disabled');\n return parts.join(' ');\n });\n\n /** Show the corner grip (hidden when direction is 'none' / autosize / disabled). */\n protected readonly showHandle = computed(\n () => this.effectiveResize() !== 'none' && !this.autosize() && !this.disabled()\n );\n\n private readonly platformId = inject(PLATFORM_ID);\n private readonly isBrowser = isPlatformBrowser(this.platformId);\n private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef);\n\n // Custom resize-grip drag state (replaces the native corner handle).\n private resizing = false;\n private resizeStartY = 0;\n private resizeStartX = 0;\n private resizeStartHeight = 0;\n private resizeStartWidth = 0;\n private resizeMinHeight = 36;\n private resizeMaxWidth = Number.POSITIVE_INFINITY;\n\n constructor() {\n // Autosize: re-fit whenever value or autosize toggles.\n effect(() => {\n if (!this.isBrowser) return;\n if (!this.autosize()) {\n // Hand the height back. Left behind, the field stays frozen at whatever\n // was last fitted and `rows` silently stops meaning anything, which reads\n // as the input having done nothing.\n this.releaseHeight();\n return;\n }\n // Read `value` to register dependency\n this.value();\n requestAnimationFrame(() => this.autofit());\n });\n }\n\n // Template handlers\n\n protected onInput(event: Event): void {\n this.value.set((event.target as HTMLTextAreaElement).value);\n }\n\n protected onBlur(): void {\n this.focused.set(false);\n this.touch.emit();\n }\n\n // Custom resize — drag the corner grip; direction comes from `effectiveResize`.\n // Pointer capture keeps move/up on the grip even when the cursor leaves it.\n protected startResize(event: PointerEvent): void {\n const el = this.native()?.nativeElement;\n if (!el) return;\n const host = this.hostEl.nativeElement;\n this.resizing = true;\n this.resizeStartY = event.clientY;\n this.resizeStartX = event.clientX;\n this.resizeStartHeight = el.offsetHeight;\n this.resizeStartWidth = host.offsetWidth;\n // Height floor = fits the current content, so shrinking never opens an\n // overflow scrollbar. Measured once, synchronously (no paint between).\n const borderY = el.offsetHeight - el.clientHeight;\n const prevHeight = el.style.height;\n el.style.height = 'auto';\n this.resizeMinHeight = el.scrollHeight + borderY;\n el.style.height = prevHeight;\n // Width ceiling = parent inner width, so horizontal drag can't overflow.\n this.resizeMaxWidth = host.parentElement?.clientWidth ?? Number.POSITIVE_INFINITY;\n (event.target as HTMLElement).setPointerCapture(event.pointerId);\n event.preventDefault();\n }\n\n protected onResize(event: PointerEvent): void {\n if (!this.resizing) return;\n const el = this.native()?.nativeElement;\n if (!el) return;\n const dir = this.effectiveResize();\n if (dir === 'vertical' || dir === 'both') {\n const h = Math.max(this.resizeMinHeight, this.resizeStartHeight + (event.clientY - this.resizeStartY));\n el.style.height = `${h}px`;\n }\n if (dir === 'horizontal' || dir === 'both') {\n const w = Math.min(\n this.resizeMaxWidth,\n Math.max(64, this.resizeStartWidth + (event.clientX - this.resizeStartX))\n );\n this.hostEl.nativeElement.style.width = `${w}px`;\n }\n }\n\n protected endResize(event: PointerEvent): void {\n if (!this.resizing) return;\n this.resizing = false;\n (event.target as HTMLElement).releasePointerCapture(event.pointerId);\n }\n\n private releaseHeight(): void {\n const el = this.native()?.nativeElement;\n if (!el) return;\n el.style.height = '';\n el.style.overflowY = '';\n }\n\n private autofit(): void {\n const el = this.native()?.nativeElement;\n if (!el) return;\n // A frame can outlive the reason it was scheduled: toggling `autosize` off\n // between the effect and the frame would otherwise fit a height nobody asked\n // for, and `releaseHeight` has already run by then.\n if (!this.autosize()) return;\n el.style.height = 'auto';\n\n const maxRows = this.maxRows();\n if (maxRows && maxRows > 0) {\n const cs = getComputedStyle(el);\n const lineHeight = parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2;\n const cap = lineHeight * maxRows;\n const next = Math.min(el.scrollHeight, cap);\n el.style.height = `${next}px`;\n el.style.overflowY = el.scrollHeight > cap ? 'auto' : 'hidden';\n } else {\n el.style.height = `${el.scrollHeight}px`;\n el.style.overflowY = 'hidden';\n }\n }\n}\n","<textarea\n #native\n class=\"wr-textarea__native\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [placeholder]=\"placeholder()\"\n [rows]=\"rows()\"\n [readonly]=\"readonly()\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\"\n></textarea>\n\n@if (showHandle()) {\n <!-- Custom corner grip — the lucide icon + cursor follow the `resize`\n direction. Icons inlined so no icon registration is required. -->\n <span\n [class]=\"`wr-textarea__resize wr-textarea__resize--${effectiveResize()}`\"\n aria-hidden=\"true\"\n (pointerdown)=\"startResize($event)\"\n (pointermove)=\"onResize($event)\"\n (pointerup)=\"endResize($event)\"\n (pointercancel)=\"endResize($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 @switch (effectiveResize()) {\n @case ('vertical') {\n <path d=\"M12 2v20\" />\n <path d=\"m8 18 4 4 4-4\" />\n <path d=\"m8 6 4-4 4 4\" />\n }\n @case ('horizontal') {\n <path d=\"M2 12h20\" />\n <path d=\"m18 8 4 4-4 4\" />\n <path d=\"m6 8-4 4 4 4\" />\n }\n @default {\n <path d=\"M19 13v6h-6\" />\n <path d=\"M5 11V5h6\" />\n <path d=\"m5 5 14 14\" />\n }\n }\n </svg>\n </span>\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-textarea.mjs","sources":["../../../projects/lib/textarea/textarea.ts","../../../projects/lib/textarea/textarea.html","../../../projects/lib/textarea/ngwr-textarea.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { Directionality } from '@angular/cdk/bidi';\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { useFormFieldAria } from 'ngwr/form';\n\n/**\n * Multi-line text input.\n *\n * A signal-forms native control: it implements `FormValueControl<string>`, so\n * `[formField]` binds straight to its `value` model — no\n * `ControlValueAccessor` in between. `[(value)]` works standalone.\n *\n * Optional `autosize` grows the textarea with its content, capped at\n * `maxRows` if provided.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-textarea placeholder=\"Notes\" [formField]=\"form.notes\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-textarea autosize [maxRows]=\"6\" [(value)]=\"text\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/textarea\n */\nexport type WrTextareaSize = 'sm' | 'md' | 'lg';\n\nexport type WrTextareaResize = 'none' | 'vertical' | 'horizontal' | 'both';\n\n@Component({\n selector: 'wr-textarea',\n templateUrl: './textarea.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrTextarea implements FormValueControl<string> {\n /** Native placeholder text. @default '' */\n readonly placeholder = input<string>('');\n\n /**\n * Accessible name for the field. Falls back to the placeholder — the native\n * `<textarea>` lives inside the component, so a `<label for>` outside cannot\n * reach it.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = computed(() => {\n // Not `??`: an empty placeholder is no name at all, so it must fall through.\n const explicit = this.ariaLabel();\n if (explicit) return explicit;\n const placeholder = this.placeholder();\n return placeholder ? placeholder : null;\n });\n\n /**\n * Control size — shares the `--wr-control-*` contract. Unset, it falls back to\n * `provideWrConfig({ textarea: { size } })`. @default 'md'\n */\n readonly size = input<WrTextareaSize | null>(null);\n\n // `null` as the input's own default, not `'md'`: with a literal sitting there,\n // \"the template said nothing\" and \"the template said md\" are the same value, so no\n // app-wide default could ever apply. The effective default is the last argument\n // below, which is where the old inline one moved to.\n protected readonly resolvedSize = useConfigValue(this.size, c => c.textarea?.size, 'md');\n\n /** Visible row count. @default 3 */\n readonly rows = input(3, { transform: (v: unknown): number => coerceNumberProperty(v, 3) });\n\n /** Allow user resize via the corner grip. @default true */\n readonly resizable = input(true, { transform: coerceBooleanProperty });\n\n /** Resize direction of the corner grip (the grip icon adapts). @default 'vertical' */\n readonly resize = input<WrTextareaResize>('vertical');\n\n /** Read-only state. @default false */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Grow the textarea to fit its content. @default false */\n readonly autosize = input(false, { transform: coerceBooleanProperty });\n\n /** Cap autosize at this many rows. Ignored when `autosize` is false. */\n readonly maxRows = input<number | null>(null);\n\n /**\n * Disable the textarea. Bound automatically from the field's disabled state\n * when used with `[formField]`.\n *\n * @default false\n */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** The edited text. Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<string>('');\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n protected readonly focused = signal(false);\n\n protected readonly native = viewChild.required<ElementRef<HTMLTextAreaElement>>('native');\n\n /** Resize direction, gated by `resizable` (false → 'none'). */\n protected readonly effectiveResize = computed<WrTextareaResize>(() => (this.resizable() ? this.resize() : 'none'));\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria();\n\n protected readonly classes = computed(() => {\n const parts = ['wr-textarea'];\n const size = this.resolvedSize();\n if (size !== 'md') parts.push(`wr-textarea--${size}`);\n if (this.autosize()) parts.push('wr-textarea--no-resize');\n const resize = this.effectiveResize();\n if (resize === 'horizontal' || resize === 'both') parts.push('wr-textarea--resize-x');\n if (this.focused()) parts.push('wr-textarea--focused');\n if (this.disabled()) parts.push('wr-textarea--disabled');\n return parts.join(' ');\n });\n\n /** Show the corner grip (hidden when direction is 'none' / autosize / disabled). */\n protected readonly showHandle = computed(\n () => this.effectiveResize() !== 'none' && !this.autosize() && !this.disabled()\n );\n\n private readonly platformId = inject(PLATFORM_ID);\n private readonly isBrowser = isPlatformBrowser(this.platformId);\n private readonly hostEl = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /**\n * Ambient reading direction, read by the horizontal half of the resize drag.\n *\n * Optional so a bare `TestBed` — or any consumer that never set a direction —\n * needs no provider; `Directionality` is root-provided, so `null` only ever\n * means \"nobody asked\", which is LTR. Read inside the handler rather than\n * cached, so a runtime flip needs no subscription to `Directionality.change`.\n */\n private readonly dir = inject(Directionality, { optional: true });\n\n // Custom resize-grip drag state (replaces the native corner handle).\n private resizing = false;\n private resizeStartY = 0;\n private resizeStartX = 0;\n private resizeStartHeight = 0;\n private resizeStartWidth = 0;\n private resizeMinHeight = 36;\n private resizeMaxWidth = Number.POSITIVE_INFINITY;\n\n constructor() {\n // Autosize: re-fit whenever value or autosize toggles.\n effect(() => {\n if (!this.isBrowser) return;\n if (!this.autosize()) {\n // Hand the height back. Left behind, the field stays frozen at whatever\n // was last fitted and `rows` silently stops meaning anything, which reads\n // as the input having done nothing.\n this.releaseHeight();\n return;\n }\n // Read `value` to register dependency\n this.value();\n requestAnimationFrame(() => this.autofit());\n });\n }\n\n // Template handlers\n\n protected onInput(event: Event): void {\n this.value.set((event.target as HTMLTextAreaElement).value);\n }\n\n protected onBlur(): void {\n this.focused.set(false);\n this.touch.emit();\n }\n\n // Custom resize — drag the corner grip; direction comes from `effectiveResize`.\n // Pointer capture keeps move/up on the grip even when the cursor leaves it.\n protected startResize(event: PointerEvent): void {\n const el = this.native()?.nativeElement;\n if (!el) return;\n const host = this.hostEl.nativeElement;\n this.resizing = true;\n this.resizeStartY = event.clientY;\n this.resizeStartX = event.clientX;\n this.resizeStartHeight = el.offsetHeight;\n this.resizeStartWidth = host.offsetWidth;\n // Height floor = fits the current content, so shrinking never opens an\n // overflow scrollbar. Measured once, synchronously (no paint between).\n const borderY = el.offsetHeight - el.clientHeight;\n const prevHeight = el.style.height;\n el.style.height = 'auto';\n this.resizeMinHeight = el.scrollHeight + borderY;\n el.style.height = prevHeight;\n // Width ceiling = parent inner width, so horizontal drag can't overflow.\n this.resizeMaxWidth = host.parentElement?.clientWidth ?? Number.POSITIVE_INFINITY;\n (event.target as HTMLElement).setPointerCapture(event.pointerId);\n event.preventDefault();\n }\n\n protected onResize(event: PointerEvent): void {\n if (!this.resizing) return;\n const el = this.native()?.nativeElement;\n if (!el) return;\n const dir = this.effectiveResize();\n if (dir === 'vertical' || dir === 'both') {\n const h = Math.max(this.resizeMinHeight, this.resizeStartHeight + (event.clientY - this.resizeStartY));\n el.style.height = `${h}px`;\n }\n if (dir === 'horizontal' || dir === 'both') {\n // The grip sits at the field's INLINE END — physically bottom-right in LTR,\n // bottom-left in RTL, where a block-level box grows because it is anchored\n // at the right edge of its containing block. So the pointer's physical\n // delta means \"wider\" in one direction and \"narrower\" in the other, and the\n // sign has to follow the reading direction. It did not, so under `dir=\"rtl\"`\n // the grip sat on the corner the box does NOT extend toward and dragging it\n // outward shrank the field.\n const dx = event.clientX - this.resizeStartX;\n const inline = this.dir?.value === 'rtl' ? -dx : dx;\n const w = Math.min(this.resizeMaxWidth, Math.max(64, this.resizeStartWidth + inline));\n this.hostEl.nativeElement.style.width = `${w}px`;\n }\n }\n\n protected endResize(event: PointerEvent): void {\n if (!this.resizing) return;\n this.resizing = false;\n (event.target as HTMLElement).releasePointerCapture(event.pointerId);\n }\n\n private releaseHeight(): void {\n const el = this.native()?.nativeElement;\n if (!el) return;\n el.style.height = '';\n el.style.overflowY = '';\n }\n\n private autofit(): void {\n const el = this.native()?.nativeElement;\n if (!el) return;\n // A frame can outlive the reason it was scheduled: toggling `autosize` off\n // between the effect and the frame would otherwise fit a height nobody asked\n // for, and `releaseHeight` has already run by then.\n if (!this.autosize()) return;\n el.style.height = 'auto';\n\n const maxRows = this.maxRows();\n if (maxRows && maxRows > 0) {\n const cs = getComputedStyle(el);\n const lineHeight = parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.2;\n const cap = lineHeight * maxRows;\n const next = Math.min(el.scrollHeight, cap);\n el.style.height = `${next}px`;\n el.style.overflowY = el.scrollHeight > cap ? 'auto' : 'hidden';\n } else {\n el.style.height = `${el.scrollHeight}px`;\n el.style.overflowY = 'hidden';\n }\n }\n}\n","<textarea\n #native\n class=\"wr-textarea__native\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [placeholder]=\"placeholder()\"\n [rows]=\"rows()\"\n [readonly]=\"readonly()\"\n [disabled]=\"disabled()\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [value]=\"value()\"\n (input)=\"onInput($event)\"\n (focus)=\"focused.set(true)\"\n (blur)=\"onBlur()\"\n></textarea>\n\n@if (showHandle()) {\n <!-- Custom corner grip — the lucide icon + cursor follow the `resize`\n direction. Icons inlined so no icon registration is required. -->\n <span\n [class]=\"`wr-textarea__resize wr-textarea__resize--${effectiveResize()}`\"\n aria-hidden=\"true\"\n (pointerdown)=\"startResize($event)\"\n (pointermove)=\"onResize($event)\"\n (pointerup)=\"endResize($event)\"\n (pointercancel)=\"endResize($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 @switch (effectiveResize()) {\n @case ('vertical') {\n <path d=\"M12 2v20\" />\n <path d=\"m8 18 4 4 4-4\" />\n <path d=\"m8 6 4-4 4 4\" />\n }\n @case ('horizontal') {\n <path d=\"M2 12h20\" />\n <path d=\"m18 8 4 4-4 4\" />\n <path d=\"m6 8-4 4 4 4\" />\n }\n @default {\n <path d=\"M19 13v6h-6\" />\n <path d=\"M5 11V5h6\" />\n <path d=\"m5 5 14 14\" />\n }\n }\n </svg>\n </span>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -51,7 +51,7 @@ class WrTourPopup {
51
51
  }, /* @ts-ignore */
52
52
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
53
53
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTourPopup, deps: [], target: i0.ɵɵFactoryTarget.Component });
54
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTourPopup, isStandalone: true, selector: "wr-tour-popup", host: { attributes: { "role": "dialog", "aria-modal": "true" }, properties: { "attr.aria-label": "ariaLabel()" }, classAttribute: "wr-tour-popup" }, ngImport: i0, template: "@let step = tour.step();\n\n<p class=\"wr-tour-popup__progress\">{{ progressText() }}</p>\n\n@if (step?.title; as title) {\n <h2 class=\"wr-tour-popup__title\">{{ title }}</h2>\n}\n\n<p class=\"wr-tour-popup__content\">{{ step?.content }}</p>\n\n<div class=\"wr-tour-popup__actions\">\n <!-- The three buttons are told apart by a class rather than by their position in\n the row: the back button is conditional, so an index means one thing on the\n first step and another everywhere else \u2014 and the labels are translated. -->\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"medium\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--skip\"\n (click)=\"tour.stop()\"\n >\n {{ skipLabel() }}\n </button>\n\n <span class=\"wr-tour-popup__spacer\"></span>\n\n @if (!isFirst()) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--back\"\n (click)=\"tour.prev()\"\n >\n {{ backLabel() }}\n </button>\n }\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n class=\"wr-tour-popup__action wr-tour-popup__action--next\"\n (click)=\"tour.next()\"\n >\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n</div>\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"] }], encapsulation: i0.ViewEncapsulation.None });
54
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTourPopup, isStandalone: true, selector: "wr-tour-popup", host: { attributes: { "role": "dialog", "aria-modal": "true" }, properties: { "attr.aria-label": "ariaLabel()" }, classAttribute: "wr-tour-popup" }, ngImport: i0, template: "@let step = tour.step();\n\n<p class=\"wr-tour-popup__progress\">{{ progressText() }}</p>\n\n@if (step?.title; as title) {\n <h2 class=\"wr-tour-popup__title\">{{ title }}</h2>\n}\n\n<p class=\"wr-tour-popup__content\">{{ step?.content }}</p>\n\n<div class=\"wr-tour-popup__actions\">\n <!-- The three buttons are told apart by a class rather than by their position in\n the row: the back button is conditional, so an index means one thing on the\n first step and another everywhere else \u2014 and the labels are translated. -->\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"medium\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--skip\"\n (click)=\"tour.stop()\"\n >\n {{ skipLabel() }}\n </button>\n\n <span class=\"wr-tour-popup__spacer\"></span>\n\n @if (!isFirst()) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--back\"\n (click)=\"tour.prev()\"\n >\n {{ backLabel() }}\n </button>\n }\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n class=\"wr-tour-popup__action wr-tour-popup__action--next\"\n (click)=\"tour.next()\"\n >\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n</div>\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", "disabledWhenLoading"] }], encapsulation: i0.ViewEncapsulation.None });
55
55
  }
56
56
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTourPopup, decorators: [{
57
57
  type: Component,