@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,499 @@
1
+ import { Injector } from '@furystack/inject'
2
+ import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
+ import { sleepAsync } from '@furystack/utils'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { CommandPaletteSuggestionList } from './command-palette-suggestion-list.js'
6
+ import { CommandPaletteManager } from './command-palette-manager.js'
7
+ import type { CommandPaletteSuggestionResult } from './command-provider.js'
8
+
9
+ describe('CommandPaletteSuggestionList', () => {
10
+ let originalAnimate: typeof Element.prototype.animate
11
+ let animateCalls: Array<{ keyframes: unknown; options: unknown }>
12
+
13
+ beforeEach(() => {
14
+ document.body.innerHTML = '<div id="root" style="width: 500px;"></div>'
15
+ animateCalls = []
16
+ originalAnimate = Element.prototype.animate
17
+
18
+ Element.prototype.animate = vi.fn(
19
+ (keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions) => {
20
+ animateCalls.push({ keyframes, options })
21
+ const mockAnimation = {
22
+ onfinish: null as ((event: AnimationPlaybackEvent) => void) | null,
23
+ oncancel: null as ((event: AnimationPlaybackEvent) => void) | null,
24
+ cancel: vi.fn(),
25
+ play: vi.fn(),
26
+ pause: vi.fn(),
27
+ finish: vi.fn(),
28
+ addEventListener: vi.fn(),
29
+ removeEventListener: vi.fn(),
30
+ }
31
+
32
+ setTimeout(() => {
33
+ mockAnimation.onfinish?.({} as AnimationPlaybackEvent)
34
+ }, 10)
35
+
36
+ return mockAnimation as unknown as Animation
37
+ },
38
+ ) as typeof Element.prototype.animate
39
+ })
40
+
41
+ afterEach(() => {
42
+ document.body.innerHTML = ''
43
+ Element.prototype.animate = originalAnimate
44
+ vi.restoreAllMocks()
45
+ })
46
+
47
+ const createManager = () => {
48
+ return new CommandPaletteManager([])
49
+ }
50
+
51
+ const createSuggestion = (text: string, score: number): CommandPaletteSuggestionResult => ({
52
+ element: <span>{text}</span>,
53
+ score,
54
+ onSelected: vi.fn(),
55
+ })
56
+
57
+ it('should render with shadow DOM', async () => {
58
+ const injector = new Injector()
59
+ const manager = createManager()
60
+ const rootElement = document.getElementById('root') as HTMLDivElement
61
+
62
+ initializeShadeRoot({
63
+ injector,
64
+ rootElement,
65
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
66
+ })
67
+
68
+ await sleepAsync(50)
69
+
70
+ const component = document.querySelector('shade-command-palette-suggestion-list')
71
+ expect(component).not.toBeNull()
72
+
73
+ manager[Symbol.dispose]()
74
+ })
75
+
76
+ it('should render suggestion items container', async () => {
77
+ const injector = new Injector()
78
+ const manager = createManager()
79
+ const rootElement = document.getElementById('root') as HTMLDivElement
80
+
81
+ initializeShadeRoot({
82
+ injector,
83
+ rootElement,
84
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
85
+ })
86
+
87
+ await sleepAsync(50)
88
+
89
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
90
+ const container = component?.querySelector('.suggestion-items-container')
91
+ expect(container).not.toBeNull()
92
+
93
+ manager[Symbol.dispose]()
94
+ })
95
+
96
+ it('should render suggestions from manager', async () => {
97
+ const injector = new Injector()
98
+ const manager = createManager()
99
+ manager.currentSuggestions.setValue([createSuggestion('Command 1', 100), createSuggestion('Command 2', 90)])
100
+ const rootElement = document.getElementById('root') as HTMLDivElement
101
+
102
+ initializeShadeRoot({
103
+ injector,
104
+ rootElement,
105
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
106
+ })
107
+
108
+ await sleepAsync(50)
109
+
110
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
111
+ const items = component?.querySelectorAll('.suggestion-item')
112
+ expect(items?.length).toBe(2)
113
+
114
+ manager[Symbol.dispose]()
115
+ })
116
+
117
+ it('should render suggestion content', async () => {
118
+ const injector = new Injector()
119
+ const manager = createManager()
120
+ manager.currentSuggestions.setValue([createSuggestion('Test Command', 100)])
121
+ const rootElement = document.getElementById('root') as HTMLDivElement
122
+
123
+ initializeShadeRoot({
124
+ injector,
125
+ rootElement,
126
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
127
+ })
128
+
129
+ await sleepAsync(50)
130
+
131
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
132
+ const item = component?.querySelector('.suggestion-item')
133
+ expect(item?.textContent).toContain('Test Command')
134
+
135
+ manager[Symbol.dispose]()
136
+ })
137
+
138
+ it('should mark first item as selected by default', async () => {
139
+ const injector = new Injector()
140
+ const manager = createManager()
141
+ manager.currentSuggestions.setValue([createSuggestion('Command 1', 100), createSuggestion('Command 2', 90)])
142
+ manager.selectedIndex.setValue(0)
143
+ const rootElement = document.getElementById('root') as HTMLDivElement
144
+
145
+ initializeShadeRoot({
146
+ injector,
147
+ rootElement,
148
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
149
+ })
150
+
151
+ await sleepAsync(50)
152
+
153
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
154
+ const items = component?.querySelectorAll('.suggestion-item')
155
+ expect(items?.[0]?.classList.contains('selected')).toBe(true)
156
+ expect(items?.[1]?.classList.contains('selected')).toBe(false)
157
+
158
+ manager[Symbol.dispose]()
159
+ })
160
+
161
+ it('should update selected class when selectedIndex changes', async () => {
162
+ const injector = new Injector()
163
+ const manager = createManager()
164
+ manager.currentSuggestions.setValue([createSuggestion('Command 1', 100), createSuggestion('Command 2', 90)])
165
+ manager.selectedIndex.setValue(0)
166
+ const rootElement = document.getElementById('root') as HTMLDivElement
167
+
168
+ initializeShadeRoot({
169
+ injector,
170
+ rootElement,
171
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
172
+ })
173
+
174
+ await sleepAsync(50)
175
+
176
+ manager.selectedIndex.setValue(1)
177
+ await sleepAsync(50)
178
+
179
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
180
+ const items = component?.querySelectorAll('.suggestion-item')
181
+ expect(items?.[0]?.classList.contains('selected')).toBe(false)
182
+ expect(items?.[1]?.classList.contains('selected')).toBe(true)
183
+
184
+ manager[Symbol.dispose]()
185
+ })
186
+
187
+ it('should call selectSuggestion when item is clicked while opened', async () => {
188
+ const injector = new Injector()
189
+ const manager = createManager()
190
+ const suggestion = createSuggestion('Click Me', 100)
191
+ manager.currentSuggestions.setValue([suggestion])
192
+ manager.isOpened.setValue(true)
193
+ const rootElement = document.getElementById('root') as HTMLDivElement
194
+
195
+ initializeShadeRoot({
196
+ injector,
197
+ rootElement,
198
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
199
+ })
200
+
201
+ await sleepAsync(50)
202
+
203
+ const selectSpy = vi.spyOn(manager, 'selectSuggestion')
204
+
205
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
206
+ const item = component?.querySelector('.suggestion-item') as HTMLElement
207
+ item?.click()
208
+
209
+ expect(selectSpy).toHaveBeenCalledWith(injector, 0)
210
+
211
+ manager[Symbol.dispose]()
212
+ })
213
+
214
+ it('should not call selectSuggestion when item is clicked while closed', async () => {
215
+ const injector = new Injector()
216
+ const manager = createManager()
217
+ const suggestion = createSuggestion('Click Me', 100)
218
+ manager.currentSuggestions.setValue([suggestion])
219
+ manager.isOpened.setValue(false)
220
+ const rootElement = document.getElementById('root') as HTMLDivElement
221
+
222
+ initializeShadeRoot({
223
+ injector,
224
+ rootElement,
225
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
226
+ })
227
+
228
+ await sleepAsync(50)
229
+
230
+ const selectSpy = vi.spyOn(manager, 'selectSuggestion')
231
+
232
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
233
+ const item = component?.querySelector('.suggestion-item') as HTMLElement
234
+ item?.click()
235
+
236
+ expect(selectSpy).not.toHaveBeenCalled()
237
+
238
+ manager[Symbol.dispose]()
239
+ })
240
+
241
+ it('should animate slide-in when opening', async () => {
242
+ const injector = new Injector()
243
+ const manager = createManager()
244
+ manager.isOpened.setValue(false)
245
+ const rootElement = document.getElementById('root') as HTMLDivElement
246
+
247
+ initializeShadeRoot({
248
+ injector,
249
+ rootElement,
250
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
251
+ })
252
+
253
+ await sleepAsync(50)
254
+ animateCalls = []
255
+
256
+ manager.isOpened.setValue(true)
257
+ await sleepAsync(50)
258
+
259
+ const slideAnimation = animateCalls.find(
260
+ (call) =>
261
+ Array.isArray(call.keyframes) &&
262
+ call.keyframes.some((kf: Keyframe) => kf.transform === 'translate(0, -50px)') &&
263
+ call.keyframes.some((kf: Keyframe) => kf.transform === 'translate(0, 0)'),
264
+ )
265
+
266
+ expect(slideAnimation).toBeDefined()
267
+ expect((slideAnimation?.options as KeyframeAnimationOptions)?.duration).toBe(500)
268
+ expect((slideAnimation?.options as KeyframeAnimationOptions)?.fill).toBe('forwards')
269
+
270
+ manager[Symbol.dispose]()
271
+ })
272
+
273
+ it('should animate slide-out when closing', async () => {
274
+ const injector = new Injector()
275
+ const manager = createManager()
276
+ manager.isOpened.setValue(true)
277
+ const rootElement = document.getElementById('root') as HTMLDivElement
278
+
279
+ initializeShadeRoot({
280
+ injector,
281
+ rootElement,
282
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
283
+ })
284
+
285
+ await sleepAsync(50)
286
+ animateCalls = []
287
+
288
+ manager.isOpened.setValue(false)
289
+ await sleepAsync(50)
290
+
291
+ const slideAnimation = animateCalls.find(
292
+ (call) =>
293
+ Array.isArray(call.keyframes) &&
294
+ call.keyframes.some((kf: Keyframe) => kf.transform === 'translate(0, 0)') &&
295
+ call.keyframes.some((kf: Keyframe) => kf.transform === 'translate(0, -50px)'),
296
+ )
297
+
298
+ expect(slideAnimation).toBeDefined()
299
+ expect((slideAnimation?.options as KeyframeAnimationOptions)?.duration).toBe(200)
300
+
301
+ manager[Symbol.dispose]()
302
+ })
303
+
304
+ it('should set container display to initial when opening', async () => {
305
+ const injector = new Injector()
306
+ const manager = createManager()
307
+ manager.isOpened.setValue(false)
308
+ const rootElement = document.getElementById('root') as HTMLDivElement
309
+
310
+ initializeShadeRoot({
311
+ injector,
312
+ rootElement,
313
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
314
+ })
315
+
316
+ await sleepAsync(50)
317
+
318
+ manager.isOpened.setValue(true)
319
+ await sleepAsync(50)
320
+
321
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
322
+ const container = component?.querySelector('.suggestion-items-container') as HTMLElement
323
+ expect(container?.style.display).toBe('initial')
324
+
325
+ manager[Symbol.dispose]()
326
+ })
327
+
328
+ it('should set container display to none when closing', async () => {
329
+ const injector = new Injector()
330
+ const manager = createManager()
331
+ manager.isOpened.setValue(true)
332
+ const rootElement = document.getElementById('root') as HTMLDivElement
333
+
334
+ initializeShadeRoot({
335
+ injector,
336
+ rootElement,
337
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
338
+ })
339
+
340
+ await sleepAsync(50)
341
+
342
+ manager.isOpened.setValue(false)
343
+ await sleepAsync(50)
344
+
345
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
346
+ const container = component?.querySelector('.suggestion-items-container') as HTMLElement
347
+ expect(container?.style.display).toBe('none')
348
+
349
+ manager[Symbol.dispose]()
350
+ })
351
+
352
+ it('should render empty list when no suggestions', async () => {
353
+ const injector = new Injector()
354
+ const manager = createManager()
355
+ manager.currentSuggestions.setValue([])
356
+ const rootElement = document.getElementById('root') as HTMLDivElement
357
+
358
+ initializeShadeRoot({
359
+ injector,
360
+ rootElement,
361
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
362
+ })
363
+
364
+ await sleepAsync(50)
365
+
366
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
367
+ const items = component?.querySelectorAll('.suggestion-item')
368
+ expect(items?.length).toBe(0)
369
+
370
+ manager[Symbol.dispose]()
371
+ })
372
+
373
+ it('should support fullScreenSuggestions prop', async () => {
374
+ const injector = new Injector()
375
+ const manager = createManager()
376
+ manager.isOpened.setValue(true)
377
+ const rootElement = document.getElementById('root') as HTMLDivElement
378
+
379
+ initializeShadeRoot({
380
+ injector,
381
+ rootElement,
382
+ jsxElement: <CommandPaletteSuggestionList manager={manager} fullScreenSuggestions />,
383
+ })
384
+
385
+ await sleepAsync(50)
386
+
387
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
388
+ const container = component?.querySelector('.suggestion-items-container') as HTMLElement
389
+ expect(container?.style.left).toBe('0px')
390
+ expect(container?.style.width).toBe('calc(100% - 42px)')
391
+
392
+ manager[Symbol.dispose]()
393
+ })
394
+
395
+ it('should set max height based on window height', async () => {
396
+ const injector = new Injector()
397
+ const manager = createManager()
398
+ manager.isOpened.setValue(true)
399
+ const rootElement = document.getElementById('root') as HTMLDivElement
400
+
401
+ initializeShadeRoot({
402
+ injector,
403
+ rootElement,
404
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
405
+ })
406
+
407
+ await sleepAsync(50)
408
+
409
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
410
+ const container = component?.querySelector('.suggestion-items-container') as HTMLElement
411
+ const expectedMaxHeight = `${window.innerHeight * 0.8}px`
412
+ expect(container?.style.maxHeight).toBe(expectedMaxHeight)
413
+
414
+ manager[Symbol.dispose]()
415
+ })
416
+
417
+ it('should have correct CSS styles for suggestion items container', async () => {
418
+ const injector = new Injector()
419
+ const manager = createManager()
420
+ const rootElement = document.getElementById('root') as HTMLDivElement
421
+
422
+ initializeShadeRoot({
423
+ injector,
424
+ rootElement,
425
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
426
+ })
427
+
428
+ await sleepAsync(50)
429
+
430
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
431
+ const container = component?.querySelector('.suggestion-items-container') as HTMLElement
432
+ const computedStyle = window.getComputedStyle(container)
433
+
434
+ expect(computedStyle.position).toBe('absolute')
435
+ expect(computedStyle.overflow).toBe('hidden')
436
+ expect(computedStyle.zIndex).toBe('1')
437
+
438
+ manager[Symbol.dispose]()
439
+ })
440
+
441
+ it('should call selectSuggestion with correct index for second item', async () => {
442
+ const injector = new Injector()
443
+ const manager = createManager()
444
+ manager.currentSuggestions.setValue([
445
+ createSuggestion('First', 100),
446
+ createSuggestion('Second', 90),
447
+ createSuggestion('Third', 80),
448
+ ])
449
+ manager.isOpened.setValue(true)
450
+ const rootElement = document.getElementById('root') as HTMLDivElement
451
+
452
+ initializeShadeRoot({
453
+ injector,
454
+ rootElement,
455
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
456
+ })
457
+
458
+ await sleepAsync(50)
459
+
460
+ const selectSpy = vi.spyOn(manager, 'selectSuggestion')
461
+
462
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
463
+ const items = component?.querySelectorAll('.suggestion-item')
464
+ ;(items[1] as HTMLElement)?.click()
465
+
466
+ expect(selectSpy).toHaveBeenCalledWith(injector, 1)
467
+
468
+ manager[Symbol.dispose]()
469
+ })
470
+
471
+ it('should update container z-index when opening and closing', async () => {
472
+ const injector = new Injector()
473
+ const manager = createManager()
474
+ manager.isOpened.setValue(false)
475
+ const rootElement = document.getElementById('root') as HTMLDivElement
476
+
477
+ initializeShadeRoot({
478
+ injector,
479
+ rootElement,
480
+ jsxElement: <CommandPaletteSuggestionList manager={manager} />,
481
+ })
482
+
483
+ await sleepAsync(50)
484
+
485
+ manager.isOpened.setValue(true)
486
+ await sleepAsync(50)
487
+
488
+ const component = document.querySelector('shade-command-palette-suggestion-list') as HTMLElement
489
+ const container = component?.querySelector('.suggestion-items-container') as HTMLElement
490
+ expect(container?.style.zIndex).toBe('1')
491
+
492
+ manager.isOpened.setValue(false)
493
+ await sleepAsync(50)
494
+
495
+ expect(container?.style.zIndex).toBe('-1')
496
+
497
+ manager[Symbol.dispose]()
498
+ })
499
+ })
@@ -1,27 +1,56 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
- import { ThemeProviderService } from '../../services/theme-provider-service.js'
2
+ import { cssVariableTheme } from '../../services/css-variable-theme.js'
3
3
  import { promisifyAnimation } from '../../utils/promisify-animation.js'
4
4
  import type { CommandPaletteManager } from './command-palette-manager.js'
5
5
 
6
6
  export const CommandPaletteSuggestionList = Shade<{ manager: CommandPaletteManager; fullScreenSuggestions?: boolean }>({
7
7
  shadowDomName: 'shade-command-palette-suggestion-list',
8
+ css: {
9
+ '& .suggestion-items-container': {
10
+ borderTop: 'none',
11
+ position: 'absolute',
12
+ borderRadius: '0px 0px 12px 12px',
13
+ marginLeft: '14px',
14
+ marginTop: '4px',
15
+ overflow: 'hidden',
16
+ overflowY: 'auto',
17
+ zIndex: '1',
18
+ left: 'auto',
19
+ backgroundColor: cssVariableTheme.background.paper,
20
+ boxShadow: '0 8px 24px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.12)',
21
+ backdropFilter: 'blur(20px)',
22
+ border: '1px solid rgba(128,128,128,0.2)',
23
+ },
24
+ '& .suggestion-item': {
25
+ padding: '0.875em 1.25em',
26
+ cursor: 'pointer',
27
+ background: 'transparent',
28
+ fontWeight: '400',
29
+ borderLeft: '3px solid transparent',
30
+ transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
31
+ fontSize: '0.95em',
32
+ letterSpacing: '0.01em',
33
+ },
34
+ '& .suggestion-item:hover': {
35
+ background: 'rgba(128,128,128,0.1)',
36
+ },
37
+ '& .suggestion-item.selected': {
38
+ background: 'rgba(128,128,128,0.2)',
39
+ fontWeight: '500',
40
+ borderLeft: `3px solid ${cssVariableTheme.text.primary}`,
41
+ },
42
+ '& .suggestion-item.selected:hover': {
43
+ background: 'rgba(128,128,128,0.2)',
44
+ },
45
+ },
8
46
  render: ({ element, injector, props, useObservable }) => {
9
47
  const { manager } = props
10
- const { theme } = injector.getInstance(ThemeProviderService)
11
48
 
12
49
  const [suggestions] = useObservable('suggestions', props.manager.currentSuggestions)
13
50
  const [selectedIndex] = useObservable('selectedIndex', props.manager.selectedIndex, {
14
51
  onChange: (idx) => {
15
- ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).map((s, i) => {
16
- if (i === idx) {
17
- s.style.background = 'rgba(128,128,128,0.2)'
18
- s.style.fontWeight = '500'
19
- s.style.borderLeft = `3px solid ${theme.text.primary}`
20
- } else {
21
- s.style.background = 'transparent'
22
- s.style.fontWeight = '400'
23
- s.style.borderLeft = '3px solid transparent'
24
- }
52
+ ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).forEach((s, i) => {
53
+ s.classList.toggle('selected', i === idx)
25
54
  })
26
55
  },
27
56
  })
@@ -62,53 +91,20 @@ export const CommandPaletteSuggestionList = Shade<{ manager: CommandPaletteManag
62
91
  <div
63
92
  className="suggestion-items-container"
64
93
  style={{
65
- borderTop: 'none',
66
- position: 'absolute',
67
94
  opacity: manager.isOpened.getValue() ? '1' : '0',
68
- borderRadius: '0px 0px 12px 12px',
69
- marginLeft: '14px',
70
- marginTop: '4px',
71
- overflow: 'hidden',
72
- overflowY: 'auto',
73
95
  maxHeight: `${window.innerHeight * 0.8}px`,
74
- zIndex: '1',
75
- left: 'auto',
76
- backgroundColor: theme.background.paper,
77
- boxShadow: '0 8px 24px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.12)',
78
- backdropFilter: 'blur(20px)',
79
- border: '1px solid rgba(128,128,128,0.2)',
80
96
  width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
81
97
  ...(props.fullScreenSuggestions ? { left: '0', width: 'calc(100% - 42px)' } : {}),
82
98
  }}
83
99
  >
84
100
  {suggestions.map((s, i) => (
85
101
  <div
86
- className="suggestion-item"
102
+ className={`suggestion-item${i === selectedIndex ? ' selected' : ''}`}
87
103
  onclick={() => {
88
104
  if (isOpenedAtRender) {
89
105
  manager.selectSuggestion(injector, i)
90
106
  }
91
107
  }}
92
- onmouseenter={(ev) => {
93
- if (i !== selectedIndex) {
94
- ;(ev.target as HTMLElement).style.background = 'rgba(128,128,128,0.1)'
95
- }
96
- }}
97
- onmouseleave={(ev) => {
98
- if (i !== selectedIndex) {
99
- ;(ev.target as HTMLElement).style.background = 'transparent'
100
- }
101
- }}
102
- style={{
103
- padding: '0.875em 1.25em',
104
- cursor: 'pointer',
105
- background: i === selectedIndex ? 'rgba(128,128,128,0.2)' : 'transparent',
106
- fontWeight: i === selectedIndex ? '500' : '400',
107
- borderLeft: i === selectedIndex ? `3px solid ${theme.text.primary}` : '3px solid transparent',
108
- transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
109
- fontSize: '0.95em',
110
- letterSpacing: '0.01em',
111
- }}
112
108
  >
113
109
  {s.element}
114
110
  </div>