ngwr 13.0.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.md +13 -6
  2. package/color-picker/styles/_index.scss +43 -5
  3. package/fesm2022/ngwr-alert.mjs +5 -5
  4. package/fesm2022/ngwr-alert.mjs.map +1 -1
  5. package/fesm2022/ngwr-back-top.mjs +11 -1
  6. package/fesm2022/ngwr-back-top.mjs.map +1 -1
  7. package/fesm2022/ngwr-blur-text.mjs +13 -2
  8. package/fesm2022/ngwr-blur-text.mjs.map +1 -1
  9. package/fesm2022/ngwr-border-glow.mjs +12 -3
  10. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  11. package/fesm2022/ngwr-breadcrumbs.mjs +14 -3
  12. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  13. package/fesm2022/ngwr-button.mjs +4 -4
  14. package/fesm2022/ngwr-button.mjs.map +1 -1
  15. package/fesm2022/ngwr-calendar-testing.mjs +15 -0
  16. package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
  17. package/fesm2022/ngwr-calendar.mjs +51 -5
  18. package/fesm2022/ngwr-calendar.mjs.map +1 -1
  19. package/fesm2022/ngwr-circular-text.mjs +20 -1
  20. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  21. package/fesm2022/ngwr-collapse.mjs.map +1 -1
  22. package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
  23. package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
  24. package/fesm2022/ngwr-color-picker.mjs +89 -10
  25. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  26. package/fesm2022/ngwr-command-palette.mjs +27 -3
  27. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  28. package/fesm2022/ngwr-context-menu.mjs +13 -3
  29. package/fesm2022/ngwr-context-menu.mjs.map +1 -1
  30. package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
  31. package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
  32. package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
  33. package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
  34. package/fesm2022/ngwr-date-picker.mjs +103 -13
  35. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  36. package/fesm2022/ngwr-date.mjs +480 -79
  37. package/fesm2022/ngwr-date.mjs.map +1 -1
  38. package/fesm2022/ngwr-dialog.mjs +22 -16
  39. package/fesm2022/ngwr-dialog.mjs.map +1 -1
  40. package/fesm2022/ngwr-directives.mjs +5 -2
  41. package/fesm2022/ngwr-directives.mjs.map +1 -1
  42. package/fesm2022/ngwr-drag-drop.mjs +30 -2
  43. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  44. package/fesm2022/ngwr-drawer.mjs +24 -18
  45. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  46. package/fesm2022/ngwr-dropdown.mjs +8 -3
  47. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  48. package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
  49. package/fesm2022/ngwr-event-calendar.mjs +1 -1
  50. package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
  51. package/fesm2022/ngwr-file-upload.mjs +52 -12
  52. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  53. package/fesm2022/ngwr-fuzzy-text.mjs +21 -4
  54. package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
  55. package/fesm2022/ngwr-gradient-text.mjs +9 -2
  56. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  57. package/fesm2022/ngwr-i18n-en.mjs +63 -1
  58. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  59. package/fesm2022/ngwr-i18n-ru.mjs +28 -1
  60. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  61. package/fesm2022/ngwr-i18n.mjs +150 -22
  62. package/fesm2022/ngwr-i18n.mjs.map +1 -1
  63. package/fesm2022/ngwr-icon-adapters-feather.mjs +11 -0
  64. package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
  65. package/fesm2022/ngwr-icon-adapters-lucide.mjs +22 -2
  66. package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
  67. package/fesm2022/ngwr-icon.mjs +474 -37
  68. package/fesm2022/ngwr-icon.mjs.map +1 -1
  69. package/fesm2022/ngwr-input-number.mjs +3 -3
  70. package/fesm2022/ngwr-input-number.mjs.map +1 -1
  71. package/fesm2022/ngwr-input-otp.mjs +21 -3
  72. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  73. package/fesm2022/ngwr-input.mjs +14 -7
  74. package/fesm2022/ngwr-input.mjs.map +1 -1
  75. package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
  76. package/fesm2022/ngwr-knob.mjs +20 -2
  77. package/fesm2022/ngwr-knob.mjs.map +1 -1
  78. package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
  79. package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
  80. package/fesm2022/ngwr-loading-bar.mjs +21 -32
  81. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  82. package/fesm2022/ngwr-markdown.mjs +63 -11
  83. package/fesm2022/ngwr-markdown.mjs.map +1 -1
  84. package/fesm2022/ngwr-mention.mjs +104 -14
  85. package/fesm2022/ngwr-mention.mjs.map +1 -1
  86. package/fesm2022/ngwr-overlay.mjs +36 -7
  87. package/fesm2022/ngwr-overlay.mjs.map +1 -1
  88. package/fesm2022/ngwr-pagination-testing.mjs +11 -10
  89. package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
  90. package/fesm2022/ngwr-pagination.mjs +65 -30
  91. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  92. package/fesm2022/ngwr-popconfirm.mjs +3 -2
  93. package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
  94. package/fesm2022/ngwr-popover.mjs +2 -1
  95. package/fesm2022/ngwr-popover.mjs.map +1 -1
  96. package/fesm2022/ngwr-qr.mjs +38 -1
  97. package/fesm2022/ngwr-qr.mjs.map +1 -1
  98. package/fesm2022/ngwr-select.mjs +93 -34
  99. package/fesm2022/ngwr-select.mjs.map +1 -1
  100. package/fesm2022/ngwr-shiny-text.mjs +10 -3
  101. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  102. package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
  103. package/fesm2022/ngwr-squircle.mjs +2 -1
  104. package/fesm2022/ngwr-squircle.mjs.map +1 -1
  105. package/fesm2022/ngwr-statistic.mjs +35 -4
  106. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  107. package/fesm2022/ngwr-table.mjs +35 -12
  108. package/fesm2022/ngwr-table.mjs.map +1 -1
  109. package/fesm2022/ngwr-tabs-router.mjs +158 -0
  110. package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
  111. package/fesm2022/ngwr-tabs-testing.mjs +13 -11
  112. package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
  113. package/fesm2022/ngwr-tabs.mjs +104 -12
  114. package/fesm2022/ngwr-tabs.mjs.map +1 -1
  115. package/fesm2022/ngwr-textarea.mjs +20 -1
  116. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  117. package/fesm2022/ngwr-tour.mjs +1 -1
  118. package/fesm2022/ngwr-tour.mjs.map +1 -1
  119. package/fesm2022/ngwr-transfer.mjs +2 -2
  120. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  121. package/fesm2022/ngwr-tree.mjs +18 -2
  122. package/fesm2022/ngwr-tree.mjs.map +1 -1
  123. package/fesm2022/ngwr-utils.mjs +143 -1
  124. package/fesm2022/ngwr-utils.mjs.map +1 -1
  125. package/fesm2022/ngwr-version.mjs +1 -1
  126. package/fesm2022/ngwr-version.mjs.map +1 -1
  127. package/fesm2022/ngwr-window-testing.mjs +10 -2
  128. package/fesm2022/ngwr-window-testing.mjs.map +1 -1
  129. package/fesm2022/ngwr-window.mjs +4 -4
  130. package/fesm2022/ngwr-window.mjs.map +1 -1
  131. package/i18n/en.json +33 -6
  132. package/i18n/ru.json +33 -6
  133. package/llms-full.txt +20 -11
  134. package/llms.txt +47 -16
  135. package/mcp/catalog.js +3 -3
  136. package/mcp/providers.js +11 -0
  137. package/package.json +11 -3
  138. package/schematics/migrations/v13/index.js +101 -0
  139. package/schematics/migrations/v14/index.js +422 -0
  140. package/schematics/migrations.json +10 -0
  141. package/schematics/page/templates.js +1 -1
  142. package/schematics/use/symbol-map.json +1 -0
  143. package/skills/ngwr/SKILL.md +4 -1
  144. package/skills/ngwr/references/catalog.md +2 -0
  145. package/skills/ngwr/references/setup.md +4 -0
  146. package/stepper/styles/_index.scss +19 -1
  147. package/textarea/styles/_index.scss +14 -6
  148. package/toast/styles/_index.scss +12 -4
  149. package/types/ngwr-alert.d.ts +3 -3
  150. package/types/ngwr-blur-text.d.ts +8 -0
  151. package/types/ngwr-breadcrumbs.d.ts +1 -0
  152. package/types/ngwr-button.d.ts +2 -2
  153. package/types/ngwr-calendar-testing.d.ts +21 -4
  154. package/types/ngwr-calendar.d.ts +37 -0
  155. package/types/ngwr-circular-text.d.ts +9 -0
  156. package/types/ngwr-collapse.d.ts +19 -15
  157. package/types/ngwr-color-picker-testing.d.ts +10 -4
  158. package/types/ngwr-color-picker.d.ts +70 -2
  159. package/types/ngwr-command-palette.d.ts +14 -0
  160. package/types/ngwr-context-menu.d.ts +1 -1
  161. package/types/ngwr-date-adapters-fns.d.ts +71 -29
  162. package/types/ngwr-date-picker-testing.d.ts +206 -185
  163. package/types/ngwr-date-picker.d.ts +7 -0
  164. package/types/ngwr-date.d.ts +143 -13
  165. package/types/ngwr-drag-drop.d.ts +19 -0
  166. package/types/ngwr-dropdown.d.ts +1 -1
  167. package/types/ngwr-event-calendar-testing.d.ts +12 -9
  168. package/types/ngwr-file-upload.d.ts +31 -3
  169. package/types/ngwr-fuzzy-text.d.ts +5 -0
  170. package/types/ngwr-i18n.d.ts +89 -7
  171. package/types/ngwr-icon-adapters-feather.d.ts +11 -0
  172. package/types/ngwr-icon.d.ts +42 -1
  173. package/types/ngwr-input-number.d.ts +1 -1
  174. package/types/ngwr-input-otp.d.ts +3 -0
  175. package/types/ngwr-input.d.ts +11 -4
  176. package/types/ngwr-knob-testing.d.ts +8 -4
  177. package/types/ngwr-knob.d.ts +18 -0
  178. package/types/ngwr-loading-bar-router.d.ts +35 -0
  179. package/types/ngwr-loading-bar.d.ts +19 -9
  180. package/types/ngwr-mention.d.ts +13 -0
  181. package/types/ngwr-pagination-testing.d.ts +11 -10
  182. package/types/ngwr-pagination.d.ts +39 -10
  183. package/types/ngwr-popconfirm.d.ts +1 -1
  184. package/types/ngwr-popover.d.ts +1 -1
  185. package/types/ngwr-select.d.ts +65 -10
  186. package/types/ngwr-shiny-text.d.ts +1 -0
  187. package/types/ngwr-splitter-testing.d.ts +9 -4
  188. package/types/ngwr-squircle.d.ts +1 -1
  189. package/types/ngwr-statistic.d.ts +22 -2
  190. package/types/ngwr-table.d.ts +24 -5
  191. package/types/ngwr-tabs-router.d.ts +67 -0
  192. package/types/ngwr-tabs-testing.d.ts +12 -10
  193. package/types/ngwr-tabs.d.ts +103 -6
  194. package/types/ngwr-textarea.d.ts +9 -0
  195. package/types/ngwr-tree.d.ts +7 -0
  196. package/types/ngwr-utils.d.ts +79 -1
  197. package/types/ngwr-version.d.ts +1 -1
  198. package/types/ngwr-window-testing.d.ts +11 -3
  199. package/types/ngwr-window.d.ts +11 -4
  200. package/window/styles/_index.scss +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-input.mjs","sources":["../../../projects/lib/input/directives/wr-input.ts","../../../projects/lib/input/directives/wr-input-prefix.ts","../../../projects/lib/input/directives/wr-input-suffix.ts","../../../projects/lib/input/input-group.ts","../../../projects/lib/input/password-toggle.ts","../../../projects/lib/input/password-toggle.html","../../../projects/lib/input/ngwr-input.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 { type BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Directive, ElementRef, computed, inject, input } from '@angular/core';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { WR_FORM_FIELD } from 'ngwr/form';\n\nimport type { WrInputSize } from '../interfaces';\n\n/**\n * Applies NGWR input styling to a native `<input>` element.\n *\n * Because this is an attribute directive on the real `<input>` (not a wrapper\n * component), any other directive that targets `input` — `[(ngModel)]`,\n * `[formControl]`, validators, third-party libraries like `ngx-mask` — composes\n * naturally on the same element.\n *\n * @example\n * ```html\n * <input wrInput [(ngModel)]=\"name\" placeholder=\"Your name\" />\n *\n * <!-- Works with ngx-mask, validators, etc. -->\n * <input wrInput [(ngModel)]=\"phone\" mask=\"(000) 000-0000\" />\n * ```\n *\n * For prefix / suffix / password-toggle layouts, wrap the input in\n * `<wr-input-group>`.\n *\n * @see https://ngwr.dev/reference/components/input\n */\n@Directive({\n selector: 'input[wrInput], textarea[wrInput]',\n host: {\n '[class]': 'classes()',\n '[attr.id]': 'resolvedId()',\n '[attr.aria-invalid]': 'ariaInvalid()',\n '[attr.aria-describedby]': 'describedBy()',\n },\n})\nexport class WrInput {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly field = inject(WR_FORM_FIELD, { optional: true });\n\n /**\n * Read once, at construction: a static `id=\"\"` in the template is already on\n * the element by the time a directive is instantiated, and it must win over\n * the field's generated one.\n */\n private readonly ownId = this.host.nativeElement.getAttribute('id');\n\n /**\n * Adopts the surrounding `<wr-form-field>`'s `controlId`, so the label it\n * already rendered actually points at this element. Without this the `for`\n * referenced an id that existed nowhere and the input had no label at all.\n */\n protected readonly resolvedId = computed(() => this.ownId ?? this.field?.controlId() ?? null);\n\n constructor() {\n // Tell the field which id actually stuck. Without this its `<label for>` keeps\n // pointing at the generated id while this element answers to the author's own —\n // a label referencing nothing, which is the same as having none.\n if (this.ownId) this.field?.adoptControlId(this.ownId);\n }\n\n /**\n * Announced state, taken from the surrounding `<wr-form-field>`.\n *\n * The field renders the message but cannot reach the projected control, so\n * the control reads back: without this the error is visible on screen and\n * invisible to a screen reader, which never learns the field is invalid or\n * what the message says.\n */\n protected readonly ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null));\n protected readonly describedBy = computed(() => this.field?.describedBy() ?? null);\n\n /**\n * Control size. Named `wrSize` (not `size`) so it never clashes with the\n * native `<input size>` attribute. Unset, it falls back to\n * `provideWrConfig({ input: { size } })`. @default 'md'\n */\n readonly wrSize = input<WrInputSize | null>(null);\n\n /**\n * Pill-shaped corners. Unset, it falls back to\n * `provideWrConfig({ input: { rounded } })`; `[rounded]=\"false\"` turns a\n * configured `true` back off. @default false\n */\n readonly rounded = input<boolean | null, BooleanInput>(null, {\n // Null-preserving: the plain `coerceBooleanProperty` folds `null` into `false`,\n // which would make \"not set\" and \"set to false\" the same value — and a config\n // default nothing could ever supply. A valueless `rounded` attribute still\n // arrives as `''` and still coerces to `true`.\n transform: (v: BooleanInput): boolean | null => (v == null ? null : coerceBooleanProperty(v)),\n });\n\n // `null` as each input's own default, not `'md'` / `false`: with a literal sitting\n // there, \"the template said nothing\" and \"the template said md\" are the same value,\n // so no app-wide default could ever apply. The effective default is the last\n // argument below, which is where the old inline one moved to.\n protected readonly resolvedSize = useConfigValue(this.wrSize, c => c.input?.size, 'md');\n protected readonly resolvedRounded = useConfigValue(this.rounded, c => c.input?.rounded, false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input'];\n const size = this.resolvedSize();\n if (size !== 'md') parts.push(`wr-input--${size}`);\n if (this.resolvedRounded()) parts.push('wr-input--rounded');\n return parts.join(' ');\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 { Directive } from '@angular/core';\n\n/**\n * Marks an element as the **left** decoration inside `<wr-input-group>`.\n * Adds the `.wr-input-group__affix` class so the group's layout flexes it\n * to the input's left edge.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <span wrInputPrefix>$</span>\n * <input wrInput [(ngModel)]=\"amount\" type=\"number\" />\n * </wr-input-group>\n * ```\n */\n@Directive({\n selector: '[wrInputPrefix]',\n host: { class: 'wr-input-group__affix wr-input-group__affix--prefix' },\n})\nexport class WrInputPrefix {}\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 { Directive } from '@angular/core';\n\n/**\n * Marks an element as the **right** decoration inside `<wr-input-group>`.\n * Adds the `.wr-input-group__affix` class so the group's layout flexes it\n * to the input's right edge.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <input wrInput [(ngModel)]=\"amount\" type=\"number\" />\n * <span wrInputSuffix>USD</span>\n * </wr-input-group>\n * ```\n */\n@Directive({\n selector: '[wrInputSuffix]',\n host: { class: 'wr-input-group__affix wr-input-group__affix--suffix' },\n})\nexport class WrInputSuffix {}\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 { type BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input } from '@angular/core';\n\nimport { useConfigValue } from 'ngwr/config';\n\n/**\n * Container for `<input wrInput>` + `[wrInputPrefix]` / `[wrInputSuffix]` /\n * `<wr-password-toggle>` siblings. Renders the border and focus ring; the\n * inner native input drops its own border so it visually melts into the\n * group.\n *\n * Pure layout — no signals shared with the inner directive, focus styling is\n * driven entirely by `:focus-within` so any focusable child counts.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <span wrInputPrefix>$</span>\n * <input wrInput [(ngModel)]=\"amount\" type=\"number\" />\n * <span wrInputSuffix>USD</span>\n * </wr-input-group>\n * ```\n *\n * @see https://ngwr.dev/reference/components/input\n */\n@Component({\n selector: 'wr-input-group',\n template: '<ng-content />',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrInputGroup {\n /**\n * Pill-shaped corners. Unset, it falls back to\n * `provideWrConfig({ input: { rounded } })` — the same key the inner `[wrInput]`\n * reads. @default false\n */\n readonly rounded = input<boolean | null, BooleanInput>(null, {\n // Null-preserving, like `[wrInput]`'s: the plain `coerceBooleanProperty` folds\n // `null` into `false`, which would make \"not set\" and \"set to false\" the same\n // value — and a config default nothing could ever supply.\n transform: (v: BooleanInput): boolean | null => (v == null ? null : coerceBooleanProperty(v)),\n });\n\n // `input.rounded`, and deliberately not a key of its own: the group owns the visible\n // border once it wraps an input, which drops its own chrome. Resolving one and not\n // the other would put a pill radius on the element nobody can see and leave the\n // corner the user does see square.\n protected readonly resolvedRounded = useConfigValue(this.rounded, c => c.input?.rounded, false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input-group'];\n if (this.resolvedRounded()) parts.push('wr-input-group--rounded');\n return parts.join(' ');\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, computed, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\n\n/**\n * Self-contained eye-toggle button. Flips the linked `<input>`'s `type`\n * attribute between `password` and `text`. Drop it inside an\n * `<wr-input-group>` next to a password input.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <input wrInput type=\"password\" [(ngModel)]=\"pw\" #pwInput />\n * <wr-password-toggle [for]=\"pwInput\" />\n * </wr-input-group>\n * ```\n */\n@Component({\n selector: 'wr-password-toggle',\n templateUrl: './password-toggle.html',\n encapsulation: ViewEncapsulation.None,\n})\nexport class WrPasswordToggle {\n /** Reference to the linked password input. */\n readonly for = input.required<HTMLInputElement>();\n\n protected readonly revealed = signal(false);\n\n /** Accessible name while the password is hidden. Falls back to `input.showPassword`. */\n readonly showLabel = input<string | null>(null);\n\n /** Accessible name while the password is revealed. Falls back to `input.hidePassword`. */\n readonly hideLabel = input<string | null>(null);\n\n private readonly resolvedShowLabel = useI18nText(this.showLabel, 'input.showPassword', 'Show password');\n private readonly resolvedHideLabel = useI18nText(this.hideLabel, 'input.hidePassword', 'Hide password');\n\n protected readonly ariaLabel = computed(() =>\n this.revealed() ? this.resolvedHideLabel() : this.resolvedShowLabel()\n );\n\n protected toggle(): void {\n const input = this.for();\n const next = input.type === 'password' ? 'text' : 'password';\n input.type = next;\n this.revealed.set(next === 'text');\n }\n}\n","<button\n type=\"button\"\n class=\"wr-input-group__toggle\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-pressed]=\"revealed()\"\n (click)=\"toggle()\"\n>\n @if (revealed()) {\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\" />\n <path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\" />\n <path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\" />\n <path d=\"m2 2 20 20\" />\n </svg>\n } @else {\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n }\n</button>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAAA;;;;;AAKG;AAUH;;;;;;;;;;;;;;;;;;;;AAoBG;MAUU,OAAO,CAAA;AACD,IAAA,IAAI,GAAG,MAAM,CAA0B,UAAU,CAAC;IAClD,KAAK,GAAG,MAAM,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAElE;;;;AAIG;IACc,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC;AAEnE;;;;AAIG;AACgB,IAAA,UAAU,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI;mFAAC;AAE7F,IAAA,WAAA,GAAA;;;;QAIE,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;IACxD;AAEA;;;;;;;AAOG;IACgB,WAAW,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC;oFAAC;AACzE,IAAA,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,IAAI;oFAAC;AAElF;;;;AAIG;IACM,MAAM,GAAG,KAAK,CAAqB,IAAI;+EAAC;AAEjD;;;;AAIG;IACM,OAAO,GAAG,KAAK,CAA+B,IAAI,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,SAAA,EAAA,8BAAA,EAAA,CAAA;;;;;QAKzD,SAAS,EAAE,CAAC,CAAe,MAAsB,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC,EAAA,CAC7F;;;;;AAMiB,IAAA,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC;AACpE,IAAA,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC;AAE5E,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;AACzC,QAAA,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC;AAC1B,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;QAChC,IAAI,IAAI,KAAK,IAAI;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,aAAa,IAAI,CAAA,CAAE,CAAC;QAClD,IAAI,IAAI,CAAC,eAAe,EAAE;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC;AAC3D,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,CAAC;gFAAC;uGArES,OAAO,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAP,OAAO,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,mCAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,IAAA,EAAA,EAAA,UAAA,EAAA,EAAA,OAAA,EAAA,WAAA,EAAA,SAAA,EAAA,cAAA,EAAA,mBAAA,EAAA,eAAA,EAAA,uBAAA,EAAA,eAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAP,OAAO,EAAA,UAAA,EAAA,CAAA;kBATnB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,mCAAmC;AAC7C,oBAAA,IAAI,EAAE;AACJ,wBAAA,SAAS,EAAE,WAAW;AACtB,wBAAA,WAAW,EAAE,cAAc;AAC3B,wBAAA,qBAAqB,EAAE,eAAe;AACtC,wBAAA,yBAAyB,EAAE,eAAe;AAC3C,qBAAA;AACF,iBAAA;;;AC5CD;;;;;AAKG;AAIH;;;;;;;;;;;;AAYG;MAKU,aAAa,CAAA;uGAAb,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAb,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,qDAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBAJzB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iBAAiB;AAC3B,oBAAA,IAAI,EAAE,EAAE,KAAK,EAAE,qDAAqD,EAAE;AACvE,iBAAA;;;ACzBD;;;;;AAKG;AAIH;;;;;;;;;;;;AAYG;MAKU,aAAa,CAAA;uGAAb,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAb,aAAa,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,cAAA,EAAA,qDAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,CAAA;;2FAAb,aAAa,EAAA,UAAA,EAAA,CAAA;kBAJzB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,iBAAiB;AAC3B,oBAAA,IAAI,EAAE,EAAE,KAAK,EAAE,qDAAqD,EAAE;AACvE,iBAAA;;;ACzBD;;;;;AAKG;AAOH;;;;;;;;;;;;;;;;;;;AAmBG;MAOU,YAAY,CAAA;AACvB;;;;AAIG;IACM,OAAO,GAAG,KAAK,CAA+B,IAAI,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,SAAA,EAAA,8BAAA,EAAA,CAAA;;;;QAIzD,SAAS,EAAE,CAAC,CAAe,MAAsB,CAAC,IAAI,IAAI,GAAG,IAAI,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAAC,EAAA,CAC7F;;;;;AAMiB,IAAA,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC;AAE5E,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;AACzC,QAAA,MAAM,KAAK,GAAG,CAAC,gBAAgB,CAAC;QAChC,IAAI,IAAI,CAAC,eAAe,EAAE;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC;AACjE,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,CAAC;gFAAC;uGAvBS,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,oQAJb,gBAAgB,EAAA,QAAA,EAAA,IAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAIf,YAAY,EAAA,UAAA,EAAA,CAAA;kBANxB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,gBAAgB;AAC1B,oBAAA,QAAQ,EAAE,gBAAgB;oBAC1B,aAAa,EAAE,iBAAiB,CAAC,IAAI;AACrC,oBAAA,IAAI,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE;AACjC,iBAAA;;;ACrCD;;;;;AAKG;AAMH;;;;;;;;;;;;AAYG;MAMU,gBAAgB,CAAA;;IAElB,GAAG,GAAG,KAAK,CAAC,QAAQ;4EAAoB;IAE9B,QAAQ,GAAG,MAAM,CAAC,KAAK;iFAAC;;IAGlC,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;;IAGtC,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;IAE9B,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,EAAE,eAAe,CAAC;IACtF,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,EAAE,eAAe,CAAC;IAEpF,SAAS,GAAG,QAAQ,CAAC,MACtC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,iBAAiB,EAAE,GAAG,IAAI,CAAC,iBAAiB,EAAE;kFACtE;IAES,MAAM,GAAA;AACd,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE;AACxB,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,UAAU,GAAG,MAAM,GAAG,UAAU;AAC5D,QAAA,KAAK,CAAC,IAAI,GAAG,IAAI;QACjB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;IACpC;uGAxBW,gBAAgB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAhB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,gBAAgB,mdC7B7B,uyCAyCA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FDZa,gBAAgB,EAAA,UAAA,EAAA,CAAA;kBAL5B,SAAS;+BACE,oBAAoB,EAAA,aAAA,EAEf,iBAAiB,CAAC,IAAI,EAAA,QAAA,EAAA,uyCAAA,EAAA;;;AE3BvC;;AAEG;;;;"}
1
+ {"version":3,"file":"ngwr-input.mjs","sources":["../../../projects/lib/input/directives/wr-input.ts","../../../projects/lib/input/directives/wr-input-prefix.ts","../../../projects/lib/input/directives/wr-input-suffix.ts","../../../projects/lib/input/input-group.ts","../../../projects/lib/input/password-toggle.ts","../../../projects/lib/input/password-toggle.html","../../../projects/lib/input/ngwr-input.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 { type BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Directive, ElementRef, computed, inject, input } from '@angular/core';\n\nimport { useConfigValue } from 'ngwr/config';\nimport { WR_FORM_FIELD } from 'ngwr/form';\n\nimport type { WrInputSize } from '../interfaces';\n\n/**\n * Applies NGWR input styling to a native `<input>` element.\n *\n * Because this is an attribute directive on the real `<input>` (not a wrapper\n * component), any other directive that targets `input` — `[(ngModel)]`,\n * `[formControl]`, validators, third-party libraries like `ngx-mask` — composes\n * naturally on the same element.\n *\n * @example\n * ```html\n * <input wrInput [(ngModel)]=\"name\" placeholder=\"Your name\" />\n *\n * <!-- Works with ngx-mask, validators, etc. -->\n * <input wrInput [(ngModel)]=\"phone\" mask=\"(000) 000-0000\" />\n * ```\n *\n * For prefix / suffix / password-toggle layouts, wrap the input in\n * `<wr-input-group>`.\n *\n * @see https://ngwr.dev/reference/components/input\n */\n@Directive({\n selector: 'input[wrInput], textarea[wrInput]',\n host: {\n '[class]': 'classes()',\n '[attr.id]': 'resolvedId()',\n '[attr.aria-invalid]': 'ariaInvalid()',\n '[attr.aria-describedby]': 'describedBy()',\n },\n})\nexport class WrInput {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly field = inject(WR_FORM_FIELD, { optional: true });\n\n /**\n * Read once, at construction: a static `id=\"\"` in the template is already on\n * the element by the time a directive is instantiated, and it must win over\n * the field's generated one.\n */\n private readonly ownId = this.host.nativeElement.getAttribute('id');\n\n /**\n * Adopts the surrounding `<wr-form-field>`'s `controlId`, so the label it\n * already rendered actually points at this element. Without this the `for`\n * referenced an id that existed nowhere and the input had no label at all.\n */\n protected readonly resolvedId = computed(() => this.ownId ?? this.field?.controlId() ?? null);\n\n constructor() {\n // Tell the field which id actually stuck. Without this its `<label for>` keeps\n // pointing at the generated id while this element answers to the author's own —\n // a label referencing nothing, which is the same as having none.\n if (this.ownId) this.field?.adoptControlId(this.ownId);\n }\n\n /**\n * Announced state, taken from the surrounding `<wr-form-field>`.\n *\n * The field renders the message but cannot reach the projected control, so\n * the control reads back: without this the error is visible on screen and\n * invisible to a screen reader, which never learns the field is invalid or\n * what the message says.\n */\n protected readonly ariaInvalid = computed(() => (this.field?.describedBy() ? 'true' : null));\n protected readonly describedBy = computed(() => this.field?.describedBy() ?? null);\n\n /**\n * Control size. Unset, it falls back to\n * `provideWrConfig({ input: { size } })`. @default 'md'\n *\n * Called `wrSize` until v14, to dodge the native `<input size>` attribute —\n * which measures the field in characters and which `.wr-input`'s `width: 100%`\n * has always overridden, so the clash it guarded against was never observable.\n * It made this the one size input in the catalog spelled differently from the\n * other twenty-three. The cost of the change is that a consumer who wanted the\n * NATIVE `size` on an ngwr input can no longer have it — but `width: 100%`\n * meant they never did.\n */\n readonly size = input<WrInputSize | null>(null);\n\n /**\n * Pill-shaped corners. Unset, it falls back to\n * `provideWrConfig({ input: { rounded } })`; `[rounded]=\"false\"` turns a\n * configured `true` back off. @default false\n */\n readonly rounded = input<boolean | null, BooleanInput>(null, {\n // Null-preserving: the plain `coerceBooleanProperty` folds `null` into `false`,\n // which would make \"not set\" and \"set to false\" the same value — and a config\n // default nothing could ever supply. A valueless `rounded` attribute still\n // arrives as `''` and still coerces to `true`.\n transform: (v: BooleanInput): boolean | null => (v == null ? null : coerceBooleanProperty(v)),\n });\n\n // `null` as each input's own default, not `'md'` / `false`: with a literal sitting\n // there, \"the template said nothing\" and \"the template said md\" are the same value,\n // so no app-wide default could ever apply. The effective default is the last\n // argument below, which is where the old inline one moved to.\n protected readonly resolvedSize = useConfigValue(this.size, c => c.input?.size, 'md');\n protected readonly resolvedRounded = useConfigValue(this.rounded, c => c.input?.rounded, false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input'];\n const size = this.resolvedSize();\n if (size !== 'md') parts.push(`wr-input--${size}`);\n if (this.resolvedRounded()) parts.push('wr-input--rounded');\n return parts.join(' ');\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 { Directive } from '@angular/core';\n\n/**\n * Marks an element as the **left** decoration inside `<wr-input-group>`.\n * Adds the `.wr-input-group__affix` class so the group's layout flexes it\n * to the input's left edge.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <span wrInputPrefix>$</span>\n * <input wrInput [(ngModel)]=\"amount\" type=\"number\" />\n * </wr-input-group>\n * ```\n */\n@Directive({\n selector: '[wrInputPrefix]',\n host: { class: 'wr-input-group__affix wr-input-group__affix--prefix' },\n})\nexport class WrInputPrefix {}\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 { Directive } from '@angular/core';\n\n/**\n * Marks an element as the **right** decoration inside `<wr-input-group>`.\n * Adds the `.wr-input-group__affix` class so the group's layout flexes it\n * to the input's right edge.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <input wrInput [(ngModel)]=\"amount\" type=\"number\" />\n * <span wrInputSuffix>USD</span>\n * </wr-input-group>\n * ```\n */\n@Directive({\n selector: '[wrInputSuffix]',\n host: { class: 'wr-input-group__affix wr-input-group__affix--suffix' },\n})\nexport class WrInputSuffix {}\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 { type BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input } from '@angular/core';\n\nimport { useConfigValue } from 'ngwr/config';\n\n/**\n * Container for `<input wrInput>` + `[wrInputPrefix]` / `[wrInputSuffix]` /\n * `<wr-password-toggle>` siblings. Renders the border and focus ring; the\n * inner native input drops its own border so it visually melts into the\n * group.\n *\n * Pure layout — no signals shared with the inner directive, focus styling is\n * driven entirely by `:focus-within` so any focusable child counts.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <span wrInputPrefix>$</span>\n * <input wrInput [(ngModel)]=\"amount\" type=\"number\" />\n * <span wrInputSuffix>USD</span>\n * </wr-input-group>\n * ```\n *\n * @see https://ngwr.dev/reference/components/input\n */\n@Component({\n selector: 'wr-input-group',\n template: '<ng-content />',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrInputGroup {\n /**\n * Pill-shaped corners. Unset, it falls back to\n * `provideWrConfig({ input: { rounded } })` — the same key the inner `[wrInput]`\n * reads. @default false\n */\n readonly rounded = input<boolean | null, BooleanInput>(null, {\n // Null-preserving, like `[wrInput]`'s: the plain `coerceBooleanProperty` folds\n // `null` into `false`, which would make \"not set\" and \"set to false\" the same\n // value — and a config default nothing could ever supply.\n transform: (v: BooleanInput): boolean | null => (v == null ? null : coerceBooleanProperty(v)),\n });\n\n // `input.rounded`, and deliberately not a key of its own: the group owns the visible\n // border once it wraps an input, which drops its own chrome. Resolving one and not\n // the other would put a pill radius on the element nobody can see and leave the\n // corner the user does see square.\n protected readonly resolvedRounded = useConfigValue(this.rounded, c => c.input?.rounded, false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-input-group'];\n if (this.resolvedRounded()) parts.push('wr-input-group--rounded');\n return parts.join(' ');\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, computed, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\n\n/**\n * Self-contained eye-toggle button. Flips the linked `<input>`'s `type`\n * attribute between `password` and `text`. Drop it inside an\n * `<wr-input-group>` next to a password input.\n *\n * @example\n * ```html\n * <wr-input-group>\n * <input wrInput type=\"password\" [(ngModel)]=\"pw\" #pwInput />\n * <wr-password-toggle [for]=\"pwInput\" />\n * </wr-input-group>\n * ```\n */\n@Component({\n selector: 'wr-password-toggle',\n templateUrl: './password-toggle.html',\n encapsulation: ViewEncapsulation.None,\n})\nexport class WrPasswordToggle {\n /** Reference to the linked password input. */\n readonly for = input.required<HTMLInputElement>();\n\n protected readonly revealed = signal(false);\n\n /** Accessible name while the password is hidden. Falls back to `input.showPassword`. */\n readonly showLabel = input<string | null>(null);\n\n /** Accessible name while the password is revealed. Falls back to `input.hidePassword`. */\n readonly hideLabel = input<string | null>(null);\n\n private readonly resolvedShowLabel = useI18nText(this.showLabel, 'input.showPassword', 'Show password');\n private readonly resolvedHideLabel = useI18nText(this.hideLabel, 'input.hidePassword', 'Hide password');\n\n protected readonly ariaLabel = computed(() =>\n this.revealed() ? this.resolvedHideLabel() : this.resolvedShowLabel()\n );\n\n protected toggle(): void {\n const input = this.for();\n const next = input.type === 'password' ? 'text' : 'password';\n input.type = next;\n this.revealed.set(next === 'text');\n }\n}\n","<button\n type=\"button\"\n class=\"wr-input-group__toggle\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-pressed]=\"revealed()\"\n (click)=\"toggle()\"\n>\n @if (revealed()) {\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\" />\n <path d=\"M14.084 14.158a3 3 0 0 1-4.242-4.242\" />\n <path d=\"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\" />\n <path d=\"m2 2 20 20\" />\n </svg>\n } @else {\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" />\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n </svg>\n }\n</button>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-knob-testing.mjs","sources":["../../../projects/lib/knob/testing/wr-knob-harness.ts","../../../projects/lib/knob/testing/ngwr-knob-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, TestKey } from '@angular/cdk/testing';\n\nimport type { WrKnobHarnessFilters } from './interfaces';\n\n/** The two arrow keys that raise the value, and the two that lower it. */\ntype Arrow = 'left' | 'right' | 'up' | 'down';\n\nconst ARROWS: Record<Arrow, TestKey> = {\n left: TestKey.LEFT_ARROW,\n right: TestKey.RIGHT_ARROW,\n up: TestKey.UP_ARROW,\n down: TestKey.DOWN_ARROW,\n};\n\n/** Enough presses to cross any sane range; a stall throws long before this. */\nconst WALK_LIMIT = 400;\n\n/**\n * Test harness for `<wr-knob>` — the radial dial.\n *\n * **Driven by the keyboard, and the drag is not merely unsupported.** Turning the\n * dial with a pointer takes `getBoundingClientRect()`, measures the angle from the\n * centre to the cursor, and maps it onto a 270° arc. jsdom lays nothing out, so the\n * centre is `(0, 0)` and every synthetic gesture writes the same corner of the arc\n * whatever coordinates it is given — a `turnTo()` built on one would pass while\n * proving nothing. The arrows reach every value on the grid and are the accessible\n * path anyway.\n *\n * **Two questions about the value, not one.** {@link getValue} is the number\n * `aria-valuenow` carries; {@link getDisplayValue} is the string in the middle of the\n * dial, which the `suffix` input can make `\"50%\"` while the announced value stays\n * `50`. They are separate methods because they can disagree, and because\n * `showValue` can remove the text entirely without touching the value.\n *\n * @example\n * ```ts\n * const knob = await loader.getHarness(WrKnobHarness);\n *\n * await knob.setValue(60);\n * expect(await knob.getValue()).toBe(60);\n * expect(await knob.getDisplayValue()).toBe('60%');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrKnobHarness extends ComponentHarness {\n static hostSelector = 'wr-knob';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrKnobHarnessFilters = {}): HarnessPredicate<WrKnobHarness> {\n return new HarnessPredicate(WrKnobHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('value', options.value, async (harness, value) => (await harness.getValue()) === value)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('readonly', options.readonly, async (harness, readonly) => (await harness.isReadonly()) === readonly);\n }\n\n private readonly surface = this.locatorFor('.wr-knob__surface');\n\n /** The value the dial announces, from `aria-valuenow`. */\n async getValue(): Promise<number> {\n return this.numeric('aria-valuenow');\n }\n\n /** The bottom of the range, from `aria-valuemin`. */\n async getMin(): Promise<number> {\n return this.numeric('aria-valuemin');\n }\n\n /** The top of the range, from `aria-valuemax`. */\n async getMax(): Promise<number> {\n return this.numeric('aria-valuemax');\n }\n\n /**\n * The text printed in the middle of the dial, suffix included, or `null` when the\n * knob was built with `[showValue]=\"false\"`.\n *\n * Not the same question as {@link getValue}: with a `suffix` the two disagree by\n * design, and a screen reader is told the bare number — the component publishes no\n * `aria-valuetext`, so `50` is what gets announced for a dial that reads `50%`.\n */\n async getDisplayValue(): Promise<string | null> {\n const text = await this.locatorForOptional('.wr-knob__text')();\n return text ? text.text() : null;\n }\n\n /** The suffix alone, or `null` when there is none (or no value text at all). */\n async getSuffix(): Promise<string | null> {\n const suffix = await this.locatorForOptional('.wr-knob__suffix')();\n if (!suffix) return null;\n const text = await suffix.text();\n return text === '' ? null : text;\n }\n\n /** The dial's accessible name — `role=\"slider\"` needs one. */\n async getLabel(): Promise<string | null> {\n return (await this.surface()).getAttribute('aria-label');\n }\n\n /** Whether the dial is disabled, from `aria-disabled`. */\n async isDisabled(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-disabled')) === 'true';\n }\n\n /** Whether the dial is read-only, from `aria-readonly`. */\n async isReadonly(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-readonly')) === 'true';\n }\n\n /**\n * Whether the dial is a tab stop.\n *\n * The component drops it out of the tab order for BOTH disabled and read-only,\n * which is worth knowing: a read-only slider that a keyboard user cannot reach is\n * also one they cannot read, and this is the method that says so.\n */\n async isFocusable(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('tabindex')) === '0';\n }\n\n /** Move keyboard focus to the dial. */\n async focus(): Promise<void> {\n return (await this.surface()).focus();\n }\n\n /** Whether the dial currently holds focus. */\n async isFocused(): Promise<boolean> {\n return (await this.surface()).isFocused();\n }\n\n /** Blur the dial — which is what emits `touch` for a knob moved with the keyboard. */\n async blur(): Promise<void> {\n return (await this.surface()).blur();\n }\n\n /**\n * Press one arrow key.\n *\n * Right and Up raise the value, Left and Down lower it — all four are live on this\n * component, which has one axis and no mirroring to do. `shift` multiplies the step\n * by ten.\n */\n async pressArrow(arrow: Arrow, options: { shift?: boolean } = {}): Promise<void> {\n const surface = await this.surface();\n await (options.shift ? surface.sendKeys({ shift: true }, ARROWS[arrow]) : surface.sendKeys(ARROWS[arrow]));\n }\n\n /** Press Home — the bottom of the range. */\n async pressHome(): Promise<void> {\n return (await this.surface()).sendKeys(TestKey.HOME);\n }\n\n /**\n * Press End — the top of the range, or the last value ON the grid below it: a range\n * that is not a whole number of steps cannot reach its own maximum.\n */\n async pressEnd(): Promise<void> {\n return (await this.surface()).sendKeys(TestKey.END);\n }\n\n /**\n * Walk the dial to a value with the arrow keys, and assert it landed there.\n *\n * It measures the step as it goes rather than being told it — `step` is an input\n * with no DOM presence, and the value can arrive off the grid, since an external\n * write is clamped but never snapped. So the first press may move by less than a\n * step (it snaps onto the grid) and the walk adapts, which also means the ten-step\n * hops only start once a real step has been seen.\n *\n * Throws rather than half-arriving, and the two reasons read differently: a target\n * outside `[min, max]` is refused before anything moves, while a target BETWEEN two\n * grid points is only discovered by overshooting it, and says where it landed. A\n * dial that will not move at all — disabled, read-only — is the third.\n */\n async setValue(target: number): Promise<void> {\n const [min, max] = [await this.getMin(), await this.getMax()];\n if (target < min || target > max) {\n throw new Error(\n `WrKnobHarness.setValue(${target}): out of range — this dial accepts ${min}–${max}. The component ` +\n 'clamps, so the walk would stop at the bound and never reach the number asked for.'\n );\n }\n\n if (target === min) return this.pressHome();\n if (target === max) return this.pressEnd();\n\n await this.focus();\n let step: number | null = null;\n\n for (let guard = 0; guard < WALK_LIMIT; guard++) {\n const current = await this.getValue();\n if (current === target) return;\n\n const forward = target > current;\n const coarse = step !== null && Math.abs(target - current) >= step * 10;\n\n await this.pressArrow(forward ? 'right' : 'left', { shift: coarse });\n const now = await this.getValue();\n\n if (now === current) {\n throw new Error(\n `WrKnobHarness.setValue(${target}): the dial stopped moving at ${now}. A disabled or read-only ` +\n 'knob ignores its arrows, which is the usual reason.'\n );\n }\n if (!coarse) step = Math.abs(now - current);\n if (now === target) return;\n\n if (forward ? now > target : now < target) {\n throw new Error(\n `WrKnobHarness.setValue(${target}): the walk stepped from ${current} to ${now}, straight past the ` +\n 'target — it is not on the step grid, which the component measures from `min` rather than from zero.'\n );\n }\n }\n\n throw new Error(\n `WrKnobHarness.setValue(${target}): gave up after ${WALK_LIMIT} presses at ${await this.getValue()}.`\n );\n }\n\n /**\n * Where the handle dot sits, in the SVG's own 100×100 viewBox units.\n *\n * The dial's whole visual state is an arc path and this dot, and neither can be\n * measured in a unit test — but the dot's centre is written as plain `cx` / `cy`\n * attributes, so it reads with no layout at all. That makes it the evidence that\n * the drawing followed the value: at `min` the handle is at the 7 o'clock end of\n * the arc, at `max` the 5 o'clock end.\n */\n async getHandlePosition(): Promise<{ x: number; y: number }> {\n const handle = await this.locatorFor('.wr-knob__handle')();\n return {\n x: Number(await handle.getAttribute('cx')),\n y: Number(await handle.getAttribute('cy')),\n };\n }\n\n private async numeric(attribute: string): Promise<number> {\n const raw = await (await this.surface()).getAttribute(attribute);\n return raw === null ? Number.NaN : Number(raw);\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-knob-testing.mjs","sources":["../../../projects/lib/knob/testing/wr-knob-harness.ts","../../../projects/lib/knob/testing/ngwr-knob-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, TestKey } from '@angular/cdk/testing';\n\nimport type { WrKnobArrowKey, WrKnobHarnessFilters } from './interfaces';\n\nconst ARROWS: Record<WrKnobArrowKey, TestKey> = {\n left: TestKey.LEFT_ARROW,\n right: TestKey.RIGHT_ARROW,\n up: TestKey.UP_ARROW,\n down: TestKey.DOWN_ARROW,\n};\n\n/** Enough presses to cross any sane range; a stall throws long before this. */\nconst WALK_LIMIT = 400;\n\n/**\n * Test harness for `<wr-knob>` — the radial dial.\n *\n * **Driven by the keyboard, and the drag is not merely unsupported.** Turning the\n * dial with a pointer takes `getBoundingClientRect()`, measures the angle from the\n * centre to the cursor, and maps it onto a 270° arc. jsdom lays nothing out, so the\n * centre is `(0, 0)` and every synthetic gesture writes the same corner of the arc\n * whatever coordinates it is given — a `turnTo()` built on one would pass while\n * proving nothing. The arrows reach every value on the grid and are the accessible\n * path anyway.\n *\n * **Two questions about the value, not one.** {@link getValue} is the number\n * `aria-valuenow` carries; {@link getDisplayValue} is the string in the middle of the\n * dial, which the `suffix` input can make `\"50%\"` while the announced value stays\n * `50`. They are separate methods because they can disagree, and because\n * `showValue` can remove the text entirely without touching the value.\n *\n * @example\n * ```ts\n * const knob = await loader.getHarness(WrKnobHarness);\n *\n * await knob.setValue(60);\n * expect(await knob.getValue()).toBe(60);\n * expect(await knob.getDisplayValue()).toBe('60%');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrKnobHarness extends ComponentHarness {\n static hostSelector = 'wr-knob';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrKnobHarnessFilters = {}): HarnessPredicate<WrKnobHarness> {\n return new HarnessPredicate(WrKnobHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('value', options.value, async (harness, value) => (await harness.getValue()) === value)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('readonly', options.readonly, async (harness, readonly) => (await harness.isReadonly()) === readonly);\n }\n\n private readonly surface = this.locatorFor('.wr-knob__surface');\n\n /** The value the dial announces, from `aria-valuenow`. */\n async getValue(): Promise<number> {\n return this.numeric('aria-valuenow');\n }\n\n /** The bottom of the range, from `aria-valuemin`. */\n async getMin(): Promise<number> {\n return this.numeric('aria-valuemin');\n }\n\n /** The top of the range, from `aria-valuemax`. */\n async getMax(): Promise<number> {\n return this.numeric('aria-valuemax');\n }\n\n /**\n * The text printed in the middle of the dial, suffix included, or `null` when the\n * knob was built with `[showValue]=\"false\"`.\n *\n * Not the same question as {@link getValue}: with a `suffix` the two disagree by\n * design, and a screen reader is told the bare number — the component publishes no\n * `aria-valuetext`, so `50` is what gets announced for a dial that reads `50%`.\n */\n async getDisplayValue(): Promise<string | null> {\n const text = await this.locatorForOptional('.wr-knob__text')();\n return text ? text.text() : null;\n }\n\n /** The suffix alone, or `null` when there is none (or no value text at all). */\n async getSuffix(): Promise<string | null> {\n const suffix = await this.locatorForOptional('.wr-knob__suffix')();\n if (!suffix) return null;\n const text = await suffix.text();\n return text === '' ? null : text;\n }\n\n /** The dial's accessible name — `role=\"slider\"` needs one. */\n async getLabel(): Promise<string | null> {\n return (await this.surface()).getAttribute('aria-label');\n }\n\n /** Whether the dial is disabled, from `aria-disabled`. */\n async isDisabled(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-disabled')) === 'true';\n }\n\n /** Whether the dial is read-only, from `aria-readonly`. */\n async isReadonly(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('aria-readonly')) === 'true';\n }\n\n /**\n * Whether the dial is a tab stop.\n *\n * The component drops it out of the tab order for BOTH disabled and read-only,\n * which is worth knowing: a read-only slider that a keyboard user cannot reach is\n * also one they cannot read, and this is the method that says so.\n */\n async isFocusable(): Promise<boolean> {\n return (await (await this.surface()).getAttribute('tabindex')) === '0';\n }\n\n /** Move keyboard focus to the dial. */\n async focus(): Promise<void> {\n return (await this.surface()).focus();\n }\n\n /** Whether the dial currently holds focus. */\n async isFocused(): Promise<boolean> {\n return (await this.surface()).isFocused();\n }\n\n /** Blur the dial — which is what emits `touch` for a knob moved with the keyboard. */\n async blur(): Promise<void> {\n return (await this.surface()).blur();\n }\n\n /**\n * Press one arrow key.\n *\n * Right and Up raise the value, Left and Down lower it — all four are live on this\n * component, which has one axis and no mirroring to do. `shift` multiplies the step\n * by ten.\n */\n async pressArrow(arrow: WrKnobArrowKey, options: { shift?: boolean } = {}): Promise<void> {\n const surface = await this.surface();\n await (options.shift ? surface.sendKeys({ shift: true }, ARROWS[arrow]) : surface.sendKeys(ARROWS[arrow]));\n }\n\n /** Press Home — the bottom of the range. */\n async pressHome(): Promise<void> {\n return (await this.surface()).sendKeys(TestKey.HOME);\n }\n\n /**\n * Press End — the top of the range, or the last value ON the grid below it: a range\n * that is not a whole number of steps cannot reach its own maximum.\n */\n async pressEnd(): Promise<void> {\n return (await this.surface()).sendKeys(TestKey.END);\n }\n\n /**\n * Walk the dial to a value with the arrow keys, and assert it landed there.\n *\n * It measures the step as it goes rather than being told it — `step` is an input\n * with no DOM presence, and the value can arrive off the grid, since an external\n * write is clamped but never snapped. So the first press may move by less than a\n * step (it snaps onto the grid) and the walk adapts, which also means the ten-step\n * hops only start once a real step has been seen.\n *\n * Throws rather than half-arriving, and the two reasons read differently: a target\n * outside `[min, max]` is refused before anything moves, while a target BETWEEN two\n * grid points is only discovered by overshooting it, and says where it landed. A\n * dial that will not move at all — disabled, read-only — is the third.\n */\n async setValue(target: number): Promise<void> {\n const [min, max] = [await this.getMin(), await this.getMax()];\n if (target < min || target > max) {\n throw new Error(\n `WrKnobHarness.setValue(${target}): out of range — this dial accepts ${min}–${max}. The component ` +\n 'clamps, so the walk would stop at the bound and never reach the number asked for.'\n );\n }\n\n if (target === min) return this.pressHome();\n if (target === max) return this.pressEnd();\n\n await this.focus();\n let step: number | null = null;\n\n for (let guard = 0; guard < WALK_LIMIT; guard++) {\n const current = await this.getValue();\n if (current === target) return;\n\n const forward = target > current;\n const coarse = step !== null && Math.abs(target - current) >= step * 10;\n\n await this.pressArrow(forward ? 'right' : 'left', { shift: coarse });\n const now = await this.getValue();\n\n if (now === current) {\n throw new Error(\n `WrKnobHarness.setValue(${target}): the dial stopped moving at ${now}. A disabled or read-only ` +\n 'knob ignores its arrows, which is the usual reason.'\n );\n }\n if (!coarse) step = Math.abs(now - current);\n if (now === target) return;\n\n if (forward ? now > target : now < target) {\n throw new Error(\n `WrKnobHarness.setValue(${target}): the walk stepped from ${current} to ${now}, straight past the ` +\n 'target — it is not on the step grid, which the component measures from `min` rather than from zero.'\n );\n }\n }\n\n throw new Error(\n `WrKnobHarness.setValue(${target}): gave up after ${WALK_LIMIT} presses at ${await this.getValue()}.`\n );\n }\n\n /**\n * Where the handle dot sits, in the SVG's own 100×100 viewBox units.\n *\n * The dial's whole visual state is an arc path and this dot, and neither can be\n * measured in a unit test — but the dot's centre is written as plain `cx` / `cy`\n * attributes, so it reads with no layout at all. That makes it the evidence that\n * the drawing followed the value: at `min` the handle is at the 7 o'clock end of\n * the arc, at `max` the 5 o'clock end.\n */\n async getHandlePosition(): Promise<{ x: number; y: number }> {\n const handle = await this.locatorFor('.wr-knob__handle')();\n return {\n x: Number(await handle.getAttribute('cx')),\n y: Number(await handle.getAttribute('cy')),\n };\n }\n\n private async numeric(attribute: string): Promise<number> {\n const raw = await (await this.surface()).getAttribute(attribute);\n return raw === null ? Number.NaN : Number(raw);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -182,6 +182,24 @@ class WrKnob {
182
182
  onBlur() {
183
183
  this.touch.emit();
184
184
  }
185
+ /**
186
+ * The APG slider keys, and they do NOT mirror under `dir="rtl"` — which is a
187
+ * decision, not an oversight, and the one place it is written down.
188
+ *
189
+ * `wr-slider` swaps ArrowLeft and ArrowRight in RTL because its TRACK mirrors:
190
+ * the thumb is placed with `inset-inline-start`, so the visual maximum moves to
191
+ * the left and a key naming a side has to move with it. This dial does not
192
+ * mirror. It is an SVG gauge drawn in a fixed 100×100 viewBox, and SVG content
193
+ * is not reordered by the reading direction — the arc sweeps clockwise from 7
194
+ * o'clock to 5 o'clock in every locale, the same way a clock face or a car's
195
+ * rev counter does. So ArrowRight still moves the handle toward the visually
196
+ * right-hand side of the arc in RTL, and swapping the keys would be the thing
197
+ * that broke the mapping.
198
+ *
199
+ * That is why nothing here injects `Directionality`, and why the stylesheet
200
+ * carries no `rtl-ok:` marker: there is no physical CSS in the value path at
201
+ * all, only fixed SVG geometry.
202
+ */
185
203
  onKeydown(event) {
186
204
  if (!this.interactive())
187
205
  return;
@@ -257,11 +275,11 @@ class WrKnob {
257
275
  return `M ${sx} ${sy} A ${r} ${r} 0 ${largeArc} 1 ${ex} ${ey}`;
258
276
  }
259
277
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrKnob, deps: [], target: i0.ɵɵFactoryTarget.Component });
260
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrKnob, isStandalone: true, selector: "wr-knob", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, trackColor: { classPropertyName: "trackColor", publicName: "trackColor", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<div\n class=\"wr-knob__surface\"\n role=\"slider\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [attr.tabindex]=\"focusable() ? 0 : -1\"\n [style.width.px]=\"size()\"\n [style.height.px]=\"size()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n>\n <svg viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n <path\n class=\"wr-knob__track\"\n [attr.d]=\"trackPath()\"\n [attr.stroke]=\"trackColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <path\n class=\"wr-knob__value\"\n [attr.d]=\"valuePath()\"\n [attr.stroke]=\"valueColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <circle\n class=\"wr-knob__handle\"\n [attr.cx]=\"handlePos().x\"\n [attr.cy]=\"handlePos().y\"\n [attr.r]=\"strokeWidth() / 2\"\n [attr.fill]=\"valueColor()\"\n />\n </svg>\n @if (showValue()) {\n <div class=\"wr-knob__text\">\n {{ value() }}<span class=\"wr-knob__suffix\">{{ suffix() }}</span>\n </div>\n }\n</div>\n", encapsulation: i0.ViewEncapsulation.None });
278
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrKnob, isStandalone: true, selector: "wr-knob", inputs: { ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, trackColor: { classPropertyName: "trackColor", publicName: "trackColor", isSignal: true, isRequired: false, transformFunction: null }, valueColor: { classPropertyName: "valueColor", publicName: "valueColor", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, suffix: { classPropertyName: "suffix", publicName: "suffix", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: "<div\n class=\"wr-knob__surface\"\n role=\"slider\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [attr.tabindex]=\"focusable() ? 0 : -1\"\n [style.width.px]=\"size()\"\n [style.height.px]=\"size()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n>\n <svg viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n <path\n class=\"wr-knob__track\"\n [attr.d]=\"trackPath()\"\n [attr.stroke]=\"trackColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <path\n class=\"wr-knob__value\"\n [attr.d]=\"valuePath()\"\n [attr.stroke]=\"valueColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <circle\n class=\"wr-knob__handle\"\n [attr.cx]=\"handlePos().x\"\n [attr.cy]=\"handlePos().y\"\n [attr.r]=\"strokeWidth() / 2\"\n [attr.fill]=\"valueColor()\"\n />\n </svg>\n @if (showValue()) {\n <!--\n `dir=\"auto\"` because this is a number and a unit read as one phrase, and\n the browser is the only thing that knows which way that phrase runs. In an\n RTL paragraph the Unicode BiDi algorithm treats a digit run as neutral\n against the letters beside it, so `40 dB` was drawn `dB 40` \u2014 and `-5 dB`\n as `dB 5-`, with the sign on the wrong end of the number. `dir=\"auto\"`\n picks the direction from the first strong character in the composed text,\n so a Latin `suffix` keeps the number first and an Arabic one reads\n right-to-left, which is correct for both.\n\n `unicode-bidi: isolate` does NOT fix this and was measured doing nothing:\n isolation seals the element off from its SURROUNDINGS, and the flip\n happens inside it.\n -->\n <div class=\"wr-knob__text\" dir=\"auto\">\n {{ value() }}<span class=\"wr-knob__suffix\">{{ suffix() }}</span>\n </div>\n }\n</div>\n", encapsulation: i0.ViewEncapsulation.None });
261
279
  }
262
280
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrKnob, decorators: [{
263
281
  type: Component,
264
- args: [{ selector: 'wr-knob', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, template: "<div\n class=\"wr-knob__surface\"\n role=\"slider\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [attr.tabindex]=\"focusable() ? 0 : -1\"\n [style.width.px]=\"size()\"\n [style.height.px]=\"size()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n>\n <svg viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n <path\n class=\"wr-knob__track\"\n [attr.d]=\"trackPath()\"\n [attr.stroke]=\"trackColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <path\n class=\"wr-knob__value\"\n [attr.d]=\"valuePath()\"\n [attr.stroke]=\"valueColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <circle\n class=\"wr-knob__handle\"\n [attr.cx]=\"handlePos().x\"\n [attr.cy]=\"handlePos().y\"\n [attr.r]=\"strokeWidth() / 2\"\n [attr.fill]=\"valueColor()\"\n />\n </svg>\n @if (showValue()) {\n <div class=\"wr-knob__text\">\n {{ value() }}<span class=\"wr-knob__suffix\">{{ suffix() }}</span>\n </div>\n }\n</div>\n" }]
282
+ args: [{ selector: 'wr-knob', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, template: "<div\n class=\"wr-knob__surface\"\n role=\"slider\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [attr.tabindex]=\"focusable() ? 0 : -1\"\n [style.width.px]=\"size()\"\n [style.height.px]=\"size()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n>\n <svg viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n <path\n class=\"wr-knob__track\"\n [attr.d]=\"trackPath()\"\n [attr.stroke]=\"trackColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <path\n class=\"wr-knob__value\"\n [attr.d]=\"valuePath()\"\n [attr.stroke]=\"valueColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <circle\n class=\"wr-knob__handle\"\n [attr.cx]=\"handlePos().x\"\n [attr.cy]=\"handlePos().y\"\n [attr.r]=\"strokeWidth() / 2\"\n [attr.fill]=\"valueColor()\"\n />\n </svg>\n @if (showValue()) {\n <!--\n `dir=\"auto\"` because this is a number and a unit read as one phrase, and\n the browser is the only thing that knows which way that phrase runs. In an\n RTL paragraph the Unicode BiDi algorithm treats a digit run as neutral\n against the letters beside it, so `40 dB` was drawn `dB 40` \u2014 and `-5 dB`\n as `dB 5-`, with the sign on the wrong end of the number. `dir=\"auto\"`\n picks the direction from the first strong character in the composed text,\n so a Latin `suffix` keeps the number first and an Arabic one reads\n right-to-left, which is correct for both.\n\n `unicode-bidi: isolate` does NOT fix this and was measured doing nothing:\n isolation seals the element off from its SURROUNDINGS, and the flip\n happens inside it.\n -->\n <div class=\"wr-knob__text\" dir=\"auto\">\n {{ value() }}<span class=\"wr-knob__suffix\">{{ suffix() }}</span>\n </div>\n }\n</div>\n" }]
265
283
  }], ctorParameters: () => [], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], trackColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackColor", required: false }] }], valueColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueColor", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], suffix: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffix", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
266
284
 
267
285
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-knob.mjs","sources":["../../../projects/lib/knob/knob.ts","../../../projects/lib/knob/knob.html","../../../projects/lib/knob/ngwr-knob.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useFormFieldAria } from 'ngwr/form';\nimport { useI18nText } from 'ngwr/i18n';\nimport { clamp, round } from 'ngwr/utils';\n\nconst ARC_START = -135; // 7 o'clock\nconst ARC_END = 135; // 5 o'clock — 270° sweep total\nconst ARC_SWEEP = ARC_END - ARC_START;\n\n/**\n * Radial dial input. A signal-forms native control: it implements\n * `FormValueControl<number>`, so `[formField]` binds straight to its `value`\n * model — no `ControlValueAccessor` in between. `[(value)]` works standalone.\n *\n * The value is clamped to `[min, max]`. Drag the indicator around the dial or\n * use ←→ / ↑↓ to step.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-knob [formField]=\"form.volume\" [min]=\"0\" [max]=\"100\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-knob [(value)]=\"vol\" suffix=\"%\" valueColor=\"var(--wr-color-success)\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/knob\n */\n@Component({\n selector: 'wr-knob',\n templateUrl: './knob.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrKnob implements FormValueControl<number> {\n /** Accessible name — the role needs one. Falls back to `knob.label`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'knob.label', 'Value');\n\n /** Minimum allowed value. @default 0 */\n readonly min = input<number | undefined>(0, { transform: (v: unknown): number => coerceNumberProperty(v, 0) });\n\n /** Maximum allowed value. @default 100 */\n readonly max = input<number | undefined>(100, { transform: (v: unknown): number => coerceNumberProperty(v, 100) });\n\n private readonly minValue = computed(() => this.min() ?? 0);\n private readonly maxValue = computed(() => this.max() ?? 100);\n\n /** Step granularity. @default 1 */\n readonly step = input(1, { transform: (v: unknown): number => Math.max(0.0001, coerceNumberProperty(v, 1)) });\n\n /** Dial diameter in CSS pixels. @default 120 */\n readonly size = input(120, { transform: (v: unknown): number => Math.max(32, coerceNumberProperty(v, 120)) });\n\n /** Stroke width of the dial arc, in CSS pixels. @default 8 */\n readonly strokeWidth = input(8, {\n transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 8)),\n });\n\n /** Track (unfilled) color. @default rgba(--wr-color-light, 0.6) */\n readonly trackColor = input<string>('rgba(var(--wr-color-light-rgb), 0.6)');\n\n /** Filled-arc color. @default var(--wr-color-primary) */\n readonly valueColor = input<string>('var(--wr-color-primary)');\n\n /** Show the value text in the center. @default true */\n readonly showValue = input(true, { transform: coerceBooleanProperty });\n\n /** Optional suffix appended to the center text (e.g. `'%'`). */\n readonly suffix = input<string>('');\n\n /** Read-only — disables interaction but keeps the visual. @default false */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<number>(0);\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n private dragging = false;\n\n protected readonly interactive = computed(() => !this.disabled() && !this.readonly());\n\n /**\n * Whether the control keeps its tab stop — `disabled` only, never `readonly`.\n *\n * The two states differ precisely here: a disabled control leaves the tab\n * order, a read-only one stays in it and keeps announcing `aria-valuenow`,\n * because a keyboard user still has to be able to READ a value they cannot\n * change. Driving `tabindex` off `interactive()` collapsed that distinction\n * and made `readonly()` unreachable — with an `aria-describedby` pointing at\n * an error message on an element nobody could focus.\n */\n protected readonly focusable = computed(() => !this.disabled());\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria();\n\n protected readonly classes = computed(() => {\n const parts = ['wr-knob'];\n if (this.disabled()) parts.push('wr-knob--disabled');\n if (this.readonly()) parts.push('wr-knob--readonly');\n return parts.join(' ');\n });\n\n // Geometry — drawn into a 100×100 viewBox, scaled by `size`.\n protected readonly cx = 50;\n protected readonly cy = 50;\n // A negative radius is invalid in the SVG path grammar, so the browser drops\n // the arc and the dial vanishes — which a fat stroke reaches at 99.\n protected readonly radius = computed(() => Math.max(1, 50 - this.strokeWidth() / 2 - 0.5));\n\n /** Ratio of the filled arc — `0` at min, `1` at max. */\n protected readonly ratio = computed(() => {\n const range = this.maxValue() - this.minValue();\n if (range <= 0) return 0;\n return clamp((this.value() - this.minValue()) / range, 0, 1);\n });\n\n /** SVG path `d` for the background arc. */\n protected readonly trackPath = computed(() => this.arcPath(ARC_START, ARC_END));\n\n /** SVG path `d` for the filled arc. */\n protected readonly valuePath = computed(() => {\n const end = ARC_START + ARC_SWEEP * this.ratio();\n return this.arcPath(ARC_START, end);\n });\n\n /** Position of the handle dot — `{ x, y }` in viewBox units. */\n protected readonly handlePos = computed(() => {\n const angle = ARC_START + ARC_SWEEP * this.ratio();\n const rad = (angle * Math.PI) / 180;\n return {\n x: this.cx + this.radius() * Math.sin(rad),\n y: this.cy - this.radius() * Math.cos(rad),\n };\n });\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n constructor() {\n // Keep the value within `[min, max]` even when written externally (via\n // `[formField]` / `[(value)]`). Interactive changes are already clamped in\n // `commit`, so this only normalizes out-of-range external writes.\n effect(() => {\n const clamped = clamp(this.value(), this.minValue(), this.maxValue());\n if (clamped !== this.value()) this.value.set(clamped);\n });\n }\n\n // Pointer / keyboard\n\n protected onPointerDown(event: PointerEvent): void {\n if (!this.interactive()) return;\n // Any pointerdown used to start a drag, so the right button turned the dial;\n // the same guard keeps a second finger out of a drag already in progress.\n if (event.button !== 0 || !event.isPrimary) return;\n event.preventDefault();\n this.dragging = true;\n const surface = event.currentTarget as HTMLElement;\n surface.setPointerCapture(event.pointerId);\n // `preventDefault` above also suppresses the click's default focus, so the\n // arrows did nothing after a mouse drag until the dial was found again with\n // Tab. `wr-slider` focuses its thumb for the same reason.\n surface.focus();\n this.updateFromPointer(event);\n }\n\n protected onPointerMove(event: PointerEvent): void {\n if (!this.dragging) return;\n this.updateFromPointer(event);\n }\n\n protected onPointerUp(event: PointerEvent): void {\n if (!this.dragging) return;\n this.dragging = false;\n (event.currentTarget as HTMLElement).releasePointerCapture(event.pointerId);\n this.touch.emit();\n }\n\n /**\n * A bound field learns it may show its validation copy from `touch`, and\n * pointer-up cannot be the only source: a knob reached by Tab and moved with\n * the arrows would leave the field untouched forever. `wr-rating`, whose\n * template this one otherwise mirrors, emits on blur for exactly this reason.\n */\n protected onBlur(): void {\n this.touch.emit();\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.interactive()) return;\n const step = event.shiftKey ? this.step() * 10 : this.step();\n let next: number | null;\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n next = this.value() + step;\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n next = this.value() - step;\n break;\n case 'Home':\n next = this.minValue();\n break;\n case 'End':\n next = this.maxValue();\n break;\n default:\n return;\n }\n event.preventDefault();\n this.commit(this.snap(next));\n }\n\n /**\n * Nearest step, measured FROM `min`. A grid anchored at zero offers values the\n * arrows can never reach whenever `min` is not a multiple of `step` — with\n * `min: 5, step: 10` the middle of the arc used to commit 60, off a grid whose\n * points are 5, 15, … 55, 65. Rounded at six decimals because a tenth is not a\n * binary fraction: three arrow presses at `step: 0.1` otherwise announce\n * `aria-valuenow=\"0.30000000000000004\"` and print it in the dial. Same shape as\n * `wr-slider`'s `snap`, deliberately — including Home / End, so a range that is\n * not a whole number of steps stops at the last reachable value.\n */\n private snap(value: number): number {\n const min = this.minValue();\n const stepped = Math.round((value - min) / this.step()) * this.step() + min;\n return clamp(round(stepped, 6), min, this.maxValue());\n }\n\n // Internals\n\n private updateFromPointer(event: PointerEvent): void {\n const rect = this.host.nativeElement.getBoundingClientRect();\n const dx = event.clientX - (rect.left + rect.width / 2);\n const dy = event.clientY - (rect.top + rect.height / 2);\n // Angle measured from 12 o'clock, clockwise.\n let angle = (Math.atan2(dx, -dy) * 180) / Math.PI;\n // Map [-180, 180] into the dial's arc range, clamping outside it.\n if (angle < ARC_START) angle = angle + 360 > ARC_END ? ARC_START : angle + 360;\n angle = clamp(angle, ARC_START, ARC_END);\n const ratio = (angle - ARC_START) / ARC_SWEEP;\n const range = this.maxValue() - this.minValue();\n const raw = this.minValue() + ratio * range;\n this.commit(this.snap(raw));\n }\n\n private commit(next: number): void {\n if (next === this.value()) return;\n this.value.set(next);\n }\n\n // Arc helper\n\n private arcPath(startDeg: number, endDeg: number): string {\n const r = this.radius();\n const s = (startDeg * Math.PI) / 180;\n const e = (endDeg * Math.PI) / 180;\n const sx = this.cx + r * Math.sin(s);\n const sy = this.cy - r * Math.cos(s);\n const ex = this.cx + r * Math.sin(e);\n const ey = this.cy - r * Math.cos(e);\n const largeArc = endDeg - startDeg > 180 ? 1 : 0;\n return `M ${sx} ${sy} A ${r} ${r} 0 ${largeArc} 1 ${ex} ${ey}`;\n }\n}\n","<div\n class=\"wr-knob__surface\"\n role=\"slider\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [attr.tabindex]=\"focusable() ? 0 : -1\"\n [style.width.px]=\"size()\"\n [style.height.px]=\"size()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n>\n <svg viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n <path\n class=\"wr-knob__track\"\n [attr.d]=\"trackPath()\"\n [attr.stroke]=\"trackColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <path\n class=\"wr-knob__value\"\n [attr.d]=\"valuePath()\"\n [attr.stroke]=\"valueColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <circle\n class=\"wr-knob__handle\"\n [attr.cx]=\"handlePos().x\"\n [attr.cy]=\"handlePos().y\"\n [attr.r]=\"strokeWidth() / 2\"\n [attr.fill]=\"valueColor()\"\n />\n </svg>\n @if (showValue()) {\n <div class=\"wr-knob__text\">\n {{ value() }}<span class=\"wr-knob__suffix\">{{ suffix() }}</span>\n </div>\n }\n</div>\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-knob.mjs","sources":["../../../projects/lib/knob/knob.ts","../../../projects/lib/knob/knob.html","../../../projects/lib/knob/ngwr-knob.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n ViewEncapsulation,\n computed,\n effect,\n inject,\n input,\n model,\n output,\n} from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { useFormFieldAria } from 'ngwr/form';\nimport { useI18nText } from 'ngwr/i18n';\nimport { clamp, round } from 'ngwr/utils';\n\nconst ARC_START = -135; // 7 o'clock\nconst ARC_END = 135; // 5 o'clock — 270° sweep total\nconst ARC_SWEEP = ARC_END - ARC_START;\n\n/**\n * Radial dial input. A signal-forms native control: it implements\n * `FormValueControl<number>`, so `[formField]` binds straight to its `value`\n * model — no `ControlValueAccessor` in between. `[(value)]` works standalone.\n *\n * The value is clamped to `[min, max]`. Drag the indicator around the dial or\n * use ←→ / ↑↓ to step.\n *\n * @example\n * ```html\n * <!-- signal forms -->\n * <wr-knob [formField]=\"form.volume\" [min]=\"0\" [max]=\"100\" />\n *\n * <!-- standalone two-way binding -->\n * <wr-knob [(value)]=\"vol\" suffix=\"%\" valueColor=\"var(--wr-color-success)\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/knob\n */\n@Component({\n selector: 'wr-knob',\n templateUrl: './knob.html',\n encapsulation: ViewEncapsulation.None,\n host: { '[class]': 'classes()' },\n})\nexport class WrKnob implements FormValueControl<number> {\n /** Accessible name — the role needs one. Falls back to `knob.label`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'knob.label', 'Value');\n\n /** Minimum allowed value. @default 0 */\n readonly min = input<number | undefined>(0, { transform: (v: unknown): number => coerceNumberProperty(v, 0) });\n\n /** Maximum allowed value. @default 100 */\n readonly max = input<number | undefined>(100, { transform: (v: unknown): number => coerceNumberProperty(v, 100) });\n\n private readonly minValue = computed(() => this.min() ?? 0);\n private readonly maxValue = computed(() => this.max() ?? 100);\n\n /** Step granularity. @default 1 */\n readonly step = input(1, { transform: (v: unknown): number => Math.max(0.0001, coerceNumberProperty(v, 1)) });\n\n /** Dial diameter in CSS pixels. @default 120 */\n readonly size = input(120, { transform: (v: unknown): number => Math.max(32, coerceNumberProperty(v, 120)) });\n\n /** Stroke width of the dial arc, in CSS pixels. @default 8 */\n readonly strokeWidth = input(8, {\n transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 8)),\n });\n\n /** Track (unfilled) color. @default rgba(--wr-color-light, 0.6) */\n readonly trackColor = input<string>('rgba(var(--wr-color-light-rgb), 0.6)');\n\n /** Filled-arc color. @default var(--wr-color-primary) */\n readonly valueColor = input<string>('var(--wr-color-primary)');\n\n /** Show the value text in the center. @default true */\n readonly showValue = input(true, { transform: coerceBooleanProperty });\n\n /** Optional suffix appended to the center text (e.g. `'%'`). */\n readonly suffix = input<string>('');\n\n /** Read-only — disables interaction but keeps the visual. @default false */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** Disable interaction. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Bound by `[formField]`, or two-way via `[(value)]`. */\n readonly value = model<number>(0);\n\n /** Emitted on blur so a bound field can mark itself touched. */\n readonly touch = output<void>();\n\n private dragging = false;\n\n protected readonly interactive = computed(() => !this.disabled() && !this.readonly());\n\n /**\n * Whether the control keeps its tab stop — `disabled` only, never `readonly`.\n *\n * The two states differ precisely here: a disabled control leaves the tab\n * order, a read-only one stays in it and keeps announcing `aria-valuenow`,\n * because a keyboard user still has to be able to READ a value they cannot\n * change. Driving `tabindex` off `interactive()` collapsed that distinction\n * and made `readonly()` unreachable — with an `aria-describedby` pointing at\n * an error message on an element nobody could focus.\n */\n protected readonly focusable = computed(() => !this.disabled());\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria();\n\n protected readonly classes = computed(() => {\n const parts = ['wr-knob'];\n if (this.disabled()) parts.push('wr-knob--disabled');\n if (this.readonly()) parts.push('wr-knob--readonly');\n return parts.join(' ');\n });\n\n // Geometry — drawn into a 100×100 viewBox, scaled by `size`.\n protected readonly cx = 50;\n protected readonly cy = 50;\n // A negative radius is invalid in the SVG path grammar, so the browser drops\n // the arc and the dial vanishes — which a fat stroke reaches at 99.\n protected readonly radius = computed(() => Math.max(1, 50 - this.strokeWidth() / 2 - 0.5));\n\n /** Ratio of the filled arc — `0` at min, `1` at max. */\n protected readonly ratio = computed(() => {\n const range = this.maxValue() - this.minValue();\n if (range <= 0) return 0;\n return clamp((this.value() - this.minValue()) / range, 0, 1);\n });\n\n /** SVG path `d` for the background arc. */\n protected readonly trackPath = computed(() => this.arcPath(ARC_START, ARC_END));\n\n /** SVG path `d` for the filled arc. */\n protected readonly valuePath = computed(() => {\n const end = ARC_START + ARC_SWEEP * this.ratio();\n return this.arcPath(ARC_START, end);\n });\n\n /** Position of the handle dot — `{ x, y }` in viewBox units. */\n protected readonly handlePos = computed(() => {\n const angle = ARC_START + ARC_SWEEP * this.ratio();\n const rad = (angle * Math.PI) / 180;\n return {\n x: this.cx + this.radius() * Math.sin(rad),\n y: this.cy - this.radius() * Math.cos(rad),\n };\n });\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n constructor() {\n // Keep the value within `[min, max]` even when written externally (via\n // `[formField]` / `[(value)]`). Interactive changes are already clamped in\n // `commit`, so this only normalizes out-of-range external writes.\n effect(() => {\n const clamped = clamp(this.value(), this.minValue(), this.maxValue());\n if (clamped !== this.value()) this.value.set(clamped);\n });\n }\n\n // Pointer / keyboard\n\n protected onPointerDown(event: PointerEvent): void {\n if (!this.interactive()) return;\n // Any pointerdown used to start a drag, so the right button turned the dial;\n // the same guard keeps a second finger out of a drag already in progress.\n if (event.button !== 0 || !event.isPrimary) return;\n event.preventDefault();\n this.dragging = true;\n const surface = event.currentTarget as HTMLElement;\n surface.setPointerCapture(event.pointerId);\n // `preventDefault` above also suppresses the click's default focus, so the\n // arrows did nothing after a mouse drag until the dial was found again with\n // Tab. `wr-slider` focuses its thumb for the same reason.\n surface.focus();\n this.updateFromPointer(event);\n }\n\n protected onPointerMove(event: PointerEvent): void {\n if (!this.dragging) return;\n this.updateFromPointer(event);\n }\n\n protected onPointerUp(event: PointerEvent): void {\n if (!this.dragging) return;\n this.dragging = false;\n (event.currentTarget as HTMLElement).releasePointerCapture(event.pointerId);\n this.touch.emit();\n }\n\n /**\n * A bound field learns it may show its validation copy from `touch`, and\n * pointer-up cannot be the only source: a knob reached by Tab and moved with\n * the arrows would leave the field untouched forever. `wr-rating`, whose\n * template this one otherwise mirrors, emits on blur for exactly this reason.\n */\n protected onBlur(): void {\n this.touch.emit();\n }\n\n /**\n * The APG slider keys, and they do NOT mirror under `dir=\"rtl\"` — which is a\n * decision, not an oversight, and the one place it is written down.\n *\n * `wr-slider` swaps ArrowLeft and ArrowRight in RTL because its TRACK mirrors:\n * the thumb is placed with `inset-inline-start`, so the visual maximum moves to\n * the left and a key naming a side has to move with it. This dial does not\n * mirror. It is an SVG gauge drawn in a fixed 100×100 viewBox, and SVG content\n * is not reordered by the reading direction — the arc sweeps clockwise from 7\n * o'clock to 5 o'clock in every locale, the same way a clock face or a car's\n * rev counter does. So ArrowRight still moves the handle toward the visually\n * right-hand side of the arc in RTL, and swapping the keys would be the thing\n * that broke the mapping.\n *\n * That is why nothing here injects `Directionality`, and why the stylesheet\n * carries no `rtl-ok:` marker: there is no physical CSS in the value path at\n * all, only fixed SVG geometry.\n */\n protected onKeydown(event: KeyboardEvent): void {\n if (!this.interactive()) return;\n const step = event.shiftKey ? this.step() * 10 : this.step();\n let next: number | null;\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n next = this.value() + step;\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n next = this.value() - step;\n break;\n case 'Home':\n next = this.minValue();\n break;\n case 'End':\n next = this.maxValue();\n break;\n default:\n return;\n }\n event.preventDefault();\n this.commit(this.snap(next));\n }\n\n /**\n * Nearest step, measured FROM `min`. A grid anchored at zero offers values the\n * arrows can never reach whenever `min` is not a multiple of `step` — with\n * `min: 5, step: 10` the middle of the arc used to commit 60, off a grid whose\n * points are 5, 15, … 55, 65. Rounded at six decimals because a tenth is not a\n * binary fraction: three arrow presses at `step: 0.1` otherwise announce\n * `aria-valuenow=\"0.30000000000000004\"` and print it in the dial. Same shape as\n * `wr-slider`'s `snap`, deliberately — including Home / End, so a range that is\n * not a whole number of steps stops at the last reachable value.\n */\n private snap(value: number): number {\n const min = this.minValue();\n const stepped = Math.round((value - min) / this.step()) * this.step() + min;\n return clamp(round(stepped, 6), min, this.maxValue());\n }\n\n // Internals\n\n private updateFromPointer(event: PointerEvent): void {\n const rect = this.host.nativeElement.getBoundingClientRect();\n const dx = event.clientX - (rect.left + rect.width / 2);\n const dy = event.clientY - (rect.top + rect.height / 2);\n // Angle measured from 12 o'clock, clockwise.\n let angle = (Math.atan2(dx, -dy) * 180) / Math.PI;\n // Map [-180, 180] into the dial's arc range, clamping outside it.\n if (angle < ARC_START) angle = angle + 360 > ARC_END ? ARC_START : angle + 360;\n angle = clamp(angle, ARC_START, ARC_END);\n const ratio = (angle - ARC_START) / ARC_SWEEP;\n const range = this.maxValue() - this.minValue();\n const raw = this.minValue() + ratio * range;\n this.commit(this.snap(raw));\n }\n\n private commit(next: number): void {\n if (next === this.value()) return;\n this.value.set(next);\n }\n\n // Arc helper\n\n private arcPath(startDeg: number, endDeg: number): string {\n const r = this.radius();\n const s = (startDeg * Math.PI) / 180;\n const e = (endDeg * Math.PI) / 180;\n const sx = this.cx + r * Math.sin(s);\n const sy = this.cy - r * Math.cos(s);\n const ex = this.cx + r * Math.sin(e);\n const ey = this.cy - r * Math.cos(e);\n const largeArc = endDeg - startDeg > 180 ? 1 : 0;\n return `M ${sx} ${sy} A ${r} ${r} 0 ${largeArc} 1 ${ex} ${ey}`;\n }\n}\n","<div\n class=\"wr-knob__surface\"\n role=\"slider\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"max()\"\n [attr.aria-valuenow]=\"value()\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n [attr.tabindex]=\"focusable() ? 0 : -1\"\n [style.width.px]=\"size()\"\n [style.height.px]=\"size()\"\n (pointerdown)=\"onPointerDown($event)\"\n (pointermove)=\"onPointerMove($event)\"\n (pointerup)=\"onPointerUp($event)\"\n (pointercancel)=\"onPointerUp($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n>\n <svg viewBox=\"0 0 100 100\" aria-hidden=\"true\">\n <path\n class=\"wr-knob__track\"\n [attr.d]=\"trackPath()\"\n [attr.stroke]=\"trackColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <path\n class=\"wr-knob__value\"\n [attr.d]=\"valuePath()\"\n [attr.stroke]=\"valueColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"none\"\n stroke-linecap=\"round\"\n />\n <circle\n class=\"wr-knob__handle\"\n [attr.cx]=\"handlePos().x\"\n [attr.cy]=\"handlePos().y\"\n [attr.r]=\"strokeWidth() / 2\"\n [attr.fill]=\"valueColor()\"\n />\n </svg>\n @if (showValue()) {\n <!--\n `dir=\"auto\"` because this is a number and a unit read as one phrase, and\n the browser is the only thing that knows which way that phrase runs. In an\n RTL paragraph the Unicode BiDi algorithm treats a digit run as neutral\n against the letters beside it, so `40 dB` was drawn `dB 40` — and `-5 dB`\n as `dB 5-`, with the sign on the wrong end of the number. `dir=\"auto\"`\n picks the direction from the first strong character in the composed text,\n so a Latin `suffix` keeps the number first and an Arabic one reads\n right-to-left, which is correct for both.\n\n `unicode-bidi: isolate` does NOT fix this and was measured doing nothing:\n isolation seals the element off from its SURROUNDINGS, and the flip\n happens inside it.\n -->\n <div class=\"wr-knob__text\" dir=\"auto\">\n {{ value() }}<span class=\"wr-knob__suffix\">{{ suffix() }}</span>\n </div>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { isPlatformBrowser } from '@angular/common';
2
+ import { makeEnvironmentProviders, provideEnvironmentInitializer, inject, PLATFORM_ID, DestroyRef } from '@angular/core';
3
+ import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
4
+ import { WrLoadingBar } from 'ngwr/loading-bar';
5
+
6
+ /**
7
+ * @license
8
+ *
9
+ * Use of this source code is governed by an MIT-style license that can be
10
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
11
+ */
12
+ /**
13
+ * Drive {@link WrLoadingBar} from router navigations: `NavigationStart` takes a
14
+ * slot, and each of `NavigationEnd` / `NavigationCancel` / `NavigationError`
15
+ * releases one.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { provideWrLoadingBarRouter } from 'ngwr/loading-bar/router';
20
+ *
21
+ * bootstrapApplication(App, {
22
+ * providers: [provideRouter(routes), provideWrLoadingBarRouter()],
23
+ * });
24
+ * ```
25
+ *
26
+ * **Why this is a call and not the default.** `WrLoadingBar` used to inject
27
+ * `Router` itself, so navigations drove the bar with no configuration — and
28
+ * every app that rendered `<wr-loading-bar>` paid 66.3 kB of `@angular/router`
29
+ * for it, two thirds of the entry point's total cost, whether or not it had
30
+ * routes. `{ optional: true }` does not help: an optional inject still names the
31
+ * class as the token, and it measured byte-for-byte identical to a required one.
32
+ * An automatic integration and a router-free bundle cannot both exist in a
33
+ * statically linked app, so the automatic half moved here.
34
+ */
35
+ function provideWrLoadingBarRouter() {
36
+ return makeEnvironmentProviders([
37
+ provideEnvironmentInitializer(() => {
38
+ // Browser only. During prerender the initial navigation runs the whole
39
+ // cycle in the Node worker — including the bar's 150 ms interval — and
40
+ // leaves `progress` at 1, because the reset that clears it is deferred and
41
+ // the HTML is serialized first: every prerendered page then ships a
42
+ // full-width bar across the top until hydration.
43
+ if (!isPlatformBrowser(inject(PLATFORM_ID)))
44
+ return;
45
+ const bar = inject(WrLoadingBar);
46
+ const destroyRef = inject(DestroyRef);
47
+ const sub = inject(Router).events.subscribe(event => {
48
+ if (event instanceof NavigationStart)
49
+ bar.start();
50
+ else if (event instanceof NavigationEnd ||
51
+ event instanceof NavigationCancel ||
52
+ event instanceof NavigationError) {
53
+ bar.complete();
54
+ }
55
+ });
56
+ destroyRef.onDestroy(() => sub.unsubscribe());
57
+ }),
58
+ ]);
59
+ }
60
+
61
+ /**
62
+ * @license
63
+ *
64
+ * Use of this source code is governed by an MIT-style license that can be
65
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
66
+ */
67
+
68
+ /**
69
+ * Generated bundle index. Do not edit.
70
+ */
71
+
72
+ export { provideWrLoadingBarRouter };
73
+ //# sourceMappingURL=ngwr-loading-bar-router.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngwr-loading-bar-router.mjs","sources":["../../../projects/lib/loading-bar/router/provide-wr-loading-bar-router.ts","../../../projects/lib/loading-bar/router/public-api.ts","../../../projects/lib/loading-bar/router/ngwr-loading-bar-router.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 { isPlatformBrowser } from '@angular/common';\nimport {\n DestroyRef,\n type EnvironmentProviders,\n PLATFORM_ID,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n} from '@angular/core';\nimport { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';\n\nimport { WrLoadingBar } from 'ngwr/loading-bar';\n\n/**\n * Drive {@link WrLoadingBar} from router navigations: `NavigationStart` takes a\n * slot, and each of `NavigationEnd` / `NavigationCancel` / `NavigationError`\n * releases one.\n *\n * @example\n * ```ts\n * import { provideWrLoadingBarRouter } from 'ngwr/loading-bar/router';\n *\n * bootstrapApplication(App, {\n * providers: [provideRouter(routes), provideWrLoadingBarRouter()],\n * });\n * ```\n *\n * **Why this is a call and not the default.** `WrLoadingBar` used to inject\n * `Router` itself, so navigations drove the bar with no configuration — and\n * every app that rendered `<wr-loading-bar>` paid 66.3 kB of `@angular/router`\n * for it, two thirds of the entry point's total cost, whether or not it had\n * routes. `{ optional: true }` does not help: an optional inject still names the\n * class as the token, and it measured byte-for-byte identical to a required one.\n * An automatic integration and a router-free bundle cannot both exist in a\n * statically linked app, so the automatic half moved here.\n */\nexport function provideWrLoadingBarRouter(): EnvironmentProviders {\n return makeEnvironmentProviders([\n provideEnvironmentInitializer(() => {\n // Browser only. During prerender the initial navigation runs the whole\n // cycle in the Node worker — including the bar's 150 ms interval — and\n // leaves `progress` at 1, because the reset that clears it is deferred and\n // the HTML is serialized first: every prerendered page then ships a\n // full-width bar across the top until hydration.\n if (!isPlatformBrowser(inject(PLATFORM_ID))) return;\n\n const bar = inject(WrLoadingBar);\n const destroyRef = inject(DestroyRef);\n const sub = inject(Router).events.subscribe(event => {\n if (event instanceof NavigationStart) bar.start();\n else if (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n ) {\n bar.complete();\n }\n });\n destroyRef.onDestroy(() => sub.unsubscribe());\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\nexport { provideWrLoadingBarRouter } from './provide-wr-loading-bar-router';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;AAAA;;;;;AAKG;AAeH;;;;;;;;;;;;;;;;;;;;;;AAsBG;SACa,yBAAyB,GAAA;AACvC,IAAA,OAAO,wBAAwB,CAAC;QAC9B,6BAA6B,CAAC,MAAK;;;;;;AAMjC,YAAA,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;gBAAE;AAE7C,YAAA,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC;AAChC,YAAA,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AACrC,YAAA,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,IAAG;gBAClD,IAAI,KAAK,YAAY,eAAe;oBAAE,GAAG,CAAC,KAAK,EAAE;qBAC5C,IACH,KAAK,YAAY,aAAa;AAC9B,oBAAA,KAAK,YAAY,gBAAgB;oBACjC,KAAK,YAAY,eAAe,EAChC;oBACA,GAAG,CAAC,QAAQ,EAAE;gBAChB;AACF,YAAA,CAAC,CAAC;YACF,UAAU,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;AAC/C,QAAA,CAAC,CAAC;AACH,KAAA,CAAC;AACJ;;ACpEA;;;;;AAKG;;ACLH;;AAEG;;;;"}
@@ -1,7 +1,6 @@
1
1
  import { isPlatformBrowser } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, DestroyRef, PLATFORM_ID, signal, computed, Service, input, ViewEncapsulation, Component } from '@angular/core';
4
- import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
3
+ import { inject, PLATFORM_ID, signal, computed, Service, input, ViewEncapsulation, Component } from '@angular/core';
5
4
 
6
5
  /**
7
6
  * @license
@@ -10,12 +9,19 @@ import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationErr
10
9
  * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
11
10
  */
12
11
  /**
13
- * Singleton state machine for a top-of-page progress indicator. Driven
14
- * by router events out of the box, plus a manual `start()` / `complete()`
15
- * API for HTTP-interceptor-style usage.
12
+ * Singleton state machine for a top-of-page progress indicator: a counter of
13
+ * in-flight tasks and a tween between 0 and 1, driven through `start()` /
14
+ * `complete()`.
16
15
  *
17
16
  * Pair with `<wr-loading-bar>` to render the bar at the top of your shell.
18
17
  *
18
+ * It knows nothing about the router on purpose. Naming `Router` here — even
19
+ * `{ optional: true }`, which is byte-for-byte identical to a required inject —
20
+ * put 66.3 kB of `@angular/router` into every app that rendered the bar,
21
+ * two thirds of what `ngwr/loading-bar` cost and 27× the component's own code.
22
+ * Call `provideWrLoadingBarRouter()` from `ngwr/loading-bar/router` to have
23
+ * navigations drive it.
24
+ *
19
25
  * @example
20
26
  * ```ts
21
27
  * // From an HttpInterceptor:
@@ -27,8 +33,6 @@ import { Router, NavigationStart, NavigationEnd, NavigationCancel, NavigationErr
27
33
  * @see https://ngwr.dev/reference/services/loading-bar
28
34
  */
29
35
  class WrLoadingBar {
30
- router = inject(Router, { optional: true });
31
- destroyRef = inject(DestroyRef);
32
36
  isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
33
37
  /** Live count of pending "tasks" (manual + router). */
34
38
  count = signal(0, /* @ts-ignore */
@@ -45,27 +49,6 @@ class WrLoadingBar {
45
49
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
46
50
  timer = null;
47
51
  holdTimer = null;
48
- constructor() {
49
- // Router-driven only in the browser. During prerender the initial
50
- // navigation ran the whole cycle in the Node worker — including a 150 ms
51
- // `setInterval` — and left `progress` at 1, because the reset that clears
52
- // it is deferred and the HTML is serialized first. Every prerendered page
53
- // shipped a full-width bar across the top until hydration.
54
- if (!this.router || !this.isBrowser)
55
- return;
56
- // Mirror router events: any in-flight navigation starts the bar,
57
- // each terminal event releases one slot.
58
- const sub = this.router.events.subscribe(event => {
59
- if (event instanceof NavigationStart)
60
- this.start();
61
- else if (event instanceof NavigationEnd ||
62
- event instanceof NavigationCancel ||
63
- event instanceof NavigationError) {
64
- this.complete();
65
- }
66
- });
67
- this.destroyRef.onDestroy(() => sub.unsubscribe());
68
- }
69
52
  /** Reserve a slot. The bar starts trickling as long as `count > 0`. */
70
53
  start() {
71
54
  if (this.count() === 0)
@@ -153,7 +136,7 @@ class WrLoadingBar {
153
136
  }
154
137
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrLoadingBar, decorators: [{
155
138
  type: Service
156
- }], ctorParameters: () => [] });
139
+ }] });
157
140
 
158
141
  /**
159
142
  * @license
@@ -164,9 +147,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
164
147
  /**
165
148
  * Thin progress bar fixed to the top of the viewport.
166
149
  *
167
- * Drop one at the root of your shell. Reads the singleton {@link WrLoadingBar}
168
- * — every router navigation drives it automatically; HTTP interceptors
169
- * can call `start()` / `complete()` to add their own slots.
150
+ * Drop one at the root of your shell. Reads the singleton {@link WrLoadingBar},
151
+ * which is driven manually by `start()` / `complete()` — an HTTP interceptor, a
152
+ * long save, anything with a beginning and an end.
153
+ *
154
+ * Router navigations drive it too, but only once you add
155
+ * `provideWrLoadingBarRouter()` from `ngwr/loading-bar/router`. That is an opt-in
156
+ * because the subscription is what pulls `@angular/router` into the bundle: this
157
+ * component is 2.4 kB of its own code and used to carry 66 kB of router into
158
+ * apps that never routed.
170
159
  *
171
160
  * @example
172
161
  * ```html
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-loading-bar.mjs","sources":["../../../projects/lib/loading-bar/services/loading-bar.ts","../../../projects/lib/loading-bar/loading-bar.component.ts","../../../projects/lib/loading-bar/loading-bar.component.html","../../../projects/lib/loading-bar/ngwr-loading-bar.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 { isPlatformBrowser } from '@angular/common';\nimport { DestroyRef, Service, PLATFORM_ID, type Signal, computed, inject, signal } from '@angular/core';\nimport { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';\n\nimport type { WrLoadingState } from '../interfaces';\n\n/**\n * Singleton state machine for a top-of-page progress indicator. Driven\n * by router events out of the box, plus a manual `start()` / `complete()`\n * API for HTTP-interceptor-style usage.\n *\n * Pair with `<wr-loading-bar>` to render the bar at the top of your shell.\n *\n * @example\n * ```ts\n * // From an HttpInterceptor:\n * const bar = inject(WrLoadingBar);\n * bar.start();\n * return next(req).pipe(finalize(() => bar.complete()));\n * ```\n *\n * @see https://ngwr.dev/reference/services/loading-bar\n */\n@Service()\nexport class WrLoadingBar {\n private readonly router = inject(Router, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Live count of pending \"tasks\" (manual + router). */\n private readonly count = signal(0);\n\n /** Tween value `[0, 1]` — the bar's actual width. */\n private readonly _progress = signal(0);\n readonly progress: Signal<number> = this._progress.asReadonly();\n\n readonly state: Signal<WrLoadingState> = computed(() => {\n if (this.count() > 0) return 'running';\n return this._progress() > 0 ? 'completing' : 'idle';\n });\n\n private timer: ReturnType<typeof setInterval> | null = null;\n private holdTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor() {\n // Router-driven only in the browser. During prerender the initial\n // navigation ran the whole cycle in the Node worker — including a 150 ms\n // `setInterval` — and left `progress` at 1, because the reset that clears\n // it is deferred and the HTML is serialized first. Every prerendered page\n // shipped a full-width bar across the top until hydration.\n if (!this.router || !this.isBrowser) return;\n\n // Mirror router events: any in-flight navigation starts the bar,\n // each terminal event releases one slot.\n const sub = this.router.events.subscribe(event => {\n if (event instanceof NavigationStart) this.start();\n else if (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n ) {\n this.complete();\n }\n });\n this.destroyRef.onDestroy(() => sub.unsubscribe());\n }\n\n /** Reserve a slot. The bar starts trickling as long as `count > 0`. */\n start(): void {\n if (this.count() === 0) this.beginTrickle();\n this.count.update(v => v + 1);\n }\n\n /** Release a slot. When the last one closes, the bar fast-forwards to 100% then resets. */\n complete(): void {\n // Only a call that actually RELEASED something may finish. `Math.max` turns\n // a stray `complete()` into a no-op on the count, but the old code still\n // read the resulting zero as \"the last task just ended\" and flashed the bar\n // to 100% — reachable from a `finalize` that runs twice, or any\n // `complete()` after a `reset()`.\n if (this.count() === 0) return;\n this.count.update(v => v - 1);\n if (this.count() === 0) this.finish();\n }\n\n /** Cancel without animating the bar to 100%. Resets immediately. */\n reset(): void {\n this.count.set(0);\n this.stopTrickle();\n this.clearHold();\n this._progress.set(0);\n }\n\n // Internals\n\n private beginTrickle(): void {\n this.stopTrickle();\n this.clearHold();\n\n // Nothing paints during prerender, so there is nothing to animate. The\n // count still moves — a task really is in flight — but no 150 ms interval\n // is left spinning inside the render worker, and the serialized bar stays\n // at zero width whether or not the task finishes before serialization.\n if (!this.isBrowser) return;\n\n // Unconditionally, not `if (progress === 0)`. A task that starts inside the\n // 220 ms hold found progress at 1, kept it, and then trickled the WRONG WAY:\n // `p + (0.9 - p) * 0.08` approaches 0.9 from above, so the bar sat at ~99%\n // and crept backwards for the whole task. A redirecting route guard hits\n // that window on every navigation.\n this._progress.set(0.08);\n this.timer = setInterval(() => {\n const p = this._progress();\n // Approach 90% asymptotically — we want the user-visible final jump\n // to clearly indicate completion.\n const next = p + (0.9 - p) * 0.08;\n this._progress.set(next);\n }, 150);\n }\n\n private stopTrickle(): void {\n if (this.timer === null) return;\n clearInterval(this.timer);\n this.timer = null;\n }\n\n private clearHold(): void {\n if (this.holdTimer === null) return;\n clearTimeout(this.holdTimer);\n this.holdTimer = null;\n }\n\n private finish(): void {\n this.stopTrickle();\n this.clearHold();\n\n // Nothing animates during prerender and nothing will run the reset before\n // the HTML is serialized, so there is no 100% worth holding — go straight\n // to rest and leave the markup clean.\n if (!this.isBrowser) {\n this._progress.set(0);\n return;\n }\n\n this._progress.set(1);\n // Give the bar a moment to render the final 100% width before resetting.\n // The handle is kept so a later cycle's hold is not cut short by this\n // one's leftover timer.\n this.holdTimer = setTimeout(() => {\n this.holdTimer = null;\n if (this.count() === 0) this._progress.set(0);\n }, 220);\n }\n}\n\nexport type { WrLoadingState } from '../interfaces';\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, computed, inject, input } from '@angular/core';\n\nimport { WrLoadingBar } from './services/loading-bar';\n\n/**\n * Thin progress bar fixed to the top of the viewport.\n *\n * Drop one at the root of your shell. Reads the singleton {@link WrLoadingBar}\n * — every router navigation drives it automatically; HTTP interceptors\n * can call `start()` / `complete()` to add their own slots.\n *\n * @example\n * ```html\n * <wr-loading-bar /> <!-- top, primary color -->\n * <wr-loading-bar color=\"#9b51e0\" height=\"3px\" />\n * ```\n */\n@Component({\n selector: 'wr-loading-bar',\n templateUrl: './loading-bar.component.html',\n styleUrl: './loading-bar.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-loading-bar' },\n})\nexport class WrLoadingBarComponent {\n /** Bar colour. Defaults to the primary brand colour. */\n readonly color = input<string>('var(--wr-color-primary)');\n\n /** Bar height. */\n readonly height = input<string>('2px');\n\n protected readonly bar = inject(WrLoadingBar);\n\n protected readonly active = computed(() => this.bar.state() !== 'idle');\n}\n","<div class=\"wr-loading-bar__track\" [class.wr-loading-bar--active]=\"active()\">\n <div\n class=\"wr-loading-bar__fill\"\n [style.width.%]=\"bar.progress() * 100\"\n [style.background]=\"color()\"\n [style.height]=\"height()\"\n ></div>\n</div>\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-loading-bar.mjs","sources":["../../../projects/lib/loading-bar/services/loading-bar.ts","../../../projects/lib/loading-bar/loading-bar.component.ts","../../../projects/lib/loading-bar/loading-bar.component.html","../../../projects/lib/loading-bar/ngwr-loading-bar.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 { isPlatformBrowser } from '@angular/common';\nimport { Service, PLATFORM_ID, type Signal, computed, inject, signal } from '@angular/core';\n\nimport type { WrLoadingState } from '../interfaces';\n\n/**\n * Singleton state machine for a top-of-page progress indicator: a counter of\n * in-flight tasks and a tween between 0 and 1, driven through `start()` /\n * `complete()`.\n *\n * Pair with `<wr-loading-bar>` to render the bar at the top of your shell.\n *\n * It knows nothing about the router on purpose. Naming `Router` here — even\n * `{ optional: true }`, which is byte-for-byte identical to a required inject —\n * put 66.3 kB of `@angular/router` into every app that rendered the bar,\n * two thirds of what `ngwr/loading-bar` cost and 27× the component's own code.\n * Call `provideWrLoadingBarRouter()` from `ngwr/loading-bar/router` to have\n * navigations drive it.\n *\n * @example\n * ```ts\n * // From an HttpInterceptor:\n * const bar = inject(WrLoadingBar);\n * bar.start();\n * return next(req).pipe(finalize(() => bar.complete()));\n * ```\n *\n * @see https://ngwr.dev/reference/services/loading-bar\n */\n@Service()\nexport class WrLoadingBar {\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Live count of pending \"tasks\" (manual + router). */\n private readonly count = signal(0);\n\n /** Tween value `[0, 1]` — the bar's actual width. */\n private readonly _progress = signal(0);\n readonly progress: Signal<number> = this._progress.asReadonly();\n\n readonly state: Signal<WrLoadingState> = computed(() => {\n if (this.count() > 0) return 'running';\n return this._progress() > 0 ? 'completing' : 'idle';\n });\n\n private timer: ReturnType<typeof setInterval> | null = null;\n private holdTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** Reserve a slot. The bar starts trickling as long as `count > 0`. */\n start(): void {\n if (this.count() === 0) this.beginTrickle();\n this.count.update(v => v + 1);\n }\n\n /** Release a slot. When the last one closes, the bar fast-forwards to 100% then resets. */\n complete(): void {\n // Only a call that actually RELEASED something may finish. `Math.max` turns\n // a stray `complete()` into a no-op on the count, but the old code still\n // read the resulting zero as \"the last task just ended\" and flashed the bar\n // to 100% — reachable from a `finalize` that runs twice, or any\n // `complete()` after a `reset()`.\n if (this.count() === 0) return;\n this.count.update(v => v - 1);\n if (this.count() === 0) this.finish();\n }\n\n /** Cancel without animating the bar to 100%. Resets immediately. */\n reset(): void {\n this.count.set(0);\n this.stopTrickle();\n this.clearHold();\n this._progress.set(0);\n }\n\n // Internals\n\n private beginTrickle(): void {\n this.stopTrickle();\n this.clearHold();\n\n // Nothing paints during prerender, so there is nothing to animate. The\n // count still moves — a task really is in flight — but no 150 ms interval\n // is left spinning inside the render worker, and the serialized bar stays\n // at zero width whether or not the task finishes before serialization.\n if (!this.isBrowser) return;\n\n // Unconditionally, not `if (progress === 0)`. A task that starts inside the\n // 220 ms hold found progress at 1, kept it, and then trickled the WRONG WAY:\n // `p + (0.9 - p) * 0.08` approaches 0.9 from above, so the bar sat at ~99%\n // and crept backwards for the whole task. A redirecting route guard hits\n // that window on every navigation.\n this._progress.set(0.08);\n this.timer = setInterval(() => {\n const p = this._progress();\n // Approach 90% asymptotically — we want the user-visible final jump\n // to clearly indicate completion.\n const next = p + (0.9 - p) * 0.08;\n this._progress.set(next);\n }, 150);\n }\n\n private stopTrickle(): void {\n if (this.timer === null) return;\n clearInterval(this.timer);\n this.timer = null;\n }\n\n private clearHold(): void {\n if (this.holdTimer === null) return;\n clearTimeout(this.holdTimer);\n this.holdTimer = null;\n }\n\n private finish(): void {\n this.stopTrickle();\n this.clearHold();\n\n // Nothing animates during prerender and nothing will run the reset before\n // the HTML is serialized, so there is no 100% worth holding — go straight\n // to rest and leave the markup clean.\n if (!this.isBrowser) {\n this._progress.set(0);\n return;\n }\n\n this._progress.set(1);\n // Give the bar a moment to render the final 100% width before resetting.\n // The handle is kept so a later cycle's hold is not cut short by this\n // one's leftover timer.\n this.holdTimer = setTimeout(() => {\n this.holdTimer = null;\n if (this.count() === 0) this._progress.set(0);\n }, 220);\n }\n}\n\nexport type { WrLoadingState } from '../interfaces';\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, computed, inject, input } from '@angular/core';\n\nimport { WrLoadingBar } from './services/loading-bar';\n\n/**\n * Thin progress bar fixed to the top of the viewport.\n *\n * Drop one at the root of your shell. Reads the singleton {@link WrLoadingBar},\n * which is driven manually by `start()` / `complete()` — an HTTP interceptor, a\n * long save, anything with a beginning and an end.\n *\n * Router navigations drive it too, but only once you add\n * `provideWrLoadingBarRouter()` from `ngwr/loading-bar/router`. That is an opt-in\n * because the subscription is what pulls `@angular/router` into the bundle: this\n * component is 2.4 kB of its own code and used to carry 66 kB of router into\n * apps that never routed.\n *\n * @example\n * ```html\n * <wr-loading-bar /> <!-- top, primary color -->\n * <wr-loading-bar color=\"#9b51e0\" height=\"3px\" />\n * ```\n */\n@Component({\n selector: 'wr-loading-bar',\n templateUrl: './loading-bar.component.html',\n styleUrl: './loading-bar.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-loading-bar' },\n})\nexport class WrLoadingBarComponent {\n /** Bar colour. Defaults to the primary brand colour. */\n readonly color = input<string>('var(--wr-color-primary)');\n\n /** Bar height. */\n readonly height = input<string>('2px');\n\n protected readonly bar = inject(WrLoadingBar);\n\n protected readonly active = computed(() => this.bar.state() !== 'idle');\n}\n","<div class=\"wr-loading-bar__track\" [class.wr-loading-bar--active]=\"active()\">\n <div\n class=\"wr-loading-bar__fill\"\n [style.width.%]=\"bar.progress() * 100\"\n [style.background]=\"color()\"\n [style.height]=\"height()\"\n ></div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}