ngwr 13.0.1 → 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 (167) 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-button.mjs +4 -4
  10. package/fesm2022/ngwr-button.mjs.map +1 -1
  11. package/fesm2022/ngwr-calendar-testing.mjs +15 -0
  12. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
  13. package/fesm2022/ngwr-calendar.mjs +51 -5
  14. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  15. package/fesm2022/ngwr-circular-text.mjs +20 -1
  16. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  17. package/fesm2022/ngwr-collapse.mjs.map +1 -1
  18. package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
  19. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
  20. package/fesm2022/ngwr-color-picker.mjs +89 -10
  21. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  22. package/fesm2022/ngwr-command-palette.mjs +27 -3
  23. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  24. package/fesm2022/ngwr-context-menu.mjs +7 -1
  25. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  26. package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
  27. package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
  28. package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
  29. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  30. package/fesm2022/ngwr-date-picker.mjs +103 -13
  31. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  32. package/fesm2022/ngwr-date.mjs +480 -79
  33. package/fesm2022/ngwr-date.mjs.map +1 -1
  34. package/fesm2022/ngwr-drag-drop.mjs +30 -2
  35. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  36. package/fesm2022/ngwr-dropdown.mjs +2 -1
  37. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  38. package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
  39. package/fesm2022/ngwr-event-calendar.mjs +1 -1
  40. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  41. package/fesm2022/ngwr-file-upload.mjs +52 -12
  42. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  43. package/fesm2022/ngwr-i18n-en.mjs +63 -1
  44. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  45. package/fesm2022/ngwr-i18n-ru.mjs +28 -1
  46. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  47. package/fesm2022/ngwr-i18n.mjs +150 -22
  48. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  49. package/fesm2022/ngwr-input-number.mjs +3 -3
  50. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  51. package/fesm2022/ngwr-input-otp.mjs +21 -3
  52. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  53. package/fesm2022/ngwr-input.mjs +14 -7
  54. package/fesm2022/ngwr-input.mjs.map +1 -1
  55. package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
  56. package/fesm2022/ngwr-knob.mjs +20 -2
  57. package/fesm2022/ngwr-knob.mjs.map +1 -1
  58. package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
  59. package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
  60. package/fesm2022/ngwr-loading-bar.mjs +21 -32
  61. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  62. package/fesm2022/ngwr-mention.mjs +104 -14
  63. package/fesm2022/ngwr-mention.mjs.map +1 -1
  64. package/fesm2022/ngwr-pagination-testing.mjs +11 -10
  65. package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
  66. package/fesm2022/ngwr-pagination.mjs +65 -30
  67. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  68. package/fesm2022/ngwr-popconfirm.mjs +3 -2
  69. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  70. package/fesm2022/ngwr-popover.mjs +2 -1
  71. package/fesm2022/ngwr-popover.mjs.map +1 -1
  72. package/fesm2022/ngwr-select.mjs +93 -34
  73. package/fesm2022/ngwr-select.mjs.map +1 -1
  74. package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
  75. package/fesm2022/ngwr-squircle.mjs +2 -1
  76. package/fesm2022/ngwr-squircle.mjs.map +1 -1
  77. package/fesm2022/ngwr-statistic.mjs +35 -4
  78. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  79. package/fesm2022/ngwr-table.mjs +35 -12
  80. package/fesm2022/ngwr-table.mjs.map +1 -1
  81. package/fesm2022/ngwr-tabs-router.mjs +158 -0
  82. package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
  83. package/fesm2022/ngwr-tabs-testing.mjs +13 -11
  84. package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
  85. package/fesm2022/ngwr-tabs.mjs +98 -10
  86. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  87. package/fesm2022/ngwr-textarea.mjs +20 -1
  88. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  89. package/fesm2022/ngwr-tour.mjs +1 -1
  90. package/fesm2022/ngwr-tour.mjs.map +1 -1
  91. package/fesm2022/ngwr-transfer.mjs +2 -2
  92. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  93. package/fesm2022/ngwr-tree.mjs +18 -2
  94. package/fesm2022/ngwr-tree.mjs.map +1 -1
  95. package/fesm2022/ngwr-utils.mjs +37 -1
  96. package/fesm2022/ngwr-utils.mjs.map +1 -1
  97. package/fesm2022/ngwr-version.mjs +1 -1
  98. package/fesm2022/ngwr-version.mjs.map +1 -1
  99. package/fesm2022/ngwr-window-testing.mjs +10 -2
  100. package/fesm2022/ngwr-window-testing.mjs.map +1 -1
  101. package/fesm2022/ngwr-window.mjs +4 -4
  102. package/fesm2022/ngwr-window.mjs.map +1 -1
  103. package/i18n/en.json +33 -6
  104. package/i18n/ru.json +33 -6
  105. package/llms-full.txt +20 -11
  106. package/llms.txt +47 -16
  107. package/mcp/catalog.js +3 -3
  108. package/mcp/providers.js +11 -0
  109. package/package.json +9 -1
  110. package/schematics/migrations/v13/index.js +101 -0
  111. package/schematics/migrations/v14/index.js +422 -0
  112. package/schematics/migrations.json +10 -0
  113. package/schematics/page/templates.js +1 -1
  114. package/schematics/use/symbol-map.json +1 -0
  115. package/skills/ngwr/SKILL.md +4 -1
  116. package/skills/ngwr/references/catalog.md +2 -0
  117. package/skills/ngwr/references/setup.md +4 -0
  118. package/stepper/styles/_index.scss +19 -1
  119. package/textarea/styles/_index.scss +14 -6
  120. package/toast/styles/_index.scss +12 -4
  121. package/types/ngwr-alert.d.ts +3 -3
  122. package/types/ngwr-blur-text.d.ts +8 -0
  123. package/types/ngwr-button.d.ts +2 -2
  124. package/types/ngwr-calendar-testing.d.ts +21 -4
  125. package/types/ngwr-calendar.d.ts +37 -0
  126. package/types/ngwr-circular-text.d.ts +9 -0
  127. package/types/ngwr-collapse.d.ts +19 -15
  128. package/types/ngwr-color-picker-testing.d.ts +10 -4
  129. package/types/ngwr-color-picker.d.ts +70 -2
  130. package/types/ngwr-command-palette.d.ts +14 -0
  131. package/types/ngwr-context-menu.d.ts +1 -1
  132. package/types/ngwr-date-adapters-fns.d.ts +71 -29
  133. package/types/ngwr-date-picker-testing.d.ts +206 -185
  134. package/types/ngwr-date-picker.d.ts +7 -0
  135. package/types/ngwr-date.d.ts +143 -13
  136. package/types/ngwr-drag-drop.d.ts +19 -0
  137. package/types/ngwr-dropdown.d.ts +1 -1
  138. package/types/ngwr-event-calendar-testing.d.ts +12 -9
  139. package/types/ngwr-file-upload.d.ts +31 -3
  140. package/types/ngwr-i18n.d.ts +89 -7
  141. package/types/ngwr-input-number.d.ts +1 -1
  142. package/types/ngwr-input-otp.d.ts +3 -0
  143. package/types/ngwr-input.d.ts +11 -4
  144. package/types/ngwr-knob-testing.d.ts +8 -4
  145. package/types/ngwr-knob.d.ts +18 -0
  146. package/types/ngwr-loading-bar-router.d.ts +35 -0
  147. package/types/ngwr-loading-bar.d.ts +19 -9
  148. package/types/ngwr-mention.d.ts +13 -0
  149. package/types/ngwr-pagination-testing.d.ts +11 -10
  150. package/types/ngwr-pagination.d.ts +39 -10
  151. package/types/ngwr-popconfirm.d.ts +1 -1
  152. package/types/ngwr-popover.d.ts +1 -1
  153. package/types/ngwr-select.d.ts +65 -10
  154. package/types/ngwr-splitter-testing.d.ts +9 -4
  155. package/types/ngwr-squircle.d.ts +1 -1
  156. package/types/ngwr-statistic.d.ts +22 -2
  157. package/types/ngwr-table.d.ts +24 -5
  158. package/types/ngwr-tabs-router.d.ts +67 -0
  159. package/types/ngwr-tabs-testing.d.ts +12 -10
  160. package/types/ngwr-tabs.d.ts +103 -6
  161. package/types/ngwr-textarea.d.ts +9 -0
  162. package/types/ngwr-tree.d.ts +7 -0
  163. package/types/ngwr-utils.d.ts +33 -1
  164. package/types/ngwr-version.d.ts +1 -1
  165. package/types/ngwr-window-testing.d.ts +11 -3
  166. package/types/ngwr-window.d.ts +11 -4
  167. package/window/styles/_index.scss +1 -1
@@ -66,6 +66,15 @@ class WrCircularText {
66
66
  platform = inject(WrPlatform);
67
67
  rotation = null;
68
68
  hovered = false;
69
+ /**
70
+ * The effect below reaches `startOrSwap()` through a `queueMicrotask`, so the
71
+ * ring can be destroyed between the two. Without this flag the continuation
72
+ * then starts an infinite Web Animation on a host Angular has already
73
+ * detached — and an animation keeps its target alive, so the whole `#spin`
74
+ * subtree is retained for the life of the page. Same hazard, and same flag, as
75
+ * `wr-split-text` and `wr-fuzzy-text`.
76
+ */
77
+ destroyed = false;
69
78
  /** Active spin duration in seconds — base, or hover-modified. */
70
79
  effectiveDuration() {
71
80
  const base = this.spinDuration();
@@ -87,6 +96,15 @@ class WrCircularText {
87
96
  constructor() {
88
97
  if (!this.isBrowser)
89
98
  return;
99
+ // Component-scoped, NOT inside the render hook below: `startOrSwap()` is
100
+ // reachable from the effect's microtask, which can create the animation
101
+ // before that hook has run — and a cleanup registered inside a callback that
102
+ // never ran is not registered at all. The listeners stay paired with the
103
+ // element they are added to.
104
+ this.destroyRef.onDestroy(() => {
105
+ this.destroyed = true;
106
+ this.rotation?.cancel();
107
+ });
90
108
  afterNextRender(() => {
91
109
  this.startOrSwap();
92
110
  const host = this.spinEl().nativeElement.parentElement;
@@ -103,7 +121,6 @@ class WrCircularText {
103
121
  this.destroyRef.onDestroy(() => {
104
122
  host.removeEventListener('mouseenter', onEnter);
105
123
  host.removeEventListener('mouseleave', onLeave);
106
- this.rotation?.cancel();
107
124
  });
108
125
  });
109
126
  // React to input changes (spinDuration, onHover) — and the OS
@@ -120,6 +137,8 @@ class WrCircularText {
120
137
  * current angle. If the new mode is `pause`, just pause the animation.
121
138
  */
122
139
  startOrSwap() {
140
+ if (this.destroyed)
141
+ return;
123
142
  if (!this.spinEl())
124
143
  return;
125
144
  // Reduced motion: hold the ring still — the circular layout is the
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-circular-text.mjs","sources":["../../../projects/lib/circular-text/circular-text.ts","../../../projects/lib/circular-text/circular-text.html","../../../projects/lib/circular-text/ngwr-circular-text.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 * Angular adaptation of the CircularText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/circular-text\n *\n * Reactbits drives the rotation with `motion/react`, which lets the spin\n * angle persist across speed changes. CSS keyframes can't do that — when\n * `animation-duration` changes mid-flight, the browser recomputes the\n * progress as `(elapsed % newDuration) / newDuration`, which visibly\n * jumps the rotation. So this port drives the rotation through the\n * Web Animations API and preserves the current angle when hover swaps\n * the duration. Bonkers `scale` is still CSS-driven (no conflict — the\n * rotation lives on an inner wrapper).\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrCircularTextHover } from './interfaces';\n\ninterface Char {\n readonly ch: string;\n readonly transform: string;\n}\n\n/**\n * Text laid out around a circle, with the whole circle spinning. Hover\n * behaviour swaps the spin rate (or pauses it) — the current rotation\n * angle is preserved across the swap so there's no visible jump.\n *\n * @example\n * ```html\n * <wr-circular-text text=\"HELLO * NGWR * \" />\n * <wr-circular-text text=\"GO BONKERS\" onHover=\"goBonkers\" [spinDuration]=\"12\" />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/circular-text\n */\n@Component({\n selector: 'wr-circular-text',\n templateUrl: './circular-text.html',\n styleUrl: './circular-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-circular-text',\n '[class.wr-circular-text--bonkers]': \"onHover() === 'goBonkers'\",\n },\n})\nexport class WrCircularText {\n /** Text to lay out around the circle. */\n readonly text = input.required<string>();\n\n /** Seconds per full revolution at rest. @default 20 */\n readonly spinDuration = input(20, { transform: numAttr(20) });\n\n /** Hover behaviour. `null` disables hover reactivity. @default 'speedUp' */\n readonly onHover = input<WrCircularTextHover>('speedUp');\n\n protected readonly chars = computed<readonly Char[]>(() => {\n const letters = Array.from(this.text());\n const len = letters.length;\n if (len === 0) return [];\n return letters.map((ch, i) => {\n const rotation = (360 / len) * i;\n // Rotate around the spinner's centre, then push each char outward\n // by the orbit radius (set via CSS var). All chars end up on a\n // circle of constant radius — not a diagonal line.\n const transform = `rotate(${rotation}deg) translateY(calc(-1 * var(--wr-circular-text-radius)))`;\n return { ch, transform };\n });\n });\n\n private readonly spinEl = viewChild.required<ElementRef<HTMLElement>>('spin');\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n private rotation: Animation | null = null;\n private hovered = false;\n\n /** Active spin duration in seconds — base, or hover-modified. */\n private effectiveDuration(): number | 'pause' {\n const base = this.spinDuration();\n if (!this.hovered) return base;\n switch (this.onHover()) {\n case 'speedUp':\n return base / 4;\n case 'slowDown':\n return base * 2;\n case 'pause':\n return 'pause';\n case 'goBonkers':\n return base / 20;\n default:\n return base;\n }\n }\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n this.startOrSwap();\n\n const host = this.spinEl().nativeElement.parentElement!;\n const onEnter = (): void => {\n this.hovered = true;\n this.startOrSwap();\n };\n const onLeave = (): void => {\n this.hovered = false;\n this.startOrSwap();\n };\n host.addEventListener('mouseenter', onEnter);\n host.addEventListener('mouseleave', onLeave);\n this.destroyRef.onDestroy(() => {\n host.removeEventListener('mouseenter', onEnter);\n host.removeEventListener('mouseleave', onLeave);\n this.rotation?.cancel();\n });\n });\n\n // React to input changes (spinDuration, onHover) — and the OS\n // reduced-motion setting — without losing angle.\n effect(() => {\n this.spinDuration();\n this.onHover();\n this.platform.prefersReducedMotion();\n queueMicrotask(() => this.startOrSwap());\n });\n }\n\n /**\n * Start the rotation, or swap its duration while preserving the\n * current angle. If the new mode is `pause`, just pause the animation.\n */\n private startOrSwap(): void {\n if (!this.spinEl()) return;\n\n // Reduced motion: hold the ring still — the circular layout is the\n // point of the component; only the revolution is decorative.\n if (this.platform.prefersReducedMotion()) {\n this.rotation?.pause();\n return;\n }\n\n const target = this.effectiveDuration();\n\n if (target === 'pause') {\n this.rotation?.pause();\n return;\n }\n\n const newDurMs = Math.max(1, target * 1000);\n\n // First start — no previous animation to preserve.\n if (!this.rotation) {\n this.rotation = this.spinEl().nativeElement.animate(\n [{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],\n { duration: newDurMs, iterations: Infinity, easing: 'linear' }\n );\n return;\n }\n\n // Compute the current angle (0..1 of a full turn) under the old\n // timing, then restart with the new duration seeked to the same\n // angle — keeps the visual continuous across speed changes.\n const oldEffect = this.rotation.effect as KeyframeEffect | null;\n const oldDurMs = (oldEffect?.getTiming().duration as number) || newDurMs || newDurMs;\n const oldTime = (this.rotation.currentTime as number) ?? 0;\n const ratio = (((oldTime % oldDurMs) + oldDurMs) % oldDurMs) / oldDurMs;\n\n this.rotation.cancel();\n this.rotation = this.spinEl().nativeElement.animate(\n [{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],\n { duration: newDurMs, iterations: Infinity, easing: 'linear' }\n );\n this.rotation.currentTime = ratio * newDurMs;\n }\n}\n\nexport type { WrCircularTextHover } from './interfaces';\n","<!--\n The readable text, once: the ring below is per-character spans, which a screen\n reader may read out one letter at a time. Same shape as `wr-blur-text` and\n `wr-decrypt-text` — the string is carried here and the pieces are taken out of\n the tree with one `aria-hidden` on the wrapper rather than N on the chars, so\n the attribute stays off the nodes the rotation touches. It sits OUTSIDE\n `__spin`, or it would be hidden with the chars and would spin with them.\n-->\n<span class=\"wr-circular-text__sr-only\">{{ text() }}</span>\n<span class=\"wr-circular-text__spin\" aria-hidden=\"true\" #spin>\n @for (c of chars(); track $index) {\n <span class=\"wr-circular-text__char\" [style.transform]=\"c.transform\">{{ c.ch }}</span>\n }\n</span>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;AAAA;;;;;;;;;;;;;;;;;AAiBG;AA2BH;;;;;;;;;;;;AAYG;MAWU,cAAc,CAAA;;IAEhB,IAAI,GAAG,KAAK,CAAC,QAAQ;6EAAU;;AAG/B,IAAA,YAAY,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,cAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;IAGpD,OAAO,GAAG,KAAK,CAAsB,SAAS;gFAAC;AAErC,IAAA,KAAK,GAAG,QAAQ,CAAkB,MAAK;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACvC,QAAA,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM;QAC1B,IAAI,GAAG,KAAK,CAAC;AAAE,YAAA,OAAO,EAAE;QACxB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,KAAI;YAC3B,MAAM,QAAQ,GAAG,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC;;;;AAIhC,YAAA,MAAM,SAAS,GAAG,CAAA,OAAA,EAAU,QAAQ,4DAA4D;AAChG,YAAA,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;AAC1B,QAAA,CAAC,CAAC;IACJ,CAAC;8EAAC;AAEe,IAAA,MAAM,GAAG,SAAS,CAAC,QAAQ,CAA0B,MAAM;+EAAC;AAE5D,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAClD,IAAA,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC;IACtC,QAAQ,GAAqB,IAAI;IACjC,OAAO,GAAG,KAAK;;IAGf,iBAAiB,GAAA;AACvB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;QAChC,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,QAAQ,IAAI,CAAC,OAAO,EAAE;AACpB,YAAA,KAAK,SAAS;gBACZ,OAAO,IAAI,GAAG,CAAC;AACjB,YAAA,KAAK,UAAU;gBACb,OAAO,IAAI,GAAG,CAAC;AACjB,YAAA,KAAK,OAAO;AACV,gBAAA,OAAO,OAAO;AAChB,YAAA,KAAK,WAAW;gBACd,OAAO,IAAI,GAAG,EAAE;AAClB,YAAA;AACE,gBAAA,OAAO,IAAI;;IAEjB;AAEA,IAAA,WAAA,GAAA;QACE,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE;QAErB,eAAe,CAAC,MAAK;YACnB,IAAI,CAAC,WAAW,EAAE;YAElB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,aAAc;YACvD,MAAM,OAAO,GAAG,MAAW;AACzB,gBAAA,IAAI,CAAC,OAAO,GAAG,IAAI;gBACnB,IAAI,CAAC,WAAW,EAAE;AACpB,YAAA,CAAC;YACD,MAAM,OAAO,GAAG,MAAW;AACzB,gBAAA,IAAI,CAAC,OAAO,GAAG,KAAK;gBACpB,IAAI,CAAC,WAAW,EAAE;AACpB,YAAA,CAAC;AACD,YAAA,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC5C,YAAA,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC5C,YAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAK;AAC7B,gBAAA,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC/C,gBAAA,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC/C,gBAAA,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE;AACzB,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;;;QAIF,MAAM,CAAC,MAAK;YACV,IAAI,CAAC,YAAY,EAAE;YACnB,IAAI,CAAC,OAAO,EAAE;AACd,YAAA,IAAI,CAAC,QAAQ,CAAC,oBAAoB,EAAE;YACpC,cAAc,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;AAC1C,QAAA,CAAC,CAAC;IACJ;AAEA;;;AAGG;IACK,WAAW,GAAA;AACjB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE;;;AAIpB,QAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,oBAAoB,EAAE,EAAE;AACxC,YAAA,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE;YACtB;QACF;AAEA,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,EAAE;AAEvC,QAAA,IAAI,MAAM,KAAK,OAAO,EAAE;AACtB,YAAA,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE;YACtB;QACF;AAEA,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;;AAG3C,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAClB,YAAA,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,OAAO,CACjD,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAChE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAC/D;YACD;QACF;;;;AAKA,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,MAA+B;AAC/D,QAAA,MAAM,QAAQ,GAAI,SAAS,EAAE,SAAS,EAAE,CAAC,QAAmB,IAAI,QAAQ,IAAI,QAAQ;QACpF,MAAM,OAAO,GAAI,IAAI,CAAC,QAAQ,CAAC,WAAsB,IAAI,CAAC;AAC1D,QAAA,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ;AAEvE,QAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;AACtB,QAAA,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,OAAO,CACjD,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAChE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAC/D;QACD,IAAI,CAAC,QAAQ,CAAC,WAAW,GAAG,KAAK,GAAG,QAAQ;IAC9C;uGAlIW,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAd,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,cAAc,qsBCnE3B,owBAcA,EAAA,MAAA,EAAA,CAAA,6vBAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FDqDa,cAAc,EAAA,UAAA,EAAA,CAAA;kBAV1B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,kBAAkB,EAAA,aAAA,EAGb,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B;AACJ,wBAAA,KAAK,EAAE,kBAAkB;AACzB,wBAAA,mCAAmC,EAAE,2BAA2B;AACjE,qBAAA,EAAA,QAAA,EAAA,owBAAA,EAAA,MAAA,EAAA,CAAA,6vBAAA,CAAA,EAAA;2XA0BqE,MAAM,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AE3F9E;;AAEG;;;;"}
1
+ {"version":3,"file":"ngwr-circular-text.mjs","sources":["../../../projects/lib/circular-text/circular-text.ts","../../../projects/lib/circular-text/circular-text.html","../../../projects/lib/circular-text/ngwr-circular-text.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 * Angular adaptation of the CircularText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/circular-text\n *\n * Reactbits drives the rotation with `motion/react`, which lets the spin\n * angle persist across speed changes. CSS keyframes can't do that — when\n * `animation-duration` changes mid-flight, the browser recomputes the\n * progress as `(elapsed % newDuration) / newDuration`, which visibly\n * jumps the rotation. So this port drives the rotation through the\n * Web Animations API and preserves the current angle when hover swaps\n * the duration. Bonkers `scale` is still CSS-driven (no conflict — the\n * rotation lives on an inner wrapper).\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrCircularTextHover } from './interfaces';\n\ninterface Char {\n readonly ch: string;\n readonly transform: string;\n}\n\n/**\n * Text laid out around a circle, with the whole circle spinning. Hover\n * behaviour swaps the spin rate (or pauses it) — the current rotation\n * angle is preserved across the swap so there's no visible jump.\n *\n * @example\n * ```html\n * <wr-circular-text text=\"HELLO * NGWR * \" />\n * <wr-circular-text text=\"GO BONKERS\" onHover=\"goBonkers\" [spinDuration]=\"12\" />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/circular-text\n */\n@Component({\n selector: 'wr-circular-text',\n templateUrl: './circular-text.html',\n styleUrl: './circular-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-circular-text',\n '[class.wr-circular-text--bonkers]': \"onHover() === 'goBonkers'\",\n },\n})\nexport class WrCircularText {\n /** Text to lay out around the circle. */\n readonly text = input.required<string>();\n\n /** Seconds per full revolution at rest. @default 20 */\n readonly spinDuration = input(20, { transform: numAttr(20) });\n\n /** Hover behaviour. `null` disables hover reactivity. @default 'speedUp' */\n readonly onHover = input<WrCircularTextHover>('speedUp');\n\n protected readonly chars = computed<readonly Char[]>(() => {\n const letters = Array.from(this.text());\n const len = letters.length;\n if (len === 0) return [];\n return letters.map((ch, i) => {\n const rotation = (360 / len) * i;\n // Rotate around the spinner's centre, then push each char outward\n // by the orbit radius (set via CSS var). All chars end up on a\n // circle of constant radius — not a diagonal line.\n const transform = `rotate(${rotation}deg) translateY(calc(-1 * var(--wr-circular-text-radius)))`;\n return { ch, transform };\n });\n });\n\n private readonly spinEl = viewChild.required<ElementRef<HTMLElement>>('spin');\n\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n private rotation: Animation | null = null;\n private hovered = false;\n\n /**\n * The effect below reaches `startOrSwap()` through a `queueMicrotask`, so the\n * ring can be destroyed between the two. Without this flag the continuation\n * then starts an infinite Web Animation on a host Angular has already\n * detached — and an animation keeps its target alive, so the whole `#spin`\n * subtree is retained for the life of the page. Same hazard, and same flag, as\n * `wr-split-text` and `wr-fuzzy-text`.\n */\n private destroyed = false;\n\n /** Active spin duration in seconds — base, or hover-modified. */\n private effectiveDuration(): number | 'pause' {\n const base = this.spinDuration();\n if (!this.hovered) return base;\n switch (this.onHover()) {\n case 'speedUp':\n return base / 4;\n case 'slowDown':\n return base * 2;\n case 'pause':\n return 'pause';\n case 'goBonkers':\n return base / 20;\n default:\n return base;\n }\n }\n\n constructor() {\n if (!this.isBrowser) return;\n\n // Component-scoped, NOT inside the render hook below: `startOrSwap()` is\n // reachable from the effect's microtask, which can create the animation\n // before that hook has run — and a cleanup registered inside a callback that\n // never ran is not registered at all. The listeners stay paired with the\n // element they are added to.\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.rotation?.cancel();\n });\n\n afterNextRender(() => {\n this.startOrSwap();\n\n const host = this.spinEl().nativeElement.parentElement!;\n const onEnter = (): void => {\n this.hovered = true;\n this.startOrSwap();\n };\n const onLeave = (): void => {\n this.hovered = false;\n this.startOrSwap();\n };\n host.addEventListener('mouseenter', onEnter);\n host.addEventListener('mouseleave', onLeave);\n this.destroyRef.onDestroy(() => {\n host.removeEventListener('mouseenter', onEnter);\n host.removeEventListener('mouseleave', onLeave);\n });\n });\n\n // React to input changes (spinDuration, onHover) — and the OS\n // reduced-motion setting — without losing angle.\n effect(() => {\n this.spinDuration();\n this.onHover();\n this.platform.prefersReducedMotion();\n queueMicrotask(() => this.startOrSwap());\n });\n }\n\n /**\n * Start the rotation, or swap its duration while preserving the\n * current angle. If the new mode is `pause`, just pause the animation.\n */\n private startOrSwap(): void {\n if (this.destroyed) return;\n if (!this.spinEl()) return;\n\n // Reduced motion: hold the ring still — the circular layout is the\n // point of the component; only the revolution is decorative.\n if (this.platform.prefersReducedMotion()) {\n this.rotation?.pause();\n return;\n }\n\n const target = this.effectiveDuration();\n\n if (target === 'pause') {\n this.rotation?.pause();\n return;\n }\n\n const newDurMs = Math.max(1, target * 1000);\n\n // First start — no previous animation to preserve.\n if (!this.rotation) {\n this.rotation = this.spinEl().nativeElement.animate(\n [{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],\n { duration: newDurMs, iterations: Infinity, easing: 'linear' }\n );\n return;\n }\n\n // Compute the current angle (0..1 of a full turn) under the old\n // timing, then restart with the new duration seeked to the same\n // angle — keeps the visual continuous across speed changes.\n const oldEffect = this.rotation.effect as KeyframeEffect | null;\n const oldDurMs = (oldEffect?.getTiming().duration as number) || newDurMs || newDurMs;\n const oldTime = (this.rotation.currentTime as number) ?? 0;\n const ratio = (((oldTime % oldDurMs) + oldDurMs) % oldDurMs) / oldDurMs;\n\n this.rotation.cancel();\n this.rotation = this.spinEl().nativeElement.animate(\n [{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],\n { duration: newDurMs, iterations: Infinity, easing: 'linear' }\n );\n this.rotation.currentTime = ratio * newDurMs;\n }\n}\n\nexport type { WrCircularTextHover } from './interfaces';\n","<!--\n The readable text, once: the ring below is per-character spans, which a screen\n reader may read out one letter at a time. Same shape as `wr-blur-text` and\n `wr-decrypt-text` — the string is carried here and the pieces are taken out of\n the tree with one `aria-hidden` on the wrapper rather than N on the chars, so\n the attribute stays off the nodes the rotation touches. It sits OUTSIDE\n `__spin`, or it would be hidden with the chars and would spin with them.\n-->\n<span class=\"wr-circular-text__sr-only\">{{ text() }}</span>\n<span class=\"wr-circular-text__spin\" aria-hidden=\"true\" #spin>\n @for (c of chars(); track $index) {\n <span class=\"wr-circular-text__char\" [style.transform]=\"c.transform\">{{ c.ch }}</span>\n }\n</span>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;AAAA;;;;;;;;;;;;;;;;;AAiBG;AA2BH;;;;;;;;;;;;AAYG;MAWU,cAAc,CAAA;;IAEhB,IAAI,GAAG,KAAK,CAAC,QAAQ;6EAAU;;AAG/B,IAAA,YAAY,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,cAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;IAGpD,OAAO,GAAG,KAAK,CAAsB,SAAS;gFAAC;AAErC,IAAA,KAAK,GAAG,QAAQ,CAAkB,MAAK;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;AACvC,QAAA,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM;QAC1B,IAAI,GAAG,KAAK,CAAC;AAAE,YAAA,OAAO,EAAE;QACxB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,KAAI;YAC3B,MAAM,QAAQ,GAAG,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC;;;;AAIhC,YAAA,MAAM,SAAS,GAAG,CAAA,OAAA,EAAU,QAAQ,4DAA4D;AAChG,YAAA,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;AAC1B,QAAA,CAAC,CAAC;IACJ,CAAC;8EAAC;AAEe,IAAA,MAAM,GAAG,SAAS,CAAC,QAAQ,CAA0B,MAAM;+EAAC;AAE5D,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAC/B,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAClD,IAAA,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC;IACtC,QAAQ,GAAqB,IAAI;IACjC,OAAO,GAAG,KAAK;AAEvB;;;;;;;AAOG;IACK,SAAS,GAAG,KAAK;;IAGjB,iBAAiB,GAAA;AACvB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;QAChC,IAAI,CAAC,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,QAAQ,IAAI,CAAC,OAAO,EAAE;AACpB,YAAA,KAAK,SAAS;gBACZ,OAAO,IAAI,GAAG,CAAC;AACjB,YAAA,KAAK,UAAU;gBACb,OAAO,IAAI,GAAG,CAAC;AACjB,YAAA,KAAK,OAAO;AACV,gBAAA,OAAO,OAAO;AAChB,YAAA,KAAK,WAAW;gBACd,OAAO,IAAI,GAAG,EAAE;AAClB,YAAA;AACE,gBAAA,OAAO,IAAI;;IAEjB;AAEA,IAAA,WAAA,GAAA;QACE,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE;;;;;;AAOrB,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAK;AAC7B,YAAA,IAAI,CAAC,SAAS,GAAG,IAAI;AACrB,YAAA,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE;AACzB,QAAA,CAAC,CAAC;QAEF,eAAe,CAAC,MAAK;YACnB,IAAI,CAAC,WAAW,EAAE;YAElB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,aAAc;YACvD,MAAM,OAAO,GAAG,MAAW;AACzB,gBAAA,IAAI,CAAC,OAAO,GAAG,IAAI;gBACnB,IAAI,CAAC,WAAW,EAAE;AACpB,YAAA,CAAC;YACD,MAAM,OAAO,GAAG,MAAW;AACzB,gBAAA,IAAI,CAAC,OAAO,GAAG,KAAK;gBACpB,IAAI,CAAC,WAAW,EAAE;AACpB,YAAA,CAAC;AACD,YAAA,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC5C,YAAA,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC5C,YAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAK;AAC7B,gBAAA,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,OAAO,CAAC;AAC/C,gBAAA,IAAI,CAAC,mBAAmB,CAAC,YAAY,EAAE,OAAO,CAAC;AACjD,YAAA,CAAC,CAAC;AACJ,QAAA,CAAC,CAAC;;;QAIF,MAAM,CAAC,MAAK;YACV,IAAI,CAAC,YAAY,EAAE;YACnB,IAAI,CAAC,OAAO,EAAE;AACd,YAAA,IAAI,CAAC,QAAQ,CAAC,oBAAoB,EAAE;YACpC,cAAc,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;AAC1C,QAAA,CAAC,CAAC;IACJ;AAEA;;;AAGG;IACK,WAAW,GAAA;QACjB,IAAI,IAAI,CAAC,SAAS;YAAE;AACpB,QAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAAE;;;AAIpB,QAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,oBAAoB,EAAE,EAAE;AACxC,YAAA,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE;YACtB;QACF;AAEA,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,EAAE;AAEvC,QAAA,IAAI,MAAM,KAAK,OAAO,EAAE;AACtB,YAAA,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE;YACtB;QACF;AAEA,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;;AAG3C,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;AAClB,YAAA,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,OAAO,CACjD,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAChE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAC/D;YACD;QACF;;;;AAKA,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,MAA+B;AAC/D,QAAA,MAAM,QAAQ,GAAI,SAAS,EAAE,SAAS,EAAE,CAAC,QAAmB,IAAI,QAAQ,IAAI,QAAQ;QACpF,MAAM,OAAO,GAAI,IAAI,CAAC,QAAQ,CAAC,WAAsB,IAAI,CAAC;AAC1D,QAAA,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,QAAQ,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ;AAEvE,QAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;AACtB,QAAA,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,OAAO,CACjD,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAChE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAC/D;QACD,IAAI,CAAC,QAAQ,CAAC,WAAW,GAAG,KAAK,GAAG,QAAQ;IAC9C;uGAtJW,cAAc,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAd,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,cAAc,qsBCnE3B,owBAcA,EAAA,MAAA,EAAA,CAAA,6vBAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FDqDa,cAAc,EAAA,UAAA,EAAA,CAAA;kBAV1B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,kBAAkB,EAAA,aAAA,EAGb,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B;AACJ,wBAAA,KAAK,EAAE,kBAAkB;AACzB,wBAAA,mCAAmC,EAAE,2BAA2B;AACjE,qBAAA,EAAA,QAAA,EAAA,owBAAA,EAAA,MAAA,EAAA,CAAA,6vBAAA,CAAA,EAAA;2XA0BqE,MAAM,EAAA,EAAA,QAAA,EAAA,IAAA,EAAA,CAAA,EAAA,CAAA,EAAA,EAAA,CAAA;;AE3F9E;;AAEG;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-collapse.mjs","sources":["../../../projects/lib/collapse/tokens/collapse-group.token.ts","../../../projects/lib/collapse/collapse.ts","../../../projects/lib/collapse/collapse.html","../../../projects/lib/collapse/collapse-group.ts","../../../projects/lib/collapse/accordion.ts","../../../projects/lib/collapse/ngwr-collapse.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 collapse uses to talk to its parent `<wr-collapse-group>`.\n *\n * @internal\n */\nexport interface WrCollapseGroupContext {\n /** When true, only one child collapse may be open at a time. */\n readonly accordion: Signal<boolean>;\n /** Called by a child when it opens — closes siblings in accordion mode. */\n notifyOpened(opener: object): void;\n /** Register a child so the group can call `closeAll()` etc. */\n register(member: { close(): void; readonly id: object }): void;\n /** Unregister on destroy. */\n unregister(memberId: object): void;\n}\n\n/**\n * Token a `<wr-collapse>` injects to register itself with — and notify of\n * open events — its parent `<wr-collapse-group>`. Drives the accordion\n * behaviour when the group has `accordion` enabled.\n *\n * @internal\n */\nexport const WR_COLLAPSE_GROUP = new InjectionToken<WrCollapseGroupContext>('WR_COLLAPSE_GROUP');\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 { Component, DestroyRef, ViewEncapsulation, computed, effect, inject, input, model } from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_COLLAPSE_GROUP } from './tokens';\n\n/**\n * Expandable panel with a clickable header. Two-way binds the `open`\n * state. When wrapped in `<wr-collapse-group accordion>`, only one\n * sibling can be open at a time.\n *\n * @example\n * ```html\n * <wr-collapse title=\"Section title\">\n * Section body…\n * </wr-collapse>\n *\n * <wr-collapse-group accordion>\n * <wr-collapse title=\"A\">…</wr-collapse>\n * <wr-collapse title=\"B\">…</wr-collapse>\n * </wr-collapse-group>\n * ```\n *\n * @see https://ngwr.dev/reference/components/collapse\n */\n@Component({\n selector: 'wr-collapse',\n templateUrl: './collapse.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrCollapse {\n /** Header text. */\n readonly title = input.required<string>();\n\n /** Two-way bindable open/closed state. @default false */\n readonly open = model<boolean>(false);\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Identity used by the parent group when tracking children. @internal */\n readonly id = { name: randomId('wr-collapse') };\n\n /** DOM id for the body element, referenced by header's `aria-controls`. */\n protected readonly bodyId = `wr-collapse-body-${this.id.name}`;\n\n private readonly group = inject(WR_COLLAPSE_GROUP, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-collapse'];\n if (this.open()) parts.push('wr-collapse--open');\n if (this.disabled()) parts.push('wr-collapse--disabled');\n return parts.join(' ');\n });\n\n constructor() {\n if (this.group) {\n this.group.register({ id: this.id, close: () => this.open.set(false) });\n this.destroyRef.onDestroy(() => this.group?.unregister(this.id));\n\n // Tell the group whenever we open — group decides whether to close siblings.\n effect(() => {\n if (this.open()) this.group?.notifyOpened(this.id);\n });\n }\n }\n\n /** Open the panel. */\n openPanel(): void {\n if (this.disabled()) return;\n this.open.set(true);\n }\n\n /** Close the panel. */\n close(): void {\n this.open.set(false);\n }\n\n /** Toggle the panel. */\n toggle(): void {\n if (this.disabled()) return;\n this.open.update(v => !v);\n }\n}\n","<button\n type=\"button\"\n class=\"wr-collapse__header\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"bodyId\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n>\n <span class=\"wr-collapse__title\">{{ title() }}</span>\n <svg\n class=\"wr-icon__svg wr-collapse__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n</button>\n\n<div\n class=\"wr-collapse__body\"\n [attr.id]=\"bodyId\"\n [class.wr-collapse__body--open]=\"open()\"\n [attr.aria-hidden]=\"!open()\"\n [attr.inert]=\"!open() ? '' : null\"\n>\n <div class=\"wr-collapse__content\">\n <div class=\"wr-collapse__inner\">\n <ng-content />\n </div>\n </div>\n</div>\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 { Component, ViewEncapsulation, forwardRef, input } from '@angular/core';\n\nimport { WR_COLLAPSE_GROUP, type WrCollapseGroupContext } from './tokens';\n\ninterface Member {\n close(): void;\n readonly id: object;\n}\n\n/**\n * Visual + behavioral grouping of `<wr-collapse>` children. When\n * `accordion` is true, opening one child closes the others.\n *\n * @see https://ngwr.dev/reference/components/collapse\n */\n@Component({\n selector: 'wr-collapse-group',\n template: '<ng-content />',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-collapse-group' },\n providers: [\n {\n provide: WR_COLLAPSE_GROUP,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrCollapseGroup),\n },\n ],\n})\nexport class WrCollapseGroup implements WrCollapseGroupContext {\n /** When true, only one child may be open at a time. @default false */\n readonly accordion = input(false, { transform: coerceBooleanProperty });\n\n private readonly members = new Map<object, Member>();\n\n register(member: Member): void {\n this.members.set(member.id, member);\n }\n\n unregister(memberId: object): void {\n this.members.delete(memberId);\n }\n\n notifyOpened(opener: object): void {\n if (!this.accordion()) return;\n for (const [id, member] of this.members) {\n if (id !== opener) member.close();\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 { Component, ViewEncapsulation, forwardRef, signal } from '@angular/core';\n\nimport { WR_COLLAPSE_GROUP, type WrCollapseGroupContext } from './tokens';\n\ninterface Member {\n close(): void;\n readonly id: object;\n}\n\n/**\n * Accordion — a `<wr-collapse>` container that enforces single-open\n * behavior: opening a child closes its siblings.\n *\n * Use `<wr-collapse-group>` when you only need visual grouping without\n * the mutually-exclusive open semantics.\n *\n * @example\n * ```html\n * <wr-accordion>\n * <wr-collapse title=\"Profile\">…</wr-collapse>\n * <wr-collapse title=\"Security\">…</wr-collapse>\n * <wr-collapse title=\"Notifications\">…</wr-collapse>\n * </wr-accordion>\n * ```\n *\n * @see https://ngwr.dev/reference/components/collapse\n */\n@Component({\n selector: 'wr-accordion',\n template: '<ng-content />',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-collapse-group wr-collapse-group--accordion' },\n providers: [\n {\n provide: WR_COLLAPSE_GROUP,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrAccordion),\n },\n ],\n})\nexport class WrAccordion implements WrCollapseGroupContext {\n /** Always behaves as an accordion. Provided for context-interface symmetry. */\n readonly accordion = signal(true).asReadonly();\n\n private readonly members = new Map<object, Member>();\n\n register(member: Member): void {\n this.members.set(member.id, member);\n }\n\n unregister(memberId: object): void {\n this.members.delete(memberId);\n }\n\n notifyOpened(opener: object): void {\n for (const [id, member] of this.members) {\n if (id !== opener) member.close();\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-collapse.mjs","sources":["../../../projects/lib/collapse/tokens/collapse-group.token.ts","../../../projects/lib/collapse/collapse.ts","../../../projects/lib/collapse/collapse.html","../../../projects/lib/collapse/collapse-group.ts","../../../projects/lib/collapse/accordion.ts","../../../projects/lib/collapse/ngwr-collapse.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 * What a `<wr-collapse>` hands its parent group when it registers.\n *\n * A named export rather than the structural type it used to be spelled as. Both\n * group components declared their OWN local `interface Member`, so d.ts\n * flattening had two of one name to resolve and published the second as\n * `Member$1` — a bundler-assigned identifier in the signature of a public method,\n * which no consumer could import and nobody had authored.\n */\nexport interface WrCollapseGroupMember {\n /** Close this collapse — the group calls it on siblings in accordion mode. */\n close(): void;\n /** Identity of the registering collapse, used as the map key and by `notifyOpened`. */\n readonly id: object;\n}\n\n/**\n * Contract a collapse uses to talk to its parent `<wr-collapse-group>`.\n *\n * @internal\n */\nexport interface WrCollapseGroupContext {\n /** When true, only one child collapse may be open at a time. */\n readonly accordion: Signal<boolean>;\n /** Called by a child when it opens — closes siblings in accordion mode. */\n notifyOpened(opener: object): void;\n /** Register a child so the group can call `closeAll()` etc. */\n register(member: WrCollapseGroupMember): void;\n /** Unregister on destroy. */\n unregister(memberId: object): void;\n}\n\n/**\n * Token a `<wr-collapse>` injects to register itself with — and notify of\n * open events — its parent `<wr-collapse-group>`. Drives the accordion\n * behaviour when the group has `accordion` enabled.\n *\n * @internal\n */\nexport const WR_COLLAPSE_GROUP = new InjectionToken<WrCollapseGroupContext>('WR_COLLAPSE_GROUP');\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 { Component, DestroyRef, ViewEncapsulation, computed, effect, inject, input, model } from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_COLLAPSE_GROUP } from './tokens';\n\n/**\n * Expandable panel with a clickable header. Two-way binds the `open`\n * state. When wrapped in `<wr-collapse-group accordion>`, only one\n * sibling can be open at a time.\n *\n * @example\n * ```html\n * <wr-collapse title=\"Section title\">\n * Section body…\n * </wr-collapse>\n *\n * <wr-collapse-group accordion>\n * <wr-collapse title=\"A\">…</wr-collapse>\n * <wr-collapse title=\"B\">…</wr-collapse>\n * </wr-collapse-group>\n * ```\n *\n * @see https://ngwr.dev/reference/components/collapse\n */\n@Component({\n selector: 'wr-collapse',\n templateUrl: './collapse.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrCollapse {\n /** Header text. */\n readonly title = input.required<string>();\n\n /** Two-way bindable open/closed state. @default false */\n readonly open = model<boolean>(false);\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Identity used by the parent group when tracking children. @internal */\n readonly id = { name: randomId('wr-collapse') };\n\n /** DOM id for the body element, referenced by header's `aria-controls`. */\n protected readonly bodyId = `wr-collapse-body-${this.id.name}`;\n\n private readonly group = inject(WR_COLLAPSE_GROUP, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-collapse'];\n if (this.open()) parts.push('wr-collapse--open');\n if (this.disabled()) parts.push('wr-collapse--disabled');\n return parts.join(' ');\n });\n\n constructor() {\n if (this.group) {\n this.group.register({ id: this.id, close: () => this.open.set(false) });\n this.destroyRef.onDestroy(() => this.group?.unregister(this.id));\n\n // Tell the group whenever we open — group decides whether to close siblings.\n effect(() => {\n if (this.open()) this.group?.notifyOpened(this.id);\n });\n }\n }\n\n /** Open the panel. */\n openPanel(): void {\n if (this.disabled()) return;\n this.open.set(true);\n }\n\n /** Close the panel. */\n close(): void {\n this.open.set(false);\n }\n\n /** Toggle the panel. */\n toggle(): void {\n if (this.disabled()) return;\n this.open.update(v => !v);\n }\n}\n","<button\n type=\"button\"\n class=\"wr-collapse__header\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"bodyId\"\n [disabled]=\"disabled()\"\n (click)=\"toggle()\"\n>\n <span class=\"wr-collapse__title\">{{ title() }}</span>\n <svg\n class=\"wr-icon__svg wr-collapse__chevron\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n</button>\n\n<div\n class=\"wr-collapse__body\"\n [attr.id]=\"bodyId\"\n [class.wr-collapse__body--open]=\"open()\"\n [attr.aria-hidden]=\"!open()\"\n [attr.inert]=\"!open() ? '' : null\"\n>\n <div class=\"wr-collapse__content\">\n <div class=\"wr-collapse__inner\">\n <ng-content />\n </div>\n </div>\n</div>\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 { Component, ViewEncapsulation, forwardRef, input } from '@angular/core';\n\nimport { WR_COLLAPSE_GROUP, type WrCollapseGroupContext, type WrCollapseGroupMember } from './tokens';\n\n/**\n * Visual + behavioral grouping of `<wr-collapse>` children. When\n * `accordion` is true, opening one child closes the others.\n *\n * @see https://ngwr.dev/reference/components/collapse\n */\n@Component({\n selector: 'wr-collapse-group',\n template: '<ng-content />',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-collapse-group' },\n providers: [\n {\n provide: WR_COLLAPSE_GROUP,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrCollapseGroup),\n },\n ],\n})\nexport class WrCollapseGroup implements WrCollapseGroupContext {\n /** When true, only one child may be open at a time. @default false */\n readonly accordion = input(false, { transform: coerceBooleanProperty });\n\n private readonly members = new Map<object, WrCollapseGroupMember>();\n\n register(member: WrCollapseGroupMember): void {\n this.members.set(member.id, member);\n }\n\n unregister(memberId: object): void {\n this.members.delete(memberId);\n }\n\n notifyOpened(opener: object): void {\n if (!this.accordion()) return;\n for (const [id, member] of this.members) {\n if (id !== opener) member.close();\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 { Component, ViewEncapsulation, forwardRef, signal } from '@angular/core';\n\nimport { WR_COLLAPSE_GROUP, type WrCollapseGroupContext, type WrCollapseGroupMember } from './tokens';\n\n/**\n * Accordion — a `<wr-collapse>` container that enforces single-open\n * behavior: opening a child closes its siblings.\n *\n * Use `<wr-collapse-group>` when you only need visual grouping without\n * the mutually-exclusive open semantics.\n *\n * @example\n * ```html\n * <wr-accordion>\n * <wr-collapse title=\"Profile\">…</wr-collapse>\n * <wr-collapse title=\"Security\">…</wr-collapse>\n * <wr-collapse title=\"Notifications\">…</wr-collapse>\n * </wr-accordion>\n * ```\n *\n * @see https://ngwr.dev/reference/components/collapse\n */\n@Component({\n selector: 'wr-accordion',\n template: '<ng-content />',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-collapse-group wr-collapse-group--accordion' },\n providers: [\n {\n provide: WR_COLLAPSE_GROUP,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrAccordion),\n },\n ],\n})\nexport class WrAccordion implements WrCollapseGroupContext {\n /** Always behaves as an accordion. Provided for context-interface symmetry. */\n readonly accordion = signal(true).asReadonly();\n\n private readonly members = new Map<object, WrCollapseGroupMember>();\n\n register(member: WrCollapseGroupMember): void {\n this.members.set(member.id, member);\n }\n\n unregister(memberId: object): void {\n this.members.delete(memberId);\n }\n\n notifyOpened(opener: object): void {\n for (const [id, member] of this.members) {\n if (id !== opener) member.close();\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -24,10 +24,20 @@ class FieldHarness extends ComponentHarness {
24
24
  return (await this.getInput()).getProperty('value');
25
25
  }
26
26
  }
27
- /** The `left: <n>%` / `top: <n>%` a thumb carries, out of its inline `style`. */
27
+ /**
28
+ * The percentage a thumb carries, out of its inline `style`.
29
+ *
30
+ * Three properties and not two, because the surfaces do not agree on the inline
31
+ * axis and that disagreement is the contract: the SV canvas is a picture and
32
+ * keeps a physical `left`, while the hue and alpha tracks are one-value sliders
33
+ * that mirror under `dir="rtl"` and so are placed with `inset-inline-start`.
34
+ * Reading `left` off a slider thumb therefore finds nothing, which is exactly
35
+ * what should happen if one of them ever regresses to the physical property.
36
+ */
28
37
  const THUMB_POS = {
29
38
  left: /(?:^|;)\s*left\s*:\s*([\d.]+)%/,
30
39
  top: /(?:^|;)\s*top\s*:\s*([\d.]+)%/,
40
+ 'inset-inline-start': /(?:^|;)\s*inset-inline-start\s*:\s*([\d.]+)%/,
31
41
  };
32
42
  /** One thumb's declared offset. See {@link WrColorPickerHarness.getThumbs} for why this is not `getCssValue()`. */
33
43
  async function percent(thumb, side, surface) {
@@ -248,10 +258,16 @@ class WrColorPickerHarness extends ComponentHarness {
248
258
  /**
249
259
  * Where the three thumbs sit, in percent along their surfaces.
250
260
  *
251
- * The component writes these as inline `left` / `top` percentages, so they are
252
- * readable with no layout at all — which makes them the only evidence a spec has
253
- * that the surfaces followed a colour set through the fields. A measured position
254
- * would be zero for all three.
261
+ * The component writes these as inline percentages, so they are readable with no
262
+ * layout at all — which makes them the only evidence a spec has that the surfaces
263
+ * followed a colour set through the fields. A measured position would be zero for
264
+ * all three.
265
+ *
266
+ * The number is always "along the surface from its own start", so it is direction
267
+ * free: the SV canvas reports a physical `left` because it does not mirror, and
268
+ * the two tracks report `inset-inline-start` because they do. An RTL spec reads
269
+ * the same value as an LTR one, which is the point — what mirrors is where that
270
+ * fraction lands on screen, not the fraction.
255
271
  *
256
272
  * Read off the `style` ATTRIBUTE rather than through `getCssValue()`, and the
257
273
  * difference is not cosmetic: `getCssValue()` is `getComputedStyle()`, and `left` /
@@ -267,8 +283,8 @@ class WrColorPickerHarness extends ComponentHarness {
267
283
  const alpha = await this.locatorForOptional('.wr-color-picker__slider--alpha .wr-color-picker__thumb')();
268
284
  return {
269
285
  sv: { x: await percent(sv, 'left', 'SV'), y: await percent(sv, 'top', 'SV') },
270
- hue: await percent(hue, 'left', 'hue'),
271
- alpha: alpha ? await percent(alpha, 'left', 'alpha') : null,
286
+ hue: await percent(hue, 'inset-inline-start', 'hue'),
287
+ alpha: alpha ? await percent(alpha, 'inset-inline-start', 'alpha') : null,
272
288
  };
273
289
  }
274
290
  /** The HEX field's value without the tab check — for filters, which must not throw. */
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-color-picker-testing.mjs","sources":["../../../projects/lib/color-picker/testing/wr-color-picker-harness.ts","../../../projects/lib/color-picker/testing/wr-color-picker-trigger-harness.ts","../../../projects/lib/color-picker/testing/ngwr-color-picker-testing.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 { ComponentHarness, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport { WrSegmentedHarness } from 'ngwr/segmented/testing';\n\nimport type { WrColorPickerHarnessFilters, WrColorPickerTab, WrColorPickerThumbs } from './interfaces';\n\n/**\n * One labelled numeric field — internal. A `TestElement` cannot be queried into, so\n * pairing an input with the label printed under it means a harness anchored on the\n * `<label>` that holds both.\n */\nclass FieldHarness extends ComponentHarness {\n static hostSelector = '.wr-color-picker__field';\n\n async getLabel(): Promise<string> {\n return (await this.locatorFor('.wr-color-picker__field-label')()).text();\n }\n\n async getInput(): Promise<TestElement> {\n return this.locatorFor('input')();\n }\n\n async getValue(): Promise<string> {\n return (await this.getInput()).getProperty<string>('value');\n }\n}\n\n/** The `left: <n>%` / `top: <n>%` a thumb carries, out of its inline `style`. */\nconst THUMB_POS = {\n left: /(?:^|;)\\s*left\\s*:\\s*([\\d.]+)%/,\n top: /(?:^|;)\\s*top\\s*:\\s*([\\d.]+)%/,\n} as const;\n\n/** One thumb's declared offset. See {@link WrColorPickerHarness.getThumbs} for why this is not `getCssValue()`. */\nasync function percent(thumb: TestElement, side: 'left' | 'top', surface: string): Promise<number> {\n const style = await thumb.getAttribute('style');\n const match = THUMB_POS[side].exec(style ?? '');\n\n if (!match) {\n throw new Error(\n `WrColorPickerHarness.getThumbs(): the ${surface} thumb carries no inline ${side} percentage ` +\n `(style: \"${style ?? ''}\"). The component writes one on every render, so its absence is the bug — ` +\n `and the stylesheet declares \\`${side}: 0\\`, so reporting 0 would read as a thumb parked at the origin.`\n );\n }\n return Number.parseFloat(match[1]);\n}\n\n/**\n * Test harness for `<wr-color-picker>` — the inline picker, and the one the\n * `[wrColorPickerTrigger]` directive mounts in an overlay.\n *\n * **What this harness will not do: drag.** The SV canvas and the hue / alpha\n * sliders are pointer surfaces that read `getBoundingClientRect()` and divide by\n * its width. A unit test has no layout, so every box is 0×0 and the division is\n * `NaN` — a `setHue()` built on a synthetic pointer event would report success and\n * write nothing, or write garbage. The numeric fields reach the same state and are\n * also the only keyboard path the component has, so they are the whole write API\n * here. {@link getThumbs} reads where the thumbs ARE, from the percentages the\n * component writes inline, which is how a spec checks a surface followed a value.\n *\n * **And it will not read the preview swatch.** The preview paints the current\n * colour as a CSS background, and jsdom normalises that to `rgb(…)` — dropping the\n * alpha channel entirely, so `#ff880080` and `#ff8800ff` come back identical. A\n * `getColor()` reading it would answer one thing in a unit test and another in a\n * browser. {@link getHex} is the colour: the field holds the canonical 8-digit\n * string, alpha included, whatever `format` writes into `value`.\n *\n * @example\n * ```ts\n * const picker = await loader.getHarness(WrColorPickerHarness);\n *\n * await picker.setHex('#3969e2');\n * expect(await picker.getHex()).toBe('#3969e2ff');\n *\n * await picker.setTab('rgb');\n * expect(await picker.getRgb()).toEqual({ r: 57, g: 105, b: 226 });\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrColorPickerHarness extends ComponentHarness {\n static hostSelector = 'wr-color-picker';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrColorPickerHarnessFilters = {}): HarnessPredicate<WrColorPickerHarness> {\n return new HarnessPredicate(WrColorPickerHarness, options)\n .addOption('color', options.color, (harness, color) => HarnessPredicate.stringMatches(harness.readHex(), color))\n .addOption('tab', options.tab, async (harness, tab) => (await harness.getTab()) === tab)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly hexField = this.locatorForOptional('.wr-color-picker__inputs--hex input');\n private readonly tabs = this.locatorFor(WrSegmentedHarness.with({ selector: '.wr-color-picker__tabs' }));\n\n /**\n * The tab switcher, as a {@link WrSegmentedHarness}.\n *\n * Handed back rather than re-queried: the switcher IS a `<wr-segmented>`, which\n * already knows that the roving tab stop is not the selection and how the sliding\n * thumb reports itself. Re-reading its buttons here would be a second, worse copy.\n */\n async getTabs(): Promise<WrSegmentedHarness> {\n return this.tabs();\n }\n\n /** Which set of numeric fields is showing. */\n async getTab(): Promise<WrColorPickerTab> {\n const label = await (await this.tabs()).getSelectedLabel();\n return (label ?? 'HEX').toLowerCase() as WrColorPickerTab;\n }\n\n /** Show a different set of numeric fields. An already-active tab is left alone. */\n async setTab(tab: WrColorPickerTab): Promise<void> {\n if ((await this.getTab()) === tab) return;\n await (await this.tabs()).select({ label: tab.toUpperCase() });\n }\n\n /**\n * The canonical hex string, as the HEX field shows it — 8 digits while `alpha` is\n * on, 6 while it is off.\n *\n * This is the picker's colour, and deliberately not the same thing as its `value`:\n * `format` decides whether `value` is written as hex, `rgb()` or `hsl()`, while the\n * field always shows hex. A spec that cares about the emitted string should read\n * the bound signal; one that cares about the COLOUR should read this.\n *\n * Throws off the HEX tab, where the field does not exist — see {@link setTab}.\n */\n async getHex(): Promise<string> {\n const field = await this.hexField();\n if (!field) {\n throw new Error(\n `WrColorPickerHarness.getHex(): the HEX field is not rendered — the picker is showing the ` +\n `${(await this.getTab()).toUpperCase()} tab. Call setTab('hex') first.`\n );\n }\n return field.getProperty<string>('value');\n }\n\n /**\n * Type a colour into the HEX field.\n *\n * Typed rather than assigned, because that is the path the component is built\n * around: it re-parses on every keystroke and applies the first spelling that\n * parses, so a partial `#ff8` commits a colour on its way to `#ff8800`. An\n * unparseable string leaves the committed colour ALONE and lives in the field\n * until {@link blurHex} snaps it back — assert that rather than expecting a throw.\n */\n async setHex(value: string): Promise<void> {\n const field = await this.requireHexField('setHex');\n await field.clear();\n await field.sendKeys(value);\n }\n\n /**\n * Blur the HEX field — re-syncing it with the committed colour and emitting\n * `touch`.\n *\n * The re-sync is the visible half: a field left holding text that never parsed\n * goes back to the canonical hex here, and nowhere else.\n */\n async blurHex(): Promise<void> {\n await (await this.requireHexField('blurHex')).blur();\n }\n\n /** Whether the HEX field currently holds focus. */\n async isHexFocused(): Promise<boolean> {\n return (await this.requireHexField('isHexFocused')).isFocused();\n }\n\n /** The three RGB channels as the RGB tab shows them. Throws off that tab. */\n async getRgb(): Promise<{ r: number; g: number; b: number }> {\n const [r, g, b] = await this.channelValues('rgb', ['R', 'G', 'B']);\n return { r, g, b };\n }\n\n /** Write one RGB channel, 0–255. Out-of-range numbers are clamped by the component. */\n async setRgbChannel(channel: 'r' | 'g' | 'b', value: number): Promise<void> {\n await this.writeChannel('rgb', channel.toUpperCase(), value);\n }\n\n /**\n * Hue, saturation and lightness as the HSL tab shows them — degrees and whole\n * percents, already rounded for display. Throws off that tab.\n */\n async getHsl(): Promise<{ h: number; s: number; l: number }> {\n const [h, s, l] = await this.channelValues('hsl', ['H', 'S%', 'L%']);\n return { h, s, l };\n }\n\n /** Write one HSL channel — `h` in degrees, `s` / `l` in percent. */\n async setHslChannel(channel: 'h' | 's' | 'l', value: number): Promise<void> {\n await this.writeChannel('hsl', channel === 'h' ? 'H' : `${channel.toUpperCase()}%`, value);\n }\n\n /**\n * Alpha as the whole percent the numeric tabs show, or `null` when the picker was\n * built without alpha.\n *\n * Only the RGB and HSL tabs carry the field — the HEX tab spells alpha into the\n * hex string instead — so this throws on the HEX tab rather than answering `null`,\n * which would read as \"this picker has no alpha\".\n */\n async getAlphaPercent(): Promise<number | null> {\n const tab = await this.getTab();\n if (tab === 'hex') {\n throw new Error(\n 'WrColorPickerHarness.getAlphaPercent(): the HEX tab has no alpha field — the alpha is the last two ' +\n \"digits of getHex(). Call setTab('rgb') or setTab('hsl') to read it as a percent.\"\n );\n }\n if (!(await this.hasAlpha())) return null;\n const [alpha] = await this.channelValues(tab, ['A%']);\n return alpha;\n }\n\n /** Write alpha as a whole percent, 0–100. */\n async setAlphaPercent(value: number): Promise<void> {\n const tab = await this.getTab();\n if (tab === 'hex') {\n throw new Error(\n \"WrColorPickerHarness.setAlphaPercent(): the HEX tab has no alpha field. Call setTab('rgb') first, \" +\n 'or write the two alpha digits through setHex().'\n );\n }\n await this.writeChannel(tab, 'A%', value);\n }\n\n /**\n * Whether the picker offers alpha at all.\n *\n * From the host modifier, which is the one answer that holds on every tab: the\n * alpha SLIDER is always rendered when it is on, while the `A%` field only exists\n * on the two numeric tabs.\n */\n async hasAlpha(): Promise<boolean> {\n return (await this.host()).hasClass('wr-color-picker--alpha');\n }\n\n /**\n * Whether the picker refuses interaction.\n *\n * Read from the modifier the host paints rather than from a field's `disabled`\n * property, because the fields come and go with the tab while the state does not —\n * and the drag surfaces, which have no `disabled` of their own, are gated by the\n * same signal.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-color-picker--disabled');\n }\n\n /** The preset colours offered under the fields, as the strings the consumer passed. */\n async getSwatches(): Promise<string[]> {\n const swatches = await this.locatorForAll('.wr-color-picker__swatch')();\n return Promise.all(swatches.map(async swatch => (await swatch.getAttribute('aria-label')) ?? ''));\n }\n\n /**\n * Click a preset.\n *\n * Matched on the string the consumer passed, which is what the button publishes as\n * its accessible name — the rendered background is a CSS colour and would come back\n * normalised and alpha-stripped. Throws when nothing matches, naming what is offered.\n */\n async pickSwatch(color: string): Promise<void> {\n const swatches = await this.locatorForAll('.wr-color-picker__swatch')();\n for (const swatch of swatches) {\n if ((await swatch.getAttribute('aria-label')) === color) {\n await swatch.click();\n return;\n }\n }\n throw new Error(\n `WrColorPickerHarness.pickSwatch(): no swatch for \"${color}\". The picker offers: ` +\n `${(await this.getSwatches()).join(', ')}.`\n );\n }\n\n /**\n * Where the three thumbs sit, in percent along their surfaces.\n *\n * The component writes these as inline `left` / `top` percentages, so they are\n * readable with no layout at all — which makes them the only evidence a spec has\n * that the surfaces followed a colour set through the fields. A measured position\n * would be zero for all three.\n *\n * Read off the `style` ATTRIBUTE rather than through `getCssValue()`, and the\n * difference is not cosmetic: `getCssValue()` is `getComputedStyle()`, and `left` /\n * `top` on a positioned element resolve to the USED value — so the declared `40%`\n * comes straight back in jsdom and arrives as pixels in a real browser, and the same\n * harness answered 40 here and 96 under a browser runner with nothing to say which\n * one you got. The stylesheet's own `top: 0; left: 0` is a second way that read lies,\n * turning a thumb whose binding is gone into one parked at the origin.\n */\n async getThumbs(): Promise<WrColorPickerThumbs> {\n const sv = await this.locatorFor('.wr-color-picker__sv .wr-color-picker__thumb')();\n const hue = await this.locatorFor('.wr-color-picker__slider--hue .wr-color-picker__thumb')();\n const alpha = await this.locatorForOptional('.wr-color-picker__slider--alpha .wr-color-picker__thumb')();\n\n return {\n sv: { x: await percent(sv, 'left', 'SV'), y: await percent(sv, 'top', 'SV') },\n hue: await percent(hue, 'left', 'hue'),\n alpha: alpha ? await percent(alpha, 'left', 'alpha') : null,\n };\n }\n\n /** The HEX field's value without the tab check — for filters, which must not throw. */\n private async readHex(): Promise<string> {\n const field = await this.hexField();\n return field ? field.getProperty<string>('value') : '';\n }\n\n private async requireHexField(method: string): Promise<TestElement> {\n const field = await this.hexField();\n if (!field) {\n throw new Error(\n `WrColorPickerHarness.${method}(): the HEX field is not rendered — the picker is showing the ` +\n `${(await this.getTab()).toUpperCase()} tab. Call setTab('hex') first.`\n );\n }\n return field;\n }\n\n private async channelValues(tab: WrColorPickerTab, labels: readonly string[]): Promise<number[]> {\n await this.requireTab(tab, 'read');\n const inputs = await this.inputsByLabel();\n\n return labels.map(label => {\n const input = inputs.get(label);\n if (!input) throw new Error(`WrColorPickerHarness: the ${tab.toUpperCase()} tab has no \"${label}\" field.`);\n return Number(input.value);\n });\n }\n\n private async writeChannel(tab: WrColorPickerTab, label: string, value: number): Promise<void> {\n await this.requireTab(tab, 'write');\n const inputs = await this.inputsByLabel();\n const found = inputs.get(label);\n if (!found) {\n throw new Error(\n `WrColorPickerHarness: the ${tab.toUpperCase()} tab has no \"${label}\" field. ${\n label === 'A%' ? 'This picker was built with `[alpha]=\"false\"`.' : ''\n }`\n );\n }\n\n // Set and dispatch once, rather than clearing and typing. Each of these inputs\n // is bound to `[value]`, and the component commits on every `input` event — so\n // typing 128 would land 1, then 12, then 128, and CLEARING would land 0 first,\n // because `Number('')` is 0 rather than NaN. A spec that asked for one colour\n // would get four, the first of them black.\n await found.element.setInputValue(String(value));\n await found.element.dispatchEvent('input');\n }\n\n private async requireTab(tab: WrColorPickerTab, verb: string): Promise<void> {\n const current = await this.getTab();\n if (current !== tab) {\n throw new Error(\n `WrColorPickerHarness: cannot ${verb} the ${tab.toUpperCase()} channels while the picker is showing ` +\n `the ${current.toUpperCase()} tab — those fields are not rendered. Call setTab('${tab}') first.`\n );\n }\n }\n\n /** Every numeric input of the current tab, keyed by the label printed under it. */\n private async inputsByLabel(): Promise<Map<string, { element: TestElement; value: string }>> {\n const found = new Map<string, { element: TestElement; value: string }>();\n\n for (const field of await this.locatorForAll(FieldHarness)()) {\n const label = await field.getLabel();\n found.set(label, { element: await field.getInput(), value: await field.getValue() });\n }\n return found;\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 { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\n\nimport type { WrColorPickerTriggerHarnessFilters } from './interfaces';\nimport { WrColorPickerHarness } from './wr-color-picker-harness';\n\n/**\n * Test harness for `[wrColorPickerTrigger]` — the element that opens a\n * `<wr-color-picker>` in an overlay.\n *\n * The trigger stays in your fixture; the picker it opens does not. So this harness\n * is loaded from the fixture loader like any element, and {@link getPicker} reaches\n * ACROSS into the overlay container for you — scoped by the panel id the trigger\n * publishes through `aria-controls`, never by `.wr-color-picker-overlay`, which\n * answers with whichever trigger opened first.\n *\n * @example\n * ```ts\n * const trigger = await loader.getHarness(WrColorPickerTriggerHarness);\n * const picker = await trigger.open();\n *\n * await picker.setHex('#3969e2');\n * await trigger.sendEscape();\n * expect(await trigger.isOpen()).toBe(false);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrColorPickerTriggerHarness extends ComponentHarness {\n static hostSelector = '[wrColorPickerTrigger]';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrColorPickerTriggerHarnessFilters = {}): HarnessPredicate<WrColorPickerTriggerHarness> {\n return new HarnessPredicate(WrColorPickerTriggerHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n /** The trigger's own text, trimmed. */\n async getText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /** Whether the picker is showing, from the trigger's `aria-expanded`. */\n async isOpen(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-expanded')) === 'true';\n }\n\n /**\n * Whether the trigger refuses to open.\n *\n * From `aria-disabled`, which the directive writes for its own `disabled` input.\n * The host is usually a `<button>`, but the directive does not touch its `disabled`\n * PROPERTY — a consumer who binds that as well turns the element off at the DOM\n * level, which is a stronger thing and reads here too.\n */\n async isDisabled(): Promise<boolean> {\n const host = await this.host();\n return (await host.getAttribute('aria-disabled')) === 'true' || (await host.getProperty<boolean>('disabled'));\n }\n\n /**\n * The picker this trigger opened, or `null` while it is closed.\n *\n * Scoped by the id in `aria-controls`, so two triggers open at once — which\n * nothing in the component prevents, since only an outside click closes one —\n * answer with their own panels rather than with whichever came first.\n */\n async getPicker(): Promise<WrColorPickerHarness | null> {\n const id = await (await this.host()).getAttribute('aria-controls');\n if (!id) return null;\n\n return this.documentRootLocatorFactory().locatorForOptional(WrColorPickerHarness.with({ ancestor: `#${id}` }))();\n }\n\n /**\n * Open the picker and hand it back. An already-open trigger is left alone.\n *\n * Throws on a disabled trigger rather than answering `null` three lines later:\n * `toggle()` returns early while `disabled` is set, so the click lands and nothing\n * happens at all.\n */\n async open(): Promise<WrColorPickerHarness> {\n if (!(await this.isOpen())) {\n if (await this.isDisabled()) {\n throw new Error(\n `WrColorPickerTriggerHarness.open(): \"${await this.getText()}\" is disabled — the directive turns its ` +\n 'own toggle away, so the click would land and change nothing.'\n );\n }\n await (await this.host()).click();\n }\n\n const picker = await this.getPicker();\n if (!picker) {\n throw new Error(\n 'WrColorPickerTriggerHarness.open(): the trigger reports itself open but published no panel to find. ' +\n 'That is a broken `aria-controls` pairing, not a timing problem.'\n );\n }\n return picker;\n }\n\n /** Close the picker. An already-closed trigger is left alone. */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n await (await this.host()).click();\n }\n\n /** Click the trigger once, whichever way that takes it. */\n async toggle(): Promise<void> {\n await (await this.host()).click();\n }\n\n /**\n * Press Escape.\n *\n * Sent to the TRIGGER, and it still reaches the picker: the overlay listens through\n * the CDK's keyboard dispatcher, which keeps one document listener and routes to\n * the top-most overlay — so this does not depend on where focus happens to be.\n */\n async sendEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n\n /** The role the trigger claims to open — `dialog`. */\n async getHasPopup(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-haspopup');\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-color-picker-testing.mjs","sources":["../../../projects/lib/color-picker/testing/wr-color-picker-harness.ts","../../../projects/lib/color-picker/testing/wr-color-picker-trigger-harness.ts","../../../projects/lib/color-picker/testing/ngwr-color-picker-testing.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 { ComponentHarness, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport { WrSegmentedHarness } from 'ngwr/segmented/testing';\n\nimport type { WrColorPickerHarnessFilters, WrColorPickerTab, WrColorPickerThumbs } from './interfaces';\n\n/**\n * One labelled numeric field — internal. A `TestElement` cannot be queried into, so\n * pairing an input with the label printed under it means a harness anchored on the\n * `<label>` that holds both.\n */\nclass FieldHarness extends ComponentHarness {\n static hostSelector = '.wr-color-picker__field';\n\n async getLabel(): Promise<string> {\n return (await this.locatorFor('.wr-color-picker__field-label')()).text();\n }\n\n async getInput(): Promise<TestElement> {\n return this.locatorFor('input')();\n }\n\n async getValue(): Promise<string> {\n return (await this.getInput()).getProperty<string>('value');\n }\n}\n\n/**\n * The percentage a thumb carries, out of its inline `style`.\n *\n * Three properties and not two, because the surfaces do not agree on the inline\n * axis and that disagreement is the contract: the SV canvas is a picture and\n * keeps a physical `left`, while the hue and alpha tracks are one-value sliders\n * that mirror under `dir=\"rtl\"` and so are placed with `inset-inline-start`.\n * Reading `left` off a slider thumb therefore finds nothing, which is exactly\n * what should happen if one of them ever regresses to the physical property.\n */\nconst THUMB_POS = {\n left: /(?:^|;)\\s*left\\s*:\\s*([\\d.]+)%/,\n top: /(?:^|;)\\s*top\\s*:\\s*([\\d.]+)%/,\n 'inset-inline-start': /(?:^|;)\\s*inset-inline-start\\s*:\\s*([\\d.]+)%/,\n} as const;\n\n/** One thumb's declared offset. See {@link WrColorPickerHarness.getThumbs} for why this is not `getCssValue()`. */\nasync function percent(thumb: TestElement, side: keyof typeof THUMB_POS, surface: string): Promise<number> {\n const style = await thumb.getAttribute('style');\n const match = THUMB_POS[side].exec(style ?? '');\n\n if (!match) {\n throw new Error(\n `WrColorPickerHarness.getThumbs(): the ${surface} thumb carries no inline ${side} percentage ` +\n `(style: \"${style ?? ''}\"). The component writes one on every render, so its absence is the bug — ` +\n `and the stylesheet declares \\`${side}: 0\\`, so reporting 0 would read as a thumb parked at the origin.`\n );\n }\n return Number.parseFloat(match[1]);\n}\n\n/**\n * Test harness for `<wr-color-picker>` — the inline picker, and the one the\n * `[wrColorPickerTrigger]` directive mounts in an overlay.\n *\n * **What this harness will not do: drag.** The SV canvas and the hue / alpha\n * sliders are pointer surfaces that read `getBoundingClientRect()` and divide by\n * its width. A unit test has no layout, so every box is 0×0 and the division is\n * `NaN` — a `setHue()` built on a synthetic pointer event would report success and\n * write nothing, or write garbage. The numeric fields reach the same state and are\n * also the only keyboard path the component has, so they are the whole write API\n * here. {@link getThumbs} reads where the thumbs ARE, from the percentages the\n * component writes inline, which is how a spec checks a surface followed a value.\n *\n * **And it will not read the preview swatch.** The preview paints the current\n * colour as a CSS background, and jsdom normalises that to `rgb(…)` — dropping the\n * alpha channel entirely, so `#ff880080` and `#ff8800ff` come back identical. A\n * `getColor()` reading it would answer one thing in a unit test and another in a\n * browser. {@link getHex} is the colour: the field holds the canonical 8-digit\n * string, alpha included, whatever `format` writes into `value`.\n *\n * @example\n * ```ts\n * const picker = await loader.getHarness(WrColorPickerHarness);\n *\n * await picker.setHex('#3969e2');\n * expect(await picker.getHex()).toBe('#3969e2ff');\n *\n * await picker.setTab('rgb');\n * expect(await picker.getRgb()).toEqual({ r: 57, g: 105, b: 226 });\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrColorPickerHarness extends ComponentHarness {\n static hostSelector = 'wr-color-picker';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrColorPickerHarnessFilters = {}): HarnessPredicate<WrColorPickerHarness> {\n return new HarnessPredicate(WrColorPickerHarness, options)\n .addOption('color', options.color, (harness, color) => HarnessPredicate.stringMatches(harness.readHex(), color))\n .addOption('tab', options.tab, async (harness, tab) => (await harness.getTab()) === tab)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled);\n }\n\n private readonly hexField = this.locatorForOptional('.wr-color-picker__inputs--hex input');\n private readonly tabs = this.locatorFor(WrSegmentedHarness.with({ selector: '.wr-color-picker__tabs' }));\n\n /**\n * The tab switcher, as a {@link WrSegmentedHarness}.\n *\n * Handed back rather than re-queried: the switcher IS a `<wr-segmented>`, which\n * already knows that the roving tab stop is not the selection and how the sliding\n * thumb reports itself. Re-reading its buttons here would be a second, worse copy.\n */\n async getTabs(): Promise<WrSegmentedHarness> {\n return this.tabs();\n }\n\n /** Which set of numeric fields is showing. */\n async getTab(): Promise<WrColorPickerTab> {\n const label = await (await this.tabs()).getSelectedLabel();\n return (label ?? 'HEX').toLowerCase() as WrColorPickerTab;\n }\n\n /** Show a different set of numeric fields. An already-active tab is left alone. */\n async setTab(tab: WrColorPickerTab): Promise<void> {\n if ((await this.getTab()) === tab) return;\n await (await this.tabs()).select({ label: tab.toUpperCase() });\n }\n\n /**\n * The canonical hex string, as the HEX field shows it — 8 digits while `alpha` is\n * on, 6 while it is off.\n *\n * This is the picker's colour, and deliberately not the same thing as its `value`:\n * `format` decides whether `value` is written as hex, `rgb()` or `hsl()`, while the\n * field always shows hex. A spec that cares about the emitted string should read\n * the bound signal; one that cares about the COLOUR should read this.\n *\n * Throws off the HEX tab, where the field does not exist — see {@link setTab}.\n */\n async getHex(): Promise<string> {\n const field = await this.hexField();\n if (!field) {\n throw new Error(\n `WrColorPickerHarness.getHex(): the HEX field is not rendered — the picker is showing the ` +\n `${(await this.getTab()).toUpperCase()} tab. Call setTab('hex') first.`\n );\n }\n return field.getProperty<string>('value');\n }\n\n /**\n * Type a colour into the HEX field.\n *\n * Typed rather than assigned, because that is the path the component is built\n * around: it re-parses on every keystroke and applies the first spelling that\n * parses, so a partial `#ff8` commits a colour on its way to `#ff8800`. An\n * unparseable string leaves the committed colour ALONE and lives in the field\n * until {@link blurHex} snaps it back — assert that rather than expecting a throw.\n */\n async setHex(value: string): Promise<void> {\n const field = await this.requireHexField('setHex');\n await field.clear();\n await field.sendKeys(value);\n }\n\n /**\n * Blur the HEX field — re-syncing it with the committed colour and emitting\n * `touch`.\n *\n * The re-sync is the visible half: a field left holding text that never parsed\n * goes back to the canonical hex here, and nowhere else.\n */\n async blurHex(): Promise<void> {\n await (await this.requireHexField('blurHex')).blur();\n }\n\n /** Whether the HEX field currently holds focus. */\n async isHexFocused(): Promise<boolean> {\n return (await this.requireHexField('isHexFocused')).isFocused();\n }\n\n /** The three RGB channels as the RGB tab shows them. Throws off that tab. */\n async getRgb(): Promise<{ r: number; g: number; b: number }> {\n const [r, g, b] = await this.channelValues('rgb', ['R', 'G', 'B']);\n return { r, g, b };\n }\n\n /** Write one RGB channel, 0–255. Out-of-range numbers are clamped by the component. */\n async setRgbChannel(channel: 'r' | 'g' | 'b', value: number): Promise<void> {\n await this.writeChannel('rgb', channel.toUpperCase(), value);\n }\n\n /**\n * Hue, saturation and lightness as the HSL tab shows them — degrees and whole\n * percents, already rounded for display. Throws off that tab.\n */\n async getHsl(): Promise<{ h: number; s: number; l: number }> {\n const [h, s, l] = await this.channelValues('hsl', ['H', 'S%', 'L%']);\n return { h, s, l };\n }\n\n /** Write one HSL channel — `h` in degrees, `s` / `l` in percent. */\n async setHslChannel(channel: 'h' | 's' | 'l', value: number): Promise<void> {\n await this.writeChannel('hsl', channel === 'h' ? 'H' : `${channel.toUpperCase()}%`, value);\n }\n\n /**\n * Alpha as the whole percent the numeric tabs show, or `null` when the picker was\n * built without alpha.\n *\n * Only the RGB and HSL tabs carry the field — the HEX tab spells alpha into the\n * hex string instead — so this throws on the HEX tab rather than answering `null`,\n * which would read as \"this picker has no alpha\".\n */\n async getAlphaPercent(): Promise<number | null> {\n const tab = await this.getTab();\n if (tab === 'hex') {\n throw new Error(\n 'WrColorPickerHarness.getAlphaPercent(): the HEX tab has no alpha field — the alpha is the last two ' +\n \"digits of getHex(). Call setTab('rgb') or setTab('hsl') to read it as a percent.\"\n );\n }\n if (!(await this.hasAlpha())) return null;\n const [alpha] = await this.channelValues(tab, ['A%']);\n return alpha;\n }\n\n /** Write alpha as a whole percent, 0–100. */\n async setAlphaPercent(value: number): Promise<void> {\n const tab = await this.getTab();\n if (tab === 'hex') {\n throw new Error(\n \"WrColorPickerHarness.setAlphaPercent(): the HEX tab has no alpha field. Call setTab('rgb') first, \" +\n 'or write the two alpha digits through setHex().'\n );\n }\n await this.writeChannel(tab, 'A%', value);\n }\n\n /**\n * Whether the picker offers alpha at all.\n *\n * From the host modifier, which is the one answer that holds on every tab: the\n * alpha SLIDER is always rendered when it is on, while the `A%` field only exists\n * on the two numeric tabs.\n */\n async hasAlpha(): Promise<boolean> {\n return (await this.host()).hasClass('wr-color-picker--alpha');\n }\n\n /**\n * Whether the picker refuses interaction.\n *\n * Read from the modifier the host paints rather than from a field's `disabled`\n * property, because the fields come and go with the tab while the state does not —\n * and the drag surfaces, which have no `disabled` of their own, are gated by the\n * same signal.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-color-picker--disabled');\n }\n\n /** The preset colours offered under the fields, as the strings the consumer passed. */\n async getSwatches(): Promise<string[]> {\n const swatches = await this.locatorForAll('.wr-color-picker__swatch')();\n return Promise.all(swatches.map(async swatch => (await swatch.getAttribute('aria-label')) ?? ''));\n }\n\n /**\n * Click a preset.\n *\n * Matched on the string the consumer passed, which is what the button publishes as\n * its accessible name — the rendered background is a CSS colour and would come back\n * normalised and alpha-stripped. Throws when nothing matches, naming what is offered.\n */\n async pickSwatch(color: string): Promise<void> {\n const swatches = await this.locatorForAll('.wr-color-picker__swatch')();\n for (const swatch of swatches) {\n if ((await swatch.getAttribute('aria-label')) === color) {\n await swatch.click();\n return;\n }\n }\n throw new Error(\n `WrColorPickerHarness.pickSwatch(): no swatch for \"${color}\". The picker offers: ` +\n `${(await this.getSwatches()).join(', ')}.`\n );\n }\n\n /**\n * Where the three thumbs sit, in percent along their surfaces.\n *\n * The component writes these as inline percentages, so they are readable with no\n * layout at all — which makes them the only evidence a spec has that the surfaces\n * followed a colour set through the fields. A measured position would be zero for\n * all three.\n *\n * The number is always \"along the surface from its own start\", so it is direction\n * free: the SV canvas reports a physical `left` because it does not mirror, and\n * the two tracks report `inset-inline-start` because they do. An RTL spec reads\n * the same value as an LTR one, which is the point — what mirrors is where that\n * fraction lands on screen, not the fraction.\n *\n * Read off the `style` ATTRIBUTE rather than through `getCssValue()`, and the\n * difference is not cosmetic: `getCssValue()` is `getComputedStyle()`, and `left` /\n * `top` on a positioned element resolve to the USED value — so the declared `40%`\n * comes straight back in jsdom and arrives as pixels in a real browser, and the same\n * harness answered 40 here and 96 under a browser runner with nothing to say which\n * one you got. The stylesheet's own `top: 0; left: 0` is a second way that read lies,\n * turning a thumb whose binding is gone into one parked at the origin.\n */\n async getThumbs(): Promise<WrColorPickerThumbs> {\n const sv = await this.locatorFor('.wr-color-picker__sv .wr-color-picker__thumb')();\n const hue = await this.locatorFor('.wr-color-picker__slider--hue .wr-color-picker__thumb')();\n const alpha = await this.locatorForOptional('.wr-color-picker__slider--alpha .wr-color-picker__thumb')();\n\n return {\n sv: { x: await percent(sv, 'left', 'SV'), y: await percent(sv, 'top', 'SV') },\n hue: await percent(hue, 'inset-inline-start', 'hue'),\n alpha: alpha ? await percent(alpha, 'inset-inline-start', 'alpha') : null,\n };\n }\n\n /** The HEX field's value without the tab check — for filters, which must not throw. */\n private async readHex(): Promise<string> {\n const field = await this.hexField();\n return field ? field.getProperty<string>('value') : '';\n }\n\n private async requireHexField(method: string): Promise<TestElement> {\n const field = await this.hexField();\n if (!field) {\n throw new Error(\n `WrColorPickerHarness.${method}(): the HEX field is not rendered — the picker is showing the ` +\n `${(await this.getTab()).toUpperCase()} tab. Call setTab('hex') first.`\n );\n }\n return field;\n }\n\n private async channelValues(tab: WrColorPickerTab, labels: readonly string[]): Promise<number[]> {\n await this.requireTab(tab, 'read');\n const inputs = await this.inputsByLabel();\n\n return labels.map(label => {\n const input = inputs.get(label);\n if (!input) throw new Error(`WrColorPickerHarness: the ${tab.toUpperCase()} tab has no \"${label}\" field.`);\n return Number(input.value);\n });\n }\n\n private async writeChannel(tab: WrColorPickerTab, label: string, value: number): Promise<void> {\n await this.requireTab(tab, 'write');\n const inputs = await this.inputsByLabel();\n const found = inputs.get(label);\n if (!found) {\n throw new Error(\n `WrColorPickerHarness: the ${tab.toUpperCase()} tab has no \"${label}\" field. ${\n label === 'A%' ? 'This picker was built with `[alpha]=\"false\"`.' : ''\n }`\n );\n }\n\n // Set and dispatch once, rather than clearing and typing. Each of these inputs\n // is bound to `[value]`, and the component commits on every `input` event — so\n // typing 128 would land 1, then 12, then 128, and CLEARING would land 0 first,\n // because `Number('')` is 0 rather than NaN. A spec that asked for one colour\n // would get four, the first of them black.\n await found.element.setInputValue(String(value));\n await found.element.dispatchEvent('input');\n }\n\n private async requireTab(tab: WrColorPickerTab, verb: string): Promise<void> {\n const current = await this.getTab();\n if (current !== tab) {\n throw new Error(\n `WrColorPickerHarness: cannot ${verb} the ${tab.toUpperCase()} channels while the picker is showing ` +\n `the ${current.toUpperCase()} tab — those fields are not rendered. Call setTab('${tab}') first.`\n );\n }\n }\n\n /** Every numeric input of the current tab, keyed by the label printed under it. */\n private async inputsByLabel(): Promise<Map<string, { element: TestElement; value: string }>> {\n const found = new Map<string, { element: TestElement; value: string }>();\n\n for (const field of await this.locatorForAll(FieldHarness)()) {\n const label = await field.getLabel();\n found.set(label, { element: await field.getInput(), value: await field.getValue() });\n }\n return found;\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 { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\n\nimport type { WrColorPickerTriggerHarnessFilters } from './interfaces';\nimport { WrColorPickerHarness } from './wr-color-picker-harness';\n\n/**\n * Test harness for `[wrColorPickerTrigger]` — the element that opens a\n * `<wr-color-picker>` in an overlay.\n *\n * The trigger stays in your fixture; the picker it opens does not. So this harness\n * is loaded from the fixture loader like any element, and {@link getPicker} reaches\n * ACROSS into the overlay container for you — scoped by the panel id the trigger\n * publishes through `aria-controls`, never by `.wr-color-picker-overlay`, which\n * answers with whichever trigger opened first.\n *\n * @example\n * ```ts\n * const trigger = await loader.getHarness(WrColorPickerTriggerHarness);\n * const picker = await trigger.open();\n *\n * await picker.setHex('#3969e2');\n * await trigger.sendEscape();\n * expect(await trigger.isOpen()).toBe(false);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrColorPickerTriggerHarness extends ComponentHarness {\n static hostSelector = '[wrColorPickerTrigger]';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrColorPickerTriggerHarnessFilters = {}): HarnessPredicate<WrColorPickerTriggerHarness> {\n return new HarnessPredicate(WrColorPickerTriggerHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n /** The trigger's own text, trimmed. */\n async getText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /** Whether the picker is showing, from the trigger's `aria-expanded`. */\n async isOpen(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-expanded')) === 'true';\n }\n\n /**\n * Whether the trigger refuses to open.\n *\n * From `aria-disabled`, which the directive writes for its own `disabled` input.\n * The host is usually a `<button>`, but the directive does not touch its `disabled`\n * PROPERTY — a consumer who binds that as well turns the element off at the DOM\n * level, which is a stronger thing and reads here too.\n */\n async isDisabled(): Promise<boolean> {\n const host = await this.host();\n return (await host.getAttribute('aria-disabled')) === 'true' || (await host.getProperty<boolean>('disabled'));\n }\n\n /**\n * The picker this trigger opened, or `null` while it is closed.\n *\n * Scoped by the id in `aria-controls`, so two triggers open at once — which\n * nothing in the component prevents, since only an outside click closes one —\n * answer with their own panels rather than with whichever came first.\n */\n async getPicker(): Promise<WrColorPickerHarness | null> {\n const id = await (await this.host()).getAttribute('aria-controls');\n if (!id) return null;\n\n return this.documentRootLocatorFactory().locatorForOptional(WrColorPickerHarness.with({ ancestor: `#${id}` }))();\n }\n\n /**\n * Open the picker and hand it back. An already-open trigger is left alone.\n *\n * Throws on a disabled trigger rather than answering `null` three lines later:\n * `toggle()` returns early while `disabled` is set, so the click lands and nothing\n * happens at all.\n */\n async open(): Promise<WrColorPickerHarness> {\n if (!(await this.isOpen())) {\n if (await this.isDisabled()) {\n throw new Error(\n `WrColorPickerTriggerHarness.open(): \"${await this.getText()}\" is disabled — the directive turns its ` +\n 'own toggle away, so the click would land and change nothing.'\n );\n }\n await (await this.host()).click();\n }\n\n const picker = await this.getPicker();\n if (!picker) {\n throw new Error(\n 'WrColorPickerTriggerHarness.open(): the trigger reports itself open but published no panel to find. ' +\n 'That is a broken `aria-controls` pairing, not a timing problem.'\n );\n }\n return picker;\n }\n\n /** Close the picker. An already-closed trigger is left alone. */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n await (await this.host()).click();\n }\n\n /** Click the trigger once, whichever way that takes it. */\n async toggle(): Promise<void> {\n await (await this.host()).click();\n }\n\n /**\n * Press Escape.\n *\n * Sent to the TRIGGER, and it still reaches the picker: the overlay listens through\n * the CDK's keyboard dispatcher, which keeps one document listener and routes to\n * the top-most overlay — so this does not depend on where focus happens to be.\n */\n async sendEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n\n /** The role the trigger claims to open — `dialog`. */\n async getHasPopup(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-haspopup');\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}