ngwr 15.2.0 → 15.4.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 (127) hide show
  1. package/alert/styles/_index.scss +13 -5
  2. package/bits/border-glow/styles/_rules.scss +60 -49
  3. package/bits/click-spark/click-spark.scss +5 -1
  4. package/bits/click-spark/styles/_index.scss +10 -15
  5. package/bits/click-spark/styles/_rules.scss +20 -0
  6. package/fesm2022/ngwr-alert.mjs +2 -2
  7. package/fesm2022/ngwr-alert.mjs.map +1 -1
  8. package/fesm2022/ngwr-bits-aurora.mjs +108 -52
  9. package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
  10. package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
  11. package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
  12. package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
  13. package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
  14. package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
  15. package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
  16. package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
  17. package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
  18. package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
  19. package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
  20. package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
  21. package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
  22. package/fesm2022/ngwr-bits-waves.mjs +10 -15
  23. package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
  24. package/fesm2022/ngwr-breadcrumbs.mjs +18 -2
  25. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  26. package/fesm2022/ngwr-hotkey.mjs +23 -1
  27. package/fesm2022/ngwr-hotkey.mjs.map +1 -1
  28. package/fesm2022/ngwr-i18n-ar.mjs +2 -0
  29. package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
  30. package/fesm2022/ngwr-i18n-cs.mjs +2 -0
  31. package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
  32. package/fesm2022/ngwr-i18n-de.mjs +2 -0
  33. package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
  34. package/fesm2022/ngwr-i18n-en.mjs +2 -0
  35. package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
  36. package/fesm2022/ngwr-i18n-es.mjs +2 -0
  37. package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
  38. package/fesm2022/ngwr-i18n-fr.mjs +2 -0
  39. package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
  40. package/fesm2022/ngwr-i18n-he.mjs +2 -0
  41. package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
  42. package/fesm2022/ngwr-i18n-hi.mjs +2 -0
  43. package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
  44. package/fesm2022/ngwr-i18n-id.mjs +2 -0
  45. package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
  46. package/fesm2022/ngwr-i18n-it.mjs +2 -0
  47. package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
  48. package/fesm2022/ngwr-i18n-ja.mjs +2 -0
  49. package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
  50. package/fesm2022/ngwr-i18n-ko.mjs +2 -0
  51. package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
  52. package/fesm2022/ngwr-i18n-nl.mjs +2 -0
  53. package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
  54. package/fesm2022/ngwr-i18n-pl.mjs +2 -0
  55. package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
  56. package/fesm2022/ngwr-i18n-pt.mjs +2 -0
  57. package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
  58. package/fesm2022/ngwr-i18n-ru.mjs +2 -0
  59. package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
  60. package/fesm2022/ngwr-i18n-sv.mjs +2 -0
  61. package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
  62. package/fesm2022/ngwr-i18n-tr.mjs +2 -0
  63. package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
  64. package/fesm2022/ngwr-i18n-uk.mjs +2 -0
  65. package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
  66. package/fesm2022/ngwr-i18n-vi.mjs +2 -0
  67. package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
  68. package/fesm2022/ngwr-i18n-zh-tw.mjs +2 -0
  69. package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
  70. package/fesm2022/ngwr-i18n-zh.mjs +2 -0
  71. package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
  72. package/fesm2022/ngwr-pagination.mjs +3 -0
  73. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  74. package/fesm2022/ngwr-pipes.mjs +163 -49
  75. package/fesm2022/ngwr-pipes.mjs.map +1 -1
  76. package/fesm2022/ngwr-schema-form.mjs +3 -0
  77. package/fesm2022/ngwr-schema-form.mjs.map +1 -1
  78. package/fesm2022/ngwr-select-testing.mjs +16 -0
  79. package/fesm2022/ngwr-select-testing.mjs.map +1 -1
  80. package/fesm2022/ngwr-select.mjs +218 -38
  81. package/fesm2022/ngwr-select.mjs.map +1 -1
  82. package/fesm2022/ngwr-statistic.mjs +20 -21
  83. package/fesm2022/ngwr-statistic.mjs.map +1 -1
  84. package/fesm2022/ngwr-version.mjs +1 -1
  85. package/fesm2022/ngwr-version.mjs.map +1 -1
  86. package/i18n/ar.json +2 -0
  87. package/i18n/cs.json +2 -0
  88. package/i18n/de.json +2 -0
  89. package/i18n/en.json +2 -0
  90. package/i18n/es.json +2 -0
  91. package/i18n/fr.json +2 -0
  92. package/i18n/he.json +2 -0
  93. package/i18n/hi.json +2 -0
  94. package/i18n/id.json +2 -0
  95. package/i18n/it.json +2 -0
  96. package/i18n/ja.json +2 -0
  97. package/i18n/ko.json +2 -0
  98. package/i18n/nl.json +2 -0
  99. package/i18n/pl.json +2 -0
  100. package/i18n/pt.json +2 -0
  101. package/i18n/ru.json +2 -0
  102. package/i18n/sv.json +2 -0
  103. package/i18n/tr.json +2 -0
  104. package/i18n/uk.json +2 -0
  105. package/i18n/vi.json +2 -0
  106. package/i18n/zh-TW.json +2 -0
  107. package/i18n/zh.json +2 -0
  108. package/llms-full.txt +5 -5
  109. package/package.json +1 -1
  110. package/schematics/use/symbol-map.json +2 -0
  111. package/select/styles/_index.scss +23 -1
  112. package/skills/ngwr/SKILL.md +3 -1
  113. package/skills/ngwr/references/catalog.md +1 -1
  114. package/types/ngwr-alert.d.ts +3 -1
  115. package/types/ngwr-bits-aurora.d.ts +24 -2
  116. package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
  117. package/types/ngwr-bits-border-glow.d.ts +10 -4
  118. package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
  119. package/types/ngwr-bits-ripple-grid.d.ts +7 -2
  120. package/types/ngwr-bits-splash-cursor.d.ts +2 -1
  121. package/types/ngwr-breadcrumbs.d.ts +15 -0
  122. package/types/ngwr-hotkey.d.ts +14 -2
  123. package/types/ngwr-pipes.d.ts +142 -32
  124. package/types/ngwr-select-testing.d.ts +9 -0
  125. package/types/ngwr-select.d.ts +133 -2
  126. package/types/ngwr-sidebar.d.ts +1 -1
  127. package/types/ngwr-version.d.ts +1 -1
@@ -108,7 +108,10 @@ $alert-colors: (
108
108
  line-height: var(--wr-alert-title-line-height);
109
109
  }
110
110
 
111
- &__message {
111
+ // Projected content reads as the message does. Hidden while empty, so an alert
112
+ // with nothing inside keeps the body's gap to what it had.
113
+ &__message,
114
+ &__content {
112
115
  margin: 0;
113
116
  color: var(--wr-alert-message);
114
117
  font-family: var(--wr-alert-font-family);
@@ -116,6 +119,10 @@ $alert-colors: (
116
119
  line-height: var(--wr-alert-message-line-height);
117
120
  }
118
121
 
122
+ &__content:empty {
123
+ display: none;
124
+ }
125
+
119
126
  &__close {
120
127
  flex: 0 0 auto;
121
128
  appearance: none;
@@ -136,11 +143,12 @@ $alert-colors: (
136
143
  // `font: inherit`. Inheriting the size makes `1em` the alert's own text in
137
144
  // every app. `--wr-icon-size` cannot do this job — only a `<wr-icon>` host
138
145
  // reads it, and the rule here used to set it for nothing. The SIZE only,
139
- // never the `font` shorthand, which would bring the line-height with it; and
140
- // no `line-height` of its own either, the way `wr-dialog` needs one — the
141
- // alert stretches its row, so this box is the alert's height whatever the
142
- // strut says.
146
+ // never the `font` shorthand, which would bring the line-height with it.
143
147
  font-size: inherit;
148
+ // Top of the row, by the title, rather than stretched to the alert's height:
149
+ // with projected content under the message, a stretched button put the ✕
150
+ // halfway down the alert and painted its hover tint as a full-height strip.
151
+ align-self: flex-start;
144
152
 
145
153
  @include theme.touch-target; // glyph -> 44px tap area on touch
146
154
 
@@ -2,12 +2,37 @@
2
2
  // All visual state is driven by two custom properties the component writes:
3
3
  // --wr-border-glow-edge-proximity 0..100, scales with pointer-to-edge distance
4
4
  // --wr-border-glow-angle 0deg..360deg, pointer-from-centre angle
5
- // Plus a colour/geometry bag set once from inputs (see border-glow.ts).
5
+ // Plus a colour/geometry bag set once from inputs (see border-glow.ts). The three
6
+ // colour inputs write an `--wr-border-glow-input-*` twin of the hook they stand
7
+ // in for, never the hook itself, and every read below prefers the twin.
6
8
 
7
9
  .wr-border-glow {
8
10
  --wr-border-glow-edge-proximity: 0;
9
11
  --wr-border-glow-angle: 45deg;
10
- // Theme-default halo (overridden inline when `[glowColor]` is set): a deep
12
+ // The card fill: the theme surface, unless an app sets this or `[backgroundColor]`.
13
+ --wr-border-glow-bg: var(--wr-color-surface);
14
+ // What `[backgroundColor]`, `[glowColor]` and `[colors]` write on the host, which
15
+ // the inline value beats. Unset here so a card projected inside another one does
16
+ // not inherit the outer card's inputs — and twins rather than the hooks' own
17
+ // names, so a `--wr-border-glow-bg` an author set on the host stays there and is
18
+ // read again the moment the input is unset.
19
+ --wr-border-glow-input-bg: initial;
20
+ --wr-border-glow-input-color: initial;
21
+ --wr-border-glow-input-color-60: initial;
22
+ --wr-border-glow-input-color-50: initial;
23
+ --wr-border-glow-input-color-40: initial;
24
+ --wr-border-glow-input-color-30: initial;
25
+ --wr-border-glow-input-color-20: initial;
26
+ --wr-border-glow-input-color-10: initial;
27
+ --wr-border-glow-input-gradient-one: initial;
28
+ --wr-border-glow-input-gradient-two: initial;
29
+ --wr-border-glow-input-gradient-three: initial;
30
+ --wr-border-glow-input-gradient-four: initial;
31
+ --wr-border-glow-input-gradient-five: initial;
32
+ --wr-border-glow-input-gradient-six: initial;
33
+ --wr-border-glow-input-gradient-seven: initial;
34
+ --wr-border-glow-input-gradient-base: initial;
35
+ // Theme-default halo (`[glowColor]` writes the `-input-` twins beside it): a deep
11
36
  // amber that reads on white, the pale pastel on dark.
12
37
  --wr-border-glow-color: light-dark(hsl(40deg 90% 45% / 100%), hsl(40deg 80% 80% / 100%));
13
38
  --wr-border-glow-color-60: light-dark(hsl(40deg 90% 45% / 60%), hsl(40deg 80% 80% / 60%));
@@ -16,8 +41,8 @@
16
41
  --wr-border-glow-color-30: light-dark(hsl(40deg 90% 45% / 30%), hsl(40deg 80% 80% / 30%));
17
42
  --wr-border-glow-color-20: light-dark(hsl(40deg 90% 45% / 20%), hsl(40deg 80% 80% / 20%));
18
43
  --wr-border-glow-color-10: light-dark(hsl(40deg 90% 45% / 10%), hsl(40deg 80% 80% / 10%));
19
- // Theme-default mesh palette (overridden inline when `[colors]` is set): deep
20
- // tones that read on white, the reactbits pastels on dark. The geometry is
44
+ // Theme-default mesh palette (`[colors]` writes the `-input-` twins beside it):
45
+ // deep tones that read on white, the reactbits pastels on dark. The geometry is
21
46
  // the same in both schemes, so only the one colour stop of each layer moves.
22
47
  --wr-border-glow-gradient-one: radial-gradient(
23
48
  at 80% 55%,
@@ -63,19 +88,19 @@
63
88
  // so an inline `[glowColor]` reaches every layer of it.
64
89
  /* stylelint-disable-next-line */
65
90
  --wr-border-glow-edge-shadow:
66
- inset 0 0 0 1px var(--wr-border-glow-color),
67
- inset 0 0 1px 0 light-dark(transparent, var(--wr-border-glow-color-60)),
68
- inset 0 0 3px 0 light-dark(transparent, var(--wr-border-glow-color-50)),
69
- inset 0 0 6px 0 light-dark(transparent, var(--wr-border-glow-color-40)),
70
- inset 0 0 15px 0 light-dark(transparent, var(--wr-border-glow-color-30)),
71
- inset 0 0 25px 2px light-dark(transparent, var(--wr-border-glow-color-20)),
72
- inset 0 0 50px 2px light-dark(transparent, var(--wr-border-glow-color-10)),
73
- 0 0 1px 0 var(--wr-border-glow-color-60),
74
- 0 0 3px 0 var(--wr-border-glow-color-50),
75
- 0 0 6px 0 var(--wr-border-glow-color-40),
76
- 0 0 15px 0 var(--wr-border-glow-color-30),
77
- 0 0 25px 2px var(--wr-border-glow-color-20),
78
- 0 0 50px 2px var(--wr-border-glow-color-10);
91
+ inset 0 0 0 1px var(--wr-border-glow-input-color, var(--wr-border-glow-color)),
92
+ inset 0 0 1px 0 light-dark(transparent, var(--wr-border-glow-input-color-60, var(--wr-border-glow-color-60))),
93
+ inset 0 0 3px 0 light-dark(transparent, var(--wr-border-glow-input-color-50, var(--wr-border-glow-color-50))),
94
+ inset 0 0 6px 0 light-dark(transparent, var(--wr-border-glow-input-color-40, var(--wr-border-glow-color-40))),
95
+ inset 0 0 15px 0 light-dark(transparent, var(--wr-border-glow-input-color-30, var(--wr-border-glow-color-30))),
96
+ inset 0 0 25px 2px light-dark(transparent, var(--wr-border-glow-input-color-20, var(--wr-border-glow-color-20))),
97
+ inset 0 0 50px 2px light-dark(transparent, var(--wr-border-glow-input-color-10, var(--wr-border-glow-color-10))),
98
+ 0 0 1px 0 var(--wr-border-glow-input-color-60, var(--wr-border-glow-color-60)),
99
+ 0 0 3px 0 var(--wr-border-glow-input-color-50, var(--wr-border-glow-color-50)),
100
+ 0 0 6px 0 var(--wr-border-glow-input-color-40, var(--wr-border-glow-color-40)),
101
+ 0 0 15px 0 var(--wr-border-glow-input-color-30, var(--wr-border-glow-color-30)),
102
+ 0 0 25px 2px var(--wr-border-glow-input-color-20, var(--wr-border-glow-color-20)),
103
+ 0 0 50px 2px var(--wr-border-glow-input-color-10, var(--wr-border-glow-color-10));
79
104
  // Blend modes and a strength are not colours, so `light-dark()` cannot switch
80
105
  // them, and each value below is the one that holds up in both schemes. The
81
106
  // additive look dark used — `plus-lighter` on the halo, `soft-light` on the
@@ -100,7 +125,7 @@
100
125
  position: relative;
101
126
  display: grid;
102
127
  border-radius: var(--wr-border-glow-radius);
103
- background: var(--wr-border-glow-bg, var(--wr-color-surface, #fff));
128
+ background: var(--wr-border-glow-input-bg, var(--wr-border-glow-bg));
104
129
  overflow: visible;
105
130
  isolation: isolate;
106
131
  transform: translate3d(0, 0, 0.01px); // create a new layer to keep blends tidy
@@ -137,23 +162,16 @@
137
162
  &::before {
138
163
  border: 1px solid transparent;
139
164
  background:
140
- linear-gradient(var(--wr-border-glow-bg, var(--wr-color-surface, #fff)) 0 100%) padding-box,
165
+ linear-gradient(var(--wr-border-glow-input-bg, var(--wr-border-glow-bg)) 0 100%) padding-box,
141
166
  linear-gradient(rgb(255 255 255 / 0%) 0 100%) border-box,
142
- var(--wr-border-glow-gradient-one, radial-gradient(at 80% 55%, hsl(268deg 100% 76%) 0px, transparent 50%))
143
- border-box,
144
- var(--wr-border-glow-gradient-two, radial-gradient(at 69% 34%, hsl(349deg 100% 74%) 0px, transparent 50%))
145
- border-box,
146
- var(--wr-border-glow-gradient-three, radial-gradient(at 8% 6%, hsl(136deg 100% 78%) 0px, transparent 50%))
147
- border-box,
148
- var(--wr-border-glow-gradient-four, radial-gradient(at 41% 38%, hsl(192deg 100% 64%) 0px, transparent 50%))
149
- border-box,
150
- var(--wr-border-glow-gradient-five, radial-gradient(at 86% 85%, hsl(186deg 100% 74%) 0px, transparent 50%))
151
- border-box,
152
- var(--wr-border-glow-gradient-six, radial-gradient(at 82% 18%, hsl(52deg 100% 65%) 0px, transparent 50%))
153
- border-box,
154
- var(--wr-border-glow-gradient-seven, radial-gradient(at 51% 4%, hsl(12deg 100% 72%) 0px, transparent 50%))
155
- border-box,
156
- var(--wr-border-glow-gradient-base, linear-gradient(#c299ff 0 100%)) border-box;
167
+ var(--wr-border-glow-input-gradient-one, var(--wr-border-glow-gradient-one)) border-box,
168
+ var(--wr-border-glow-input-gradient-two, var(--wr-border-glow-gradient-two)) border-box,
169
+ var(--wr-border-glow-input-gradient-three, var(--wr-border-glow-gradient-three)) border-box,
170
+ var(--wr-border-glow-input-gradient-four, var(--wr-border-glow-gradient-four)) border-box,
171
+ var(--wr-border-glow-input-gradient-five, var(--wr-border-glow-gradient-five)) border-box,
172
+ var(--wr-border-glow-input-gradient-six, var(--wr-border-glow-gradient-six)) border-box,
173
+ var(--wr-border-glow-input-gradient-seven, var(--wr-border-glow-gradient-seven)) border-box,
174
+ var(--wr-border-glow-input-gradient-base, var(--wr-border-glow-gradient-base)) border-box;
157
175
 
158
176
  opacity: calc(
159
177
  (var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-color-sensitivity)) /
@@ -173,21 +191,14 @@
173
191
  &::after {
174
192
  border: 1px solid transparent;
175
193
  background:
176
- var(--wr-border-glow-gradient-one, radial-gradient(at 80% 55%, hsl(268deg 100% 76%) 0px, transparent 50%))
177
- padding-box,
178
- var(--wr-border-glow-gradient-two, radial-gradient(at 69% 34%, hsl(349deg 100% 74%) 0px, transparent 50%))
179
- padding-box,
180
- var(--wr-border-glow-gradient-three, radial-gradient(at 8% 6%, hsl(136deg 100% 78%) 0px, transparent 50%))
181
- padding-box,
182
- var(--wr-border-glow-gradient-four, radial-gradient(at 41% 38%, hsl(192deg 100% 64%) 0px, transparent 50%))
183
- padding-box,
184
- var(--wr-border-glow-gradient-five, radial-gradient(at 86% 85%, hsl(186deg 100% 74%) 0px, transparent 50%))
185
- padding-box,
186
- var(--wr-border-glow-gradient-six, radial-gradient(at 82% 18%, hsl(52deg 100% 65%) 0px, transparent 50%))
187
- padding-box,
188
- var(--wr-border-glow-gradient-seven, radial-gradient(at 51% 4%, hsl(12deg 100% 72%) 0px, transparent 50%))
189
- padding-box,
190
- var(--wr-border-glow-gradient-base, linear-gradient(#c299ff 0 100%)) padding-box;
194
+ var(--wr-border-glow-input-gradient-one, var(--wr-border-glow-gradient-one)) padding-box,
195
+ var(--wr-border-glow-input-gradient-two, var(--wr-border-glow-gradient-two)) padding-box,
196
+ var(--wr-border-glow-input-gradient-three, var(--wr-border-glow-gradient-three)) padding-box,
197
+ var(--wr-border-glow-input-gradient-four, var(--wr-border-glow-gradient-four)) padding-box,
198
+ var(--wr-border-glow-input-gradient-five, var(--wr-border-glow-gradient-five)) padding-box,
199
+ var(--wr-border-glow-input-gradient-six, var(--wr-border-glow-gradient-six)) padding-box,
200
+ var(--wr-border-glow-input-gradient-seven, var(--wr-border-glow-gradient-seven)) padding-box,
201
+ var(--wr-border-glow-input-gradient-base, var(--wr-border-glow-gradient-base)) padding-box;
191
202
 
192
203
  mask-image:
193
204
  linear-gradient(to bottom, black, black), radial-gradient(ellipse at 50% 50%, black 40%, transparent 65%),
@@ -1 +1,5 @@
1
- @forward './styles';
1
+ // Component-scoped, but the lib uses `ViewEncapsulation.None` everywhere so
2
+ // the selector starts with `.wr-click-spark` — the same as the public Sass
3
+ // entry. Forwards the rules alone: `styles/_index.scss` is the entry that
4
+ // loads the theme, and a `styleUrl` is its own compilation.
5
+ @forward './styles/rules';
@@ -1,16 +1,11 @@
1
- // Click spark — canvas overlay that ignores pointer events so projected
2
- // children stay interactive.
3
- .wr-click-spark {
4
- position: relative;
5
- display: block;
1
+ // Public Sass entry for `@use 'ngwr/bits/click-spark'`.
2
+ //
3
+ // Loads the theme although `_rules` reads no token: the component strokes its
4
+ // sparks in `sparkColor`, whose default is `var(--wr-color-on-surface)` resolved
5
+ // against the canvas, so an app that writes this one `@use` still has the token
6
+ // the default paints with. `click-spark.scss` — the component's `styleUrl` —
7
+ // forwards `_rules` and nothing else, so the compiled component styles carry this
8
+ // component's own CSS and no second copy of the theme layer.
9
+ @use '../../../theme/styles';
6
10
 
7
- &__canvas {
8
- position: absolute;
9
- inset: 0;
10
- width: 100%;
11
- height: 100%;
12
- pointer-events: none;
13
- user-select: none;
14
- z-index: 1;
15
- }
16
- }
11
+ @forward 'rules';
@@ -0,0 +1,20 @@
1
+ // Click spark — canvas overlay that ignores pointer events so projected
2
+ // children stay interactive. The sparks themselves are stroked in
3
+ // `click-spark.ts`, in `sparkColor`, which defaults to `var(--wr-color-on-surface)`
4
+ // and is resolved against the canvas: the token is read from TypeScript rather than
5
+ // from a rule here, which is why `_index.scss` loads the theme for a sheet that
6
+ // names no token itself.
7
+ .wr-click-spark {
8
+ position: relative;
9
+ display: block;
10
+
11
+ &__canvas {
12
+ position: absolute;
13
+ inset: 0;
14
+ width: 100%;
15
+ height: 100%;
16
+ pointer-events: none;
17
+ user-select: none;
18
+ z-index: 1;
19
+ }
20
+ }
@@ -125,7 +125,7 @@ var WrAlert = class WrAlert {
125
125
  "attr.aria-live": "liveLevel()"
126
126
  } },
127
127
  ngImport: i0,
128
- template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n",
128
+ template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n",
129
129
  dependencies: [{
130
130
  kind: "component",
131
131
  type: WrIcon,
@@ -152,7 +152,7 @@ i0.ɵɵngDeclareClassMetadata({
152
152
  "[attr.role]": "liveRole()",
153
153
  "[attr.aria-live]": "liveLevel()"
154
154
  },
155
- template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"
155
+ template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"
156
156
  }]
157
157
  }],
158
158
  propDecorators: {
@@ -1 +1 @@
1
- {"version":3,"file":"ngwr-alert.mjs","names":[],"sources":["../../../projects/lib/alert/alert.ts","../../../projects/lib/alert/alert.html"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n Injector,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\nimport { getFocusableElements } from 'ngwr/utils';\n\nimport type { WrAlertType } from './types';\n\n/**\n * Inline status banner. Use for feedback messages — saved/failed/notice etc.\n *\n * @example\n * ```html\n * <wr-alert title=\"Saved\" message=\"Your changes are live.\" type=\"success\" />\n * <wr-alert title=\"Failed\" type=\"danger\" closable (closed)=\"onClose()\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/alert\n */\n@Component({\n selector: 'wr-alert',\n templateUrl: './alert.html',\n imports: [WrIcon],\n encapsulation: ViewEncapsulation.None,\n host: {\n // `title` is an input, and a static attribute that feeds an input is ALSO\n // written onto the host — where the browser shows it as a native tooltip\n // over the whole component, and where it feeds the accessible name of any\n // element that has a role. `<wr-alert title=\"Heads up\">` is the documented\n // spelling, so every page writing it shipped both. A bound `[title]` never\n // did this, which is why the two forms behaved differently for no reason a\n // reader could see.\n '[attr.title]': 'null',\n '[class]': 'classes()',\n '[attr.role]': 'liveRole()',\n '[attr.aria-live]': 'liveLevel()',\n },\n})\nexport class WrAlert {\n /** Accessible name. Falls back to `alert.close`, then `'Close alert'`. */\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'alert.close', 'Close alert');\n\n /**\n * Optional headline shown at the top of the alert.\n *\n * @default null\n */\n readonly title = input<string | null>(null);\n\n /**\n * Visual variant.\n *\n * @default 'info'\n */\n readonly type = input<WrAlertType>('info');\n\n /**\n * Override the default per-type icon with any ngwr icon name.\n *\n * @default null\n */\n readonly iconName = input<WrIconName | null>(null);\n\n /**\n * Optional secondary message rendered below the title.\n *\n * @default null\n */\n readonly message = input<string | null>(null);\n\n /**\n * When `true`, renders a leading status icon matching the `type`.\n * Pass `false` to hide. Ignored when `iconName` is set.\n *\n * @default true\n */\n readonly icon = input(true, { transform: coerceBooleanProperty });\n\n /**\n * When `true`, renders a close button.\n *\n * @default false\n */\n readonly closable = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Emitted when the user dismisses the alert via the close button. If the close\n * button had focus, focus moves on to the next focusable element after the alert\n * (or the one before it, when nothing follows) — unless a `(closed)` handler has\n * placed it somewhere itself.\n */\n readonly closed = output<void>();\n\n protected readonly dismissed = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-alert', `wr-alert--${this.type()}`];\n if (this.dismissed()) parts.push('wr-alert--dismissed');\n return parts.join(' ');\n });\n\n /** Escalate role/aria-live for danger/warning alerts. */\n protected readonly liveRole = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' ? 'alert' : 'status'\n );\n protected readonly liveLevel = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' || this.type() === 'warning' ? 'assertive' : 'polite'\n );\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n protected onClose(): void {\n const host = this.host.nativeElement;\n const hadFocus = host.contains(host.ownerDocument.activeElement);\n this.dismissed.set(true);\n this.closed.emit();\n if (hadFocus) afterNextRender(() => this.handOnFocus(), { injector: this.injector });\n }\n\n /**\n * The close button is removed with the rest of the content, and a focused element\n * that leaves the DOM drops the caret on `<body>`: a keyboard user's next Tab, and\n * a screen reader's reading position, started over from the top of the page. Run\n * after the render that removed it, so a `(closed)` handler that moved focus on\n * purpose is seen and left alone.\n */\n private handOnFocus(): void {\n const host = this.host.nativeElement;\n const doc = host.ownerDocument;\n const active = doc.activeElement;\n if (active && active !== doc.body) return;\n // Tab stops only: a roving composite parks its other items at -1. The host has\n // nothing focusable left inside it, so every stop is either before or after it.\n const stops = getFocusableElements(doc.body).filter(el => el.tabIndex >= 0);\n // eslint-disable-next-line no-bitwise -- compareDocumentPosition answers with a bitmask\n const after = stops.findIndex(el => (host.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0);\n (after >= 0 ? stops[after] : stops.at(-1))?.focus();\n }\n}\n","@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ngwr-alert.mjs","names":[],"sources":["../../../projects/lib/alert/alert.ts","../../../projects/lib/alert/alert.html"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n ElementRef,\n Injector,\n ViewEncapsulation,\n afterNextRender,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\nimport { getFocusableElements } from 'ngwr/utils';\n\nimport type { WrAlertType } from './types';\n\n/**\n * Inline status banner. Use for feedback messages — saved/failed/notice etc.\n * Content placed inside renders below the message, in `.wr-alert__content`.\n *\n * @example\n * ```html\n * <wr-alert title=\"Saved\" message=\"Your changes are live.\" type=\"success\" />\n * <wr-alert title=\"Failed\" type=\"danger\" closable (closed)=\"onClose()\" />\n * <wr-alert title=\"Sync failed\" type=\"danger\">Request <code>8f2c41</code> timed out.</wr-alert>\n * ```\n *\n * @see https://ngwr.dev/reference/components/alert\n */\n@Component({\n selector: 'wr-alert',\n templateUrl: './alert.html',\n imports: [WrIcon],\n encapsulation: ViewEncapsulation.None,\n host: {\n // `title` is an input, and a static attribute that feeds an input is ALSO\n // written onto the host — where the browser shows it as a native tooltip\n // over the whole component, and where it feeds the accessible name of any\n // element that has a role. `<wr-alert title=\"Heads up\">` is the documented\n // spelling, so every page writing it shipped both. A bound `[title]` never\n // did this, which is why the two forms behaved differently for no reason a\n // reader could see.\n '[attr.title]': 'null',\n '[class]': 'classes()',\n '[attr.role]': 'liveRole()',\n '[attr.aria-live]': 'liveLevel()',\n },\n})\nexport class WrAlert {\n /** Accessible name. Falls back to `alert.close`, then `'Close alert'`. */\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'alert.close', 'Close alert');\n\n /**\n * Optional headline shown at the top of the alert.\n *\n * @default null\n */\n readonly title = input<string | null>(null);\n\n /**\n * Visual variant.\n *\n * @default 'info'\n */\n readonly type = input<WrAlertType>('info');\n\n /**\n * Override the default per-type icon with any ngwr icon name.\n *\n * @default null\n */\n readonly iconName = input<WrIconName | null>(null);\n\n /**\n * Optional secondary message rendered below the title.\n *\n * @default null\n */\n readonly message = input<string | null>(null);\n\n /**\n * When `true`, renders a leading status icon matching the `type`.\n * Pass `false` to hide. Ignored when `iconName` is set.\n *\n * @default true\n */\n readonly icon = input(true, { transform: coerceBooleanProperty });\n\n /**\n * When `true`, renders a close button.\n *\n * @default false\n */\n readonly closable = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Emitted when the user dismisses the alert via the close button. If the close\n * button had focus, focus moves on to the next focusable element after the alert\n * (or the one before it, when nothing follows) — unless a `(closed)` handler has\n * placed it somewhere itself.\n */\n readonly closed = output<void>();\n\n protected readonly dismissed = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-alert', `wr-alert--${this.type()}`];\n if (this.dismissed()) parts.push('wr-alert--dismissed');\n return parts.join(' ');\n });\n\n /** Escalate role/aria-live for danger/warning alerts. */\n protected readonly liveRole = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' ? 'alert' : 'status'\n );\n protected readonly liveLevel = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' || this.type() === 'warning' ? 'assertive' : 'polite'\n );\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly injector = inject(Injector);\n\n protected onClose(): void {\n const host = this.host.nativeElement;\n const hadFocus = host.contains(host.ownerDocument.activeElement);\n this.dismissed.set(true);\n this.closed.emit();\n if (hadFocus) afterNextRender(() => this.handOnFocus(), { injector: this.injector });\n }\n\n /**\n * The close button is removed with the rest of the content, and a focused element\n * that leaves the DOM drops the caret on `<body>`: a keyboard user's next Tab, and\n * a screen reader's reading position, started over from the top of the page. Run\n * after the render that removed it, so a `(closed)` handler that moved focus on\n * purpose is seen and left alone.\n */\n private handOnFocus(): void {\n const host = this.host.nativeElement;\n const doc = host.ownerDocument;\n const active = doc.activeElement;\n if (active && active !== doc.body) return;\n // Tab stops only: a roving composite parks its other items at -1. The host has\n // nothing focusable left inside it, so every stop is either before or after it.\n const stops = getFocusableElements(doc.body).filter(el => el.tabIndex >= 0);\n // eslint-disable-next-line no-bitwise -- compareDocumentPosition answers with a bitmask\n const after = stops.findIndex(el => (host.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0);\n (after >= 0 ? stops[after] : stops.at(-1))?.focus();\n }\n}\n","@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n <!-- An OCTAGON, and the shape is the point. `danger` used to be a\n circle holding a bar over a dot, which is the vertical mirror of\n the `i` the @default branch draws — at the rendered 18px the two\n differ only by where a 1.5px gap sits. The stop-sign silhouette\n reads at that size and survives greyscale, which a mirrored gap\n does not. -->\n @case ('danger') {\n <path\n d=\"M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586z\"\n />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n <!-- A bar, not the exclamation mark `danger` used to carry: `neutral`\n emitted that glyph byte for byte. The icon is the whole difference\n between these two, because what the component actually paints —\n `danger-ink` #a32a33 (L 0.0968) against `on-surface-muted` #5f6c7d\n (L 0.1464) on tints 1.036:1 apart — is a hue difference and little\n else. -->\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M8 12h8\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n <div class=\"wr-alert__content\"><ng-content /></div>\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import * as i0 from "@angular/core";
2
- import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, effect, inject, input, signal, viewChild } from "@angular/core";
2
+ import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, afterRenderEffect, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
3
3
  import { isPlatformBrowser } from "@angular/common";
4
4
  import { numAttr, resolveCssColorChannels, watchColorScheme } from "ngwr/utils";
5
5
  import { WrPlatform } from "ngwr/platform";
@@ -154,7 +154,7 @@ var WrAurora = class WrAurora {
154
154
  platform = inject(WrPlatform);
155
155
  isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
156
156
  gl = null;
157
- uniforms = {};
157
+ gpu = null;
158
158
  rafId = 0;
159
159
  time = 0;
160
160
  booted = signal(false, ...ngDevMode ? [{ debugName: "booted" }] : /* istanbul ignore next */ []);
@@ -163,16 +163,19 @@ var WrAurora = class WrAurora {
163
163
  constructor() {
164
164
  if (!this.isBrowser) return;
165
165
  afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));
166
- effect(() => {
166
+ afterRenderEffect(() => {
167
+ if (!this.booted()) return;
168
+ const still = this.platform.prefersReducedMotion();
167
169
  this.colorStops();
168
170
  this.amplitude();
169
171
  this.blend();
170
- if (!this.gl) return;
171
- this.pushUniforms();
172
- if (this.platform.prefersReducedMotion()) this.draw(0);
172
+ untracked(() => {
173
+ this.pushUniforms();
174
+ if (still) this.draw(0);
175
+ });
173
176
  });
174
177
  watchColorScheme(() => {
175
- if (!this.gl || !this.platform.prefersReducedMotion()) return;
178
+ if (!this.gpu || !this.platform.prefersReducedMotion()) return;
176
179
  this.pushUniforms();
177
180
  this.draw(0);
178
181
  });
@@ -196,41 +199,11 @@ var WrAurora = class WrAurora {
196
199
  });
197
200
  if (!gl) return;
198
201
  this.gl = gl;
199
- gl.clearColor(0, 0, 0, 0);
200
- gl.enable(gl.BLEND);
201
- gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
202
- const compile = (type, source) => {
203
- const shader = gl.createShader(type);
204
- gl.shaderSource(shader, source);
205
- gl.compileShader(shader);
206
- if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) console.error("wr-aurora shader error:", gl.getShaderInfoLog(shader));
207
- return shader;
208
- };
209
- const program = gl.createProgram();
210
- gl.attachShader(program, compile(gl.VERTEX_SHADER, VERT));
211
- gl.attachShader(program, compile(gl.FRAGMENT_SHADER, FRAG));
212
- gl.linkProgram(program);
213
- gl.useProgram(program);
214
- for (const name of [
215
- "uTime",
216
- "uAmplitude",
217
- "uColorStops",
218
- "uResolution",
219
- "uBlend"
220
- ]) this.uniforms[name] = gl.getUniformLocation(program, name);
221
- const buffer = gl.createBuffer();
222
- gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
223
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
224
- -1,
225
- -1,
226
- 3,
227
- -1,
228
- -1,
229
- 3
230
- ]), gl.STATIC_DRAW);
231
- const position = gl.getAttribLocation(program, "position");
232
- gl.enableVertexAttribArray(position);
233
- gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
202
+ this.gpu = this.build(gl);
203
+ if (!this.gpu && !gl.isContextLost()) {
204
+ this.release(gl);
205
+ return;
206
+ }
234
207
  const hostEl = this.host.nativeElement;
235
208
  const fit = () => {
236
209
  const width = hostEl.clientWidth;
@@ -239,8 +212,21 @@ var WrAurora = class WrAurora {
239
212
  canvas.width = Math.max(1, width);
240
213
  canvas.height = Math.max(1, height);
241
214
  gl.viewport(0, 0, canvas.width, canvas.height);
242
- gl.uniform2f(this.uniforms["uResolution"], canvas.width, canvas.height);
215
+ if (this.gpu) gl.uniform2f(this.gpu.uniforms["uResolution"], canvas.width, canvas.height);
216
+ };
217
+ const onLost = (event) => {
218
+ event.preventDefault();
219
+ this.gpu = null;
220
+ this.booted.set(false);
221
+ };
222
+ const onRestored = () => {
223
+ this.gpu = this.build(gl);
224
+ if (!this.gpu) return;
225
+ fit();
226
+ this.booted.set(true);
243
227
  };
228
+ canvas.addEventListener("webglcontextlost", onLost);
229
+ canvas.addEventListener("webglcontextrestored", onRestored);
244
230
  const resize = () => {
245
231
  fit();
246
232
  this.draw(this.platform.prefersReducedMotion() ? 0 : this.time);
@@ -260,17 +246,87 @@ var WrAurora = class WrAurora {
260
246
  window.removeEventListener("resize", resize);
261
247
  ro.disconnect();
262
248
  io?.disconnect();
249
+ canvas.removeEventListener("webglcontextlost", onLost);
250
+ canvas.removeEventListener("webglcontextrestored", onRestored);
251
+ this.release(gl);
263
252
  });
264
- this.booted.set(true);
253
+ if (this.gpu) this.booted.set(true);
254
+ }
255
+ build(gl) {
256
+ const vert = this.compile(gl, gl.VERTEX_SHADER, VERT);
257
+ const frag = this.compile(gl, gl.FRAGMENT_SHADER, FRAG);
258
+ const program = vert && frag ? gl.createProgram() : null;
259
+ if (vert && frag && program) {
260
+ gl.attachShader(program, vert);
261
+ gl.attachShader(program, frag);
262
+ gl.linkProgram(program);
263
+ }
264
+ gl.deleteShader(vert);
265
+ gl.deleteShader(frag);
266
+ if (!program) return null;
267
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
268
+ if (!gl.isContextLost()) console.error("wr-aurora program error:", gl.getProgramInfoLog(program));
269
+ gl.deleteProgram(program);
270
+ return null;
271
+ }
272
+ gl.useProgram(program);
273
+ gl.clearColor(0, 0, 0, 0);
274
+ gl.enable(gl.BLEND);
275
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
276
+ const uniforms = {};
277
+ for (const name of [
278
+ "uTime",
279
+ "uAmplitude",
280
+ "uColorStops",
281
+ "uResolution",
282
+ "uBlend"
283
+ ]) uniforms[name] = gl.getUniformLocation(program, name);
284
+ const buffer = gl.createBuffer();
285
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
286
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
287
+ -1,
288
+ -1,
289
+ 3,
290
+ -1,
291
+ -1,
292
+ 3
293
+ ]), gl.STATIC_DRAW);
294
+ const position = gl.getAttribLocation(program, "position");
295
+ gl.enableVertexAttribArray(position);
296
+ gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
297
+ return {
298
+ program,
299
+ buffer,
300
+ uniforms
301
+ };
302
+ }
303
+ compile(gl, type, source) {
304
+ const shader = gl.createShader(type);
305
+ if (!shader) return null;
306
+ gl.shaderSource(shader, source);
307
+ gl.compileShader(shader);
308
+ if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;
309
+ if (!gl.isContextLost()) console.error("wr-aurora shader error:", gl.getShaderInfoLog(shader));
310
+ gl.deleteShader(shader);
311
+ return null;
312
+ }
313
+ release(gl) {
314
+ if (this.gpu) {
315
+ gl.deleteProgram(this.gpu.program);
316
+ gl.deleteBuffer(this.gpu.buffer);
317
+ }
318
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
319
+ this.gpu = null;
320
+ this.gl = null;
265
321
  }
266
322
  pushUniforms() {
267
- const gl = this.gl;
268
- if (!gl) return;
323
+ const { gl, gpu } = this;
324
+ if (!gl || !gpu) return;
269
325
  const flat = /* @__PURE__ */ new Float32Array(9);
270
326
  for (let i = 0; i < 3; i++) flat.set(this.resolveStop(i), i * 3);
271
- gl.uniform3fv(this.uniforms["uColorStops"], flat);
272
- gl.uniform1f(this.uniforms["uAmplitude"], this.amplitude());
273
- gl.uniform1f(this.uniforms["uBlend"], this.blend());
327
+ gl.uniform3fv(gpu.uniforms["uColorStops"], flat);
328
+ gl.uniform1f(gpu.uniforms["uAmplitude"], this.amplitude());
329
+ gl.uniform1f(gpu.uniforms["uBlend"], this.blend());
274
330
  }
275
331
  resolveStop(index) {
276
332
  const explicit = this.colorStops();
@@ -279,10 +335,10 @@ var WrAurora = class WrAurora {
279
335
  return resolveCssColorChannels(this.canvasRef().nativeElement, expression, fallback);
280
336
  }
281
337
  draw(time) {
282
- const gl = this.gl;
283
- if (!gl || !this.hasBox()) return;
338
+ const { gl, gpu } = this;
339
+ if (!gl || !gpu || !this.hasBox()) return;
284
340
  this.time = time;
285
- gl.uniform1f(this.uniforms["uTime"], time);
341
+ gl.uniform1f(gpu.uniforms["uTime"], time);
286
342
  gl.clear(gl.COLOR_BUFFER_BIT);
287
343
  gl.drawArrays(gl.TRIANGLES, 0, 3);
288
344
  }