@api-client/ui 0.4.1 → 0.4.3

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 (109) hide show
  1. package/.vscode/settings.json +1 -0
  2. package/build/src/index.d.ts +3 -0
  3. package/build/src/index.d.ts.map +1 -1
  4. package/build/src/index.js +4 -0
  5. package/build/src/index.js.map +1 -1
  6. package/build/src/lib/Dom.d.ts +5 -0
  7. package/build/src/lib/Dom.d.ts.map +1 -0
  8. package/build/src/lib/Dom.js +24 -0
  9. package/build/src/lib/Dom.js.map +1 -0
  10. package/build/src/md/button/internals/base.d.ts +27 -0
  11. package/build/src/md/button/internals/base.d.ts.map +1 -1
  12. package/build/src/md/button/internals/base.js +90 -1
  13. package/build/src/md/button/internals/base.js.map +1 -1
  14. package/build/src/md/icons/internals/Icon.js +2 -2
  15. package/build/src/md/icons/internals/Icon.js.map +1 -1
  16. package/build/src/md/list/internals/List.d.ts +1 -1
  17. package/build/src/md/list/internals/List.d.ts.map +1 -1
  18. package/build/src/md/list/internals/List.js +4 -4
  19. package/build/src/md/list/internals/List.js.map +1 -1
  20. package/build/src/md/list/internals/ListItem.d.ts +1 -0
  21. package/build/src/md/list/internals/ListItem.d.ts.map +1 -1
  22. package/build/src/md/list/internals/ListItem.js +10 -10
  23. package/build/src/md/list/internals/ListItem.js.map +1 -1
  24. package/build/src/md/list/internals/ListItem.styles.d.ts.map +1 -1
  25. package/build/src/md/list/internals/ListItem.styles.js +2 -20
  26. package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
  27. package/build/src/md/menu/index.d.ts +4 -0
  28. package/build/src/md/menu/index.d.ts.map +1 -0
  29. package/build/src/md/menu/index.js +4 -0
  30. package/build/src/md/menu/index.js.map +1 -0
  31. package/build/src/md/menu/internal/Menu.d.ts +76 -0
  32. package/build/src/md/menu/internal/Menu.d.ts.map +1 -0
  33. package/build/src/md/menu/internal/Menu.js +235 -0
  34. package/build/src/md/menu/internal/Menu.js.map +1 -0
  35. package/build/src/md/menu/internal/Menu.styles.d.ts +3 -0
  36. package/build/src/md/menu/internal/Menu.styles.d.ts.map +1 -0
  37. package/build/src/md/menu/internal/Menu.styles.js +185 -0
  38. package/build/src/md/menu/internal/Menu.styles.js.map +1 -0
  39. package/build/src/md/menu/internal/MenuItem.d.ts +77 -0
  40. package/build/src/md/menu/internal/MenuItem.d.ts.map +1 -0
  41. package/build/src/md/menu/internal/MenuItem.js +216 -0
  42. package/build/src/md/menu/internal/MenuItem.js.map +1 -0
  43. package/build/src/md/menu/internal/MenuItem.styles.d.ts +3 -0
  44. package/build/src/md/menu/internal/MenuItem.styles.d.ts.map +1 -0
  45. package/build/src/md/menu/internal/MenuItem.styles.js +64 -0
  46. package/build/src/md/menu/internal/MenuItem.styles.js.map +1 -0
  47. package/build/src/md/menu/internal/SubMenu.d.ts +56 -0
  48. package/build/src/md/menu/internal/SubMenu.d.ts.map +1 -0
  49. package/build/src/md/menu/internal/SubMenu.js +171 -0
  50. package/build/src/md/menu/internal/SubMenu.js.map +1 -0
  51. package/build/src/md/menu/internal/SubMenu.styles.d.ts +3 -0
  52. package/build/src/md/menu/internal/SubMenu.styles.d.ts.map +1 -0
  53. package/build/src/md/menu/internal/SubMenu.styles.js +8 -0
  54. package/build/src/md/menu/internal/SubMenu.styles.js.map +1 -0
  55. package/build/src/md/menu/ui-menu-item.d.ts +20 -0
  56. package/build/src/md/menu/ui-menu-item.d.ts.map +1 -0
  57. package/build/src/md/menu/ui-menu-item.js +37 -0
  58. package/build/src/md/menu/ui-menu-item.js.map +1 -0
  59. package/build/src/md/menu/ui-menu.d.ts +22 -0
  60. package/build/src/md/menu/ui-menu.d.ts.map +1 -0
  61. package/build/src/md/menu/ui-menu.js +38 -0
  62. package/build/src/md/menu/ui-menu.js.map +1 -0
  63. package/build/src/md/menu/ui-sub-menu.d.ts +20 -0
  64. package/build/src/md/menu/ui-sub-menu.d.ts.map +1 -0
  65. package/build/src/md/menu/ui-sub-menu.js +37 -0
  66. package/build/src/md/menu/ui-sub-menu.js.map +1 -0
  67. package/build/src/mixins/FileDropMixin.d.ts.map +1 -1
  68. package/build/src/mixins/FileDropMixin.js +7 -8
  69. package/build/src/mixins/FileDropMixin.js.map +1 -1
  70. package/build/src/mixins/RenderableMixin.d.ts.map +1 -1
  71. package/build/src/mixins/RenderableMixin.js +2 -3
  72. package/build/src/mixins/RenderableMixin.js.map +1 -1
  73. package/demo/md/index.html +2 -0
  74. package/demo/md/menu/index.html +19 -0
  75. package/demo/md/menu/index.ts +154 -0
  76. package/package.json +2 -3
  77. package/src/index.ts +5 -0
  78. package/src/lib/Dom.ts +26 -0
  79. package/src/md/button/internals/base.ts +77 -0
  80. package/src/md/icons/internals/Icon.ts +2 -2
  81. package/src/md/list/internals/List.ts +4 -4
  82. package/src/md/list/internals/ListItem.styles.ts +2 -20
  83. package/src/md/list/internals/ListItem.ts +11 -11
  84. package/src/md/menu/README.md +253 -0
  85. package/src/md/menu/index.ts +3 -0
  86. package/src/md/menu/internal/Menu.styles.ts +185 -0
  87. package/src/md/menu/internal/Menu.ts +205 -0
  88. package/src/md/menu/internal/MenuItem.styles.ts +64 -0
  89. package/src/md/menu/internal/MenuItem.ts +217 -0
  90. package/src/md/menu/internal/SubMenu.styles.ts +8 -0
  91. package/src/md/menu/internal/SubMenu.ts +179 -0
  92. package/src/md/menu/ui-menu-item.ts +25 -0
  93. package/src/md/menu/ui-menu.ts +26 -0
  94. package/src/md/menu/ui-sub-menu.ts +25 -0
  95. package/src/mixins/FileDropMixin.ts +106 -107
  96. package/src/mixins/RenderableMixin.ts +107 -108
  97. package/test/md/menu/Menu.test.ts +509 -0
  98. package/test/md/menu/MenuIntegration.test.ts +426 -0
  99. package/test/md/menu/MenuItem.test.ts +361 -0
  100. package/test/md/menu/SubMenu.test.ts +411 -0
  101. /package/test/{ui → md}/button/UiButton.test.ts +0 -0
  102. /package/test/{ui → md}/button/UiIconButton.test.ts +0 -0
  103. /package/test/{ui → md}/chip/UiChip.test.ts +0 -0
  104. /package/test/{ui → md}/collapse/UiCollapse.test.ts +0 -0
  105. /package/test/{ui → md}/collapse/flex-layout.test.ts +0 -0
  106. /package/test/{ui → md}/date-time/DateTime.test.ts +0 -0
  107. /package/test/{ui → md}/dialog/UiDialog.test.ts +0 -0
  108. /package/test/{ui → md}/progress/UiProgressElement.test.ts +0 -0
  109. /package/test/{ui → md}/progress/UiRangeElement.test.ts +0 -0
@@ -0,0 +1,411 @@
1
+ import { assert, fixture, html, nextFrame, oneEvent } from '@open-wc/testing'
2
+ import sinon from 'sinon'
3
+ import UiSubMenu from '../../../src/md/menu/internal/SubMenu.js'
4
+ import UiMenuItem from '../../../src/md/menu/internal/MenuItem.js'
5
+ import Menu from '../../../src/md/menu/internal/Menu.js'
6
+
7
+ import '../../../src/md/menu/ui-menu.js'
8
+ import '../../../src/md/menu/ui-menu-item.js'
9
+ import '../../../src/md/menu/ui-sub-menu.js'
10
+ import '../../../src/md/icons/ui-icon.js'
11
+
12
+ describe('md', () => {
13
+ describe('SubMenu', () => {
14
+ async function basicFixture(): Promise<UiSubMenu> {
15
+ return fixture(html`
16
+ <ui-sub-menu id="test-submenu">
17
+ <ui-menu-item>Item 1</ui-menu-item>
18
+ <ui-menu-item>Item 2</ui-menu-item>
19
+ <ui-menu-item disabled>Item 3 (Disabled)</ui-menu-item>
20
+ </ui-sub-menu>
21
+ `)
22
+ }
23
+
24
+ async function withAnchorFixture(): Promise<HTMLElement> {
25
+ return fixture(html`
26
+ <div>
27
+ <ui-menu-item id="anchor-item">Anchor Item</ui-menu-item>
28
+ <ui-sub-menu id="test-submenu" anchor="anchor-item">
29
+ <ui-menu-item>Submenu Item 1</ui-menu-item>
30
+ <ui-menu-item>Submenu Item 2</ui-menu-item>
31
+ </ui-sub-menu>
32
+ </div>
33
+ `)
34
+ }
35
+
36
+ async function withParentMenuFixture(): Promise<HTMLElement> {
37
+ return fixture(html`
38
+ <ui-menu id="parent-menu">
39
+ <ui-menu-item id="trigger-item" submenu="child-submenu">
40
+ <span>Parent Item</span>
41
+ </ui-menu-item>
42
+
43
+ <ui-sub-menu id="child-submenu" anchor="trigger-item">
44
+ <ui-menu-item>Child Item 1</ui-menu-item>
45
+ <ui-menu-item>Child Item 2</ui-menu-item>
46
+ </ui-sub-menu>
47
+ </ui-menu>
48
+ `)
49
+ }
50
+
51
+ async function nestedSubmenuFixture(): Promise<HTMLElement> {
52
+ return fixture(html`
53
+ <ui-menu id="main-menu">
54
+ <ui-menu-item id="level1-item" submenu="level1-submenu">Level 1</ui-menu-item>
55
+
56
+ <ui-sub-menu id="level1-submenu" anchor="level1-item">
57
+ <ui-menu-item id="level2-item" submenu="level2-submenu">Level 2</ui-menu-item>
58
+ <ui-menu-item>Regular Item</ui-menu-item>
59
+
60
+ <ui-sub-menu id="level2-submenu" anchor="level2-item">
61
+ <ui-menu-item>Level 3 Item 1</ui-menu-item>
62
+ <ui-menu-item>Level 3 Item 2</ui-menu-item>
63
+ </ui-sub-menu>
64
+ </ui-sub-menu>
65
+ </ui-menu>
66
+ `)
67
+ }
68
+
69
+ describe('Basic functionality', () => {
70
+ it('should create submenu element', async () => {
71
+ const element = await basicFixture()
72
+ assert.instanceOf(element, UiSubMenu)
73
+ assert.equal(element.tagName.toLowerCase(), 'ui-sub-menu')
74
+ })
75
+
76
+ it('should inherit from Menu', async () => {
77
+ const element = await basicFixture()
78
+ assert.instanceOf(element, Menu)
79
+ })
80
+
81
+ it('should have correct default properties', async () => {
82
+ const element = await basicFixture()
83
+ assert.isFalse(element.open)
84
+ assert.isFalse(element.disabled)
85
+ assert.isNull(element.parentMenu)
86
+ assert.isUndefined(element.anchor)
87
+ })
88
+
89
+ it('should set correct ARIA attributes', async () => {
90
+ const element = await basicFixture()
91
+ assert.equal(element.getAttribute('role'), 'menu')
92
+ assert.equal(element.getAttribute('aria-label'), 'Submenu')
93
+ })
94
+ })
95
+
96
+ describe('Anchor functionality', () => {
97
+ it('should get anchor element reference', async () => {
98
+ const container = await withAnchorFixture()
99
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
100
+ const anchorItem = container.querySelector('#anchor-item') as UiMenuItem
101
+ await nextFrame()
102
+
103
+ assert.equal(submenu.anchorElement, anchorItem)
104
+ })
105
+
106
+ it('should return null for invalid anchor', async () => {
107
+ const submenu: UiSubMenu = await fixture(html`<ui-sub-menu anchor="nonexistent"></ui-sub-menu>`)
108
+ await nextFrame()
109
+
110
+ assert.isNull(submenu.anchorElement)
111
+ })
112
+
113
+ it('should update anchor positioning', async () => {
114
+ const container = await withAnchorFixture()
115
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
116
+ const anchorItem = container.querySelector('#anchor-item') as UiMenuItem
117
+ await nextFrame()
118
+
119
+ // This tests that the CSS positioning properties are set
120
+ // The actual values depend on the CSS implementation
121
+ assert.isNotNull(submenu.anchorElement)
122
+ assert.equal(submenu.anchorElement, anchorItem)
123
+ })
124
+ })
125
+
126
+ describe('Show/Hide functionality', () => {
127
+ it('should show submenu', async () => {
128
+ const container = await withAnchorFixture()
129
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
130
+ const showSpy = sinon.spy(submenu, 'showPopover')
131
+ await nextFrame()
132
+
133
+ submenu.show()
134
+ await nextFrame()
135
+
136
+ assert.isTrue(submenu.open)
137
+ assert.isTrue(showSpy.calledOnce)
138
+ })
139
+
140
+ it('should not show submenu without anchor', async () => {
141
+ const element = await basicFixture()
142
+ const showSpy = sinon.spy(element, 'showPopover')
143
+
144
+ element.show()
145
+ await nextFrame()
146
+
147
+ assert.isFalse(element.open)
148
+ assert.isFalse(showSpy.called)
149
+ })
150
+
151
+ it('should hide submenu', async () => {
152
+ const container = await withAnchorFixture()
153
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
154
+ const hideSpy = sinon.spy(submenu, 'hidePopover')
155
+ await nextFrame()
156
+
157
+ submenu.show()
158
+ await nextFrame()
159
+ submenu.hide()
160
+ await nextFrame()
161
+
162
+ assert.isFalse(submenu.open)
163
+ assert.isTrue(hideSpy.calledOnce)
164
+ })
165
+
166
+ it('should dispatch open event when shown', async () => {
167
+ const container = await withAnchorFixture()
168
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
169
+ await nextFrame()
170
+
171
+ setTimeout(() => submenu.show())
172
+ const event = await oneEvent(submenu, 'open')
173
+
174
+ assert.instanceOf(event, CustomEvent)
175
+ assert.isTrue(event.bubbles)
176
+ assert.isTrue(event.composed)
177
+ })
178
+
179
+ it('should dispatch close event when hidden', async () => {
180
+ const container = await withAnchorFixture()
181
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
182
+ await nextFrame()
183
+
184
+ submenu.show()
185
+ await nextFrame()
186
+
187
+ setTimeout(() => submenu.hide())
188
+ const event = await oneEvent(submenu, 'close')
189
+
190
+ assert.instanceOf(event, CustomEvent)
191
+ assert.isFalse(event.bubbles)
192
+ assert.isTrue(event.composed)
193
+ })
194
+ })
195
+
196
+ describe('Parent menu integration', () => {
197
+ it('should set parent menu', async () => {
198
+ const container = await withParentMenuFixture()
199
+ const parentMenu = container.querySelector('#parent-menu') as Menu
200
+ const submenu = container.querySelector('#child-submenu') as UiSubMenu
201
+ await nextFrame()
202
+
203
+ submenu.setParentMenu(parentMenu)
204
+
205
+ assert.equal(submenu.parentMenu, parentMenu)
206
+ })
207
+
208
+ it('should set parent menu reference', async () => {
209
+ const container = await withParentMenuFixture()
210
+ const parentMenu = container.querySelector('#parent-menu') as Menu
211
+ const submenu = container.querySelector('#child-submenu') as UiSubMenu
212
+ await nextFrame()
213
+
214
+ // Test setting parent menu
215
+ submenu.setParentMenu(parentMenu)
216
+ assert.equal(submenu.parentMenu, parentMenu)
217
+ })
218
+ })
219
+
220
+ describe('Nested submenus', () => {
221
+ it('should handle multiple levels of nesting', async () => {
222
+ const container = await nestedSubmenuFixture()
223
+ const level1Submenu = container.querySelector('#level1-submenu') as UiSubMenu
224
+ const level2Submenu = container.querySelector('#level2-submenu') as UiSubMenu
225
+ const level1Item = container.querySelector('#level1-item') as UiMenuItem
226
+ const level2Item = container.querySelector('#level2-item') as UiMenuItem
227
+ await nextFrame()
228
+
229
+ // Verify the structure
230
+ assert.equal(level1Submenu.anchor, 'level1-item')
231
+ assert.equal(level2Submenu.anchor, 'level2-item')
232
+ assert.equal(level1Submenu.anchorElement, level1Item)
233
+ assert.equal(level2Submenu.anchorElement, level2Item)
234
+ })
235
+
236
+ it('should show nested submenus properly', async () => {
237
+ const container = await nestedSubmenuFixture()
238
+ const mainMenu = container.querySelector('#main-menu') as Menu
239
+ const level1Submenu = container.querySelector('#level1-submenu') as UiSubMenu
240
+ const level2Submenu = container.querySelector('#level2-submenu') as UiSubMenu
241
+ await nextFrame()
242
+
243
+ // Set up parent relationships
244
+ level1Submenu.setParentMenu(mainMenu)
245
+ level2Submenu.setParentMenu(level1Submenu)
246
+
247
+ // Show level 1
248
+ level1Submenu.show()
249
+ await nextFrame()
250
+ assert.isTrue(level1Submenu.open)
251
+
252
+ // Show level 2
253
+ level2Submenu.show()
254
+ await nextFrame()
255
+ assert.isTrue(level2Submenu.open)
256
+ })
257
+ })
258
+
259
+ describe('Keyboard navigation inheritance', () => {
260
+ it('should inherit keyboard navigation from Menu', async () => {
261
+ const container = await withAnchorFixture()
262
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
263
+ await nextFrame()
264
+
265
+ submenu.show()
266
+ await nextFrame()
267
+
268
+ // Test that escape key closes submenu
269
+ const hideSpy = sinon.spy(submenu, 'hide')
270
+ const event = new KeyboardEvent('keydown', { key: 'Escape' })
271
+ submenu.dispatchEvent(event)
272
+
273
+ assert.isTrue(hideSpy.calledOnce)
274
+ })
275
+
276
+ it('should handle arrow key navigation', async () => {
277
+ const container = await withAnchorFixture()
278
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
279
+ await nextFrame()
280
+
281
+ submenu.show()
282
+ await nextFrame()
283
+
284
+ // Test arrow down navigation - check that it doesn't throw and menu stays open
285
+ const event = new KeyboardEvent('keydown', { key: 'ArrowDown' })
286
+ submenu.dispatchEvent(event)
287
+
288
+ // The menu should still be open and functioning
289
+ assert.isTrue(submenu.open)
290
+ assert.isNotNull(submenu.querySelector('ui-menu-item'))
291
+ })
292
+ })
293
+
294
+ describe('Selection handling', () => {
295
+ it('should hide submenu when item is selected', async () => {
296
+ const container = await withAnchorFixture()
297
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
298
+ const menuItem = submenu.querySelector('ui-menu-item') as UiMenuItem
299
+ await nextFrame()
300
+
301
+ submenu.show()
302
+ await nextFrame()
303
+ assert.isTrue(submenu.open)
304
+
305
+ // Click menu item - it should close the submenu
306
+ menuItem.click()
307
+ await nextFrame()
308
+
309
+ assert.isFalse(submenu.open)
310
+ })
311
+
312
+ it('should dispatch select event', async () => {
313
+ const container = await withAnchorFixture()
314
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
315
+ const menuItem = submenu.querySelector('ui-menu-item') as UiMenuItem
316
+ await nextFrame()
317
+
318
+ submenu.show()
319
+ await nextFrame()
320
+
321
+ setTimeout(() => menuItem.click())
322
+ const event = (await oneEvent(submenu, 'select')) as CustomEvent<{ item: UiMenuItem; index: number }>
323
+
324
+ assert.instanceOf(event, CustomEvent)
325
+ assert.equal(event.detail.item, menuItem)
326
+ assert.equal(event.detail.index, 0)
327
+ })
328
+ })
329
+
330
+ describe('Edge cases', () => {
331
+ it('should handle anchor property changes', async () => {
332
+ const container = await withAnchorFixture()
333
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
334
+ await nextFrame()
335
+
336
+ // Change anchor
337
+ submenu.anchor = 'new-anchor'
338
+ await nextFrame()
339
+
340
+ // Should not throw
341
+ assert.equal(submenu.anchor, 'new-anchor')
342
+ assert.isNull(submenu.anchorElement) // Since 'new-anchor' doesn't exist
343
+ })
344
+
345
+ it('should handle missing parent menu gracefully', async () => {
346
+ const element = await basicFixture()
347
+
348
+ // Should not throw when no parent menu is set
349
+ element.show()
350
+ element.hide()
351
+
352
+ assert.isNull(element.parentMenu)
353
+ })
354
+
355
+ it('should handle disabled state', async () => {
356
+ const element = await basicFixture()
357
+
358
+ element.disabled = true
359
+ await nextFrame()
360
+
361
+ assert.isTrue(element.disabled)
362
+ assert.isTrue(element.hasAttribute('disabled'))
363
+ })
364
+ })
365
+
366
+ describe('CSS anchor positioning', () => {
367
+ it('should set CSS anchor positioning properties', async () => {
368
+ const container = await withAnchorFixture()
369
+ const submenu = container.querySelector('#test-submenu') as UiSubMenu
370
+ const anchorItem = container.querySelector('#anchor-item') as UiMenuItem
371
+ await nextFrame()
372
+
373
+ submenu.show()
374
+ await nextFrame()
375
+
376
+ // Check that CSS properties are set for anchor positioning
377
+ // The exact values depend on the implementation
378
+ const anchorStyle = anchorItem.style.getPropertyValue('anchor-name')
379
+ const submenuStyle = submenu.style.getPropertyValue('position-anchor')
380
+
381
+ // These should be set when the submenu is shown
382
+ assert.isTrue(anchorStyle.includes('anchor-') || anchorStyle.length > 0)
383
+ assert.isTrue(submenuStyle.includes('anchor-') || submenuStyle.length > 0)
384
+ })
385
+ })
386
+
387
+ describe('Rendering', () => {
388
+ it('should render slot for submenu items', async () => {
389
+ const element = await basicFixture()
390
+ await nextFrame()
391
+
392
+ const slot = element.shadowRoot!.querySelector('slot')
393
+ assert.isNotNull(slot)
394
+
395
+ const menuItems = element.querySelectorAll('ui-menu-item')
396
+ assert.equal(menuItems.length, 3)
397
+ })
398
+
399
+ it('should render slot for submenu items', async () => {
400
+ const element = await basicFixture()
401
+ await nextFrame()
402
+
403
+ const slot = element.shadowRoot!.querySelector('slot')
404
+ assert.isNotNull(slot)
405
+
406
+ const menuItems = element.querySelectorAll('ui-menu-item')
407
+ assert.equal(menuItems.length, 3)
408
+ })
409
+ })
410
+ })
411
+ })
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes