ngwr 14.0.0 → 14.1.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 (207) hide show
  1. package/README.md +57 -8
  2. package/aurora/aurora.scss +1 -1
  3. package/aurora/styles/_dark.scss +21 -0
  4. package/aurora/styles/_index.scss +11 -30
  5. package/aurora/styles/_rules.scss +25 -0
  6. package/border-glow/styles/_dark.scss +46 -0
  7. package/border-glow/styles/_index.scss +5 -0
  8. package/border-glow/styles/_rules.scss +0 -31
  9. package/button/styles/_index.scss +16 -0
  10. package/event-calendar/styles/_index.scss +15 -0
  11. package/fesm2022/ngwr-action-sheet.mjs +3 -3
  12. package/fesm2022/ngwr-affix.mjs +3 -3
  13. package/fesm2022/ngwr-alert.mjs +3 -3
  14. package/fesm2022/ngwr-anchor.mjs +3 -3
  15. package/fesm2022/ngwr-aurora.mjs +4 -4
  16. package/fesm2022/ngwr-aurora.mjs.map +1 -1
  17. package/fesm2022/ngwr-avatar.mjs +3 -3
  18. package/fesm2022/ngwr-back-top.mjs +3 -3
  19. package/fesm2022/ngwr-badge.mjs +6 -6
  20. package/fesm2022/ngwr-bar-chart.mjs +3 -3
  21. package/fesm2022/ngwr-blur-text.mjs +3 -3
  22. package/fesm2022/ngwr-border-glow.mjs +4 -4
  23. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  24. package/fesm2022/ngwr-breadcrumbs.mjs +7 -7
  25. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  26. package/fesm2022/ngwr-burger.mjs +3 -3
  27. package/fesm2022/ngwr-button.mjs +86 -8
  28. package/fesm2022/ngwr-button.mjs.map +1 -1
  29. package/fesm2022/ngwr-calendar-heatmap.mjs +3 -3
  30. package/fesm2022/ngwr-calendar.mjs +3 -3
  31. package/fesm2022/ngwr-card.mjs +9 -9
  32. package/fesm2022/ngwr-carousel.mjs +6 -6
  33. package/fesm2022/ngwr-cascader.mjs +4 -4
  34. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  35. package/fesm2022/ngwr-checkbox.mjs +6 -6
  36. package/fesm2022/ngwr-circular-text.mjs +3 -3
  37. package/fesm2022/ngwr-click-spark.mjs +3 -3
  38. package/fesm2022/ngwr-clipboard.mjs +3 -3
  39. package/fesm2022/ngwr-collapse.mjs +9 -9
  40. package/fesm2022/ngwr-color-picker.mjs +6 -6
  41. package/fesm2022/ngwr-command-palette.mjs +84 -7
  42. package/fesm2022/ngwr-command-palette.mjs.map +1 -1
  43. package/fesm2022/ngwr-compare.mjs +3 -3
  44. package/fesm2022/ngwr-confetti.mjs +3 -3
  45. package/fesm2022/ngwr-context-menu.mjs +12 -12
  46. package/fesm2022/ngwr-cookie.mjs +3 -3
  47. package/fesm2022/ngwr-counter.mjs +6 -6
  48. package/fesm2022/ngwr-date-adapters-fns.mjs +3 -3
  49. package/fesm2022/ngwr-date-adapters-luxon.mjs +3 -3
  50. package/fesm2022/ngwr-date-picker.mjs +18 -18
  51. package/fesm2022/ngwr-date.mjs +3 -3
  52. package/fesm2022/ngwr-decrypt-text.mjs +3 -3
  53. package/fesm2022/ngwr-density.mjs +6 -6
  54. package/fesm2022/ngwr-descriptions.mjs +6 -6
  55. package/fesm2022/ngwr-dialog.mjs +15 -15
  56. package/fesm2022/ngwr-directives.mjs +12 -12
  57. package/fesm2022/ngwr-divider.mjs +3 -3
  58. package/fesm2022/ngwr-donut-chart.mjs +3 -3
  59. package/fesm2022/ngwr-drag-drop.mjs +7 -7
  60. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  61. package/fesm2022/ngwr-drawer.mjs +18 -18
  62. package/fesm2022/ngwr-dropdown.mjs +28 -12
  63. package/fesm2022/ngwr-dropdown.mjs.map +1 -1
  64. package/fesm2022/ngwr-empty.mjs +3 -3
  65. package/fesm2022/ngwr-event-calendar.mjs +6 -6
  66. package/fesm2022/ngwr-falling-text.mjs +3 -3
  67. package/fesm2022/ngwr-file-upload.mjs +3 -3
  68. package/fesm2022/ngwr-form-testing.mjs +13 -7
  69. package/fesm2022/ngwr-form-testing.mjs.map +1 -1
  70. package/fesm2022/ngwr-form.mjs +44 -25
  71. package/fesm2022/ngwr-form.mjs.map +1 -1
  72. package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
  73. package/fesm2022/ngwr-gauge.mjs +3 -3
  74. package/fesm2022/ngwr-glitch-text.mjs +3 -3
  75. package/fesm2022/ngwr-gradient-text.mjs +3 -3
  76. package/fesm2022/ngwr-hotkey.mjs +6 -6
  77. package/fesm2022/ngwr-i18n-en.mjs +4 -0
  78. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  79. package/fesm2022/ngwr-i18n-ru.mjs +1 -0
  80. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  81. package/fesm2022/ngwr-i18n.mjs +9 -9
  82. package/fesm2022/ngwr-icon.mjs +6 -6
  83. package/fesm2022/ngwr-image-cropper.mjs +3 -3
  84. package/fesm2022/ngwr-input-number.mjs +3 -3
  85. package/fesm2022/ngwr-input-otp.mjs +3 -3
  86. package/fesm2022/ngwr-input.mjs +24 -20
  87. package/fesm2022/ngwr-input.mjs.map +1 -1
  88. package/fesm2022/ngwr-keyboard.mjs +3 -3
  89. package/fesm2022/ngwr-knob.mjs +3 -3
  90. package/fesm2022/ngwr-layout.mjs +15 -15
  91. package/fesm2022/ngwr-lightbox.mjs +4 -4
  92. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  93. package/fesm2022/ngwr-line-chart.mjs +3 -3
  94. package/fesm2022/ngwr-list.mjs +7 -7
  95. package/fesm2022/ngwr-list.mjs.map +1 -1
  96. package/fesm2022/ngwr-loading-bar.mjs +6 -6
  97. package/fesm2022/ngwr-markdown.mjs +6 -6
  98. package/fesm2022/ngwr-marquee.mjs +3 -3
  99. package/fesm2022/ngwr-media.mjs +3 -3
  100. package/fesm2022/ngwr-mention.mjs +6 -6
  101. package/fesm2022/ngwr-meta.mjs +6 -6
  102. package/fesm2022/ngwr-meter-group.mjs +3 -3
  103. package/fesm2022/ngwr-overlay.mjs +6 -6
  104. package/fesm2022/ngwr-page-header.mjs +3 -3
  105. package/fesm2022/ngwr-pagination.mjs +3 -3
  106. package/fesm2022/ngwr-pipes.mjs +21 -21
  107. package/fesm2022/ngwr-platform.mjs +9 -9
  108. package/fesm2022/ngwr-popconfirm.mjs +6 -6
  109. package/fesm2022/ngwr-popover.mjs +6 -6
  110. package/fesm2022/ngwr-progress.mjs +3 -3
  111. package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
  112. package/fesm2022/ngwr-qr.mjs +3 -3
  113. package/fesm2022/ngwr-radio.mjs +6 -6
  114. package/fesm2022/ngwr-rating.mjs +48 -22
  115. package/fesm2022/ngwr-rating.mjs.map +1 -1
  116. package/fesm2022/ngwr-result.mjs +12 -12
  117. package/fesm2022/ngwr-rotating-text.mjs +3 -3
  118. package/fesm2022/ngwr-scroll.mjs +3 -3
  119. package/fesm2022/ngwr-segmented.mjs +12 -8
  120. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  121. package/fesm2022/ngwr-select.mjs +125 -26
  122. package/fesm2022/ngwr-select.mjs.map +1 -1
  123. package/fesm2022/ngwr-shiny-text.mjs +7 -7
  124. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  125. package/fesm2022/ngwr-sidebar.mjs +3 -3
  126. package/fesm2022/ngwr-skeleton.mjs +3 -3
  127. package/fesm2022/ngwr-slider.mjs +12 -8
  128. package/fesm2022/ngwr-slider.mjs.map +1 -1
  129. package/fesm2022/ngwr-sparkline.mjs +3 -3
  130. package/fesm2022/ngwr-speed-dial.mjs +3 -3
  131. package/fesm2022/ngwr-spinner.mjs +3 -3
  132. package/fesm2022/ngwr-splash-cursor.mjs +3 -3
  133. package/fesm2022/ngwr-split-text.mjs +3 -3
  134. package/fesm2022/ngwr-splitter.mjs +3 -3
  135. package/fesm2022/ngwr-spotlight-card.mjs +7 -7
  136. package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
  137. package/fesm2022/ngwr-squircle.mjs +6 -6
  138. package/fesm2022/ngwr-star-border.mjs +4 -4
  139. package/fesm2022/ngwr-star-border.mjs.map +1 -1
  140. package/fesm2022/ngwr-statistic.mjs +9 -9
  141. package/fesm2022/ngwr-stepper.mjs +6 -6
  142. package/fesm2022/ngwr-storage.mjs +11 -3
  143. package/fesm2022/ngwr-storage.mjs.map +1 -1
  144. package/fesm2022/ngwr-switch.mjs +3 -3
  145. package/fesm2022/ngwr-table.mjs +18 -18
  146. package/fesm2022/ngwr-tabs-router.mjs +3 -3
  147. package/fesm2022/ngwr-tabs.mjs +6 -6
  148. package/fesm2022/ngwr-textarea.mjs +26 -5
  149. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  150. package/fesm2022/ngwr-theme.mjs +90 -4
  151. package/fesm2022/ngwr-theme.mjs.map +1 -1
  152. package/fesm2022/ngwr-tilt-card.mjs +7 -7
  153. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  154. package/fesm2022/ngwr-timeline.mjs +6 -6
  155. package/fesm2022/ngwr-toast.mjs +116 -11
  156. package/fesm2022/ngwr-toast.mjs.map +1 -1
  157. package/fesm2022/ngwr-toolbar.mjs +3 -3
  158. package/fesm2022/ngwr-tour.mjs +6 -6
  159. package/fesm2022/ngwr-transfer.mjs +3 -3
  160. package/fesm2022/ngwr-tree.mjs +18 -11
  161. package/fesm2022/ngwr-tree.mjs.map +1 -1
  162. package/fesm2022/ngwr-typewriter.mjs +3 -3
  163. package/fesm2022/ngwr-typography.mjs +3 -3
  164. package/fesm2022/ngwr-version.mjs +1 -1
  165. package/fesm2022/ngwr-version.mjs.map +1 -1
  166. package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
  167. package/fesm2022/ngwr-waves.mjs +4 -4
  168. package/fesm2022/ngwr-waves.mjs.map +1 -1
  169. package/fesm2022/ngwr-window.mjs +12 -12
  170. package/i18n/en.json +1 -0
  171. package/i18n/ru.json +1 -0
  172. package/llms-full.txt +9 -9
  173. package/llms.txt +82 -11
  174. package/package.json +1 -1
  175. package/shiny-text/styles/_dark.scss +20 -0
  176. package/shiny-text/styles/_index.scss +5 -0
  177. package/shiny-text/styles/_rules.scss +0 -4
  178. package/spotlight-card/styles/_dark.scss +21 -0
  179. package/spotlight-card/styles/_index.scss +5 -0
  180. package/spotlight-card/styles/_rules.scss +0 -6
  181. package/star-border/styles/_dark.scss +21 -0
  182. package/star-border/styles/_index.scss +5 -0
  183. package/star-border/styles/_rules.scss +0 -5
  184. package/theme/styles/_colors.scss +102 -0
  185. package/theme/styles/_focus.scss +31 -1
  186. package/tilt-card/styles/_dark.scss +21 -0
  187. package/tilt-card/styles/_index.scss +5 -0
  188. package/tilt-card/styles/_rules.scss +0 -5
  189. package/types/ngwr-button.d.ts +43 -0
  190. package/types/ngwr-command-palette.d.ts +59 -2
  191. package/types/ngwr-dropdown.d.ts +17 -1
  192. package/types/ngwr-form-testing.d.ts +13 -7
  193. package/types/ngwr-form.d.ts +47 -12
  194. package/types/ngwr-input.d.ts +2 -1
  195. package/types/ngwr-rating.d.ts +25 -4
  196. package/types/ngwr-segmented.d.ts +1 -0
  197. package/types/ngwr-select.d.ts +45 -9
  198. package/types/ngwr-slider.d.ts +1 -0
  199. package/types/ngwr-storage.d.ts +39 -1
  200. package/types/ngwr-textarea.d.ts +20 -0
  201. package/types/ngwr-theme.d.ts +92 -2
  202. package/types/ngwr-toast.d.ts +53 -0
  203. package/types/ngwr-tree.d.ts +3 -2
  204. package/types/ngwr-version.d.ts +1 -1
  205. package/waves/styles/_dark.scss +19 -0
  206. package/waves/styles/_index.scss +5 -0
  207. package/waves/styles/_rules.scss +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngwr",
3
- "version": "14.0.0",
3
+ "version": "14.1.0",
4
4
  "bin": {
5
5
  "ngwr-mcp": "mcp/server.js"
6
6
  },
@@ -0,0 +1,20 @@
1
+ // Dark theme.
2
+ //
3
+ // Keyed on `theme.dark-selector()`, never on a literal `[data-theme='dark']`.
4
+ // The attribute is renameable — `@use 'ngwr' with ($theme-attribute: '…')`
5
+ // alongside `provideWrTheme({ attribute: '…' })` — and a hand-written literal
6
+ // misses the rename in both directions: this block stops firing under the
7
+ // configured name, and it keeps firing under `data-theme`, which is the
8
+ // attribute a foreign design system owns and the rename exists to escape.
9
+ //
10
+ // It lives outside the `styleUrl` closure for the same reason. A component
11
+ // stylesheet is compiled when the LIBRARY is built, where `$theme-attribute` can
12
+ // only ever be the default, and Angular appends that copy after the app's linked
13
+ // sheet — so an inlined literal would not merely be dead, it would outrank the
14
+ // correctly-named rule beside it.
15
+ @use '../../theme/styles' as theme;
16
+
17
+ #{theme.dark-selector()} .wr-shiny-text {
18
+ --wr-shiny-text-base: #b5b5b5;
19
+ --wr-shiny-text-shine: #ffffff;
20
+ }
@@ -11,5 +11,10 @@
11
11
  // box-sizing reset into the compiled component styles, which ng-packagr then
12
12
  // embeds in the FESM bundle as a string. `shiny-text.scss` forwards `_rules`
13
13
  // instead, so it carries this component's own CSS and nothing else.
14
+ //
15
+ // `_dark` is loaded from HERE for a second reason: its selector is built out of
16
+ // `$theme-attribute`, which only a stylesheet compiled by the consuming app can
17
+ // configure, so a copy inside the bundle could only ever carry the default name.
14
18
  @use '../../theme/styles';
15
19
  @forward 'rules';
20
+ @forward 'dark';
@@ -95,7 +95,3 @@
95
95
  }
96
96
  }
97
97
 
98
- [data-theme='dark'] .wr-shiny-text {
99
- --wr-shiny-text-base: #b5b5b5;
100
- --wr-shiny-text-shine: #ffffff;
101
- }
@@ -0,0 +1,21 @@
1
+ // Dark theme: `--wr-color-on-surface-rgb` already flips to a light
2
+ // value, so the default tint becomes a light beam on the navy surface — nudge
3
+ // the alpha up a touch so it reads as bright as the classic spotlight.
4
+ //
5
+ // Keyed on `theme.dark-selector()`, never on a literal `[data-theme='dark']`.
6
+ // The attribute is renameable — `@use 'ngwr' with ($theme-attribute: '…')`
7
+ // alongside `provideWrTheme({ attribute: '…' })` — and a hand-written literal
8
+ // misses the rename in both directions: this block stops firing under the
9
+ // configured name, and it keeps firing under `data-theme`, which is the
10
+ // attribute a foreign design system owns and the rename exists to escape.
11
+ //
12
+ // It lives outside the `styleUrl` closure for the same reason. A component
13
+ // stylesheet is compiled when the LIBRARY is built, where `$theme-attribute` can
14
+ // only ever be the default, and Angular appends that copy after the app's linked
15
+ // sheet — so an inlined literal would not merely be dead, it would outrank the
16
+ // correctly-named rule beside it.
17
+ @use '../../theme/styles' as theme;
18
+
19
+ #{theme.dark-selector()} .wr-spotlight-card {
20
+ --wr-spotlight-color: rgba(var(--wr-color-on-surface-rgb), 0.22);
21
+ }
@@ -11,5 +11,10 @@
11
11
  // box-sizing reset into the compiled component styles, which ng-packagr then
12
12
  // embeds in the FESM bundle as a string. `spotlight-card.scss` forwards `_rules`
13
13
  // instead, so it carries this component's own CSS and nothing else.
14
+ //
15
+ // `_dark` is loaded from HERE for a second reason: its selector is built out of
16
+ // `$theme-attribute`, which only a stylesheet compiled by the consuming app can
17
+ // configure, so a copy inside the bundle could only ever carry the default name.
14
18
  @use '../../theme/styles';
15
19
  @forward 'rules';
20
+ @forward 'dark';
@@ -39,9 +39,3 @@
39
39
  }
40
40
  }
41
41
 
42
- // Dark mode: `--wr-color-on-surface-rgb` already flips to a light value, so the
43
- // default tint above becomes a light beam on the navy surface — nudge the
44
- // alpha up a touch so it reads as bright as the classic spotlight.
45
- [data-theme='dark'] .wr-spotlight-card {
46
- --wr-spotlight-color: rgba(var(--wr-color-on-surface-rgb), 0.22);
47
- }
@@ -0,0 +1,21 @@
1
+ // Dark theme.
2
+ //
3
+ // Keyed on `theme.dark-selector()`, never on a literal `[data-theme='dark']`.
4
+ // The attribute is renameable — `@use 'ngwr' with ($theme-attribute: '…')`
5
+ // alongside `provideWrTheme({ attribute: '…' })` — and a hand-written literal
6
+ // misses the rename in both directions: this block stops firing under the
7
+ // configured name, and it keeps firing under `data-theme`, which is the
8
+ // attribute a foreign design system owns and the rename exists to escape.
9
+ //
10
+ // It lives outside the `styleUrl` closure for the same reason. A component
11
+ // stylesheet is compiled when the LIBRARY is built, where `$theme-attribute` can
12
+ // only ever be the default, and Angular appends that copy after the app's linked
13
+ // sheet — so an inlined literal would not merely be dead, it would outrank the
14
+ // correctly-named rule beside it.
15
+ @use '../../theme/styles' as theme;
16
+
17
+ #{theme.dark-selector()} .wr-star-border {
18
+ --wr-star-border-color: #ffffff;
19
+ --wr-star-border-stop: 10%;
20
+ --wr-star-border-opacity: 0.7;
21
+ }
@@ -11,5 +11,10 @@
11
11
  // box-sizing reset into the compiled component styles, which ng-packagr then
12
12
  // embeds in the FESM bundle as a string. `star-border.scss` forwards `_rules`
13
13
  // instead, so it carries this component's own CSS and nothing else.
14
+ //
15
+ // `_dark` is loaded from HERE for a second reason: its selector is built out of
16
+ // `$theme-attribute`, which only a stylesheet compiled by the consuming app can
17
+ // configure, so a copy inside the bundle could only ever carry the default name.
14
18
  @use '../../theme/styles';
15
19
  @forward 'rules';
20
+ @forward 'dark';
@@ -82,11 +82,6 @@
82
82
  }
83
83
  }
84
84
 
85
- [data-theme='dark'] .wr-star-border {
86
- --wr-star-border-color: #ffffff;
87
- --wr-star-border-stop: 10%;
88
- --wr-star-border-opacity: 0.7;
89
- }
90
85
 
91
86
  @keyframes wr-star-border-bottom {
92
87
  0% {
@@ -8,6 +8,38 @@
8
8
  // @use 'ngwr/theme/styles/colors' with (
9
9
  // $base-colors: (primary: #...)
10
10
  // );
11
+ //
12
+ // **This is the LIGHT palette, and configuring it does not reach the dark
13
+ // theme.** `_dark.scss` re-tunes the intents by hand for the dark canvas and
14
+ // hard-codes what it lands on, so an app that seeds an indigo `primary` here
15
+ // gets indigo in light and the shipped `#3567ff` in dark, with nothing said
16
+ // about it — which is exactly how it reads as a bug rather than a boundary.
17
+ //
18
+ // It is a boundary, and the deriving is what could not be shipped. The dark
19
+ // intents are not the light ones lightened: `success` and `danger` are moved
20
+ // 33% and 21% to clear the black/white flip point on a dark ground, `warning`
21
+ // is deliberately left alone because white cannot reach it at any tone worth
22
+ // shipping, and `primary` is deepened until its label can go white — a
23
+ // direction no formula would take on its own. A `color.scale()` applied to an
24
+ // arbitrary seed reproduces none of that; what it reproduces is the class of
25
+ // failure the hand-tuning exists to prevent, silently, in the theme where
26
+ // nobody looks first. Deriving the dark palette from the seed would trade a
27
+ // visible wrong colour for an invisible failing contrast ratio, and that is
28
+ // the worse of the two.
29
+ //
30
+ // So carry a rebrand across yourself, with the same arithmetic the light
31
+ // palette uses. `rebrand()` below is that arithmetic:
32
+ //
33
+ // @use 'ngwr/theme' as theme;
34
+ //
35
+ // @include theme.dark {
36
+ // @include theme.rebrand((primary: #818cf8));
37
+ // }
38
+ //
39
+ // Pick the dark seed for the dark canvas rather than reusing the light one, and
40
+ // measure it — `pnpm check:contrast` in this repo, axe or any contrast checker
41
+ // in yours. `contrast()` is exported for the same reason: it tells you which
42
+ // label a candidate fill will get before you ship it.
11
43
 
12
44
  // Five intents are deliberately deeper than their "brand" tone, and the reason is
13
45
  // arithmetic rather than taste. `_contrast()` picks whichever of black / white
@@ -319,6 +351,76 @@ $contrast-light: #ffffff !default;
319
351
  --wr-color-dark-ink: var(--wr-color-dark);
320
352
  }
321
353
 
354
+ /// Re-emit an intent's WHOLE token set on the current selector, from one seed.
355
+ ///
356
+ /// `$colors` is a map of intent name to base colour, keyed by the same nine
357
+ /// names as `$base-colors`. Anything else is an `@error`: a typo would otherwise
358
+ /// emit a family of `--wr-color-brnad-*` properties that resolve for nothing.
359
+ ///
360
+ /// **This is the supported way to recolour a SUBTREE, and hand-writing the three
361
+ /// obvious properties is not.** Setting `--wr-color-primary`, its `-rgb` and its
362
+ /// `-contrast` on a `.marketing` wrapper looks like it should be enough, and the
363
+ /// guide used to say so. It is not, for two separate reasons, and each hits a
364
+ /// different half of the set:
365
+ ///
366
+ /// - `-dark`, `-darker`, `-light` and `-lighter` are `color.adjust()` — Sass
367
+ /// arithmetic, resolved when the stylesheet is COMPILED. No runtime value can
368
+ /// feed them, so a pink subtree kept a blue `:hover`, which is `-dark`.
369
+ /// - `-soft`, `-soft-border`, `-soft-contrast`, `-active` and `-ink` are written
370
+ /// in terms of `var()`, which is the mechanism that lets them re-derive — but
371
+ /// a custom property's references are substituted ON THE ELEMENT THAT DECLARES
372
+ /// IT, and these are declared at `:root`. What inherits into the subtree is
373
+ /// the substituted literal, so an outlined button inside a pink section drew a
374
+ /// pink border around blue text: `-ink` had been frozen two levels up. It is
375
+ /// the same trap `derived()` documents for a scoped dark block, arriving by a
376
+ /// different door.
377
+ ///
378
+ /// So the mixin does both halves: it recomputes the four shades and the contrast
379
+ /// pick in Sass, then re-includes `derived()` so every composed token resolves
380
+ /// against the bases on THIS element.
381
+ ///
382
+ /// ```scss
383
+ /// @use 'ngwr/theme' as theme;
384
+ ///
385
+ /// .marketing {
386
+ /// @include theme.rebrand((primary: #be123c));
387
+ /// }
388
+ /// ```
389
+ ///
390
+ /// It is compile-time, so the seed has to be known when the app's stylesheet is
391
+ /// built. For a colour chosen at runtime — a tenant, a builder, a preset picked
392
+ /// from a registry — reach for `wrThemeTokens()` from `ngwr/theme` instead; it
393
+ /// is the same recipe in TypeScript.
394
+ ///
395
+ /// The dark theme needs the same treatment and does not get it for free: see the
396
+ /// note on `$base-colors` for why compile-time configuration is light-only, and
397
+ /// wrap this in `theme.dark` to carry a rebrand across.
398
+ @mixin rebrand($colors) {
399
+ @each $name, $base in $colors {
400
+ @if not map.has-key($base-colors, $name) {
401
+ @error 'rebrand(): unknown intent `#{$name}`. Expected one of: #{map.keys($base-colors)}.';
402
+ }
403
+
404
+ --wr-color-#{$name}: #{$base};
405
+ --wr-color-#{$name}-rgb: #{_rgb-channels($base)};
406
+ --wr-color-#{$name}-contrast: #{_contrast($base)};
407
+ --wr-color-#{$name}-dark: #{color.adjust($base, $lightness: -5%)};
408
+ --wr-color-#{$name}-darker: #{color.adjust($base, $lightness: -10%)};
409
+ // unused-ok: `-light` and `-lighter`, for the reason their `:root` twins
410
+ // carry — hover and active DEEPEN an intent, so the catalog reads `-dark` /
411
+ // `-darker` and never these two, and a shade set with holes in it is worse
412
+ // than one half of which is for consumers. They are here rather than only
413
+ // there because a rebrand has to emit the SAME set the `:root` loop does: a
414
+ // subtree that redefines four shades out of six leaves a consumer's own
415
+ // `var(--wr-color-primary-lighter)` on the page palette, which is the
416
+ // half-recoloured subtree this mixin exists to end.
417
+ --wr-color-#{$name}-light: #{color.adjust($base, $lightness: 5%)};
418
+ --wr-color-#{$name}-lighter: #{color.adjust($base, $lightness: 10%)};
419
+ }
420
+
421
+ @include derived;
422
+ }
423
+
322
424
  :root {
323
425
  // Declared here so the light token set also claims the light UA scheme.
324
426
  // Without it the browser fell back to the OS preference, and an app running
@@ -18,6 +18,35 @@
18
18
  box-shadow: 0 0 0 var(--wr-focus-ring-offset) var(--wr-focus-ring-halo);
19
19
  }
20
20
 
21
+ // Button — `wr-btn`, `button[wr-btn]` and `a[wr-btn]` all carry `.wr-btn` on the
22
+ // element that takes focus, so one rule covers all three forms, and with them
23
+ // every control BUILT out of a button: the pagination page cells and its prev /
24
+ // next arrows are `<wr-btn>` elements with a `.wr-pagination__*` class beside
25
+ // this one.
26
+ //
27
+ // It was missing until v14.1, and the button is the widest hole that could have
28
+ // been left: `--wr-focus-ring-*` is documented as "retheme the ring once and
29
+ // every control follows", and rethemed the ring on nine components while the
30
+ // most-used control in the catalog kept the UA outline — which no token
31
+ // reaches, does not honour `--wr-focus-ring-width`, and renders differently in
32
+ // every browser. The rule sits here rather than in `button/styles` because this
33
+ // file IS the ring's home; a second hand-rolled copy is what the mixin exists to
34
+ // prevent.
35
+ //
36
+ // Safe as an OUTSET ring: `.wr-btn` sets no `overflow`, and the one place
37
+ // buttons are packed edge to edge — `.wr-btn-group` — already raises the focused
38
+ // segment with `z-index: 1`, so the ring paints over its neighbours instead of
39
+ // under them.
40
+ .wr-btn:focus-visible {
41
+ @include focus-ring;
42
+ }
43
+
44
+ // Table column-sort control — a bare `<button>` inside `.wr-table__th-inner`,
45
+ // with no focus style of its own.
46
+ .wr-table__sort-btn:focus-visible {
47
+ @include focus-ring;
48
+ }
49
+
21
50
  // Tabs — `.wr-tabs__strip` is `overflow-y: hidden` (to suppress a stray
22
51
  // vertical scrollbar) and its box is exactly the tab's height, so an outset
23
52
  // ring loses both horizontal strokes and renders as two disconnected bars.
@@ -102,7 +131,8 @@
102
131
  @include focus-ring;
103
132
  }
104
133
 
105
- // Pagination ellipsis is non-interactive; page buttons inherit wr-btn focus.
134
+ // Pagination ellipsis is non-interactive; page buttons take the `.wr-btn` rule
135
+ // at the top of this file, since that is what they are.
106
136
 
107
137
  // Dropdown menu items
108
138
  .wr-dropdown-item:focus-visible {
@@ -0,0 +1,21 @@
1
+ // Dark theme.
2
+ //
3
+ // Keyed on `theme.dark-selector()`, never on a literal `[data-theme='dark']`.
4
+ // The attribute is renameable — `@use 'ngwr' with ($theme-attribute: '…')`
5
+ // alongside `provideWrTheme({ attribute: '…' })` — and a hand-written literal
6
+ // misses the rename in both directions: this block stops firing under the
7
+ // configured name, and it keeps firing under `data-theme`, which is the
8
+ // attribute a foreign design system owns and the rename exists to escape.
9
+ //
10
+ // It lives outside the `styleUrl` closure for the same reason. A component
11
+ // stylesheet is compiled when the LIBRARY is built, where `$theme-attribute` can
12
+ // only ever be the default, and Angular appends that copy after the app's linked
13
+ // sheet — so an inlined literal would not merely be dead, it would outrank the
14
+ // correctly-named rule beside it.
15
+ @use '../../theme/styles' as theme;
16
+
17
+ #{theme.dark-selector()} .wr-tilt-glare {
18
+ --wr-tilt-glare-color: rgb(255 255 255 / 25%);
19
+
20
+ mix-blend-mode: overlay;
21
+ }
@@ -11,5 +11,10 @@
11
11
  // box-sizing reset into the compiled component styles, which ng-packagr then
12
12
  // embeds in the FESM bundle as a string. `tilt-card.scss` forwards `_rules`
13
13
  // instead, so it carries this component's own CSS and nothing else.
14
+ //
15
+ // `_dark` is loaded from HERE for a second reason: its selector is built out of
16
+ // `$theme-attribute`, which only a stylesheet compiled by the consuming app can
17
+ // configure, so a copy inside the bundle could only ever carry the default name.
14
18
  @use '../../theme/styles';
15
19
  @forward 'rules';
20
+ @forward 'dark';
@@ -33,8 +33,3 @@
33
33
  mix-blend-mode: multiply;
34
34
  }
35
35
 
36
- [data-theme='dark'] .wr-tilt-glare {
37
- --wr-tilt-glare-color: rgb(255 255 255 / 25%);
38
-
39
- mix-blend-mode: overlay;
40
- }
@@ -65,6 +65,16 @@ type WrButtonIconPosition = 'start' | 'end';
65
65
  * every child — child `[shape]` inputs are ignored so the group reads as
66
66
  * one coherent control.
67
67
  *
68
+ * **Inside a `<form>`, use the native form.** `<wr-btn>` is a custom element
69
+ * carrying `role="button"`, and no custom element submits a form — the platform
70
+ * submits for `<button>`, `<input type="submit">` and `<input type="image">`
71
+ * and nothing else. That is why there is no `type` input: it would look like it
72
+ * worked. Implicit submission (Enter in a text field) does not reach it either.
73
+ *
74
+ * ```html
75
+ * <button wr-btn type="submit" color="primary">Save</button>
76
+ * ```
77
+ *
68
78
  * @see https://ngwr.dev/reference/components/button
69
79
  */
70
80
  declare class WrButton {
@@ -162,6 +172,39 @@ declare class WrButton {
162
172
  * `pointer-events: none`.
163
173
  */
164
174
  protected onHostKeydown(event: KeyboardEvent): void;
175
+ private readonly injector;
176
+ /**
177
+ * Whether this button is the one the caret was on, so it can be handed back
178
+ * the focus that goes missing while it is off.
179
+ *
180
+ * A control that becomes `disabled` while focused stops being a focusable
181
+ * area, and the browser runs the unfocusing steps: nothing else is nominated,
182
+ * so the caret lands on `<body>`. With `disabledWhenLoading` on by default
183
+ * that happens on EVERY async button — a keyboard user presses Enter on
184
+ * "Save" and the next Tab restarts from the top of the document, while a
185
+ * screen-reader user is told neither that the wait began nor that it ended,
186
+ * because the element they were on has left the a11y tree. The `<wr-btn>`
187
+ * element form loses its `tabindex` instead of gaining `disabled` and is
188
+ * unfocused for the same reason.
189
+ *
190
+ * Deliberately NOT keyed on reading `activeElement` when `isOff()` flips: an
191
+ * effect in this component's own view runs AFTER the parent has applied this
192
+ * directive's host bindings, so by then the attribute has landed and the
193
+ * browser has already moved the caret — the read would answer `<body>` on
194
+ * every real browser and the button on jsdom, which is the worst of both.
195
+ *
196
+ * The flag is written by the focus listeners instead, and a `blur` fired
197
+ * WHILE off keeps it set rather than clearing it: that blur is the browser
198
+ * taking the caret away, not the user leaving. A browser that fires no blur
199
+ * on disable at all is covered too — the flag simply stays as `focus` left it,
200
+ * and the `<body>` test below is what decides whether the focus is orphaned.
201
+ */
202
+ private hadFocus;
203
+ /** @internal Host `focus` — see `hadFocus`. */
204
+ protected onHostFocus(): void;
205
+ /** @internal Host `blur` — see `hadFocus`. */
206
+ protected onHostBlur(): void;
207
+ constructor();
165
208
  protected readonly classes: _angular_core.Signal<string>;
166
209
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrButton, never>;
167
210
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrButton, "wr-btn, button[wr-btn], a[wr-btn]", never, { "color": { "alias": "color"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "iconPosition": { "alias": "iconPosition"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "outlined": { "alias": "outlined"; "required": false; "isSignal": true; }; "block": { "alias": "block"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "disabledWhenLoading": { "alias": "disabledWhenLoading"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
@@ -69,8 +69,14 @@ declare class WrCommandPalette {
69
69
  readonly placeholder: _angular_core.InputSignal<string | null>;
70
70
  /** Text shown when no items match. Falls back to `commandPalette.noResults`. */
71
71
  readonly emptyText: _angular_core.InputSignal<string | null>;
72
+ /**
73
+ * Overrides the `commandPalette.loading` catalog string, shown instead of
74
+ * {@link emptyText} while {@link loading} is true.
75
+ */
76
+ readonly loadingText: _angular_core.InputSignal<string | null>;
72
77
  protected readonly resolvedPlaceholder: _angular_core.Signal<string>;
73
78
  protected readonly resolvedEmpty: _angular_core.Signal<string>;
79
+ protected readonly resolvedLoading: _angular_core.Signal<string>;
74
80
  /**
75
81
  * The key-cap chip beside the search field. It was the literal `esc` in the
76
82
  * template — the one string on this component no catalog could reach, so a
@@ -89,7 +95,47 @@ declare class WrCommandPalette {
89
95
  readonly responsive: _angular_core.InputSignalWithTransform<boolean | undefined, BooleanInput>;
90
96
  /** Fires when the user commits an item (Enter / click). */
91
97
  readonly picked: _angular_core.OutputEmitterRef<WrCommandItem>;
92
- protected readonly query: _angular_core.WritableSignal<string>;
98
+ /**
99
+ * The search text. Two-way, so a host can seed it, clear it, or read what the
100
+ * user is looking for — the palette used to keep this to itself, which is what
101
+ * made it impossible to back with anything but a static `items` array.
102
+ *
103
+ * Writing it does NOT re-run a host's search on its own: the debounced
104
+ * {@link searchChange} output is what fires, on exactly the cadence
105
+ * {@link debounceMs} sets.
106
+ */
107
+ readonly query: _angular_core.ModelSignal<string>;
108
+ /**
109
+ * The item list is already scoped to the query upstream — skip the built-in
110
+ * client-side filter. Set it when `[items]` is fed from a server via the
111
+ * {@link searchChange} output.
112
+ *
113
+ * Without this, a backend that ranks or tolerates typos (returning rows whose
114
+ * labels do not literally contain the query) would have those rows hidden
115
+ * again on the client — the same rule `wr-select` follows, and the same
116
+ * reason. @default false
117
+ */
118
+ readonly serverSearch: _angular_core.InputSignalWithTransform<boolean, any>;
119
+ /**
120
+ * A host-driven search is in flight. Swaps the empty row for
121
+ * {@link loadingText} — an async palette that says "No results" between every
122
+ * keystroke and its answer is stating something false. @default false
123
+ */
124
+ readonly loading: _angular_core.InputSignalWithTransform<boolean, any>;
125
+ /**
126
+ * How long the query must settle before {@link searchChange} fires, in ms.
127
+ * A search request per keystroke is what exhausts a metered search backend:
128
+ * typing `select` is six requests at 0 and roughly one at 250. @default 250
129
+ */
130
+ readonly debounceMs: _angular_core.InputSignalWithTransform<number, unknown>;
131
+ /**
132
+ * The settled query. This is the hook for a server-backed palette: dispatch
133
+ * on it, then feed the result into `[items]` with `[serverSearch]` set.
134
+ *
135
+ * Fires on the {@link debounceMs} cadence, NOT per keystroke — `queryChange`
136
+ * is the per-keystroke signal if that is what you want.
137
+ */
138
+ readonly searchChange: _angular_core.OutputEmitterRef<string>;
93
139
  protected readonly activeIndex: _angular_core.WritableSignal<number>;
94
140
  /** Whether the current opening is presented full-screen. Decided on open. */
95
141
  protected readonly presentAsSheet: _angular_core.WritableSignal<boolean>;
@@ -124,6 +170,17 @@ declare class WrCommandPalette {
124
170
  * and holds it, so that is retained DOM, not just a stray object.
125
171
  */
126
172
  private destroyed;
173
+ /** Whether this palette filters `items` itself. */
174
+ protected readonly clientFilter: _angular_core.Signal<boolean>;
175
+ /**
176
+ * Nothing has been asked yet, so there is nothing to report as missing.
177
+ *
178
+ * A client-filtered palette cannot reach this state — an empty query matches
179
+ * every item, so the list is never empty on open. A server-backed one opens
180
+ * with no items by construction, and saying "No results" before a single
181
+ * keystroke answers a question nobody asked.
182
+ */
183
+ protected readonly awaitingQuery: _angular_core.Signal<boolean>;
127
184
  /** Grouped view — what the template renders. */
128
185
  protected readonly buckets: _angular_core.Signal<readonly Bucket[]>;
129
186
  /**
@@ -158,7 +215,7 @@ declare class WrCommandPalette {
158
215
  private revealActive;
159
216
  private commit;
160
217
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrCommandPalette, never>;
161
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCommandPalette, "wr-command-palette", never, { "paletteLabel": { "alias": "paletteLabel"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "closeOnPick": { "alias": "closeOnPick"; "required": false; "isSignal": true; }; "responsive": { "alias": "responsive"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "picked": "picked"; }, never, never, true, never>;
218
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCommandPalette, "wr-command-palette", never, { "paletteLabel": { "alias": "paletteLabel"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "loadingText": { "alias": "loadingText"; "required": false; "isSignal": true; }; "closeOnPick": { "alias": "closeOnPick"; "required": false; "isSignal": true; }; "responsive": { "alias": "responsive"; "required": false; "isSignal": true; }; "query": { "alias": "query"; "required": false; "isSignal": true; }; "serverSearch": { "alias": "serverSearch"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "debounceMs": { "alias": "debounceMs"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "picked": "picked"; "query": "queryChange"; "searchChange": "searchChange"; }, never, never, true, never>;
162
219
  }
163
220
 
164
221
  export { WrCommandPalette };
@@ -80,14 +80,30 @@ declare class WrDropdown {
80
80
  private readonly destroyRef;
81
81
  /** @internal Public so host bindings can read it. */
82
82
  readonly isOpen: _angular_core.WritableSignal<boolean>;
83
+ /** The id used only when the consumer's element carries none of its own. */
84
+ private readonly fallbackId;
83
85
  /**
84
86
  * Id the menu names itself after. The trigger is the CONSUMER's own element, so
85
87
  * an `id` they already put there wins: overwriting it broke `<label for>`,
86
88
  * `document.getElementById` and any `aria-labelledby` aimed at that button from
87
89
  * elsewhere in their app. The generated one is only a fallback so the menu has
88
90
  * something to reference.
91
+ *
92
+ * A GETTER, read fresh by the host binding on every pass, rather than a field
93
+ * resolved in the constructor. A static `id="…"` is on the element before a
94
+ * directive is instantiated and so was already honoured; a BOUND `[id]` is
95
+ * not — it lands with the rest of the template's bindings, which run before
96
+ * host bindings but after construction, so the constructor read an empty
97
+ * string, fell through to the fallback, and the host binding then wrote that
98
+ * fallback over the value the template had just set. Everything bound was
99
+ * lost — `[id]`, `[attr.id]` and an interpolated `id="dd-{{ n }}"` alike —
100
+ * while the static form survived, which is the shape that makes it look like
101
+ * a naming convention rather than a defect.
102
+ *
103
+ * `.id` rather than `getAttribute`: it is always a string, and an empty one is
104
+ * no id at all, so it falls through to the fallback.
89
105
  */
90
- protected readonly triggerId: string;
106
+ protected get triggerId(): string;
91
107
  private overlayRef;
92
108
  constructor();
93
109
  /** Open the menu. */
@@ -169,8 +169,9 @@ declare class WrFormFieldHarness extends ContentContainerComponentHarness {
169
169
  *
170
170
  * `null` does not mean the consumer set no hint: the error block takes the
171
171
  * hint's slot outright, so a field with a visible message has no hint in the DOM
172
- * at all. Note also that the hint carries no id and is NOT referenced by the
173
- * control's `aria-describedby` — see {@link getAnnouncedDescription}.
172
+ * at all — which is also why the hint's id stops being published at the same
173
+ * moment. Whether the CONTROL announces it is a separate question, and one this
174
+ * harness answers through {@link getAnnouncedDescription}.
174
175
  */
175
176
  getHint(): Promise<string | null>;
176
177
  /**
@@ -243,11 +244,16 @@ declare class WrFormFieldHarness extends ContentContainerComponentHarness {
243
244
  /**
244
245
  * The ids the projected control names in `aria-describedby`, in order.
245
246
  *
246
- * The field points this at the error block and nothing else — the hint has no
247
- * id — so an empty list while a hint is showing is correct rather than a bug.
248
- * That is not an oversight to fix from a harness either: `[wrInput]` derives
249
- * `aria-invalid` from the same signal, so describing the hint would announce
250
- * every hinted field as invalid.
247
+ * Which id that is depends on the CONTROL, not on the field, and the answer is
248
+ * worth knowing before reading a result. A control that composes the pair with
249
+ * `useFormFieldAria()` names the error block while one is showing and the hint
250
+ * otherwise. A control that reads the field's `describedBy` on its own — the
251
+ * `[wrInput]` directive, `wr-segmented` and `wr-slider` — names the error block
252
+ * and nothing else, so an empty list beside a visible hint is that control's
253
+ * answer rather than a broken field.
254
+ *
255
+ * The two are separate because folding the hint into the one signal announced
256
+ * every hinted field as invalid: those three derive `aria-invalid` from it.
251
257
  */
252
258
  getDescribedByIds(): Promise<string[]>;
253
259
  /**