@furystack/shades-common-components 10.0.35 → 11.0.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 (295) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/esm/components/animations.spec.d.ts +2 -0
  3. package/esm/components/animations.spec.d.ts.map +1 -0
  4. package/esm/components/animations.spec.js +201 -0
  5. package/esm/components/animations.spec.js.map +1 -0
  6. package/esm/components/app-bar-link.js +21 -20
  7. package/esm/components/app-bar-link.js.map +1 -1
  8. package/esm/components/app-bar-link.spec.d.ts +2 -0
  9. package/esm/components/app-bar-link.spec.d.ts.map +1 -0
  10. package/esm/components/app-bar-link.spec.js +252 -0
  11. package/esm/components/app-bar-link.spec.js.map +1 -0
  12. package/esm/components/app-bar.js +21 -21
  13. package/esm/components/app-bar.js.map +1 -1
  14. package/esm/components/app-bar.spec.d.ts +2 -0
  15. package/esm/components/app-bar.spec.d.ts.map +1 -0
  16. package/esm/components/app-bar.spec.js +117 -0
  17. package/esm/components/app-bar.spec.js.map +1 -0
  18. package/esm/components/avatar.d.ts.map +1 -1
  19. package/esm/components/avatar.js +15 -19
  20. package/esm/components/avatar.js.map +1 -1
  21. package/esm/components/avatar.spec.d.ts +2 -0
  22. package/esm/components/avatar.spec.d.ts.map +1 -0
  23. package/esm/components/avatar.spec.js +114 -0
  24. package/esm/components/avatar.spec.js.map +1 -0
  25. package/esm/components/button.d.ts.map +1 -1
  26. package/esm/components/button.js +145 -156
  27. package/esm/components/button.js.map +1 -1
  28. package/esm/components/button.spec.d.ts +2 -0
  29. package/esm/components/button.spec.d.ts.map +1 -0
  30. package/esm/components/button.spec.js +155 -0
  31. package/esm/components/button.spec.js.map +1 -0
  32. package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
  33. package/esm/components/command-palette/command-palette-input.js +18 -16
  34. package/esm/components/command-palette/command-palette-input.js.map +1 -1
  35. package/esm/components/command-palette/command-palette-input.spec.d.ts +2 -0
  36. package/esm/components/command-palette/command-palette-input.spec.d.ts.map +1 -0
  37. package/esm/components/command-palette/command-palette-input.spec.js +233 -0
  38. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -0
  39. package/esm/components/command-palette/command-palette-manager.spec.d.ts +2 -0
  40. package/esm/components/command-palette/command-palette-manager.spec.d.ts.map +1 -0
  41. package/esm/components/command-palette/command-palette-manager.spec.js +362 -0
  42. package/esm/components/command-palette/command-palette-manager.spec.js.map +1 -0
  43. package/esm/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  44. package/esm/components/command-palette/command-palette-suggestion-list.js +42 -46
  45. package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  46. package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts +2 -0
  47. package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts.map +1 -0
  48. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +376 -0
  49. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -0
  50. package/esm/components/command-palette/index.d.ts.map +1 -1
  51. package/esm/components/command-palette/index.js +100 -110
  52. package/esm/components/command-palette/index.js.map +1 -1
  53. package/esm/components/command-palette/index.spec.d.ts +2 -0
  54. package/esm/components/command-palette/index.spec.d.ts.map +1 -0
  55. package/esm/components/command-palette/index.spec.js +509 -0
  56. package/esm/components/command-palette/index.spec.js.map +1 -0
  57. package/esm/components/data-grid/body.js +1 -1
  58. package/esm/components/data-grid/body.js.map +1 -1
  59. package/esm/components/data-grid/body.spec.d.ts +2 -0
  60. package/esm/components/data-grid/body.spec.d.ts.map +1 -0
  61. package/esm/components/data-grid/body.spec.js +228 -0
  62. package/esm/components/data-grid/body.spec.js.map +1 -0
  63. package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
  64. package/esm/components/data-grid/data-grid-row.js +49 -73
  65. package/esm/components/data-grid/data-grid-row.js.map +1 -1
  66. package/esm/components/data-grid/data-grid-row.spec.d.ts +2 -0
  67. package/esm/components/data-grid/data-grid-row.spec.d.ts.map +1 -0
  68. package/esm/components/data-grid/data-grid-row.spec.js +296 -0
  69. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -0
  70. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  71. package/esm/components/data-grid/data-grid.js +35 -28
  72. package/esm/components/data-grid/data-grid.js.map +1 -1
  73. package/esm/components/data-grid/data-grid.spec.d.ts +2 -0
  74. package/esm/components/data-grid/data-grid.spec.d.ts.map +1 -0
  75. package/esm/components/data-grid/data-grid.spec.js +544 -0
  76. package/esm/components/data-grid/data-grid.spec.js.map +1 -0
  77. package/esm/components/data-grid/footer.js +21 -15
  78. package/esm/components/data-grid/footer.js.map +1 -1
  79. package/esm/components/data-grid/footer.spec.d.ts +2 -0
  80. package/esm/components/data-grid/footer.spec.d.ts.map +1 -0
  81. package/esm/components/data-grid/footer.spec.js +264 -0
  82. package/esm/components/data-grid/footer.spec.js.map +1 -0
  83. package/esm/components/data-grid/header.d.ts.map +1 -1
  84. package/esm/components/data-grid/header.js +55 -33
  85. package/esm/components/data-grid/header.js.map +1 -1
  86. package/esm/components/data-grid/header.spec.d.ts +2 -0
  87. package/esm/components/data-grid/header.spec.d.ts.map +1 -0
  88. package/esm/components/data-grid/header.spec.js +421 -0
  89. package/esm/components/data-grid/header.spec.js.map +1 -0
  90. package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
  91. package/esm/components/data-grid/selection-cell.js +13 -6
  92. package/esm/components/data-grid/selection-cell.js.map +1 -1
  93. package/esm/components/data-grid/selection-cell.spec.d.ts +2 -0
  94. package/esm/components/data-grid/selection-cell.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/selection-cell.spec.js +118 -0
  96. package/esm/components/data-grid/selection-cell.spec.js.map +1 -0
  97. package/esm/components/fab.d.ts.map +1 -1
  98. package/esm/components/fab.js +10 -1
  99. package/esm/components/fab.js.map +1 -1
  100. package/esm/components/fab.spec.d.ts +2 -0
  101. package/esm/components/fab.spec.d.ts.map +1 -0
  102. package/esm/components/fab.spec.js +95 -0
  103. package/esm/components/fab.spec.js.map +1 -0
  104. package/esm/components/form.spec.d.ts +2 -0
  105. package/esm/components/form.spec.d.ts.map +1 -0
  106. package/esm/components/form.spec.js +314 -0
  107. package/esm/components/form.spec.js.map +1 -0
  108. package/esm/components/grid.d.ts.map +1 -1
  109. package/esm/components/grid.js +40 -37
  110. package/esm/components/grid.js.map +1 -1
  111. package/esm/components/grid.spec.d.ts +2 -0
  112. package/esm/components/grid.spec.d.ts.map +1 -0
  113. package/esm/components/grid.spec.js +316 -0
  114. package/esm/components/grid.spec.js.map +1 -0
  115. package/esm/components/inputs/autocomplete.spec.d.ts +2 -0
  116. package/esm/components/inputs/autocomplete.spec.d.ts.map +1 -0
  117. package/esm/components/inputs/autocomplete.spec.js +194 -0
  118. package/esm/components/inputs/autocomplete.spec.js.map +1 -0
  119. package/esm/components/inputs/input.d.ts.map +1 -1
  120. package/esm/components/inputs/input.js +141 -109
  121. package/esm/components/inputs/input.js.map +1 -1
  122. package/esm/components/inputs/input.spec.d.ts +2 -0
  123. package/esm/components/inputs/input.spec.d.ts.map +1 -0
  124. package/esm/components/inputs/input.spec.js +577 -0
  125. package/esm/components/inputs/input.spec.js.map +1 -0
  126. package/esm/components/inputs/text-area.d.ts.map +1 -1
  127. package/esm/components/inputs/text-area.js +54 -58
  128. package/esm/components/inputs/text-area.js.map +1 -1
  129. package/esm/components/inputs/text-area.spec.d.ts +2 -0
  130. package/esm/components/inputs/text-area.spec.d.ts.map +1 -0
  131. package/esm/components/inputs/text-area.spec.js +214 -0
  132. package/esm/components/inputs/text-area.spec.js.map +1 -0
  133. package/esm/components/loader.js +1 -1
  134. package/esm/components/loader.js.map +1 -1
  135. package/esm/components/loader.spec.d.ts +2 -0
  136. package/esm/components/loader.spec.d.ts.map +1 -0
  137. package/esm/components/loader.spec.js +251 -0
  138. package/esm/components/loader.spec.js.map +1 -0
  139. package/esm/components/modal.d.ts.map +1 -1
  140. package/esm/components/modal.js +11 -9
  141. package/esm/components/modal.js.map +1 -1
  142. package/esm/components/modal.spec.d.ts +2 -0
  143. package/esm/components/modal.spec.d.ts.map +1 -0
  144. package/esm/components/modal.spec.js +227 -0
  145. package/esm/components/modal.spec.js.map +1 -0
  146. package/esm/components/noty-list.d.ts.map +1 -1
  147. package/esm/components/noty-list.js +39 -40
  148. package/esm/components/noty-list.js.map +1 -1
  149. package/esm/components/noty-list.spec.d.ts +2 -0
  150. package/esm/components/noty-list.spec.d.ts.map +1 -0
  151. package/esm/components/noty-list.spec.js +486 -0
  152. package/esm/components/noty-list.spec.js.map +1 -0
  153. package/esm/components/paper.d.ts.map +1 -1
  154. package/esm/components/paper.js +15 -12
  155. package/esm/components/paper.js.map +1 -1
  156. package/esm/components/paper.spec.d.ts +2 -0
  157. package/esm/components/paper.spec.d.ts.map +1 -0
  158. package/esm/components/paper.spec.js +63 -0
  159. package/esm/components/paper.spec.js.map +1 -0
  160. package/esm/components/skeleton.js +1 -1
  161. package/esm/components/skeleton.js.map +1 -1
  162. package/esm/components/skeleton.spec.d.ts +2 -0
  163. package/esm/components/skeleton.spec.d.ts.map +1 -0
  164. package/esm/components/skeleton.spec.js +159 -0
  165. package/esm/components/skeleton.spec.js.map +1 -0
  166. package/esm/components/styles.spec.d.ts +2 -0
  167. package/esm/components/styles.spec.d.ts.map +1 -0
  168. package/esm/components/styles.spec.js +56 -0
  169. package/esm/components/styles.spec.js.map +1 -0
  170. package/esm/components/suggest/index.d.ts.map +1 -1
  171. package/esm/components/suggest/index.js +74 -83
  172. package/esm/components/suggest/index.js.map +1 -1
  173. package/esm/components/suggest/index.spec.d.ts +2 -0
  174. package/esm/components/suggest/index.spec.d.ts.map +1 -0
  175. package/esm/components/suggest/index.spec.js +515 -0
  176. package/esm/components/suggest/index.spec.js.map +1 -0
  177. package/esm/components/suggest/suggest-input.d.ts.map +1 -1
  178. package/esm/components/suggest/suggest-input.js +16 -17
  179. package/esm/components/suggest/suggest-input.js.map +1 -1
  180. package/esm/components/suggest/suggest-input.spec.d.ts +2 -0
  181. package/esm/components/suggest/suggest-input.spec.d.ts.map +1 -0
  182. package/esm/components/suggest/suggest-input.spec.js +138 -0
  183. package/esm/components/suggest/suggest-input.spec.js.map +1 -0
  184. package/esm/components/suggest/suggest-manager.spec.d.ts +2 -0
  185. package/esm/components/suggest/suggest-manager.spec.d.ts.map +1 -0
  186. package/esm/components/suggest/suggest-manager.spec.js +308 -0
  187. package/esm/components/suggest/suggest-manager.spec.js.map +1 -0
  188. package/esm/components/suggest/suggestion-list.d.ts.map +1 -1
  189. package/esm/components/suggest/suggestion-list.js +43 -48
  190. package/esm/components/suggest/suggestion-list.js.map +1 -1
  191. package/esm/components/suggest/suggestion-list.spec.d.ts +2 -0
  192. package/esm/components/suggest/suggestion-list.spec.d.ts.map +1 -0
  193. package/esm/components/suggest/suggestion-list.spec.js +252 -0
  194. package/esm/components/suggest/suggestion-list.spec.js.map +1 -0
  195. package/esm/components/tabs.d.ts.map +1 -1
  196. package/esm/components/tabs.js +32 -18
  197. package/esm/components/tabs.js.map +1 -1
  198. package/esm/components/tabs.spec.d.ts +2 -0
  199. package/esm/components/tabs.spec.d.ts.map +1 -0
  200. package/esm/components/tabs.spec.js +187 -0
  201. package/esm/components/tabs.spec.js.map +1 -0
  202. package/esm/components/wizard/index.d.ts.map +1 -1
  203. package/esm/components/wizard/index.js +10 -7
  204. package/esm/components/wizard/index.js.map +1 -1
  205. package/esm/components/wizard/index.spec.d.ts +2 -0
  206. package/esm/components/wizard/index.spec.d.ts.map +1 -0
  207. package/esm/components/wizard/index.spec.js +171 -0
  208. package/esm/components/wizard/index.spec.js.map +1 -0
  209. package/esm/services/collection-service.spec.js +391 -2
  210. package/esm/services/collection-service.spec.js.map +1 -1
  211. package/esm/services/css-variable-theme.d.ts.map +1 -1
  212. package/esm/services/css-variable-theme.js +21 -1
  213. package/esm/services/css-variable-theme.js.map +1 -1
  214. package/esm/services/css-variable-theme.spec.d.ts +2 -0
  215. package/esm/services/css-variable-theme.spec.d.ts.map +1 -0
  216. package/esm/services/css-variable-theme.spec.js +169 -0
  217. package/esm/services/css-variable-theme.spec.js.map +1 -0
  218. package/esm/services/default-palette.d.ts +4 -0
  219. package/esm/services/default-palette.d.ts.map +1 -1
  220. package/esm/services/default-palette.js +22 -0
  221. package/esm/services/default-palette.js.map +1 -1
  222. package/esm/services/theme-provider-service.d.ts +59 -1
  223. package/esm/services/theme-provider-service.d.ts.map +1 -1
  224. package/esm/services/theme-provider-service.js.map +1 -1
  225. package/esm/services/theme-provider-service.spec.d.ts +2 -0
  226. package/esm/services/theme-provider-service.spec.d.ts.map +1 -0
  227. package/esm/services/theme-provider-service.spec.js +166 -0
  228. package/esm/services/theme-provider-service.spec.js.map +1 -0
  229. package/package.json +2 -2
  230. package/src/components/animations.spec.ts +299 -0
  231. package/src/components/app-bar-link.spec.tsx +341 -0
  232. package/src/components/app-bar-link.tsx +21 -21
  233. package/src/components/app-bar.spec.tsx +142 -0
  234. package/src/components/app-bar.tsx +22 -22
  235. package/src/components/avatar.spec.tsx +146 -0
  236. package/src/components/avatar.tsx +17 -20
  237. package/src/components/button.spec.tsx +193 -0
  238. package/src/components/button.tsx +162 -197
  239. package/src/components/command-palette/command-palette-input.spec.tsx +320 -0
  240. package/src/components/command-palette/command-palette-input.tsx +19 -22
  241. package/src/components/command-palette/command-palette-manager.spec.ts +470 -0
  242. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +499 -0
  243. package/src/components/command-palette/command-palette-suggestion-list.tsx +42 -46
  244. package/src/components/command-palette/index.spec.tsx +684 -0
  245. package/src/components/command-palette/index.tsx +107 -136
  246. package/src/components/data-grid/body.spec.tsx +340 -0
  247. package/src/components/data-grid/body.tsx +1 -1
  248. package/src/components/data-grid/data-grid-row.spec.tsx +382 -0
  249. package/src/components/data-grid/data-grid-row.tsx +50 -82
  250. package/src/components/data-grid/data-grid.spec.tsx +939 -0
  251. package/src/components/data-grid/data-grid.tsx +38 -35
  252. package/src/components/data-grid/footer.spec.tsx +344 -0
  253. package/src/components/data-grid/footer.tsx +19 -19
  254. package/src/components/data-grid/header.spec.tsx +563 -0
  255. package/src/components/data-grid/header.tsx +53 -44
  256. package/src/components/data-grid/selection-cell.spec.tsx +150 -0
  257. package/src/components/data-grid/selection-cell.tsx +12 -6
  258. package/src/components/fab.spec.tsx +108 -0
  259. package/src/components/fab.tsx +10 -1
  260. package/src/components/form.spec.tsx +481 -0
  261. package/src/components/grid.spec.tsx +334 -0
  262. package/src/components/grid.tsx +57 -63
  263. package/src/components/inputs/autocomplete.spec.tsx +258 -0
  264. package/src/components/inputs/input.spec.tsx +808 -0
  265. package/src/components/inputs/input.tsx +153 -139
  266. package/src/components/inputs/text-area.spec.tsx +285 -0
  267. package/src/components/inputs/text-area.tsx +53 -79
  268. package/src/components/loader.spec.tsx +346 -0
  269. package/src/components/loader.tsx +1 -1
  270. package/src/components/modal.spec.tsx +304 -0
  271. package/src/components/modal.tsx +11 -9
  272. package/src/components/noty-list.spec.tsx +631 -0
  273. package/src/components/noty-list.tsx +39 -50
  274. package/src/components/paper.spec.tsx +72 -0
  275. package/src/components/paper.tsx +15 -13
  276. package/src/components/skeleton.spec.tsx +219 -0
  277. package/src/components/skeleton.tsx +1 -1
  278. package/src/components/styles.spec.ts +70 -0
  279. package/src/components/suggest/index.spec.tsx +861 -0
  280. package/src/components/suggest/index.tsx +74 -101
  281. package/src/components/suggest/suggest-input.spec.tsx +181 -0
  282. package/src/components/suggest/suggest-input.tsx +16 -24
  283. package/src/components/suggest/suggest-manager.spec.ts +409 -0
  284. package/src/components/suggest/suggestion-list.spec.tsx +334 -0
  285. package/src/components/suggest/suggestion-list.tsx +43 -48
  286. package/src/components/tabs.spec.tsx +236 -0
  287. package/src/components/tabs.tsx +33 -21
  288. package/src/components/wizard/index.spec.tsx +224 -0
  289. package/src/components/wizard/index.tsx +10 -9
  290. package/src/services/collection-service.spec.ts +492 -3
  291. package/src/services/css-variable-theme.spec.ts +204 -0
  292. package/src/services/css-variable-theme.ts +21 -1
  293. package/src/services/default-palette.ts +22 -0
  294. package/src/services/theme-provider-service.spec.ts +195 -0
  295. package/src/services/theme-provider-service.ts +60 -2
@@ -0,0 +1,684 @@
1
+ import { Injector } from '@furystack/inject'
2
+ import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
+ import { sleepAsync, usingAsync } from '@furystack/utils'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import type { CommandPaletteSuggestionResult, CommandProvider } from './command-provider.js'
6
+ import { CommandPalette } from './index.js'
7
+
8
+ describe('CommandPalette', () => {
9
+ let originalAnimate: typeof Element.prototype.animate
10
+
11
+ beforeEach(() => {
12
+ document.body.innerHTML = '<div id="root"></div>'
13
+
14
+ originalAnimate = Element.prototype.animate
15
+
16
+ Element.prototype.animate = vi.fn(() => {
17
+ const mockAnimation = {
18
+ onfinish: null as ((event: AnimationPlaybackEvent) => void) | null,
19
+ oncancel: null as ((event: AnimationPlaybackEvent) => void) | null,
20
+ cancel: vi.fn(),
21
+ play: vi.fn(),
22
+ pause: vi.fn(),
23
+ finish: vi.fn(),
24
+ addEventListener: vi.fn(),
25
+ removeEventListener: vi.fn(),
26
+ }
27
+ return mockAnimation as unknown as Animation
28
+ }) as typeof Element.prototype.animate
29
+ })
30
+
31
+ afterEach(() => {
32
+ document.body.innerHTML = ''
33
+ Element.prototype.animate = originalAnimate
34
+ vi.restoreAllMocks()
35
+ })
36
+
37
+ const createMockProvider = (results: CommandPaletteSuggestionResult[]): CommandProvider => {
38
+ return vi.fn(async () => results)
39
+ }
40
+
41
+ const createSuggestion = (
42
+ text: string,
43
+ score: number,
44
+ onSelected: (options: { injector: Injector }) => void = vi.fn(),
45
+ ): CommandPaletteSuggestionResult => ({
46
+ element: <span>{text}</span>,
47
+ score,
48
+ onSelected,
49
+ })
50
+
51
+ describe('rendering', () => {
52
+ it('should render the shade-command-palette custom element', async () => {
53
+ await usingAsync(new Injector(), async (injector) => {
54
+ const rootElement = document.getElementById('root') as HTMLDivElement
55
+
56
+ initializeShadeRoot({
57
+ injector,
58
+ rootElement,
59
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
60
+ })
61
+
62
+ await sleepAsync(50)
63
+
64
+ const commandPalette = document.querySelector('shade-command-palette')
65
+ expect(commandPalette).not.toBeNull()
66
+ expect(commandPalette?.tagName.toLowerCase()).toBe('shade-command-palette')
67
+ })
68
+ })
69
+
70
+ it('should render the default prefix', async () => {
71
+ await usingAsync(new Injector(), async (injector) => {
72
+ const rootElement = document.getElementById('root') as HTMLDivElement
73
+
74
+ initializeShadeRoot({
75
+ injector,
76
+ rootElement,
77
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
78
+ })
79
+
80
+ await sleepAsync(50)
81
+
82
+ expect(document.body.innerHTML).toContain('>')
83
+ })
84
+ })
85
+
86
+ it('should render input component', async () => {
87
+ await usingAsync(new Injector(), async (injector) => {
88
+ const rootElement = document.getElementById('root') as HTMLDivElement
89
+
90
+ initializeShadeRoot({
91
+ injector,
92
+ rootElement,
93
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
94
+ })
95
+
96
+ await sleepAsync(50)
97
+
98
+ const input = document.querySelector('shades-command-palette-input')
99
+ expect(input).not.toBeNull()
100
+ })
101
+ })
102
+
103
+ it('should render suggestion list component', async () => {
104
+ await usingAsync(new Injector(), async (injector) => {
105
+ const rootElement = document.getElementById('root') as HTMLDivElement
106
+
107
+ initializeShadeRoot({
108
+ injector,
109
+ rootElement,
110
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
111
+ })
112
+
113
+ await sleepAsync(50)
114
+
115
+ const suggestionList = document.querySelector('shade-command-palette-suggestion-list')
116
+ expect(suggestionList).not.toBeNull()
117
+ })
118
+ })
119
+ })
120
+
121
+ describe('keyboard navigation', () => {
122
+ const triggerKeyup = (input: HTMLInputElement, key: string) => {
123
+ const event = new KeyboardEvent('keyup', { key, bubbles: true })
124
+ Object.defineProperty(event, 'target', { value: input, writable: false })
125
+ input.dispatchEvent(event)
126
+ }
127
+
128
+ const getSuggestionItems = (commandPalette: HTMLElement) => {
129
+ const suggestionList = commandPalette.querySelector('shade-command-palette-suggestion-list') as HTMLElement
130
+ return suggestionList?.querySelectorAll('.suggestion-item') || []
131
+ }
132
+
133
+ it('should navigate down with ArrowDown key', async () => {
134
+ await usingAsync(new Injector(), async (injector) => {
135
+ const provider = createMockProvider([
136
+ createSuggestion('Item 1', 100),
137
+ createSuggestion('Item 2', 90),
138
+ createSuggestion('Item 3', 80),
139
+ ])
140
+
141
+ const rootElement = document.getElementById('root') as HTMLDivElement
142
+
143
+ initializeShadeRoot({
144
+ injector,
145
+ rootElement,
146
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
147
+ })
148
+
149
+ await sleepAsync(50)
150
+
151
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
152
+ const input = commandPalette.querySelector('input') as HTMLInputElement
153
+
154
+ // Open and trigger suggestions
155
+ input.value = 'test'
156
+ triggerKeyup(input, 'a')
157
+
158
+ await sleepAsync(300)
159
+
160
+ // Press ArrowDown
161
+ triggerKeyup(input, 'ArrowDown')
162
+ await sleepAsync(50)
163
+
164
+ const suggestionItems = getSuggestionItems(commandPalette)
165
+ expect(suggestionItems[1]?.classList.contains('selected')).toBe(true)
166
+ })
167
+ })
168
+
169
+ it('should navigate up with ArrowUp key', async () => {
170
+ await usingAsync(new Injector(), async (injector) => {
171
+ const provider = createMockProvider([
172
+ createSuggestion('Item 1', 100),
173
+ createSuggestion('Item 2', 90),
174
+ createSuggestion('Item 3', 80),
175
+ ])
176
+
177
+ const rootElement = document.getElementById('root') as HTMLDivElement
178
+
179
+ initializeShadeRoot({
180
+ injector,
181
+ rootElement,
182
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
183
+ })
184
+
185
+ await sleepAsync(50)
186
+
187
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
188
+ const input = commandPalette.querySelector('input') as HTMLInputElement
189
+
190
+ // Open and trigger suggestions
191
+ input.value = 'test'
192
+ triggerKeyup(input, 'a')
193
+
194
+ await sleepAsync(300)
195
+
196
+ // Navigate down then up
197
+ triggerKeyup(input, 'ArrowDown')
198
+ await sleepAsync(50)
199
+ triggerKeyup(input, 'ArrowUp')
200
+ await sleepAsync(50)
201
+
202
+ const suggestionItems = getSuggestionItems(commandPalette)
203
+ expect(suggestionItems[0]?.classList.contains('selected')).toBe(true)
204
+ })
205
+ })
206
+
207
+ it('should not navigate below the last item', async () => {
208
+ await usingAsync(new Injector(), async (injector) => {
209
+ const provider = createMockProvider([createSuggestion('Item 1', 100), createSuggestion('Item 2', 90)])
210
+
211
+ const rootElement = document.getElementById('root') as HTMLDivElement
212
+
213
+ initializeShadeRoot({
214
+ injector,
215
+ rootElement,
216
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
217
+ })
218
+
219
+ await sleepAsync(50)
220
+
221
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
222
+ const input = commandPalette.querySelector('input') as HTMLInputElement
223
+
224
+ input.value = 'test'
225
+ triggerKeyup(input, 'a')
226
+
227
+ await sleepAsync(300)
228
+
229
+ // Press ArrowDown multiple times
230
+ triggerKeyup(input, 'ArrowDown')
231
+ triggerKeyup(input, 'ArrowDown')
232
+ triggerKeyup(input, 'ArrowDown')
233
+ await sleepAsync(50)
234
+
235
+ const suggestionItems = getSuggestionItems(commandPalette)
236
+ expect(suggestionItems[1]?.classList.contains('selected')).toBe(true)
237
+ })
238
+ })
239
+
240
+ it('should not navigate above the first item', async () => {
241
+ await usingAsync(new Injector(), async (injector) => {
242
+ const provider = createMockProvider([createSuggestion('Item 1', 100), createSuggestion('Item 2', 90)])
243
+
244
+ const rootElement = document.getElementById('root') as HTMLDivElement
245
+
246
+ initializeShadeRoot({
247
+ injector,
248
+ rootElement,
249
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
250
+ })
251
+
252
+ await sleepAsync(50)
253
+
254
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
255
+ const input = commandPalette.querySelector('input') as HTMLInputElement
256
+
257
+ input.value = 'test'
258
+ triggerKeyup(input, 'a')
259
+
260
+ await sleepAsync(300)
261
+
262
+ // Press ArrowUp when already at first item
263
+ triggerKeyup(input, 'ArrowUp')
264
+ await sleepAsync(50)
265
+
266
+ const suggestionItems = getSuggestionItems(commandPalette)
267
+ expect(suggestionItems[0]?.classList.contains('selected')).toBe(true)
268
+ })
269
+ })
270
+
271
+ it('should select suggestion on Enter key', async () => {
272
+ await usingAsync(new Injector(), async (injector) => {
273
+ const onSelected = vi.fn()
274
+ const provider = createMockProvider([createSuggestion('Item 1', 100, onSelected)])
275
+
276
+ const rootElement = document.getElementById('root') as HTMLDivElement
277
+
278
+ initializeShadeRoot({
279
+ injector,
280
+ rootElement,
281
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
282
+ })
283
+
284
+ await sleepAsync(50)
285
+
286
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
287
+ const input = commandPalette.querySelector('input') as HTMLInputElement
288
+
289
+ input.value = 'test'
290
+ triggerKeyup(input, 'a')
291
+
292
+ await sleepAsync(300)
293
+
294
+ // Press Enter
295
+ triggerKeyup(input, 'Enter')
296
+ await sleepAsync(50)
297
+
298
+ expect(onSelected).toHaveBeenCalledTimes(1)
299
+ expect(onSelected).toHaveBeenCalledWith(expect.objectContaining({ injector: expect.any(Injector) as unknown }))
300
+ })
301
+ })
302
+ })
303
+
304
+ describe('selection', () => {
305
+ const triggerKeyup = (input: HTMLInputElement, key: string) => {
306
+ const event = new KeyboardEvent('keyup', { key, bubbles: true })
307
+ Object.defineProperty(event, 'target', { value: input, writable: false })
308
+ input.dispatchEvent(event)
309
+ }
310
+
311
+ const getSuggestionItems = (commandPalette: HTMLElement) => {
312
+ const suggestionList = commandPalette.querySelector('shade-command-palette-suggestion-list') as HTMLElement
313
+ return suggestionList?.querySelectorAll('.suggestion-item') || []
314
+ }
315
+
316
+ it('should close palette when clicking a suggestion', async () => {
317
+ await usingAsync(new Injector(), async (injector) => {
318
+ const provider = createMockProvider([createSuggestion('Item 1', 100), createSuggestion('Item 2', 90)])
319
+
320
+ const rootElement = document.getElementById('root') as HTMLDivElement
321
+
322
+ initializeShadeRoot({
323
+ injector,
324
+ rootElement,
325
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
326
+ })
327
+
328
+ await sleepAsync(50)
329
+
330
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
331
+
332
+ // Open palette by clicking prefix
333
+ const termIcon = commandPalette.querySelector('.term-icon') as HTMLElement
334
+ termIcon.click()
335
+ await sleepAsync(100) // Wait longer for the opened state to propagate
336
+
337
+ expect(commandPalette.classList.contains('opened')).toBe(true)
338
+
339
+ const input = commandPalette.querySelector('input') as HTMLInputElement
340
+ input.value = 'test'
341
+ triggerKeyup(input, 'a')
342
+
343
+ await sleepAsync(300)
344
+
345
+ // Click on first suggestion
346
+ const suggestionItems = getSuggestionItems(commandPalette)
347
+ expect(suggestionItems.length).toBeGreaterThan(0)
348
+ ;(suggestionItems[0] as HTMLElement).click()
349
+ await sleepAsync(100)
350
+
351
+ // Clicking a suggestion should close the palette
352
+ expect(commandPalette.classList.contains('opened')).toBe(false)
353
+ })
354
+ })
355
+
356
+ it('should close palette after selection', async () => {
357
+ await usingAsync(new Injector(), async (injector) => {
358
+ const provider = createMockProvider([createSuggestion('Item 1', 100)])
359
+
360
+ const rootElement = document.getElementById('root') as HTMLDivElement
361
+
362
+ initializeShadeRoot({
363
+ injector,
364
+ rootElement,
365
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
366
+ })
367
+
368
+ await sleepAsync(50)
369
+
370
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
371
+
372
+ // Open palette
373
+ const termIcon = commandPalette.querySelector('.term-icon') as HTMLElement
374
+ termIcon.click()
375
+ await sleepAsync(50)
376
+
377
+ expect(commandPalette.classList.contains('opened')).toBe(true)
378
+
379
+ const input = commandPalette.querySelector('input') as HTMLInputElement
380
+ input.value = 'test'
381
+ triggerKeyup(input, 'a')
382
+
383
+ await sleepAsync(300)
384
+
385
+ // Select via Enter
386
+ triggerKeyup(input, 'Enter')
387
+ await sleepAsync(50)
388
+
389
+ expect(commandPalette.classList.contains('opened')).toBe(false)
390
+ })
391
+ })
392
+ })
393
+
394
+ describe('command providers', () => {
395
+ const triggerKeyup = (input: HTMLInputElement, key: string) => {
396
+ const event = new KeyboardEvent('keyup', { key, bubbles: true })
397
+ Object.defineProperty(event, 'target', { value: input, writable: false })
398
+ input.dispatchEvent(event)
399
+ }
400
+
401
+ const getSuggestionItems = (commandPalette: HTMLElement) => {
402
+ const suggestionList = commandPalette.querySelector('shade-command-palette-suggestion-list') as HTMLElement
403
+ return suggestionList?.querySelectorAll('.suggestion-item') || []
404
+ }
405
+
406
+ it('should call all command providers when searching', async () => {
407
+ await usingAsync(new Injector(), async (injector) => {
408
+ const provider1 = createMockProvider([createSuggestion('Provider 1 Result', 100)])
409
+ const provider2 = createMockProvider([createSuggestion('Provider 2 Result', 90)])
410
+
411
+ const rootElement = document.getElementById('root') as HTMLDivElement
412
+
413
+ initializeShadeRoot({
414
+ injector,
415
+ rootElement,
416
+ jsxElement: <CommandPalette commandProviders={[provider1, provider2]} defaultPrefix=">" />,
417
+ })
418
+
419
+ await sleepAsync(50)
420
+
421
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
422
+ const input = commandPalette.querySelector('input') as HTMLInputElement
423
+
424
+ input.value = 'search'
425
+ triggerKeyup(input, 'h')
426
+
427
+ await sleepAsync(300)
428
+
429
+ expect(provider1).toHaveBeenCalled()
430
+ expect(provider2).toHaveBeenCalled()
431
+ })
432
+ })
433
+
434
+ it('should aggregate results from multiple providers', async () => {
435
+ await usingAsync(new Injector(), async (injector) => {
436
+ const provider1 = createMockProvider([createSuggestion('Provider 1 Result', 100)])
437
+ const provider2 = createMockProvider([createSuggestion('Provider 2 Result', 90)])
438
+
439
+ const rootElement = document.getElementById('root') as HTMLDivElement
440
+
441
+ initializeShadeRoot({
442
+ injector,
443
+ rootElement,
444
+ jsxElement: <CommandPalette commandProviders={[provider1, provider2]} defaultPrefix=">" />,
445
+ })
446
+
447
+ await sleepAsync(50)
448
+
449
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
450
+ const input = commandPalette.querySelector('input') as HTMLInputElement
451
+
452
+ input.value = 'search'
453
+ triggerKeyup(input, 'h')
454
+
455
+ await sleepAsync(300)
456
+
457
+ const suggestionItems = getSuggestionItems(commandPalette)
458
+ expect(suggestionItems.length).toBe(2)
459
+ expect(document.body.innerHTML).toContain('Provider 1 Result')
460
+ expect(document.body.innerHTML).toContain('Provider 2 Result')
461
+ })
462
+ })
463
+
464
+ it('should sort results by score', async () => {
465
+ await usingAsync(new Injector(), async (injector) => {
466
+ const provider = createMockProvider([
467
+ createSuggestion('Low Score', 50),
468
+ createSuggestion('High Score', 100),
469
+ createSuggestion('Medium Score', 75),
470
+ ])
471
+
472
+ const rootElement = document.getElementById('root') as HTMLDivElement
473
+
474
+ initializeShadeRoot({
475
+ injector,
476
+ rootElement,
477
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
478
+ })
479
+
480
+ await sleepAsync(50)
481
+
482
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
483
+ const input = commandPalette.querySelector('input') as HTMLInputElement
484
+
485
+ input.value = 'search'
486
+ triggerKeyup(input, 'h')
487
+
488
+ await sleepAsync(300)
489
+
490
+ const suggestionItems = getSuggestionItems(commandPalette)
491
+ expect(suggestionItems.length).toBe(3)
492
+
493
+ // Results should be sorted by score ascending (sortBy sorts ascending)
494
+ expect(suggestionItems[0]?.textContent).toContain('Low Score')
495
+ expect(suggestionItems[1]?.textContent).toContain('Medium Score')
496
+ expect(suggestionItems[2]?.textContent).toContain('High Score')
497
+ })
498
+ })
499
+ })
500
+
501
+ describe('opening and closing', () => {
502
+ it('should open when clicking the prefix icon', async () => {
503
+ await usingAsync(new Injector(), async (injector) => {
504
+ const rootElement = document.getElementById('root') as HTMLDivElement
505
+
506
+ initializeShadeRoot({
507
+ injector,
508
+ rootElement,
509
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
510
+ })
511
+
512
+ await sleepAsync(50)
513
+
514
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
515
+ expect(commandPalette.classList.contains('opened')).toBe(false)
516
+
517
+ const termIcon = commandPalette.querySelector('.term-icon') as HTMLElement
518
+ termIcon.click()
519
+ await sleepAsync(50)
520
+
521
+ expect(commandPalette.classList.contains('opened')).toBe(true)
522
+ })
523
+ })
524
+
525
+ it('should close when clicking the close button', async () => {
526
+ await usingAsync(new Injector(), async (injector) => {
527
+ const rootElement = document.getElementById('root') as HTMLDivElement
528
+
529
+ initializeShadeRoot({
530
+ injector,
531
+ rootElement,
532
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
533
+ })
534
+
535
+ await sleepAsync(50)
536
+
537
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
538
+
539
+ // Open first
540
+ const termIcon = commandPalette.querySelector('.term-icon') as HTMLElement
541
+ termIcon.click()
542
+ await sleepAsync(50)
543
+ expect(commandPalette.classList.contains('opened')).toBe(true)
544
+
545
+ // Close
546
+ const closeButton = commandPalette.querySelector('.close-suggestions') as HTMLElement
547
+ closeButton.click()
548
+ await sleepAsync(50)
549
+
550
+ expect(commandPalette.classList.contains('opened')).toBe(false)
551
+ })
552
+ })
553
+
554
+ it('should add loading class when fetching suggestions', async () => {
555
+ await usingAsync(new Injector(), async (injector) => {
556
+ const provider = vi.fn(
557
+ () =>
558
+ new Promise<CommandPaletteSuggestionResult[]>((resolve) => {
559
+ setTimeout(() => resolve([createSuggestion('Result', 100)]), 100)
560
+ }),
561
+ )
562
+
563
+ const rootElement = document.getElementById('root') as HTMLDivElement
564
+
565
+ initializeShadeRoot({
566
+ injector,
567
+ rootElement,
568
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" />,
569
+ })
570
+
571
+ await sleepAsync(50)
572
+
573
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
574
+ const input = commandPalette.querySelector('input') as HTMLInputElement
575
+
576
+ input.value = 'test'
577
+ const event = new KeyboardEvent('keyup', { key: 't', bubbles: true })
578
+ Object.defineProperty(event, 'target', { value: input, writable: false })
579
+ input.dispatchEvent(event)
580
+
581
+ await sleepAsync(260) // After debounce but before provider resolves
582
+
583
+ expect(commandPalette.classList.contains('loading')).toBe(true)
584
+
585
+ await sleepAsync(200) // Wait for provider to resolve
586
+
587
+ expect(commandPalette.classList.contains('loading')).toBe(false)
588
+ })
589
+ })
590
+ })
591
+
592
+ describe('click away', () => {
593
+ it('should close when clicking outside the component', async () => {
594
+ await usingAsync(new Injector(), async (injector) => {
595
+ const rootElement = document.getElementById('root') as HTMLDivElement
596
+
597
+ initializeShadeRoot({
598
+ injector,
599
+ rootElement,
600
+ jsxElement: (
601
+ <div>
602
+ <div id="outside">Outside element</div>
603
+ <CommandPalette commandProviders={[]} defaultPrefix=">" />
604
+ </div>
605
+ ),
606
+ })
607
+
608
+ await sleepAsync(50)
609
+
610
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
611
+
612
+ // Open first
613
+ const termIcon = commandPalette.querySelector('.term-icon') as HTMLElement
614
+ termIcon.click()
615
+ await sleepAsync(50)
616
+ expect(commandPalette.classList.contains('opened')).toBe(true)
617
+
618
+ // Click outside
619
+ const outsideElement = document.getElementById('outside') as HTMLElement
620
+ outsideElement.dispatchEvent(new MouseEvent('click', { bubbles: true }))
621
+ await sleepAsync(50)
622
+
623
+ expect(commandPalette.classList.contains('opened')).toBe(false)
624
+ })
625
+ })
626
+ })
627
+
628
+ describe('styling', () => {
629
+ it('should apply custom style to input container', async () => {
630
+ await usingAsync(new Injector(), async (injector) => {
631
+ const rootElement = document.getElementById('root') as HTMLDivElement
632
+
633
+ initializeShadeRoot({
634
+ injector,
635
+ rootElement,
636
+ jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" style={{ maxWidth: '500px' }} />,
637
+ })
638
+
639
+ await sleepAsync(50)
640
+
641
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
642
+ const inputContainer = commandPalette.querySelector('.input-container') as HTMLElement
643
+
644
+ expect(inputContainer.style.maxWidth).toBe('500px')
645
+ })
646
+ })
647
+
648
+ it('should pass fullScreenSuggestions to suggestion list', async () => {
649
+ await usingAsync(new Injector(), async (injector) => {
650
+ const provider = createMockProvider([createSuggestion('Item', 100)])
651
+
652
+ const rootElement = document.getElementById('root') as HTMLDivElement
653
+
654
+ initializeShadeRoot({
655
+ injector,
656
+ rootElement,
657
+ jsxElement: <CommandPalette commandProviders={[provider]} defaultPrefix=">" fullScreenSuggestions />,
658
+ })
659
+
660
+ await sleepAsync(50)
661
+
662
+ const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
663
+
664
+ // Open and search
665
+ const termIcon = commandPalette.querySelector('.term-icon') as HTMLElement
666
+ termIcon.click()
667
+ await sleepAsync(50)
668
+
669
+ const input = commandPalette.querySelector('input') as HTMLInputElement
670
+ input.value = 'test'
671
+ const event = new KeyboardEvent('keyup', { key: 't', bubbles: true })
672
+ Object.defineProperty(event, 'target', { value: input, writable: false })
673
+ input.dispatchEvent(event)
674
+
675
+ await sleepAsync(300)
676
+
677
+ const suggestionList = commandPalette.querySelector('shade-command-palette-suggestion-list') as HTMLElement
678
+ const suggestionContainer = suggestionList.querySelector('.suggestion-items-container') as HTMLElement
679
+ // fullScreenSuggestions sets left: '0' and specific width
680
+ expect(suggestionContainer.style.left).toBe('0px')
681
+ })
682
+ })
683
+ })
684
+ })