@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
@@ -1,6 +1,6 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
2
  import { ClickAwayService } from '../../services/click-away-service.js'
3
- import { ThemeProviderService } from '../../services/theme-provider-service.js'
3
+ import { cssVariableTheme } from '../../services/css-variable-theme.js'
4
4
  import { promisifyAnimation } from '../../utils/promisify-animation.js'
5
5
  import { Loader } from '../loader.js'
6
6
  import { CommandPaletteInput } from './command-palette-input.js'
@@ -22,85 +22,127 @@ export interface CommandPaletteProps {
22
22
 
23
23
  export const CommandPalette = Shade<CommandPaletteProps>({
24
24
  shadowDomName: 'shade-command-palette',
25
+ css: {
26
+ flexGrow: '1',
27
+ '& .command-palette-wrapper': {
28
+ display: 'flex',
29
+ flexDirection: 'column',
30
+ },
31
+ '& .input-container': {
32
+ display: 'flex',
33
+ alignItems: 'center',
34
+ justifyContent: 'flex-end',
35
+ padding: '0 1.25em',
36
+ borderRadius: '12px',
37
+ position: 'relative',
38
+ background: 'rgba(128,128,128,0.08)',
39
+ border: '1px solid rgba(128,128,128,0.15)',
40
+ boxShadow: '0 2px 4px rgba(0,0,0,0.05)',
41
+ transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
42
+ },
43
+ '&.opened .input-container': {
44
+ border: '1px solid rgba(128,128,128,0.3)',
45
+ boxShadow: '0 4px 12px rgba(0,0,0,0.15), 0 0 0 3px rgba(128,128,128,0.05)',
46
+ },
47
+ '& .term-icon': {
48
+ cursor: 'pointer',
49
+ color: cssVariableTheme.text.secondary,
50
+ fontWeight: '600',
51
+ fontSize: '0.95em',
52
+ transition: 'color 0.2s ease',
53
+ padding: '0.5em 0.75em 0.5em 0',
54
+ userSelect: 'none',
55
+ },
56
+ '& .term-icon:hover': {
57
+ color: cssVariableTheme.text.primary,
58
+ },
59
+ '& .post-controls': {
60
+ display: 'flex',
61
+ alignItems: 'center',
62
+ justifyContent: 'space-between',
63
+ width: '0px',
64
+ overflow: 'hidden',
65
+ },
66
+ '&.opened .post-controls': {
67
+ width: '50px',
68
+ },
69
+ '& .loader-container': {
70
+ width: '20px',
71
+ height: '20px',
72
+ opacity: '0',
73
+ },
74
+ '&.loading .loader-container': {
75
+ opacity: '1',
76
+ },
77
+ '& .close-suggestions': {
78
+ width: '24px',
79
+ height: '24px',
80
+ opacity: '0',
81
+ display: 'flex',
82
+ alignItems: 'center',
83
+ justifyContent: 'center',
84
+ cursor: 'pointer',
85
+ borderRadius: '6px',
86
+ transition: 'all 0.2s ease',
87
+ fontSize: '14px',
88
+ color: cssVariableTheme.text.secondary,
89
+ background: 'transparent',
90
+ transform: 'scale(1)',
91
+ },
92
+ '&.opened .close-suggestions': {
93
+ opacity: '1',
94
+ },
95
+ '& .close-suggestions:hover': {
96
+ background: 'rgba(255,255,255,0.15)',
97
+ transform: 'scale(1.1)',
98
+ },
99
+ },
25
100
  render: ({ props, injector, element, useState, useDisposable, useObservable }) => {
26
- element.style.flexGrow = '1'
27
101
  const [manager] = useState('manager', new CommandPaletteManager(props.commandProviders))
28
- const { theme } = injector.getInstance(ThemeProviderService)
29
102
 
30
103
  useDisposable('clickAwayService', () => new ClickAwayService(element, () => manager.isOpened.setValue(false)))
31
104
 
32
- const [isLoadingAtRender] = useObservable('isLoading', manager.isLoading, {
105
+ useObservable('isLoading', manager.isLoading, {
33
106
  onChange: (isLoading) => {
34
- const loader = element.querySelector('.loader-container')
35
- if (isLoading) {
36
- void promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
37
- duration: 100,
38
- fill: 'forwards',
39
- })
40
- } else {
41
- void promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
42
- duration: 100,
43
- fill: 'forwards',
44
- })
45
- }
107
+ element.classList.toggle('loading', isLoading)
46
108
  },
47
109
  })
48
110
 
49
111
  const [isOpenedAtRender, setIsOpened] = useObservable('isOpened', manager.isOpened, {
50
112
  onChange: (isOpened) => {
51
- {
52
- const suggestions = element.querySelector('.close-suggestions')
53
- const postControls = element.querySelector('.post-controls')
54
- const inputContainer = element.querySelector('.input-container') as HTMLDivElement
55
- if (isOpened) {
56
- void promisifyAnimation(suggestions, [{ opacity: 0 }, { opacity: 1 }], {
57
- duration: 500,
58
- fill: 'forwards',
59
- })
60
-
61
- void promisifyAnimation(postControls, [{ width: '0px' }, { width: '50px' }], {
62
- duration: 100,
63
- fill: 'forwards',
64
- })
65
-
66
- void promisifyAnimation(
67
- inputContainer,
68
- [{ background: 'transparent' }, { background: theme.background.default }],
69
- {
70
- duration: 500,
71
- fill: 'forwards',
72
- easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
73
- },
74
- )
75
- } else {
76
- void promisifyAnimation(suggestions, [{ opacity: 1 }, { opacity: 0 }], {
113
+ element.classList.toggle('opened', isOpened)
114
+ const inputContainer = element.querySelector('.input-container') as HTMLDivElement
115
+ if (isOpened) {
116
+ void promisifyAnimation(
117
+ inputContainer,
118
+ [{ background: 'transparent' }, { background: cssVariableTheme.background.default }],
119
+ {
77
120
  duration: 500,
78
121
  fill: 'forwards',
79
- })
80
-
81
- void promisifyAnimation(postControls, [{ width: '50px' }, { width: '0px' }], {
82
- duration: 500,
122
+ easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
123
+ },
124
+ )
125
+ } else {
126
+ void promisifyAnimation(
127
+ inputContainer,
128
+ [{ background: cssVariableTheme.background.default }, { background: 'transparent' }],
129
+ {
130
+ duration: 300,
83
131
  fill: 'forwards',
84
- delay: 300,
85
- })
86
-
87
- void promisifyAnimation(
88
- inputContainer,
89
- [{ background: theme.background.default }, { background: 'transparent' }],
90
- {
91
- duration: 300,
92
- fill: 'forwards',
93
- easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
94
- },
95
- )
96
- }
132
+ easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
133
+ },
134
+ )
97
135
  }
98
136
  },
99
137
  })
100
138
 
139
+ if (isOpenedAtRender) {
140
+ element.classList.add('opened')
141
+ }
142
+
101
143
  return (
102
144
  <div
103
- style={{ display: 'flex', flexDirection: 'column' }}
145
+ className="command-palette-wrapper"
104
146
  onkeyup={(ev) => {
105
147
  if (ev.key === 'Enter') {
106
148
  ev.preventDefault()
@@ -121,87 +163,16 @@ export const CommandPalette = Shade<CommandPaletteProps>({
121
163
  void manager.getSuggestion({ injector, term: (ev.target as HTMLInputElement).value })
122
164
  }}
123
165
  >
124
- <div
125
- className="input-container"
126
- style={{
127
- display: 'flex',
128
- alignItems: 'center',
129
- justifyContent: 'flex-end',
130
- padding: '0 1.25em',
131
- borderRadius: '12px',
132
- position: 'relative',
133
- background: 'rgba(128,128,128,0.08)',
134
- border: `1px solid ${manager.isOpened.getValue() ? 'rgba(128,128,128,0.3)' : 'rgba(128,128,128,0.15)'}`,
135
- boxShadow: manager.isOpened.getValue()
136
- ? '0 4px 12px rgba(0,0,0,0.15), 0 0 0 3px rgba(128,128,128,0.05)'
137
- : '0 2px 4px rgba(0,0,0,0.05)',
138
- transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
139
- ...props.style,
140
- }}
141
- >
142
- <div
143
- className="term-icon"
144
- style={{
145
- cursor: 'pointer',
146
- color: theme.text.secondary,
147
- fontWeight: '600',
148
- fontSize: '0.95em',
149
- transition: 'color 0.2s ease',
150
- padding: '0.5em 0.75em 0.5em 0',
151
- userSelect: 'none',
152
- }}
153
- onmouseenter={(ev) => {
154
- ;(ev.target as HTMLElement).style.color = theme.text.primary
155
- }}
156
- onmouseleave={(ev) => {
157
- ;(ev.target as HTMLElement).style.color = theme.text.secondary
158
- }}
159
- onclick={() => setIsOpened(true)}
160
- >
166
+ <div className="input-container" style={props.style}>
167
+ <div className="term-icon" onclick={() => setIsOpened(true)}>
161
168
  {props.defaultPrefix}
162
169
  </div>
163
170
  <CommandPaletteInput manager={manager} />
164
- <div
165
- className="post-controls"
166
- style={{
167
- display: 'flex',
168
- alignItems: 'center',
169
- justifyContent: 'space-between',
170
- width: isOpenedAtRender ? '50px' : '0px',
171
- overflow: 'hidden',
172
- }}
173
- >
174
- <div
175
- className="loader-container"
176
- style={{ width: '20px', height: '20px', opacity: isLoadingAtRender ? '1' : '0' }}
177
- >
171
+ <div className="post-controls">
172
+ <div className="loader-container">
178
173
  <Loader style={{ width: '100%', height: '100%' }} />
179
174
  </div>
180
- <div
181
- className="close-suggestions"
182
- onclick={() => setIsOpened(false)}
183
- onmouseenter={(ev) => {
184
- ;(ev.target as HTMLElement).style.background = 'rgba(255,255,255,0.15)'
185
- ;(ev.target as HTMLElement).style.transform = 'scale(1.1)'
186
- }}
187
- onmouseleave={(ev) => {
188
- ;(ev.target as HTMLElement).style.background = 'transparent'
189
- ;(ev.target as HTMLElement).style.transform = 'scale(1)'
190
- }}
191
- style={{
192
- width: '24px',
193
- height: '24px',
194
- opacity: manager.isOpened.getValue() ? '1' : '0',
195
- display: 'flex',
196
- alignItems: 'center',
197
- justifyContent: 'center',
198
- cursor: 'pointer',
199
- borderRadius: '6px',
200
- transition: 'all 0.2s ease',
201
- fontSize: '14px',
202
- color: theme.text.secondary,
203
- }}
204
- >
175
+ <div className="close-suggestions" onclick={() => setIsOpened(false)}>
205
176
 
206
177
  </div>
207
178
  </div>
@@ -0,0 +1,340 @@
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 { CollectionService } from '../../services/collection-service.js'
6
+ import { DataGridBody } from './body.js'
7
+
8
+ type TestEntry = { id: number; name: string }
9
+
10
+ describe('DataGridBody', () => {
11
+ beforeEach(() => {
12
+ document.body.innerHTML = '<div id="root"></div>'
13
+ })
14
+
15
+ afterEach(() => {
16
+ document.body.innerHTML = ''
17
+ })
18
+
19
+ it('should render default empty component when no data', async () => {
20
+ const injector = new Injector()
21
+ const rootElement = document.getElementById('root') as HTMLDivElement
22
+ const service = new CollectionService<TestEntry>()
23
+
24
+ initializeShadeRoot({
25
+ injector,
26
+ rootElement,
27
+ jsxElement: (
28
+ <table>
29
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
30
+ </table>
31
+ ),
32
+ })
33
+
34
+ await sleepAsync(50)
35
+
36
+ const body = document.querySelector('tbody[is="shade-data-grid-body"]')
37
+ expect(body).not.toBeNull()
38
+ expect(body?.textContent).toContain('- No Data -')
39
+
40
+ service[Symbol.dispose]()
41
+ })
42
+
43
+ it('should render custom empty component when provided and no data', async () => {
44
+ const injector = new Injector()
45
+ const rootElement = document.getElementById('root') as HTMLDivElement
46
+ const service = new CollectionService<TestEntry>()
47
+
48
+ initializeShadeRoot({
49
+ injector,
50
+ rootElement,
51
+ jsxElement: (
52
+ <table>
53
+ <DataGridBody<TestEntry, 'id' | 'name'>
54
+ service={service}
55
+ columns={['id', 'name']}
56
+ emptyComponent={<div data-testid="custom-empty">Custom Empty State</div>}
57
+ />
58
+ </table>
59
+ ),
60
+ })
61
+
62
+ await sleepAsync(50)
63
+
64
+ const body = document.querySelector('tbody[is="shade-data-grid-body"]')
65
+ expect(body).not.toBeNull()
66
+ expect(body?.querySelector('[data-testid="custom-empty"]')).not.toBeNull()
67
+ expect(body?.textContent).toContain('Custom Empty State')
68
+
69
+ service[Symbol.dispose]()
70
+ })
71
+
72
+ it('should render rows for each data entry', async () => {
73
+ const injector = new Injector()
74
+ const rootElement = document.getElementById('root') as HTMLDivElement
75
+ const service = new CollectionService<TestEntry>()
76
+
77
+ service.data.setValue({
78
+ count: 2,
79
+ entries: [
80
+ { id: 1, name: 'First' },
81
+ { id: 2, name: 'Second' },
82
+ ],
83
+ })
84
+
85
+ initializeShadeRoot({
86
+ injector,
87
+ rootElement,
88
+ jsxElement: (
89
+ <table>
90
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
91
+ </table>
92
+ ),
93
+ })
94
+
95
+ await sleepAsync(50)
96
+
97
+ const body = document.querySelector('tbody[is="shade-data-grid-body"]')
98
+ expect(body).not.toBeNull()
99
+
100
+ const rows = body?.querySelectorAll('shades-data-grid-row')
101
+ expect(rows?.length).toBe(2)
102
+
103
+ service[Symbol.dispose]()
104
+ })
105
+
106
+ it('should render cell content from entry properties', async () => {
107
+ const injector = new Injector()
108
+ const rootElement = document.getElementById('root') as HTMLDivElement
109
+ const service = new CollectionService<TestEntry>()
110
+
111
+ service.data.setValue({
112
+ count: 1,
113
+ entries: [{ id: 42, name: 'Test Entry' }],
114
+ })
115
+
116
+ initializeShadeRoot({
117
+ injector,
118
+ rootElement,
119
+ jsxElement: (
120
+ <table>
121
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
122
+ </table>
123
+ ),
124
+ })
125
+
126
+ await sleepAsync(50)
127
+
128
+ const body = document.querySelector('tbody[is="shade-data-grid-body"]')
129
+ const cells = body?.querySelectorAll('td')
130
+
131
+ expect(cells?.length).toBe(2)
132
+ expect(cells?.[0]?.textContent).toBe('42')
133
+ expect(cells?.[1]?.textContent).toBe('Test Entry')
134
+
135
+ service[Symbol.dispose]()
136
+ })
137
+
138
+ it('should re-render when data observable changes', async () => {
139
+ const injector = new Injector()
140
+ const rootElement = document.getElementById('root') as HTMLDivElement
141
+ const service = new CollectionService<TestEntry>()
142
+
143
+ initializeShadeRoot({
144
+ injector,
145
+ rootElement,
146
+ jsxElement: (
147
+ <table>
148
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
149
+ </table>
150
+ ),
151
+ })
152
+
153
+ await sleepAsync(50)
154
+
155
+ let body = document.querySelector('tbody[is="shade-data-grid-body"]')
156
+ expect(body?.textContent).toContain('- No Data -')
157
+
158
+ service.data.setValue({
159
+ count: 1,
160
+ entries: [{ id: 1, name: 'New Entry' }],
161
+ })
162
+
163
+ await sleepAsync(50)
164
+
165
+ body = document.querySelector('tbody[is="shade-data-grid-body"]')
166
+ const rows = body?.querySelectorAll('shades-data-grid-row')
167
+ expect(rows?.length).toBe(1)
168
+
169
+ service[Symbol.dispose]()
170
+ })
171
+
172
+ it('should call onRowClick callback when row is clicked', async () => {
173
+ const injector = new Injector()
174
+ const rootElement = document.getElementById('root') as HTMLDivElement
175
+ const service = new CollectionService<TestEntry>()
176
+ const onRowClick = vi.fn()
177
+
178
+ const entry = { id: 1, name: 'Clickable' }
179
+ service.data.setValue({
180
+ count: 1,
181
+ entries: [entry],
182
+ })
183
+
184
+ initializeShadeRoot({
185
+ injector,
186
+ rootElement,
187
+ jsxElement: (
188
+ <table>
189
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} onRowClick={onRowClick} />
190
+ </table>
191
+ ),
192
+ })
193
+
194
+ await sleepAsync(50)
195
+
196
+ const cell = document.querySelector('td') as HTMLTableCellElement
197
+ cell.click()
198
+
199
+ expect(onRowClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
200
+
201
+ service[Symbol.dispose]()
202
+ })
203
+
204
+ it('should call onRowDoubleClick callback when row is double-clicked', async () => {
205
+ const injector = new Injector()
206
+ const rootElement = document.getElementById('root') as HTMLDivElement
207
+ const service = new CollectionService<TestEntry>()
208
+ const onRowDoubleClick = vi.fn()
209
+
210
+ const entry = { id: 1, name: 'DoubleClickable' }
211
+ service.data.setValue({
212
+ count: 1,
213
+ entries: [entry],
214
+ })
215
+
216
+ initializeShadeRoot({
217
+ injector,
218
+ rootElement,
219
+ jsxElement: (
220
+ <table>
221
+ <DataGridBody<TestEntry, 'id' | 'name'>
222
+ service={service}
223
+ columns={['id', 'name']}
224
+ onRowDoubleClick={onRowDoubleClick}
225
+ />
226
+ </table>
227
+ ),
228
+ })
229
+
230
+ await sleepAsync(50)
231
+
232
+ const cell = document.querySelector('td') as HTMLTableCellElement
233
+ const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
234
+ cell.dispatchEvent(dblClickEvent)
235
+
236
+ expect(onRowDoubleClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
237
+
238
+ service[Symbol.dispose]()
239
+ })
240
+
241
+ it('should use custom row components when provided', async () => {
242
+ const injector = new Injector()
243
+ const rootElement = document.getElementById('root') as HTMLDivElement
244
+ const service = new CollectionService<TestEntry>()
245
+
246
+ service.data.setValue({
247
+ count: 1,
248
+ entries: [{ id: 1, name: 'Custom' }],
249
+ })
250
+
251
+ initializeShadeRoot({
252
+ injector,
253
+ rootElement,
254
+ jsxElement: (
255
+ <table>
256
+ <DataGridBody<TestEntry, 'id' | 'name'>
257
+ service={service}
258
+ columns={['id', 'name']}
259
+ rowComponents={{
260
+ id: (entry) => <span data-testid="custom-id">ID: {entry.id}</span>,
261
+ name: (entry) => <strong data-testid="custom-name">{entry.name}</strong>,
262
+ }}
263
+ />
264
+ </table>
265
+ ),
266
+ })
267
+
268
+ await sleepAsync(50)
269
+
270
+ const customId = document.querySelector('[data-testid="custom-id"]')
271
+ const customName = document.querySelector('[data-testid="custom-name"]')
272
+
273
+ expect(customId?.textContent).toContain('ID: 1')
274
+ expect(customName?.textContent).toBe('Custom')
275
+
276
+ service[Symbol.dispose]()
277
+ })
278
+
279
+ it('should use default row component when column-specific one is not provided', async () => {
280
+ const injector = new Injector()
281
+ const rootElement = document.getElementById('root') as HTMLDivElement
282
+ const service = new CollectionService<TestEntry>()
283
+
284
+ service.data.setValue({
285
+ count: 1,
286
+ entries: [{ id: 1, name: 'Default' }],
287
+ })
288
+
289
+ initializeShadeRoot({
290
+ injector,
291
+ rootElement,
292
+ jsxElement: (
293
+ <table>
294
+ <DataGridBody<TestEntry, 'id' | 'name'>
295
+ service={service}
296
+ columns={['id', 'name']}
297
+ rowComponents={{
298
+ default: (entry) => <em data-testid="default-cell">{JSON.stringify(entry)}</em>,
299
+ }}
300
+ />
301
+ </table>
302
+ ),
303
+ })
304
+
305
+ await sleepAsync(50)
306
+
307
+ const defaultCells = document.querySelectorAll('[data-testid="default-cell"]')
308
+ expect(defaultCells.length).toBe(2)
309
+
310
+ service[Symbol.dispose]()
311
+ })
312
+
313
+ it('should render with empty entries array', async () => {
314
+ const injector = new Injector()
315
+ const rootElement = document.getElementById('root') as HTMLDivElement
316
+ const service = new CollectionService<TestEntry>()
317
+
318
+ service.data.setValue({
319
+ count: 0,
320
+ entries: [],
321
+ })
322
+
323
+ initializeShadeRoot({
324
+ injector,
325
+ rootElement,
326
+ jsxElement: (
327
+ <table>
328
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
329
+ </table>
330
+ ),
331
+ })
332
+
333
+ await sleepAsync(50)
334
+
335
+ const body = document.querySelector('tbody[is="shade-data-grid-body"]')
336
+ expect(body?.textContent).toContain('- No Data -')
337
+
338
+ service[Symbol.dispose]()
339
+ })
340
+ })
@@ -26,7 +26,7 @@ export const DataGridBody: <T, Column extends string>(
26
26
  shadowDomName: 'shade-data-grid-body',
27
27
  elementBase: HTMLTableSectionElement,
28
28
  elementBaseName: 'tbody',
29
- style: {
29
+ css: {
30
30
  display: 'table-row-group',
31
31
  },
32
32
  render: ({ props, useObservable }) => {