@nuvia-ui/components 4.0.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 (230) hide show
  1. package/package.json +27 -0
  2. package/src/ds-accordion/ds-accordion-item.js +288 -0
  3. package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
  4. package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
  5. package/src/ds-accordion/ds-accordion.js +68 -0
  6. package/src/ds-accordion/ds-accordion.stories.js +118 -0
  7. package/src/ds-accordion/ds-accordion.test.js +146 -0
  8. package/src/ds-accordion/index.js +2 -0
  9. package/src/ds-action-bar/ds-action-bar.js +116 -0
  10. package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
  11. package/src/ds-action-bar/ds-action-bar.test.js +64 -0
  12. package/src/ds-action-bar/index.js +1 -0
  13. package/src/ds-alert/ds-alert.a11y.test.js +151 -0
  14. package/src/ds-alert/ds-alert.js +223 -0
  15. package/src/ds-alert/ds-alert.mdx +142 -0
  16. package/src/ds-alert/ds-alert.stories.js +166 -0
  17. package/src/ds-alert/ds-alert.test.js +256 -0
  18. package/src/ds-alert/index.js +1 -0
  19. package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
  20. package/src/ds-avatar/ds-avatar.js +216 -0
  21. package/src/ds-avatar/ds-avatar.stories.js +120 -0
  22. package/src/ds-avatar/ds-avatar.test.js +83 -0
  23. package/src/ds-avatar/index.js +1 -0
  24. package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
  25. package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
  26. package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
  27. package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
  28. package/src/ds-avatar-extended/index.js +1 -0
  29. package/src/ds-banner/ds-banner.a11y.test.js +51 -0
  30. package/src/ds-banner/ds-banner.js +233 -0
  31. package/src/ds-banner/ds-banner.stories.js +185 -0
  32. package/src/ds-banner/ds-banner.test.js +116 -0
  33. package/src/ds-banner/index.js +1 -0
  34. package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
  35. package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
  36. package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
  37. package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
  38. package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
  39. package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
  40. package/src/ds-button/ds-button.a11y.test.js +49 -0
  41. package/src/ds-button/ds-button.js +205 -0
  42. package/src/ds-button/ds-button.mdx +141 -0
  43. package/src/ds-button/ds-button.stories.js +152 -0
  44. package/src/ds-button/ds-button.test.js +62 -0
  45. package/src/ds-button/index.js +1 -0
  46. package/src/ds-button-group/ds-button-group.js +82 -0
  47. package/src/ds-button-group/ds-button-group.mdx +39 -0
  48. package/src/ds-button-group/ds-button-group.stories.js +47 -0
  49. package/src/ds-button-group/ds-button-group.test.js +47 -0
  50. package/src/ds-button-group/index.js +1 -0
  51. package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
  52. package/src/ds-checkbox/ds-checkbox.js +271 -0
  53. package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
  54. package/src/ds-checkbox/ds-checkbox.test.js +191 -0
  55. package/src/ds-checkbox/index.js +1 -0
  56. package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
  57. package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
  58. package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
  59. package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
  60. package/src/ds-checkbox-group/index.js +1 -0
  61. package/src/ds-dialog/ds-dialog.js +466 -0
  62. package/src/ds-dialog/ds-dialog.stories.js +274 -0
  63. package/src/ds-dialog/ds-dialog.test.js +441 -0
  64. package/src/ds-dialog/index.js +1 -0
  65. package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
  66. package/src/ds-dropdown/ds-dropdown.js +891 -0
  67. package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
  68. package/src/ds-dropdown/ds-dropdown.test.js +268 -0
  69. package/src/ds-dropdown/index.js +1 -0
  70. package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
  71. package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
  72. package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
  73. package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
  74. package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
  75. package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
  76. package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
  77. package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
  78. package/src/ds-file-uploader/ds-file-uploader.js +442 -0
  79. package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
  80. package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
  81. package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
  82. package/src/ds-header/ds-header.a11y.test.js +38 -0
  83. package/src/ds-header/ds-header.js +149 -0
  84. package/src/ds-header/ds-header.stories.js +63 -0
  85. package/src/ds-header/ds-header.test.js +52 -0
  86. package/src/ds-header/index.js +1 -0
  87. package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
  88. package/src/ds-header-nav/ds-header-nav.js +114 -0
  89. package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
  90. package/src/ds-header-nav/ds-header-nav.test.js +93 -0
  91. package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
  92. package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
  93. package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
  94. package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
  95. package/src/ds-icon/ds-icon.a11y.test.js +49 -0
  96. package/src/ds-icon/ds-icon.js +75 -0
  97. package/src/ds-icon/ds-icon.mdx +36 -0
  98. package/src/ds-icon/ds-icon.stories.js +88 -0
  99. package/src/ds-icon/ds-icon.test.js +97 -0
  100. package/src/ds-icon/index.js +1 -0
  101. package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
  102. package/src/ds-icon-button/ds-icon-button.js +224 -0
  103. package/src/ds-icon-button/ds-icon-button.mdx +131 -0
  104. package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
  105. package/src/ds-icon-button/ds-icon-button.test.js +90 -0
  106. package/src/ds-icon-button/index.js +1 -0
  107. package/src/ds-input/ds-input.a11y.test.js +145 -0
  108. package/src/ds-input/ds-input.js +645 -0
  109. package/src/ds-input/ds-input.mdx +251 -0
  110. package/src/ds-input/ds-input.stories.js +298 -0
  111. package/src/ds-input/ds-input.test.js +792 -0
  112. package/src/ds-input/index.js +1 -0
  113. package/src/ds-link/ds-link.js +111 -0
  114. package/src/ds-link/ds-link.stories.js +56 -0
  115. package/src/ds-link/ds-link.test.js +74 -0
  116. package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
  117. package/src/ds-list-item/ds-list-item.js +292 -0
  118. package/src/ds-list-item/ds-list-item.stories.js +101 -0
  119. package/src/ds-list-item/ds-list-item.test.js +63 -0
  120. package/src/ds-menu/ds-menu.js +30 -0
  121. package/src/ds-menu/ds-menu.stories.js +120 -0
  122. package/src/ds-menu/ds-menu.test.js +123 -0
  123. package/src/ds-menu-group/ds-menu-group.js +101 -0
  124. package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
  125. package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
  126. package/src/ds-nav-item/ds-nav-item.js +307 -0
  127. package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
  128. package/src/ds-nav-item/ds-nav-item.test.js +169 -0
  129. package/src/ds-nav-item/index.js +1 -0
  130. package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
  131. package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
  132. package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
  133. package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
  134. package/src/ds-nav-vertical/index.js +1 -0
  135. package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
  136. package/src/ds-pagination/ds-pagination.js +232 -0
  137. package/src/ds-pagination/ds-pagination.stories.js +63 -0
  138. package/src/ds-pagination/ds-pagination.test.js +141 -0
  139. package/src/ds-pagination/index.js +1 -0
  140. package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
  141. package/src/ds-progress-bar/ds-progress-bar.js +81 -0
  142. package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
  143. package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
  144. package/src/ds-radio/ds-radio.a11y.test.js +69 -0
  145. package/src/ds-radio/ds-radio.js +240 -0
  146. package/src/ds-radio/ds-radio.stories.js +102 -0
  147. package/src/ds-radio/ds-radio.test.js +114 -0
  148. package/src/ds-radio/index.js +1 -0
  149. package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
  150. package/src/ds-radio-group/ds-radio-group.js +257 -0
  151. package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
  152. package/src/ds-radio-group/ds-radio-group.test.js +194 -0
  153. package/src/ds-radio-group/index.js +1 -0
  154. package/src/ds-rich-list/ds-rich-list.js +246 -0
  155. package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
  156. package/src/ds-rich-list/ds-rich-list.test.js +293 -0
  157. package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
  158. package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
  159. package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
  160. package/src/ds-slider/ds-slider.js +399 -0
  161. package/src/ds-slider/ds-slider.stories.js +107 -0
  162. package/src/ds-slider/ds-slider.test.js +308 -0
  163. package/src/ds-spinner/ds-spinner.js +173 -0
  164. package/src/ds-spinner/ds-spinner.stories.js +52 -0
  165. package/src/ds-spinner/ds-spinner.test.js +50 -0
  166. package/src/ds-status-border/ds-status-border.js +88 -0
  167. package/src/ds-status-border/ds-status-border.stories.js +242 -0
  168. package/src/ds-status-border/ds-status-border.test.js +168 -0
  169. package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
  170. package/src/ds-stepper/ds-stepper.js +207 -0
  171. package/src/ds-stepper/ds-stepper.stories.js +530 -0
  172. package/src/ds-stepper/ds-stepper.test.js +311 -0
  173. package/src/ds-stepper-item/ds-stepper-item.js +485 -0
  174. package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
  175. package/src/ds-switch/ds-switch.js +348 -0
  176. package/src/ds-switch/ds-switch.stories.js +145 -0
  177. package/src/ds-switch/ds-switch.test.js +226 -0
  178. package/src/ds-switch/index.js +1 -0
  179. package/src/ds-tab-item/ds-tab-item.js +341 -0
  180. package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
  181. package/src/ds-tabs/ds-tab-panel.js +48 -0
  182. package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
  183. package/src/ds-tabs/ds-tabs.js +180 -0
  184. package/src/ds-tabs/ds-tabs.stories.js +152 -0
  185. package/src/ds-tabs/ds-tabs.test.js +306 -0
  186. package/src/ds-tabs/index.js +3 -0
  187. package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
  188. package/src/ds-tag-action/ds-tag-action.js +185 -0
  189. package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
  190. package/src/ds-tag-action/ds-tag-action.test.js +44 -0
  191. package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
  192. package/src/ds-tag-removable/ds-tag-removable.js +146 -0
  193. package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
  194. package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
  195. package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
  196. package/src/ds-tag-status/ds-tag-status.js +164 -0
  197. package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
  198. package/src/ds-tag-status/ds-tag-status.test.js +140 -0
  199. package/src/ds-tag-status/index.js +1 -0
  200. package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
  201. package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
  202. package/src/ds-textarea/ds-textarea.js +505 -0
  203. package/src/ds-textarea/ds-textarea.stories.js +335 -0
  204. package/src/ds-textarea/ds-textarea.test.js +218 -0
  205. package/src/ds-textarea/index.js +1 -0
  206. package/src/ds-thumbnail/ds-thumbnail.js +207 -0
  207. package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
  208. package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
  209. package/src/ds-toast/ds-toast-provider.js +110 -0
  210. package/src/ds-toast/ds-toast.a11y.test.js +34 -0
  211. package/src/ds-toast/ds-toast.js +243 -0
  212. package/src/ds-toast/ds-toast.stories.js +143 -0
  213. package/src/ds-toast/ds-toast.test.js +93 -0
  214. package/src/ds-toast/index.js +2 -0
  215. package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
  216. package/src/ds-tooltip/ds-tooltip.js +217 -0
  217. package/src/ds-tooltip/ds-tooltip.mdx +75 -0
  218. package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
  219. package/src/ds-tooltip/ds-tooltip.test.js +191 -0
  220. package/src/ds-tooltip/index.js +1 -0
  221. package/src/ds-tooltip/positioner.js +117 -0
  222. package/src/index.js +50 -0
  223. package/src/mixins/field-label.mixin.js +113 -0
  224. package/src/mixins/field-message.mixin.js +66 -0
  225. package/src/token-provider/index.js +1 -0
  226. package/src/token-provider/token-provider.a11y.test.js +44 -0
  227. package/src/token-provider/token-provider.js +85 -0
  228. package/src/token-provider/token-provider.stories.js +105 -0
  229. package/src/token-provider/token-provider.test.js +134 -0
  230. package/src/utils/number-input.utils.js +42 -0
@@ -0,0 +1,293 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-rich-list.js';
3
+ import '../ds-rich-list-item/ds-rich-list-item.js';
4
+
5
+ describe('ds-rich-list', () => {
6
+ let container;
7
+
8
+ const tick = (ms = 50) => new Promise(resolve => setTimeout(resolve, ms));
9
+
10
+ beforeEach(() => {
11
+ container = document.createElement('div');
12
+ document.body.appendChild(container);
13
+ });
14
+
15
+ afterEach(() => {
16
+ container.remove();
17
+ });
18
+
19
+ // ─── Rendering ───
20
+
21
+ it('renders with default values', async () => {
22
+ container.innerHTML = '<ds-rich-list></ds-rich-list>';
23
+ const el = container.querySelector('ds-rich-list');
24
+ await tick();
25
+
26
+ expect(el.selectable).toBe('none');
27
+ expect(el.name).toBe('');
28
+ });
29
+
30
+ it('renders items via slot', async () => {
31
+ container.innerHTML = `
32
+ <ds-rich-list>
33
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
34
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
35
+ </ds-rich-list>
36
+ `;
37
+ const el = container.querySelector('ds-rich-list');
38
+ await tick();
39
+
40
+ const items = el.querySelectorAll('ds-rich-list-item');
41
+ expect(items.length).toBe(2);
42
+ });
43
+
44
+ // ─── ARIA ───
45
+
46
+ it('sets role=list for non-selectable', async () => {
47
+ container.innerHTML = '<ds-rich-list><ds-rich-list-item><span slot="title">A</span></ds-rich-list-item></ds-rich-list>';
48
+ const el = container.querySelector('ds-rich-list');
49
+ await tick();
50
+
51
+ const listDiv = el.shadowRoot.querySelector('.list');
52
+ expect(listDiv.getAttribute('role')).toBe('list');
53
+ });
54
+
55
+ it('sets role=listbox for selectable', async () => {
56
+ container.innerHTML = '<ds-rich-list selectable="single"><ds-rich-list-item><span slot="title">A</span></ds-rich-list-item></ds-rich-list>';
57
+ const el = container.querySelector('ds-rich-list');
58
+ await tick();
59
+
60
+ const listDiv = el.shadowRoot.querySelector('.list');
61
+ expect(listDiv.getAttribute('role')).toBe('listbox');
62
+ });
63
+
64
+ it('sets aria-multiselectable for multiple', async () => {
65
+ container.innerHTML = '<ds-rich-list selectable="multiple"><ds-rich-list-item><span slot="title">A</span></ds-rich-list-item></ds-rich-list>';
66
+ const el = container.querySelector('ds-rich-list');
67
+ await tick();
68
+
69
+ const listDiv = el.shadowRoot.querySelector('.list');
70
+ expect(listDiv.getAttribute('aria-multiselectable')).toBe('true');
71
+ });
72
+
73
+ // ─── Selection Propagation ───
74
+
75
+ it('propagates selectable to items', async () => {
76
+ container.innerHTML = `
77
+ <ds-rich-list selectable="multiple">
78
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
79
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
80
+ </ds-rich-list>
81
+ `;
82
+ const el = container.querySelector('ds-rich-list');
83
+ await tick();
84
+
85
+ const items = el.querySelectorAll('ds-rich-list-item');
86
+ items.forEach(item => {
87
+ expect(item._selectable).toBe('multiple');
88
+ });
89
+ });
90
+
91
+ it('propagates name to items', async () => {
92
+ container.innerHTML = `
93
+ <ds-rich-list selectable="single" name="test-group">
94
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
95
+ </ds-rich-list>
96
+ `;
97
+ const el = container.querySelector('ds-rich-list');
98
+ await tick();
99
+
100
+ const item = el.querySelector('ds-rich-list-item');
101
+ expect(item._name).toBe('test-group');
102
+ });
103
+
104
+ // ─── Single Selection ───
105
+
106
+ it('deselects siblings on single selection', async () => {
107
+ container.innerHTML = `
108
+ <ds-rich-list selectable="single">
109
+ <ds-rich-list-item value="a" selected><span slot="title">A</span></ds-rich-list-item>
110
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
111
+ </ds-rich-list>
112
+ `;
113
+ const el = container.querySelector('ds-rich-list');
114
+ await tick();
115
+
116
+ const items = el.querySelectorAll('ds-rich-list-item');
117
+ expect(items[0].selected).toBe(true);
118
+
119
+ // Click item B
120
+ const itemB = items[1].shadowRoot.querySelector('.item');
121
+ itemB.click();
122
+ await tick();
123
+
124
+ expect(items[0].selected).toBe(false);
125
+ expect(items[1].selected).toBe(true);
126
+ });
127
+
128
+ // ─── Multiple Selection ───
129
+
130
+ it('allows multiple selections', async () => {
131
+ container.innerHTML = `
132
+ <ds-rich-list selectable="multiple">
133
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
134
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
135
+ </ds-rich-list>
136
+ `;
137
+ const el = container.querySelector('ds-rich-list');
138
+ await tick();
139
+
140
+ const items = el.querySelectorAll('ds-rich-list-item');
141
+
142
+ items[0].shadowRoot.querySelector('.item').click();
143
+ await tick();
144
+ items[1].shadowRoot.querySelector('.item').click();
145
+ await tick();
146
+
147
+ expect(items[0].selected).toBe(true);
148
+ expect(items[1].selected).toBe(true);
149
+ });
150
+
151
+ // ─── Change Event ───
152
+
153
+ it('dispatches aggregated change event', async () => {
154
+ container.innerHTML = `
155
+ <ds-rich-list selectable="single">
156
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
157
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
158
+ </ds-rich-list>
159
+ `;
160
+ const el = container.querySelector('ds-rich-list');
161
+ await tick();
162
+
163
+ let detail = null;
164
+ el.addEventListener('change', (e) => { detail = e.detail; });
165
+
166
+ const items = el.querySelectorAll('ds-rich-list-item');
167
+ items[0].shadowRoot.querySelector('.item').click();
168
+ await tick();
169
+
170
+ expect(detail).not.toBeNull();
171
+ expect(detail.value).toBe('a');
172
+ expect(detail.values).toEqual(['a']);
173
+ });
174
+
175
+ // ─── Layout Propagation ───
176
+
177
+ it('forces media column when any item has media', async () => {
178
+ container.innerHTML = `
179
+ <ds-rich-list>
180
+ <ds-rich-list-item media="test.jpg"><span slot="title">With media</span></ds-rich-list-item>
181
+ <ds-rich-list-item><span slot="title">Without media</span></ds-rich-list-item>
182
+ </ds-rich-list>
183
+ `;
184
+ const el = container.querySelector('ds-rich-list');
185
+ await tick(100);
186
+
187
+ const items = el.querySelectorAll('ds-rich-list-item');
188
+ // Item with media has has-media
189
+ expect(items[0].hasAttribute('has-media')).toBe(true);
190
+ // Item without media should get force-media from parent
191
+ expect(items[1].hasAttribute('force-media')).toBe(true);
192
+ });
193
+
194
+ // ─── Keyboard Navigation ───
195
+
196
+ it('handles arrow key navigation', async () => {
197
+ container.innerHTML = `
198
+ <ds-rich-list selectable="single">
199
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
200
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
201
+ <ds-rich-list-item value="c"><span slot="title">C</span></ds-rich-list-item>
202
+ </ds-rich-list>
203
+ `;
204
+ const el = container.querySelector('ds-rich-list');
205
+ await tick();
206
+
207
+ const items = el.querySelectorAll('ds-rich-list-item');
208
+
209
+ // Focus first item
210
+ items[0].focus();
211
+ await tick();
212
+
213
+ // ArrowDown should move focus to next item
214
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
215
+ await tick();
216
+
217
+ expect(document.activeElement).toBe(items[1]);
218
+ });
219
+
220
+ it('handles Home/End key navigation', async () => {
221
+ container.innerHTML = `
222
+ <ds-rich-list selectable="single">
223
+ <ds-rich-list-item value="a"><span slot="title">A</span></ds-rich-list-item>
224
+ <ds-rich-list-item value="b"><span slot="title">B</span></ds-rich-list-item>
225
+ <ds-rich-list-item value="c"><span slot="title">C</span></ds-rich-list-item>
226
+ </ds-rich-list>
227
+ `;
228
+ const el = container.querySelector('ds-rich-list');
229
+ await tick();
230
+
231
+ const items = el.querySelectorAll('ds-rich-list-item');
232
+
233
+ items[0].focus();
234
+ await tick();
235
+
236
+ // End goes to last
237
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', bubbles: true }));
238
+ await tick();
239
+
240
+ expect(document.activeElement).toBe(items[2]);
241
+
242
+ // Home goes back to first
243
+ el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home', bubbles: true }));
244
+ await tick();
245
+
246
+ expect(document.activeElement).toBe(items[0]);
247
+ });
248
+
249
+ // ─── Custom Width ───
250
+
251
+ it('updates layout when item structure changes', async () => {
252
+ container.innerHTML = `
253
+ <ds-rich-list>
254
+ <ds-rich-list-item id="item1"><span slot="action-group">Action</span></ds-rich-list-item>
255
+ <ds-rich-list-item id="item2"><span slot="title">No Action</span></ds-rich-list-item>
256
+ </ds-rich-list>
257
+ `;
258
+ const el = container.querySelector('ds-rich-list');
259
+ await tick(100);
260
+
261
+ const item1 = el.querySelector('#item1');
262
+ const item2 = el.querySelector('#item2');
263
+
264
+ // Initially item2 should be forced to show action group
265
+ expect(item1.hasAttribute('has-action-group')).toBe(true);
266
+ expect(item2.hasAttribute('force-action-group')).toBe(true);
267
+
268
+ // Remove action group from item1
269
+ item1.innerHTML = '<span slot="title">Updated</span>';
270
+ await tick(100);
271
+
272
+ // item1 no longer has actions
273
+ expect(item1.hasAttribute('has-action-group')).toBe(false);
274
+ // item2 should no longer be forced
275
+ expect(item2.hasAttribute('force-action-group')).toBe(false);
276
+ });
277
+
278
+ // ─── Custom Width ───
279
+
280
+ it('propagates custom-width to items', async () => {
281
+ container.innerHTML = `
282
+ <ds-rich-list custom-width="200px">
283
+ <ds-rich-list-item><span slot="title">A</span><div slot="custom">Custom</div></ds-rich-list-item>
284
+ </ds-rich-list>
285
+ `;
286
+ const el = container.querySelector('ds-rich-list');
287
+ await tick(100);
288
+
289
+ const item = el.querySelector('ds-rich-list-item');
290
+ const value = item.style.getPropertyValue('--ds-rich-list-custom-width');
291
+ expect(value).toBe('200px');
292
+ });
293
+ });