@happyvertical/smrt-ui 0.52.0 → 0.53.1

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 (208) hide show
  1. package/AGENTS.md +11 -1
  2. package/README.md +300 -2
  3. package/dist/components/data/DataTable.svelte +60 -15
  4. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  5. package/dist/components/data/DataTableResponsive.d.ts +4 -0
  6. package/dist/components/data/DataTableResponsive.d.ts.map +1 -0
  7. package/dist/components/data/DataTableResponsive.js +22 -0
  8. package/dist/components/data/__tests__/DataTableResponsive.test.js +80 -0
  9. package/dist/components/data/__tests__/responsive-table.fixture.svelte +29 -0
  10. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts +7 -0
  11. package/dist/components/data/__tests__/responsive-table.fixture.svelte.d.ts.map +1 -0
  12. package/dist/components/data/types.d.ts +8 -0
  13. package/dist/components/data/types.d.ts.map +1 -1
  14. package/dist/components/display/DateDisplay.svelte +76 -14
  15. package/dist/components/display/DateDisplay.svelte.d.ts +2 -0
  16. package/dist/components/display/DateDisplay.svelte.d.ts.map +1 -1
  17. package/dist/components/display/Icon.svelte +3 -16
  18. package/dist/components/display/Icon.svelte.d.ts +0 -9
  19. package/dist/components/display/Icon.svelte.d.ts.map +1 -1
  20. package/dist/components/display/StatusBadge.svelte +29 -2
  21. package/dist/components/display/StatusBadge.svelte.d.ts +4 -2
  22. package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
  23. package/dist/components/display/__tests__/DateDisplay.test.js +82 -1
  24. package/dist/components/display/__tests__/Icon.test.js +132 -2
  25. package/dist/components/display/__tests__/StatusBadge.test.js +78 -0
  26. package/dist/components/display/icons.svelte.d.ts +11 -0
  27. package/dist/components/display/icons.svelte.d.ts.map +1 -0
  28. package/dist/components/display/icons.svelte.js +60 -0
  29. package/dist/components/display/index.d.ts +3 -1
  30. package/dist/components/display/index.d.ts.map +1 -1
  31. package/dist/components/display/index.js +1 -0
  32. package/dist/components/display/types.d.ts +2 -0
  33. package/dist/components/display/types.d.ts.map +1 -1
  34. package/dist/components/feedback/ConfirmDialog.svelte +59 -48
  35. package/dist/components/feedback/ConfirmDialog.svelte.d.ts +14 -2
  36. package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
  37. package/dist/components/feedback/ToastViewport.svelte +156 -8
  38. package/dist/components/feedback/ToastViewport.svelte.d.ts +5 -1
  39. package/dist/components/feedback/ToastViewport.svelte.d.ts.map +1 -1
  40. package/dist/components/feedback/__tests__/ConfirmDialog.test.js +38 -20
  41. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte +9 -0
  42. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts +19 -0
  43. package/dist/components/feedback/__tests__/confirm-message.fixture.svelte.d.ts.map +1 -0
  44. package/dist/components/feedback/__tests__/toast.test.js +142 -1
  45. package/dist/components/feedback/index.d.ts +1 -1
  46. package/dist/components/feedback/index.d.ts.map +1 -1
  47. package/dist/components/feedback/toast.d.ts +13 -1
  48. package/dist/components/feedback/toast.d.ts.map +1 -1
  49. package/dist/components/feedback/toast.js +51 -10
  50. package/dist/components/forms/CameraCapture.svelte +619 -0
  51. package/dist/components/forms/CameraCapture.svelte.d.ts +42 -0
  52. package/dist/components/forms/CameraCapture.svelte.d.ts.map +1 -0
  53. package/dist/components/forms/Checkbox.svelte +8 -1
  54. package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
  55. package/dist/components/forms/Checkbox.svelte.d.ts.map +1 -1
  56. package/dist/components/forms/Combobox.svelte +40 -0
  57. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  58. package/dist/components/forms/Form.svelte +5 -0
  59. package/dist/components/forms/Form.svelte.d.ts +1 -0
  60. package/dist/components/forms/Form.svelte.d.ts.map +1 -1
  61. package/dist/components/forms/Input.svelte +8 -1
  62. package/dist/components/forms/Input.svelte.d.ts +2 -0
  63. package/dist/components/forms/Input.svelte.d.ts.map +1 -1
  64. package/dist/components/forms/Listbox.svelte +39 -1
  65. package/dist/components/forms/Listbox.svelte.d.ts.map +1 -1
  66. package/dist/components/forms/MultiSelect.svelte +41 -1
  67. package/dist/components/forms/MultiSelect.svelte.d.ts.map +1 -1
  68. package/dist/components/forms/Radio.svelte +1 -0
  69. package/dist/components/forms/RadioGroup.svelte +7 -1
  70. package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
  71. package/dist/components/forms/RadioGroup.svelte.d.ts.map +1 -1
  72. package/dist/components/forms/SegmentedControl.svelte +152 -11
  73. package/dist/components/forms/SegmentedControl.svelte.d.ts +4 -0
  74. package/dist/components/forms/SegmentedControl.svelte.d.ts.map +1 -1
  75. package/dist/components/forms/Select.svelte +8 -1
  76. package/dist/components/forms/Select.svelte.d.ts +2 -0
  77. package/dist/components/forms/Select.svelte.d.ts.map +1 -1
  78. package/dist/components/forms/SignaturePad.svelte +436 -0
  79. package/dist/components/forms/SignaturePad.svelte.d.ts +32 -0
  80. package/dist/components/forms/SignaturePad.svelte.d.ts.map +1 -0
  81. package/dist/components/forms/Switch.svelte +8 -1
  82. package/dist/components/forms/Switch.svelte.d.ts +2 -0
  83. package/dist/components/forms/Switch.svelte.d.ts.map +1 -1
  84. package/dist/components/forms/Textarea.svelte +8 -1
  85. package/dist/components/forms/Textarea.svelte.d.ts +2 -0
  86. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -1
  87. package/dist/components/forms/__tests__/CameraCapture.test.js +504 -0
  88. package/dist/components/forms/__tests__/Combobox.test.js +279 -12
  89. package/dist/components/forms/__tests__/Form.test.js +46 -0
  90. package/dist/components/forms/__tests__/Listbox.test.js +303 -0
  91. package/dist/components/forms/__tests__/MultiSelect.test.js +281 -0
  92. package/dist/components/forms/__tests__/SignaturePad.test.js +338 -0
  93. package/dist/components/forms/__tests__/camera-capture-session.test.js +207 -0
  94. package/dist/components/forms/__tests__/capture-form-retry.test.js +166 -0
  95. package/dist/components/forms/__tests__/capture-form.fixture.svelte +38 -0
  96. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts +10 -0
  97. package/dist/components/forms/__tests__/capture-form.fixture.svelte.d.ts.map +1 -0
  98. package/dist/components/forms/__tests__/capture-test-env.d.ts +70 -0
  99. package/dist/components/forms/__tests__/capture-test-env.d.ts.map +1 -0
  100. package/dist/components/forms/__tests__/capture-test-env.js +122 -0
  101. package/dist/components/forms/__tests__/capture-touch.fixture.svelte +15 -0
  102. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts +8 -0
  103. package/dist/components/forms/__tests__/capture-touch.fixture.svelte.d.ts.map +1 -0
  104. package/dist/components/forms/__tests__/combobox-form.fixture.svelte +29 -0
  105. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts +14 -0
  106. package/dist/components/forms/__tests__/combobox-form.fixture.svelte.d.ts.map +1 -0
  107. package/dist/components/forms/__tests__/form-attachment.fixture.svelte +28 -0
  108. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts +11 -0
  109. package/dist/components/forms/__tests__/form-attachment.fixture.svelte.d.ts.map +1 -0
  110. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte +31 -0
  111. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts +13 -0
  112. package/dist/components/forms/__tests__/form-retry-form.fixture.svelte.d.ts.map +1 -0
  113. package/dist/components/forms/__tests__/form-retry-form.test.js +107 -0
  114. package/dist/components/forms/__tests__/hidden-controls-form-retry.test.js +69 -0
  115. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte +29 -0
  116. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts +9 -0
  117. package/dist/components/forms/__tests__/hidden-controls-form.fixture.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/__tests__/listbox-form.fixture.svelte +27 -0
  119. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts +13 -0
  120. package/dist/components/forms/__tests__/listbox-form.fixture.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte +27 -0
  122. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts +13 -0
  123. package/dist/components/forms/__tests__/multiselect-form.fixture.svelte.d.ts.map +1 -0
  124. package/dist/components/forms/__tests__/native-file-field.test.js +177 -0
  125. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte +28 -0
  126. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts +12 -0
  127. package/dist/components/forms/__tests__/segmented-posting.fixture.svelte.d.ts.map +1 -0
  128. package/dist/components/forms/__tests__/segmented-posting.test.js +92 -0
  129. package/dist/components/forms/__tests__/signature-pad-logic.test.js +54 -0
  130. package/dist/components/forms/__tests__/touch-density.fixture.svelte +34 -0
  131. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts +8 -0
  132. package/dist/components/forms/__tests__/touch-density.fixture.svelte.d.ts.map +1 -0
  133. package/dist/components/forms/__tests__/touch-density.test.js +66 -0
  134. package/dist/components/forms/camera-capture-session.d.ts +81 -0
  135. package/dist/components/forms/camera-capture-session.d.ts.map +1 -0
  136. package/dist/components/forms/camera-capture-session.js +130 -0
  137. package/dist/components/forms/form-retry/__tests__/boundaries.test.js +23 -0
  138. package/dist/components/forms/form-retry/__tests__/content-digest.test.js +70 -0
  139. package/dist/components/forms/form-retry/__tests__/controller.test.js +802 -0
  140. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts +82 -0
  141. package/dist/components/forms/form-retry/__tests__/fake-kit.d.ts.map +1 -0
  142. package/dist/components/forms/form-retry/__tests__/fake-kit.js +180 -0
  143. package/dist/components/forms/form-retry/__tests__/form-draft.test.js +169 -0
  144. package/dist/components/forms/form-retry/__tests__/ssr.test.js +37 -0
  145. package/dist/components/forms/form-retry/__tests__/storage.d.ts +26 -0
  146. package/dist/components/forms/form-retry/__tests__/storage.d.ts.map +1 -0
  147. package/dist/components/forms/form-retry/__tests__/storage.js +63 -0
  148. package/dist/components/forms/form-retry/__tests__/submission-key.test.js +236 -0
  149. package/dist/components/forms/form-retry/__tests__/submitted-draft.test.js +334 -0
  150. package/dist/components/forms/form-retry/content-digest.d.ts +54 -0
  151. package/dist/components/forms/form-retry/content-digest.d.ts.map +1 -0
  152. package/dist/components/forms/form-retry/content-digest.js +169 -0
  153. package/dist/components/forms/form-retry/controller.d.ts +214 -0
  154. package/dist/components/forms/form-retry/controller.d.ts.map +1 -0
  155. package/dist/components/forms/form-retry/controller.js +404 -0
  156. package/dist/components/forms/form-retry/form-draft.d.ts +68 -0
  157. package/dist/components/forms/form-retry/form-draft.d.ts.map +1 -0
  158. package/dist/components/forms/form-retry/form-draft.js +161 -0
  159. package/dist/components/forms/form-retry/index.d.ts +19 -0
  160. package/dist/components/forms/form-retry/index.d.ts.map +1 -0
  161. package/dist/components/forms/form-retry/index.js +16 -0
  162. package/dist/components/forms/form-retry/submission-key.d.ts +244 -0
  163. package/dist/components/forms/form-retry/submission-key.d.ts.map +1 -0
  164. package/dist/components/forms/form-retry/submission-key.js +342 -0
  165. package/dist/components/forms/form-retry/submitted-draft.d.ts +112 -0
  166. package/dist/components/forms/form-retry/submitted-draft.d.ts.map +1 -0
  167. package/dist/components/forms/form-retry/submitted-draft.js +270 -0
  168. package/dist/components/forms/form-retry/types.d.ts +84 -0
  169. package/dist/components/forms/form-retry/types.d.ts.map +1 -0
  170. package/dist/components/forms/form-retry/types.js +13 -0
  171. package/dist/components/forms/index.d.ts +7 -1
  172. package/dist/components/forms/index.d.ts.map +1 -1
  173. package/dist/components/forms/index.js +8 -0
  174. package/dist/components/forms/native-file-field.d.ts +94 -0
  175. package/dist/components/forms/native-file-field.d.ts.map +1 -0
  176. package/dist/components/forms/native-file-field.js +147 -0
  177. package/dist/components/forms/signature-pad-logic.d.ts +50 -0
  178. package/dist/components/forms/signature-pad-logic.d.ts.map +1 -0
  179. package/dist/components/forms/signature-pad-logic.js +48 -0
  180. package/dist/components/forms/types.d.ts +41 -0
  181. package/dist/components/forms/types.d.ts.map +1 -1
  182. package/dist/components/nav/FilterChips.svelte +8 -1
  183. package/dist/components/nav/FilterChips.svelte.d.ts +2 -0
  184. package/dist/components/nav/FilterChips.svelte.d.ts.map +1 -1
  185. package/dist/components/ui/Button.svelte +9 -2
  186. package/dist/components/ui/Button.svelte.d.ts +2 -0
  187. package/dist/components/ui/Button.svelte.d.ts.map +1 -1
  188. package/dist/i18n/strings.ui.d.ts +24 -0
  189. package/dist/i18n/strings.ui.d.ts.map +1 -1
  190. package/dist/i18n/strings.ui.js +26 -0
  191. package/dist/styles/tokens.css +2 -0
  192. package/dist/svelte/__tests__/playground.test.js +2 -1
  193. package/dist/svelte/playground/CapturePreview.svelte +56 -0
  194. package/dist/svelte/playground/CapturePreview.svelte.d.ts +4 -0
  195. package/dist/svelte/playground/CapturePreview.svelte.d.ts.map +1 -0
  196. package/dist/svelte/playground.d.ts.map +1 -1
  197. package/dist/svelte/playground.js +10 -0
  198. package/dist/themes/ThemeProvider.svelte +9 -0
  199. package/dist/themes/ThemeProvider.svelte.d.ts +2 -0
  200. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  201. package/dist/themes/css-generator.d.ts.map +1 -1
  202. package/dist/themes/css-generator.js +3 -0
  203. package/dist/themes/styles/glass.css +4 -0
  204. package/dist/themes/styles/happyvertical.css +4 -0
  205. package/dist/themes/styles/material.css +4 -0
  206. package/dist/themes/styles/smrt.css +4 -0
  207. package/dist/themes/styles/studio.css +4 -0
  208. package/package.json +8 -2
@@ -6,10 +6,14 @@
6
6
  * from Icon.svelte — preset/path resolution, size/color, and axe-cleanliness in
7
7
  * both decorative and labelled modes.
8
8
  */
9
- import { render } from '@testing-library/svelte';
10
- import { describe, expect, it } from 'vitest';
9
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
10
+ import { act, render } from '@testing-library/svelte';
11
+ import { hydrate, tick, unmount } from 'svelte';
12
+ import { createServer } from 'vite';
13
+ import { afterEach, describe, expect, it } from 'vitest';
11
14
  import { expectNoA11yViolations } from '../../../test-support/a11y';
12
15
  import Icon from '../Icon.svelte';
16
+ import { registerIcons } from '../icons.svelte.js';
13
17
  /** The 'check' preset path, used to assert preset resolution. */
14
18
  const CHECK_PATH = 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z';
15
19
  describe('Icon', () => {
@@ -91,3 +95,129 @@ describe('Icon', () => {
91
95
  await expectNoA11yViolations(container);
92
96
  });
93
97
  });
98
+ const cleanupRegistrations = [];
99
+ afterEach(() => {
100
+ for (const cleanup of cleanupRegistrations.splice(0))
101
+ cleanup();
102
+ });
103
+ describe('Icon catalog and application registration', () => {
104
+ it.each([
105
+ 'alert',
106
+ 'warning',
107
+ 'info',
108
+ 'home',
109
+ 'user',
110
+ 'settings',
111
+ 'trash',
112
+ 'edit',
113
+ 'calendar',
114
+ 'clock',
115
+ 'camera',
116
+ 'upload',
117
+ 'download',
118
+ ])('ships a nonempty %s glyph', (name) => {
119
+ const { container } = render(Icon, { props: { name } });
120
+ expect(container.querySelector('path')?.getAttribute('d')).toMatch(/^M/);
121
+ });
122
+ it('updates already mounted icons when an application set is registered and removed', async () => {
123
+ const { container } = render(Icon, { props: { name: 'shop-machine' } });
124
+ const path = container.querySelector('path');
125
+ expect(path).not.toHaveAttribute('d');
126
+ let cleanup = () => { };
127
+ await act(() => {
128
+ cleanup = registerIcons({ 'shop-machine': 'M2 2h20v20H2z' });
129
+ });
130
+ cleanupRegistrations.push(cleanup);
131
+ expect(path).toHaveAttribute('d', 'M2 2h20v20H2z');
132
+ await act(cleanup);
133
+ expect(path).not.toHaveAttribute('d');
134
+ });
135
+ it('preserves override precedence and supports out-of-order, repeated cleanup', async () => {
136
+ const { container } = render(Icon, { props: { name: 'check' } });
137
+ const path = container.querySelector('path');
138
+ const first = registerIcons({ check: 'M1 1h2v2H1z' });
139
+ const second = registerIcons({ check: 'M3 3h4v4H3z' });
140
+ cleanupRegistrations.push(first, second);
141
+ await act(() => { });
142
+ expect(path).toHaveAttribute('d', 'M3 3h4v4H3z');
143
+ await act(first);
144
+ expect(path).toHaveAttribute('d', 'M3 3h4v4H3z');
145
+ await act(first);
146
+ expect(path).toHaveAttribute('d', 'M3 3h4v4H3z');
147
+ await act(second);
148
+ expect(path).toHaveAttribute('d', CHECK_PATH);
149
+ });
150
+ it('keeps explicit path higher priority than a registered name', () => {
151
+ cleanupRegistrations.push(registerIcons({ check: 'M1 1h2v2H1z' }));
152
+ const { container } = render(Icon, {
153
+ props: {
154
+ name: 'check',
155
+ path: 'M0 0h10v10H0z',
156
+ 'aria-label': 'Custom check',
157
+ },
158
+ });
159
+ expect(container.querySelector('path')).toHaveAttribute('d', 'M0 0h10v10H0z');
160
+ expect(container.querySelector('svg')).toHaveAccessibleName('Custom check');
161
+ });
162
+ it('snapshots caller maps and rejects malformed mixed sets atomically', () => {
163
+ const icons = { brand: 'M1 1h2v2H1z' };
164
+ cleanupRegistrations.push(registerIcons(icons));
165
+ icons.brand = 'M3 3h4v4H3z';
166
+ expect(() => registerIcons({ brand: 'M5 5h6v6H5z', bad: 3 })).toThrow(TypeError);
167
+ expect(() => registerIcons({ '': 'M1 1h2v2H1z' })).toThrow(TypeError);
168
+ expect(() => registerIcons({ empty: '' })).toThrow(TypeError);
169
+ const { container } = render(Icon, { props: { name: 'brand' } });
170
+ expect(container.querySelector('path')).toHaveAttribute('d', 'M1 1h2v2H1z');
171
+ });
172
+ it('does not resolve inherited map properties or builtin prototype names', () => {
173
+ cleanupRegistrations.push(registerIcons(Object.create({ inherited: 'M1 1h2v2H1z' })));
174
+ for (const name of ['inherited', 'toString', '__proto__', 'constructor']) {
175
+ const { container } = render(Icon, { props: { name } });
176
+ expect(container.querySelector('path')).not.toHaveAttribute('d');
177
+ }
178
+ });
179
+ });
180
+ it('renders registered icons on the server and hydrates with the same application set', async () => {
181
+ const vite = await createServer({
182
+ configFile: false,
183
+ plugins: [svelte()],
184
+ root: process.cwd(),
185
+ server: { middlewareMode: true },
186
+ });
187
+ const custom = 'M4 4h16v16H4z';
188
+ const unregisterClient = registerIcons({ brand: custom });
189
+ cleanupRegistrations.push(unregisterClient);
190
+ try {
191
+ const { default: ServerIcon } = await vite.ssrLoadModule('/src/components/display/Icon.svelte');
192
+ const { registerIcons: registerServerIcons } = await vite.ssrLoadModule('/src/components/display/icons.svelte.ts');
193
+ const unregisterServer = registerServerIcons({ brand: custom });
194
+ try {
195
+ const { render: renderSsr } = await vite.ssrLoadModule('svelte/server');
196
+ const props = { name: 'brand', 'aria-label': 'Brand' };
197
+ const result = await renderSsr(ServerIcon, { props });
198
+ expect(result.body).toContain(`d="${custom}"`);
199
+ const host = document.createElement('div');
200
+ host.innerHTML = result.body;
201
+ document.body.append(host);
202
+ const instance = hydrate(Icon, { target: host, props });
203
+ try {
204
+ expect(host.querySelector('svg')).toHaveAccessibleName('Brand');
205
+ expect(host.querySelector('path')).toHaveAttribute('d', custom);
206
+ const override = registerIcons({ brand: 'M2 2h20v20H2z' });
207
+ cleanupRegistrations.push(override);
208
+ await tick();
209
+ expect(host.querySelector('path')).toHaveAttribute('d', 'M2 2h20v20H2z');
210
+ }
211
+ finally {
212
+ await unmount(instance);
213
+ host.remove();
214
+ }
215
+ }
216
+ finally {
217
+ unregisterServer();
218
+ }
219
+ }
220
+ finally {
221
+ await vite.close();
222
+ }
223
+ });
@@ -12,6 +12,84 @@ import { describe, expect, it } from 'vitest';
12
12
  import { expectNoA11yViolations } from '../../../test-support/a11y';
13
13
  import StatusBadge from '../StatusBadge.svelte';
14
14
  describe('StatusBadge', () => {
15
+ it.each([
16
+ [
17
+ 'success',
18
+ 'var(--smrt-color-success-container)',
19
+ 'var(--smrt-color-on-success-container)',
20
+ ],
21
+ [
22
+ 'warning',
23
+ 'var(--smrt-color-warning-container)',
24
+ 'var(--smrt-color-on-warning-container)',
25
+ ],
26
+ [
27
+ 'danger',
28
+ 'var(--smrt-color-error-container)',
29
+ 'var(--smrt-color-on-error-container)',
30
+ ],
31
+ [
32
+ 'info',
33
+ 'var(--smrt-color-primary-container)',
34
+ 'var(--smrt-color-on-primary-container)',
35
+ ],
36
+ [
37
+ 'neutral',
38
+ 'var(--smrt-color-surface-container-highest)',
39
+ 'var(--smrt-color-on-surface-variant)',
40
+ ],
41
+ ])('applies %s tone to a custom status', (tone, background, foreground) => {
42
+ const { container } = render(StatusBadge, {
43
+ props: { status: 'awaiting_cert', tone },
44
+ });
45
+ const span = container.querySelector('span');
46
+ expect(span.style.getPropertyValue('--badge-bg')).toBe(background);
47
+ expect(span.style.getPropertyValue('--badge-text')).toBe(foreground);
48
+ expect(screen.getByText('awaiting cert')).toBeInTheDocument();
49
+ });
50
+ it('lets explicit tone override a domain scheme and updates reactively', async () => {
51
+ const { container, rerender } = render(StatusBadge, {
52
+ props: {
53
+ status: 'overdue',
54
+ type: 'invoice',
55
+ tone: 'success',
56
+ variant: 'outline',
57
+ size: 'sm',
58
+ label: 'Reviewed',
59
+ },
60
+ });
61
+ const span = container.querySelector('span');
62
+ expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-success-container)');
63
+ expect(span).toHaveClass('outline', 'sm');
64
+ expect(screen.getByText('Reviewed')).toBeInTheDocument();
65
+ await rerender({ status: 'overdue', type: 'invoice', tone: 'warning' });
66
+ expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-warning-container)');
67
+ await rerender({ status: 'overdue', type: 'invoice', tone: undefined });
68
+ expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-error-container)');
69
+ });
70
+ it.each([
71
+ 'unknown',
72
+ '__proto__',
73
+ 'constructor',
74
+ ])('safely ignores invalid runtime tone %s', (tone) => {
75
+ const { container } = render(StatusBadge, {
76
+ props: { status: 'paid', type: 'invoice', tone: tone },
77
+ });
78
+ const span = container.querySelector('span');
79
+ expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-primary-container)');
80
+ });
81
+ it.each([
82
+ 'success',
83
+ 'warning',
84
+ 'danger',
85
+ 'info',
86
+ 'neutral',
87
+ ])('is axe-clean for custom status tone=%s', async (tone) => {
88
+ const { container } = render(StatusBadge, {
89
+ props: { status: 'awaiting_cert', tone },
90
+ });
91
+ await expectNoA11yViolations(container);
92
+ });
15
93
  it('renders the status value as its label', () => {
16
94
  render(StatusBadge, { props: { status: 'active' } });
17
95
  expect(screen.getByText('active')).toBeInTheDocument();
@@ -0,0 +1,11 @@
1
+ /** Application icon paths, using the Icon component's configured viewBox. */
2
+ export type IconPathMap = Readonly<Record<string, string>>;
3
+ /**
4
+ * Register an application icon set, returning an idempotent cleanup function.
5
+ * Last active registration wins; caller-owned maps are snapshotted. Register
6
+ * static application sets at startup, not request-specific or identity data.
7
+ */
8
+ export declare function registerIcons(icons: IconPathMap): () => void;
9
+ /** Resolve the last active application path, falling back to the builtin set. */
10
+ export declare function resolveIconPath(name: string): string | undefined;
11
+ //# sourceMappingURL=icons.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/display/icons.svelte.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AA0C3D;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,IAAI,CAkB5D;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMhE"}
@@ -0,0 +1,60 @@
1
+ // Common M3 Icon Paths (Simplified)
2
+ const presets = new Map(Object.entries({
3
+ menu: 'M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z',
4
+ search: 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z',
5
+ close: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z',
6
+ 'chevron-right': 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6-6-6z',
7
+ 'chevron-left': 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12l4.58-4.59z',
8
+ 'chevron-down': 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z',
9
+ check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z',
10
+ add: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
11
+ mic: 'M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.04 6.43 5 7.07V21h4v-2.93c2.96-.64 5-3.54 5-7.07h-2z',
12
+ alert: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm-1 5h2v6h-2zm0 8h2v2h-2z',
13
+ warning: 'M1 21h22L12 2 1 21zm12-3h-2v-2h2zm0-4h-2v-4h2z',
14
+ info: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm1 15h-2v-6h2zm0-8h-2V7h2z',
15
+ home: 'M3 10 12 2 21 10v11h-6v-7H9v7H3z',
16
+ user: 'M12 3a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM4 21v-2c0-4 16-4 16 0v2H4z',
17
+ settings: 'M10 2h4l1 3 3-1 3 3-1 3 3 1v4l-3 1 1 3-3 3-3-1-1 3h-4l-1-3-3 1-3-3 1-3-3-1v-4l3-1-1-3 3-3 3 1 1-3zm2 6a4 4 0 1 0 0 8 4 4 0 0 0 0-8z',
18
+ trash: 'M7 3h10v2h4v2H3V5h4V3zm-2 6h14v12H5V9zm4 2v8h2v-8H9zm4 0v8h2v-8h-2z',
19
+ edit: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
20
+ calendar: 'M7 2h2v2h6V2h2v2h4v18H3V4h4V2zm-2 8v10h14V10H5zm2 2h4v4H7v-4z',
21
+ clock: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16zm-1 2h2v5.5l4 2.5-1 1.5-5-3V6z',
22
+ camera: 'M9 3h6l2 3h4v15H3V6h4l2-3zm3 5a5 5 0 1 0 0 10 5 5 0 0 0 0-10zm0 2a3 3 0 1 1 0 6 3 3 0 0 1 0-6z',
23
+ upload: 'M11 16V7l-4 4-1.5-1.5L12 3l6.5 6.5L17 11l-4-4v9h-2zM3 18h18v3H3v-3z',
24
+ download: 'M11 3h2v9l4-4 1.5 1.5L12 16l-6.5-6.5L7 8l4 4V3zM3 18h18v3H3v-3z',
25
+ }));
26
+ let registrations = $state.raw([]);
27
+ /**
28
+ * Register an application icon set, returning an idempotent cleanup function.
29
+ * Last active registration wins; caller-owned maps are snapshotted. Register
30
+ * static application sets at startup, not request-specific or identity data.
31
+ */
32
+ export function registerIcons(icons) {
33
+ if (!icons || typeof icons !== 'object' || Array.isArray(icons)) {
34
+ throw new TypeError('Icon set must be a map of names to SVG path strings');
35
+ }
36
+ const entries = Object.entries(icons);
37
+ for (const [name, path] of entries) {
38
+ if (!name.trim() || typeof path !== 'string' || !path.trim()) {
39
+ throw new TypeError('Icon names and paths must be nonempty strings');
40
+ }
41
+ }
42
+ const registration = { paths: new Map(entries) };
43
+ registrations = [...registrations, registration];
44
+ let active = true;
45
+ return () => {
46
+ if (!active)
47
+ return;
48
+ active = false;
49
+ registrations = registrations.filter((entry) => entry !== registration);
50
+ };
51
+ }
52
+ /** Resolve the last active application path, falling back to the builtin set. */
53
+ export function resolveIconPath(name) {
54
+ for (let index = registrations.length - 1; index >= 0; index--) {
55
+ const path = registrations[index].paths.get(name);
56
+ if (path !== undefined)
57
+ return path;
58
+ }
59
+ return presets.get(name);
60
+ }
@@ -6,5 +6,7 @@ export { default as CurrencyDisplay } from './CurrencyDisplay.svelte';
6
6
  export { default as DateDisplay } from './DateDisplay.svelte';
7
7
  export { default as Icon } from './Icon.svelte';
8
8
  export { default as StatusBadge } from './StatusBadge.svelte';
9
- export type { StatusType } from './types.js';
9
+ export type { IconPathMap } from './icons.svelte.js';
10
+ export { registerIcons } from './icons.svelte.js';
11
+ export type { StatusTone, StatusType } from './types.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAO9D,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/display/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAM9D,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -7,3 +7,4 @@ export { default as CurrencyDisplay } from './CurrencyDisplay.svelte';
7
7
  export { default as DateDisplay } from './DateDisplay.svelte';
8
8
  export { default as Icon } from './Icon.svelte';
9
9
  export { default as StatusBadge } from './StatusBadge.svelte';
10
+ export { registerIcons } from './icons.svelte.js';
@@ -2,4 +2,6 @@
2
2
  * Display component types
3
3
  */
4
4
  export type StatusType = 'default' | 'invoice' | 'project' | 'expense' | 'time' | 'compliance' | 'estimate';
5
+ /** Semantic tone for custom status vocabulary. */
6
+ export type StatusTone = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
5
7
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/display/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,MAAM,GACN,YAAY,GACZ,UAAU,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/display/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,SAAS,GACT,SAAS,GACT,SAAS,GACT,MAAM,GACN,YAAY,GACZ,UAAU,CAAC;AAEf,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC"}
@@ -6,13 +6,11 @@
6
6
  * Provides a consistent confirmation dialog for destructive actions
7
7
  * or important decisions.
8
8
  *
9
- * Accessibility (#1586): the dialog is a `role="dialog" aria-modal="true"`
10
- * surface with managed focus — on open it records the previously-focused element
11
- * and moves focus to the confirm button; Tab/Shift+Tab are trapped within the
12
- * dialog; Escape (caught at the document level while open) cancels; and focus is
13
- * restored to the opener when the dialog closes.
9
+ * Native showModal() puts confirmations above existing Modal/Drawer surfaces,
10
+ * traps focus and makes the background inert. Escape is handled locally so
11
+ * only the active confirmation requests cancellation.
14
12
  */
15
- import { tick } from 'svelte';
13
+ import { type Snippet, tick } from 'svelte';
16
14
  import { ripple } from '../../actions/ripple.js';
17
15
 
18
16
  /** Props for ConfirmDialog component */
@@ -21,8 +19,8 @@ export interface Props {
21
19
  open: boolean;
22
20
  /** Dialog title */
23
21
  title: string;
24
- /** Dialog message */
25
- message: string;
22
+ /** Plain text or rich message snippet */
23
+ message: string | Snippet;
26
24
  /** Confirm button label */
27
25
  confirmLabel?: string;
28
26
  /** Cancel button label */
@@ -49,7 +47,8 @@ const {
49
47
  oncancel,
50
48
  }: Props = $props();
51
49
 
52
- let backdropEl = $state<HTMLElement | null>(null);
50
+ const instanceId = $props.id();
51
+ let backdropEl = $state<HTMLDialogElement | null>(null);
53
52
  let confirmBtnEl = $state<HTMLButtonElement | null>(null);
54
53
  // The element focused before the dialog opened, restored on close.
55
54
  let previouslyFocused: HTMLElement | null = null;
@@ -63,38 +62,43 @@ function focusableEls(): HTMLElement[] {
63
62
  );
64
63
  }
65
64
 
66
- // Move focus into the dialog on open; restore it to the opener on close.
65
+ // Keep the parent's open prop authoritative; native cancellation requests it.
67
66
  $effect(() => {
68
- if (typeof document === 'undefined') return;
69
-
70
- if (open) {
71
- previouslyFocused = document.activeElement as HTMLElement | null;
72
- // Wait for the dialog DOM to mount, then move focus into the dialog: the
73
- // confirm action by default, the first focusable if confirm is disabled, or
74
- // the dialog container itself when every control is disabled (loading) so
75
- // focus is still contained and Escape stays reachable.
76
- void tick().then(() => {
77
- if (!open) return;
78
- const target = confirmBtnEl?.disabled
79
- ? (focusableEls()[0] ?? backdropEl)
80
- : confirmBtnEl;
81
- target?.focus();
82
- });
83
- } else if (previouslyFocused) {
84
- previouslyFocused.focus();
67
+ const dialog = backdropEl;
68
+ if (!open || !dialog) return;
69
+ previouslyFocused = document.activeElement as HTMLElement | null;
70
+ dialog.showModal();
71
+ void tick().then(() => {
72
+ if (!dialog.open) return;
73
+ const target = confirmBtnEl?.disabled
74
+ ? (focusableEls()[0] ?? dialog)
75
+ : confirmBtnEl;
76
+ target?.focus();
77
+ });
78
+ return () => {
79
+ if (dialog.open) dialog.close();
80
+ previouslyFocused?.focus();
85
81
  previouslyFocused = null;
86
- }
82
+ };
87
83
  });
88
84
 
85
+ function handleCancel(event: Event) {
86
+ event.preventDefault();
87
+ event.stopPropagation();
88
+ oncancel?.();
89
+ }
90
+
89
91
  function handleBackdropClick(e: MouseEvent) {
90
92
  if (e.target === e.currentTarget) {
91
93
  oncancel?.();
92
94
  }
93
95
  }
94
96
 
95
- // Escape handling lives at the document level (below) so it fires regardless of
96
- // where focus currently is. This handler, bound to the dialog, only traps Tab.
97
97
  function handleKeydown(e: KeyboardEvent) {
98
+ if (e.key === 'Escape') {
99
+ handleCancel(e);
100
+ return;
101
+ }
98
102
  if (e.key !== 'Tab') return;
99
103
 
100
104
  // Trap focus within the dialog so Tab can't escape to the page behind.
@@ -117,33 +121,30 @@ function handleKeydown(e: KeyboardEvent) {
117
121
  first.focus();
118
122
  }
119
123
  }
120
-
121
- function handleWindowKeydown(e: KeyboardEvent) {
122
- if (open && e.key === 'Escape') {
123
- e.preventDefault();
124
- oncancel?.();
125
- }
126
- }
127
124
  </script>
128
125
 
129
- <svelte:window onkeydown={handleWindowKeydown} />
130
-
131
126
  {#if open}
132
127
  <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
133
- <div
128
+ <dialog
134
129
  bind:this={backdropEl}
135
130
  class="dialog-backdrop"
136
- role="dialog"
137
131
  aria-modal="true"
138
- aria-labelledby="dialog-title"
139
- aria-describedby="dialog-message"
132
+ aria-labelledby={`${instanceId}-title`}
133
+ aria-describedby={`${instanceId}-message`}
140
134
  tabindex="-1"
141
135
  onclick={handleBackdropClick}
142
136
  onkeydown={handleKeydown}
137
+ oncancel={handleCancel}
143
138
  >
144
139
  <div class="dialog-content">
145
- <h2 id="dialog-title" class="dialog-title">{title}</h2>
146
- <p id="dialog-message" class="dialog-message">{message}</p>
140
+ <h2 id={`${instanceId}-title`} class="dialog-title">{title}</h2>
141
+ <div id={`${instanceId}-message`} class="dialog-message">
142
+ {#if typeof message === 'string'}
143
+ {message}
144
+ {:else}
145
+ {@render message()}
146
+ {/if}
147
+ </div>
147
148
 
148
149
  <div class="dialog-actions">
149
150
  <button
@@ -171,7 +172,7 @@ function handleWindowKeydown(e: KeyboardEvent) {
171
172
  </button>
172
173
  </div>
173
174
  </div>
174
- </div>
175
+ </dialog>
175
176
  {/if}
176
177
 
177
178
  <style>
@@ -181,9 +182,19 @@ function handleWindowKeydown(e: KeyboardEvent) {
181
182
  display: flex;
182
183
  align-items: center;
183
184
  justify-content: center;
184
- background-color: var(--smrt-color-scrim, rgba(0, 0, 0, 0.4));
185
- z-index: var(--smrt-z-index-dialog, 1000);
185
+ width: 100%;
186
+ height: 100%;
187
+ max-width: 100%;
188
+ max-height: 100%;
189
+ margin: 0;
190
+ border: none;
191
+ box-sizing: border-box;
192
+ background: transparent;
186
193
  padding: 1rem;
194
+ }
195
+
196
+ .dialog-backdrop::backdrop {
197
+ background-color: var(--smrt-color-scrim, rgba(0, 0, 0, 0.4));
187
198
  backdrop-filter: blur(2px);
188
199
  }
189
200
 
@@ -1,11 +1,23 @@
1
+ /**
2
+ * ConfirmDialog - Modal confirmation dialog
3
+ * refactored for Material 3
4
+ *
5
+ * Provides a consistent confirmation dialog for destructive actions
6
+ * or important decisions.
7
+ *
8
+ * Native showModal() puts confirmations above existing Modal/Drawer surfaces,
9
+ * traps focus and makes the background inert. Escape is handled locally so
10
+ * only the active confirmation requests cancellation.
11
+ */
12
+ import { type Snippet } from 'svelte';
1
13
  /** Props for ConfirmDialog component */
2
14
  export interface Props {
3
15
  /** Whether the dialog is open */
4
16
  open: boolean;
5
17
  /** Dialog title */
6
18
  title: string;
7
- /** Dialog message */
8
- message: string;
19
+ /** Plain text or rich message snippet */
20
+ message: string | Snippet;
9
21
  /** Confirm button label */
10
22
  confirmLabel?: string;
11
23
  /** Cancel button label */
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/ConfirmDialog.svelte.ts"],"names":[],"mappings":"AAoBA,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA2HD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"ConfirmDialog.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/feedback/ConfirmDialog.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,OAAO,EAAQ,MAAM,QAAQ,CAAC;AAI5C,wCAAwC;AACxC,MAAM,WAAW,KAAK;IACpB,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,2BAA2B;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AA8HD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}