@furystack/shades-common-components 12.4.0 → 12.5.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 (263) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/esm/components/app-bar-link.spec.js +16 -19
  3. package/esm/components/app-bar-link.spec.js.map +1 -1
  4. package/esm/components/app-bar.spec.js +6 -4
  5. package/esm/components/app-bar.spec.js.map +1 -1
  6. package/esm/components/avatar.spec.js +9 -9
  7. package/esm/components/avatar.spec.js.map +1 -1
  8. package/esm/components/breadcrumb.spec.js +2 -2
  9. package/esm/components/breadcrumb.spec.js.map +1 -1
  10. package/esm/components/button-group.d.ts +32 -0
  11. package/esm/components/button-group.d.ts.map +1 -1
  12. package/esm/components/button-group.js +26 -2
  13. package/esm/components/button-group.js.map +1 -1
  14. package/esm/components/button-group.spec.js +127 -11
  15. package/esm/components/button-group.spec.js.map +1 -1
  16. package/esm/components/button.spec.js +4 -4
  17. package/esm/components/button.spec.js.map +1 -1
  18. package/esm/components/cache-view.spec.js +2 -3
  19. package/esm/components/cache-view.spec.js.map +1 -1
  20. package/esm/components/carousel.spec.js +47 -47
  21. package/esm/components/carousel.spec.js.map +1 -1
  22. package/esm/components/circular-progress.spec.js +2 -2
  23. package/esm/components/command-palette/command-palette-input.spec.js +23 -19
  24. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  25. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +27 -27
  26. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  27. package/esm/components/command-palette/index.spec.js +64 -51
  28. package/esm/components/command-palette/index.spec.js.map +1 -1
  29. package/esm/components/context-menu/context-menu.spec.js +33 -33
  30. package/esm/components/context-menu/context-menu.spec.js.map +1 -1
  31. package/esm/components/data-grid/body.spec.js +13 -13
  32. package/esm/components/data-grid/body.spec.js.map +1 -1
  33. package/esm/components/data-grid/data-grid-row.spec.js +8 -8
  34. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -1
  35. package/esm/components/data-grid/data-grid.d.ts +40 -2
  36. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  37. package/esm/components/data-grid/data-grid.js +7 -10
  38. package/esm/components/data-grid/data-grid.js.map +1 -1
  39. package/esm/components/data-grid/data-grid.spec.js +71 -28
  40. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  41. package/esm/components/data-grid/filters/boolean-filter.d.ts +12 -0
  42. package/esm/components/data-grid/filters/boolean-filter.d.ts.map +1 -0
  43. package/esm/components/data-grid/filters/boolean-filter.js +27 -0
  44. package/esm/components/data-grid/filters/boolean-filter.js.map +1 -0
  45. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts +2 -0
  46. package/esm/components/data-grid/filters/boolean-filter.spec.d.ts.map +1 -0
  47. package/esm/components/data-grid/filters/boolean-filter.spec.js +114 -0
  48. package/esm/components/data-grid/filters/boolean-filter.spec.js.map +1 -0
  49. package/esm/components/data-grid/filters/date-filter.d.ts +12 -0
  50. package/esm/components/data-grid/filters/date-filter.d.ts.map +1 -0
  51. package/esm/components/data-grid/filters/date-filter.js +109 -0
  52. package/esm/components/data-grid/filters/date-filter.js.map +1 -0
  53. package/esm/components/data-grid/filters/date-filter.spec.d.ts +2 -0
  54. package/esm/components/data-grid/filters/date-filter.spec.d.ts.map +1 -0
  55. package/esm/components/data-grid/filters/date-filter.spec.js +145 -0
  56. package/esm/components/data-grid/filters/date-filter.spec.js.map +1 -0
  57. package/esm/components/data-grid/filters/enum-filter.d.ts +16 -0
  58. package/esm/components/data-grid/filters/enum-filter.d.ts.map +1 -0
  59. package/esm/components/data-grid/filters/enum-filter.js +72 -0
  60. package/esm/components/data-grid/filters/enum-filter.js.map +1 -0
  61. package/esm/components/data-grid/filters/enum-filter.spec.d.ts +2 -0
  62. package/esm/components/data-grid/filters/enum-filter.spec.d.ts.map +1 -0
  63. package/esm/components/data-grid/filters/enum-filter.spec.js +136 -0
  64. package/esm/components/data-grid/filters/enum-filter.spec.js.map +1 -0
  65. package/esm/components/data-grid/filters/filter-dropdown.d.ts +6 -0
  66. package/esm/components/data-grid/filters/filter-dropdown.d.ts.map +1 -0
  67. package/esm/components/data-grid/filters/filter-dropdown.js +41 -0
  68. package/esm/components/data-grid/filters/filter-dropdown.js.map +1 -0
  69. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts +2 -0
  70. package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts.map +1 -0
  71. package/esm/components/data-grid/filters/filter-dropdown.spec.js +69 -0
  72. package/esm/components/data-grid/filters/filter-dropdown.spec.js.map +1 -0
  73. package/esm/components/data-grid/filters/filter-styles.d.ts +24 -0
  74. package/esm/components/data-grid/filters/filter-styles.d.ts.map +1 -0
  75. package/esm/components/data-grid/filters/filter-styles.js +25 -0
  76. package/esm/components/data-grid/filters/filter-styles.js.map +1 -0
  77. package/esm/components/data-grid/filters/index.d.ts +7 -0
  78. package/esm/components/data-grid/filters/index.d.ts.map +1 -0
  79. package/esm/components/data-grid/filters/index.js +7 -0
  80. package/esm/components/data-grid/filters/index.js.map +1 -0
  81. package/esm/components/data-grid/filters/number-filter.d.ts +12 -0
  82. package/esm/components/data-grid/filters/number-filter.d.ts.map +1 -0
  83. package/esm/components/data-grid/filters/number-filter.js +65 -0
  84. package/esm/components/data-grid/filters/number-filter.js.map +1 -0
  85. package/esm/components/data-grid/filters/number-filter.spec.d.ts +2 -0
  86. package/esm/components/data-grid/filters/number-filter.spec.d.ts.map +1 -0
  87. package/esm/components/data-grid/filters/number-filter.spec.js +142 -0
  88. package/esm/components/data-grid/filters/number-filter.spec.js.map +1 -0
  89. package/esm/components/data-grid/filters/string-filter.d.ts +12 -0
  90. package/esm/components/data-grid/filters/string-filter.d.ts.map +1 -0
  91. package/esm/components/data-grid/filters/string-filter.js +63 -0
  92. package/esm/components/data-grid/filters/string-filter.js.map +1 -0
  93. package/esm/components/data-grid/filters/string-filter.spec.d.ts +2 -0
  94. package/esm/components/data-grid/filters/string-filter.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/filters/string-filter.spec.js +128 -0
  96. package/esm/components/data-grid/filters/string-filter.spec.js.map +1 -0
  97. package/esm/components/data-grid/footer.d.ts.map +1 -1
  98. package/esm/components/data-grid/footer.js +24 -9
  99. package/esm/components/data-grid/footer.js.map +1 -1
  100. package/esm/components/data-grid/footer.spec.js +38 -36
  101. package/esm/components/data-grid/footer.spec.js.map +1 -1
  102. package/esm/components/data-grid/header.d.ts +6 -9
  103. package/esm/components/data-grid/header.d.ts.map +1 -1
  104. package/esm/components/data-grid/header.js +51 -117
  105. package/esm/components/data-grid/header.js.map +1 -1
  106. package/esm/components/data-grid/header.spec.js +116 -187
  107. package/esm/components/data-grid/header.spec.js.map +1 -1
  108. package/esm/components/data-grid/index.d.ts +1 -0
  109. package/esm/components/data-grid/index.d.ts.map +1 -1
  110. package/esm/components/data-grid/index.js +1 -0
  111. package/esm/components/data-grid/index.js.map +1 -1
  112. package/esm/components/data-grid/selection-cell.spec.js +8 -8
  113. package/esm/components/data-grid/selection-cell.spec.js.map +1 -1
  114. package/esm/components/drawer/drawer-toggle-button.spec.js +22 -22
  115. package/esm/components/drawer/drawer-toggle-button.spec.js.map +1 -1
  116. package/esm/components/drawer/index.spec.js +36 -36
  117. package/esm/components/drawer/index.spec.js.map +1 -1
  118. package/esm/components/dropdown.spec.js +38 -30
  119. package/esm/components/dropdown.spec.js.map +1 -1
  120. package/esm/components/fab.spec.js +4 -4
  121. package/esm/components/fab.spec.js.map +1 -1
  122. package/esm/components/form.spec.js +37 -37
  123. package/esm/components/form.spec.js.map +1 -1
  124. package/esm/components/grid.spec.js +3 -3
  125. package/esm/components/grid.spec.js.map +1 -1
  126. package/esm/components/image.spec.js +55 -52
  127. package/esm/components/image.spec.js.map +1 -1
  128. package/esm/components/inputs/autocomplete.spec.js +7 -14
  129. package/esm/components/inputs/autocomplete.spec.js.map +1 -1
  130. package/esm/components/inputs/checkbox.spec.js +22 -22
  131. package/esm/components/inputs/checkbox.spec.js.map +1 -1
  132. package/esm/components/inputs/input-number.spec.js +47 -47
  133. package/esm/components/inputs/input-number.spec.js.map +1 -1
  134. package/esm/components/inputs/input.spec.js +53 -53
  135. package/esm/components/inputs/input.spec.js.map +1 -1
  136. package/esm/components/inputs/radio-group.spec.js +14 -14
  137. package/esm/components/inputs/radio-group.spec.js.map +1 -1
  138. package/esm/components/inputs/radio.spec.js +16 -16
  139. package/esm/components/inputs/radio.spec.js.map +1 -1
  140. package/esm/components/inputs/select.spec.js +74 -74
  141. package/esm/components/inputs/select.spec.js.map +1 -1
  142. package/esm/components/inputs/slider.spec.js +16 -16
  143. package/esm/components/inputs/slider.spec.js.map +1 -1
  144. package/esm/components/inputs/switch.spec.js +24 -24
  145. package/esm/components/inputs/switch.spec.js.map +1 -1
  146. package/esm/components/inputs/text-area.spec.js +17 -17
  147. package/esm/components/inputs/text-area.spec.js.map +1 -1
  148. package/esm/components/linear-progress.spec.js +2 -2
  149. package/esm/components/list/list.spec.js +36 -36
  150. package/esm/components/list/list.spec.js.map +1 -1
  151. package/esm/components/markdown/markdown-display.spec.js +15 -15
  152. package/esm/components/markdown/markdown-display.spec.js.map +1 -1
  153. package/esm/components/markdown/markdown-editor.spec.js +8 -8
  154. package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
  155. package/esm/components/markdown/markdown-input.spec.js +17 -17
  156. package/esm/components/markdown/markdown-input.spec.js.map +1 -1
  157. package/esm/components/menu/menu.spec.js +28 -28
  158. package/esm/components/menu/menu.spec.js.map +1 -1
  159. package/esm/components/modal.spec.js +15 -18
  160. package/esm/components/modal.spec.js.map +1 -1
  161. package/esm/components/noty-list.spec.js +25 -23
  162. package/esm/components/noty-list.spec.js.map +1 -1
  163. package/esm/components/page-container/index.spec.js +16 -16
  164. package/esm/components/page-container/index.spec.js.map +1 -1
  165. package/esm/components/page-container/page-header.spec.js +16 -16
  166. package/esm/components/page-container/page-header.spec.js.map +1 -1
  167. package/esm/components/page-layout/index.spec.js +29 -29
  168. package/esm/components/page-layout/index.spec.js.map +1 -1
  169. package/esm/components/paper.spec.js +3 -3
  170. package/esm/components/paper.spec.js.map +1 -1
  171. package/esm/components/rating.spec.js +61 -61
  172. package/esm/components/rating.spec.js.map +1 -1
  173. package/esm/components/skeleton.spec.js +10 -6
  174. package/esm/components/skeleton.spec.js.map +1 -1
  175. package/esm/components/suggest/suggest-input.spec.js +4 -10
  176. package/esm/components/suggest/suggest-input.spec.js.map +1 -1
  177. package/esm/components/tabs.spec.js +30 -30
  178. package/esm/components/tabs.spec.js.map +1 -1
  179. package/esm/components/tree/tree.spec.js +27 -27
  180. package/esm/components/tree/tree.spec.js.map +1 -1
  181. package/esm/components/typography.spec.js +3 -3
  182. package/esm/components/typography.spec.js.map +1 -1
  183. package/esm/components/wizard/index.spec.js +5 -5
  184. package/esm/components/wizard/index.spec.js.map +1 -1
  185. package/esm/utils/promisify-animation.d.ts.map +1 -1
  186. package/esm/utils/promisify-animation.js +3 -0
  187. package/esm/utils/promisify-animation.js.map +1 -1
  188. package/package.json +2 -2
  189. package/src/components/app-bar-link.spec.tsx +16 -19
  190. package/src/components/app-bar.spec.tsx +6 -4
  191. package/src/components/avatar.spec.tsx +9 -9
  192. package/src/components/breadcrumb.spec.tsx +2 -2
  193. package/src/components/button-group.spec.tsx +155 -11
  194. package/src/components/button-group.tsx +49 -2
  195. package/src/components/button.spec.tsx +4 -4
  196. package/src/components/cache-view.spec.tsx +3 -3
  197. package/src/components/carousel.spec.tsx +47 -47
  198. package/src/components/circular-progress.spec.tsx +2 -2
  199. package/src/components/command-palette/command-palette-input.spec.tsx +23 -19
  200. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +27 -27
  201. package/src/components/command-palette/index.spec.tsx +64 -51
  202. package/src/components/context-menu/context-menu.spec.tsx +33 -33
  203. package/src/components/data-grid/body.spec.tsx +13 -13
  204. package/src/components/data-grid/data-grid-row.spec.tsx +8 -8
  205. package/src/components/data-grid/data-grid.spec.tsx +106 -28
  206. package/src/components/data-grid/data-grid.tsx +44 -11
  207. package/src/components/data-grid/filters/boolean-filter.spec.tsx +142 -0
  208. package/src/components/data-grid/filters/boolean-filter.tsx +45 -0
  209. package/src/components/data-grid/filters/date-filter.spec.tsx +181 -0
  210. package/src/components/data-grid/filters/date-filter.tsx +162 -0
  211. package/src/components/data-grid/filters/enum-filter.spec.tsx +168 -0
  212. package/src/components/data-grid/filters/enum-filter.tsx +119 -0
  213. package/src/components/data-grid/filters/filter-dropdown.spec.tsx +89 -0
  214. package/src/components/data-grid/filters/filter-dropdown.tsx +60 -0
  215. package/src/components/data-grid/filters/filter-styles.ts +26 -0
  216. package/src/components/data-grid/filters/index.ts +6 -0
  217. package/src/components/data-grid/filters/number-filter.spec.tsx +174 -0
  218. package/src/components/data-grid/filters/number-filter.tsx +115 -0
  219. package/src/components/data-grid/filters/string-filter.spec.tsx +157 -0
  220. package/src/components/data-grid/filters/string-filter.tsx +112 -0
  221. package/src/components/data-grid/footer.spec.tsx +38 -36
  222. package/src/components/data-grid/footer.tsx +21 -8
  223. package/src/components/data-grid/header.spec.tsx +128 -212
  224. package/src/components/data-grid/header.tsx +95 -183
  225. package/src/components/data-grid/index.tsx +1 -0
  226. package/src/components/data-grid/selection-cell.spec.tsx +8 -8
  227. package/src/components/drawer/drawer-toggle-button.spec.tsx +22 -22
  228. package/src/components/drawer/index.spec.tsx +36 -36
  229. package/src/components/dropdown.spec.tsx +38 -30
  230. package/src/components/fab.spec.tsx +4 -4
  231. package/src/components/form.spec.tsx +37 -37
  232. package/src/components/grid.spec.tsx +3 -3
  233. package/src/components/image.spec.tsx +55 -52
  234. package/src/components/inputs/autocomplete.spec.tsx +7 -14
  235. package/src/components/inputs/checkbox.spec.tsx +22 -22
  236. package/src/components/inputs/input-number.spec.tsx +47 -47
  237. package/src/components/inputs/input.spec.tsx +53 -53
  238. package/src/components/inputs/radio-group.spec.tsx +14 -14
  239. package/src/components/inputs/radio.spec.tsx +16 -16
  240. package/src/components/inputs/select.spec.tsx +74 -74
  241. package/src/components/inputs/slider.spec.tsx +16 -16
  242. package/src/components/inputs/switch.spec.tsx +24 -24
  243. package/src/components/inputs/text-area.spec.tsx +17 -17
  244. package/src/components/linear-progress.spec.tsx +2 -2
  245. package/src/components/list/list.spec.tsx +36 -36
  246. package/src/components/markdown/markdown-display.spec.tsx +15 -15
  247. package/src/components/markdown/markdown-editor.spec.tsx +8 -8
  248. package/src/components/markdown/markdown-input.spec.tsx +17 -17
  249. package/src/components/menu/menu.spec.tsx +28 -28
  250. package/src/components/modal.spec.tsx +15 -18
  251. package/src/components/noty-list.spec.tsx +25 -23
  252. package/src/components/page-container/index.spec.tsx +16 -16
  253. package/src/components/page-container/page-header.spec.tsx +16 -16
  254. package/src/components/page-layout/index.spec.tsx +29 -29
  255. package/src/components/paper.spec.tsx +3 -3
  256. package/src/components/rating.spec.tsx +61 -61
  257. package/src/components/skeleton.spec.tsx +10 -6
  258. package/src/components/suggest/suggest-input.spec.tsx +4 -10
  259. package/src/components/tabs.spec.tsx +30 -30
  260. package/src/components/tree/tree.spec.tsx +27 -27
  261. package/src/components/typography.spec.tsx +3 -3
  262. package/src/components/wizard/index.spec.tsx +5 -5
  263. package/src/utils/promisify-animation.ts +3 -0
@@ -1,8 +1,9 @@
1
1
  import type { FindOptions } from '@furystack/core'
2
2
  import { Injector } from '@furystack/inject'
3
- import { createComponent, initializeShadeRoot } from '@furystack/shades'
4
- import { ObservableValue, sleepAsync, usingAsync } from '@furystack/utils'
3
+ import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
4
+ import { ObservableValue, usingAsync } from '@furystack/utils'
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6
+ import type { FilterableFindOptions } from './data-grid.js'
6
7
  import { DataGridHeader, OrderButton } from './header.js'
7
8
 
8
9
  type TestItem = { id: number; name: string; email: string }
@@ -46,6 +47,12 @@ describe('DataGridHeader', () => {
46
47
  return new ObservableValue<FindOptions<TestItem, Array<keyof TestItem>>>(options)
47
48
  }
48
49
 
50
+ const createFilterableFindOptions = (
51
+ options: Partial<FilterableFindOptions> = {},
52
+ ): ObservableValue<FilterableFindOptions> => {
53
+ return new ObservableValue<FilterableFindOptions>(options)
54
+ }
55
+
49
56
  describe('rendering', () => {
50
57
  it('should render with custom element', async () => {
51
58
  await usingAsync(new Injector(), async (injector) => {
@@ -58,7 +65,7 @@ describe('DataGridHeader', () => {
58
65
  jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
59
66
  })
60
67
 
61
- await sleepAsync(50)
68
+ await flushUpdates()
62
69
 
63
70
  const header = document.querySelector('data-grid-header')
64
71
  expect(header).not.toBeNull()
@@ -76,7 +83,7 @@ describe('DataGridHeader', () => {
76
83
  jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
77
84
  })
78
85
 
79
- await sleepAsync(50)
86
+ await flushUpdates()
80
87
 
81
88
  const header = document.querySelector('data-grid-header')
82
89
  const fieldName = header?.querySelector('.header-field-name')
@@ -95,14 +102,14 @@ describe('DataGridHeader', () => {
95
102
  jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
96
103
  })
97
104
 
98
- await sleepAsync(50)
105
+ await flushUpdates()
99
106
 
100
107
  const orderButton = document.querySelector('data-grid-order-button')
101
108
  expect(orderButton).not.toBeNull()
102
109
  })
103
110
  })
104
111
 
105
- it('should render search button', async () => {
112
+ it('should not render filter button when no filterConfig is provided', async () => {
106
113
  await usingAsync(new Injector(), async (injector) => {
107
114
  const rootElement = document.getElementById('root') as HTMLDivElement
108
115
  const findOptions = createFindOptions()
@@ -113,14 +120,14 @@ describe('DataGridHeader', () => {
113
120
  jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
114
121
  })
115
122
 
116
- await sleepAsync(50)
123
+ await flushUpdates()
117
124
 
118
- const searchButton = document.querySelector('data-grid-search-button')
119
- expect(searchButton).not.toBeNull()
125
+ const filterButton = document.querySelector('data-grid-filter-button')
126
+ expect(filterButton).toBeNull()
120
127
  })
121
128
  })
122
129
 
123
- it('should render search form', async () => {
130
+ it('should render filter button when filterConfig is provided', async () => {
124
131
  await usingAsync(new Injector(), async (injector) => {
125
132
  const rootElement = document.getElementById('root') as HTMLDivElement
126
133
  const findOptions = createFindOptions()
@@ -128,13 +135,13 @@ describe('DataGridHeader', () => {
128
135
  initializeShadeRoot({
129
136
  injector,
130
137
  rootElement,
131
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
138
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
132
139
  })
133
140
 
134
- await sleepAsync(50)
141
+ await flushUpdates()
135
142
 
136
- const searchForm = document.querySelector('data-grid-search-form')
137
- expect(searchForm).not.toBeNull()
143
+ const filterButton = document.querySelector('data-grid-filter-button')
144
+ expect(filterButton).not.toBeNull()
138
145
  })
139
146
  })
140
147
  })
@@ -143,43 +150,7 @@ describe('DataGridHeader', () => {
143
150
  it('should show neutral icon when no order is set', async () => {
144
151
  await usingAsync(new Injector(), async (injector) => {
145
152
  const rootElement = document.getElementById('root') as HTMLDivElement
146
- const findOptions = createFindOptions()
147
-
148
- initializeShadeRoot({
149
- injector,
150
- rootElement,
151
- jsxElement: <OrderButton field="name" findOptions={findOptions} />,
152
- })
153
-
154
- await sleepAsync(50)
155
-
156
- const button = document.querySelector('data-grid-order-button')
157
- expect(button?.querySelector('shade-icon')).not.toBeNull()
158
- })
159
- })
160
-
161
- it('should show descending icon when ASC order is set for field', async () => {
162
- await usingAsync(new Injector(), async (injector) => {
163
- const rootElement = document.getElementById('root') as HTMLDivElement
164
- const findOptions = createFindOptions({ order: { name: 'ASC' } })
165
-
166
- initializeShadeRoot({
167
- injector,
168
- rootElement,
169
- jsxElement: <OrderButton field="name" findOptions={findOptions} />,
170
- })
171
-
172
- await sleepAsync(50)
173
-
174
- const button = document.querySelector('data-grid-order-button')
175
- expect(button?.querySelector('shade-icon')).not.toBeNull()
176
- })
177
- })
178
-
179
- it('should show ascending icon when DESC order is set for field', async () => {
180
- await usingAsync(new Injector(), async (injector) => {
181
- const rootElement = document.getElementById('root') as HTMLDivElement
182
- const findOptions = createFindOptions({ order: { name: 'DESC' } })
153
+ const findOptions = createFilterableFindOptions()
183
154
 
184
155
  initializeShadeRoot({
185
156
  injector,
@@ -187,25 +158,7 @@ describe('DataGridHeader', () => {
187
158
  jsxElement: <OrderButton field="name" findOptions={findOptions} />,
188
159
  })
189
160
 
190
- await sleepAsync(50)
191
-
192
- const button = document.querySelector('data-grid-order-button')
193
- expect(button?.querySelector('shade-icon')).not.toBeNull()
194
- })
195
- })
196
-
197
- it('should show neutral icon when order is set for different field', async () => {
198
- await usingAsync(new Injector(), async (injector) => {
199
- const rootElement = document.getElementById('root') as HTMLDivElement
200
- const findOptions = createFindOptions({ order: { id: 'ASC' } })
201
-
202
- initializeShadeRoot({
203
- injector,
204
- rootElement,
205
- jsxElement: <OrderButton field="name" findOptions={findOptions} />,
206
- })
207
-
208
- await sleepAsync(50)
161
+ await flushUpdates()
209
162
 
210
163
  const button = document.querySelector('data-grid-order-button')
211
164
  expect(button?.querySelector('shade-icon')).not.toBeNull()
@@ -215,7 +168,7 @@ describe('DataGridHeader', () => {
215
168
  it('should toggle order to ASC when clicking on unsorted field', async () => {
216
169
  await usingAsync(new Injector(), async (injector) => {
217
170
  const rootElement = document.getElementById('root') as HTMLDivElement
218
- const findOptions = createFindOptions()
171
+ const findOptions = createFilterableFindOptions()
219
172
 
220
173
  initializeShadeRoot({
221
174
  injector,
@@ -223,12 +176,12 @@ describe('DataGridHeader', () => {
223
176
  jsxElement: <OrderButton field="name" findOptions={findOptions} />,
224
177
  })
225
178
 
226
- await sleepAsync(50)
179
+ await flushUpdates()
227
180
 
228
181
  const button = document.querySelector('data-grid-order-button')?.querySelector('button')
229
182
  button?.click()
230
183
 
231
- await sleepAsync(50)
184
+ await flushUpdates()
232
185
 
233
186
  const updatedOptions = findOptions.getValue()
234
187
  expect(updatedOptions.order).toEqual({ name: 'ASC' })
@@ -238,7 +191,7 @@ describe('DataGridHeader', () => {
238
191
  it('should toggle order from ASC to DESC when clicking', async () => {
239
192
  await usingAsync(new Injector(), async (injector) => {
240
193
  const rootElement = document.getElementById('root') as HTMLDivElement
241
- const findOptions = createFindOptions({ order: { name: 'ASC' } })
194
+ const findOptions = createFilterableFindOptions({ order: { name: 'ASC' } })
242
195
 
243
196
  initializeShadeRoot({
244
197
  injector,
@@ -246,12 +199,12 @@ describe('DataGridHeader', () => {
246
199
  jsxElement: <OrderButton field="name" findOptions={findOptions} />,
247
200
  })
248
201
 
249
- await sleepAsync(50)
202
+ await flushUpdates()
250
203
 
251
204
  const button = document.querySelector('data-grid-order-button')?.querySelector('button')
252
205
  button?.click()
253
206
 
254
- await sleepAsync(50)
207
+ await flushUpdates()
255
208
 
256
209
  const updatedOptions = findOptions.getValue()
257
210
  expect(updatedOptions.order).toEqual({ name: 'DESC' })
@@ -261,7 +214,7 @@ describe('DataGridHeader', () => {
261
214
  it('should toggle order from DESC to ASC when clicking', async () => {
262
215
  await usingAsync(new Injector(), async (injector) => {
263
216
  const rootElement = document.getElementById('root') as HTMLDivElement
264
- const findOptions = createFindOptions({ order: { name: 'DESC' } })
217
+ const findOptions = createFilterableFindOptions({ order: { name: 'DESC' } })
265
218
 
266
219
  initializeShadeRoot({
267
220
  injector,
@@ -269,12 +222,12 @@ describe('DataGridHeader', () => {
269
222
  jsxElement: <OrderButton field="name" findOptions={findOptions} />,
270
223
  })
271
224
 
272
- await sleepAsync(50)
225
+ await flushUpdates()
273
226
 
274
227
  const button = document.querySelector('data-grid-order-button')?.querySelector('button')
275
228
  button?.click()
276
229
 
277
- await sleepAsync(50)
230
+ await flushUpdates()
278
231
 
279
232
  const updatedOptions = findOptions.getValue()
280
233
  expect(updatedOptions.order).toEqual({ name: 'ASC' })
@@ -284,7 +237,7 @@ describe('DataGridHeader', () => {
284
237
  it('should react to external findOptions changes', async () => {
285
238
  await usingAsync(new Injector(), async (injector) => {
286
239
  const rootElement = document.getElementById('root') as HTMLDivElement
287
- const findOptions = createFindOptions()
240
+ const findOptions = createFilterableFindOptions()
288
241
 
289
242
  initializeShadeRoot({
290
243
  injector,
@@ -292,13 +245,13 @@ describe('DataGridHeader', () => {
292
245
  jsxElement: <OrderButton field="name" findOptions={findOptions} />,
293
246
  })
294
247
 
295
- await sleepAsync(50)
248
+ await flushUpdates()
296
249
 
297
250
  let button = document.querySelector('data-grid-order-button')
298
251
  expect(button?.querySelector('shade-icon')).not.toBeNull()
299
252
 
300
253
  findOptions.setValue({ order: { name: 'ASC' } })
301
- await sleepAsync(50)
254
+ await flushUpdates()
302
255
 
303
256
  button = document.querySelector('data-grid-order-button')
304
257
  expect(button?.querySelector('shade-icon')).not.toBeNull()
@@ -306,8 +259,8 @@ describe('DataGridHeader', () => {
306
259
  })
307
260
  })
308
261
 
309
- describe('SearchButton', () => {
310
- it('should show inactive icon when no filter is set', async () => {
262
+ describe('FilterButton', () => {
263
+ it('should show inactive state when no filter is set', async () => {
311
264
  await usingAsync(new Injector(), async (injector) => {
312
265
  const rootElement = document.getElementById('root') as HTMLDivElement
313
266
  const findOptions = createFindOptions()
@@ -315,17 +268,17 @@ describe('DataGridHeader', () => {
315
268
  initializeShadeRoot({
316
269
  injector,
317
270
  rootElement,
318
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
271
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
319
272
  })
320
273
 
321
- await sleepAsync(50)
274
+ await flushUpdates()
322
275
 
323
- const searchButton = document.querySelector('data-grid-search-button')
324
- expect(searchButton?.querySelector('shade-icon')).not.toBeNull()
276
+ const filterButton = document.querySelector('data-grid-filter-button')
277
+ expect(filterButton?.querySelector('shade-icon')).not.toBeNull()
325
278
  })
326
279
  })
327
280
 
328
- it('should show active icon when filter is set for field', async () => {
281
+ it('should show active state when filter is set for field', async () => {
329
282
  await usingAsync(new Injector(), async (injector) => {
330
283
  const rootElement = document.getElementById('root') as HTMLDivElement
331
284
  const findOptions = createFindOptions({ filter: { name: { $regex: 'test' } } })
@@ -333,37 +286,42 @@ describe('DataGridHeader', () => {
333
286
  initializeShadeRoot({
334
287
  injector,
335
288
  rootElement,
336
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
289
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
337
290
  })
338
291
 
339
- await sleepAsync(50)
292
+ await flushUpdates()
340
293
 
341
- const searchButton = document.querySelector('data-grid-search-button')
342
- expect(searchButton?.querySelector('shade-icon')).not.toBeNull()
294
+ const filterButton = document.querySelector('data-grid-filter-button')
295
+ expect(filterButton?.querySelector('shade-icon')).not.toBeNull()
343
296
  })
344
297
  })
345
298
 
346
- it('should show inactive icon when filter is set for different field', async () => {
299
+ it('should transition from active to inactive when filter is externally cleared', async () => {
347
300
  await usingAsync(new Injector(), async (injector) => {
348
301
  const rootElement = document.getElementById('root') as HTMLDivElement
349
- const findOptions = createFindOptions({ filter: { email: { $regex: 'test' } } })
302
+ const findOptions = createFindOptions({ filter: { name: { $regex: 'test' } } })
350
303
 
351
304
  initializeShadeRoot({
352
305
  injector,
353
306
  rootElement,
354
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
307
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
355
308
  })
356
309
 
357
- await sleepAsync(50)
310
+ // Parent renders, then child FilterButton renders
311
+ await flushUpdates()
358
312
 
359
- const searchButton = document.querySelector('data-grid-search-button')
360
- expect(searchButton?.querySelector('shade-icon')).not.toBeNull()
313
+ const filterButton = document.querySelector('data-grid-filter-button button')
314
+ expect(filterButton?.hasAttribute('data-selected')).toBe(true)
315
+
316
+ findOptions.setValue({ filter: {} })
317
+ await flushUpdates()
318
+
319
+ const updatedButton = document.querySelector('data-grid-filter-button button')
320
+ expect(updatedButton?.hasAttribute('data-selected')).toBe(false)
361
321
  })
362
322
  })
363
- })
364
323
 
365
- describe('SearchForm', () => {
366
- it('should expand search form when search button is clicked', async () => {
324
+ it('should open filter dropdown when clicked', async () => {
367
325
  await usingAsync(new Injector(), async (injector) => {
368
326
  const rootElement = document.getElementById('root') as HTMLDivElement
369
327
  const findOptions = createFindOptions()
@@ -371,22 +329,24 @@ describe('DataGridHeader', () => {
371
329
  initializeShadeRoot({
372
330
  injector,
373
331
  rootElement,
374
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
332
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
375
333
  })
376
334
 
377
- await sleepAsync(50)
335
+ await flushUpdates()
378
336
 
379
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
380
- searchButton?.click()
337
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
338
+ filterButton?.click()
381
339
 
382
- await sleepAsync(150)
340
+ await flushUpdates()
383
341
 
384
- const searchForm = document.querySelector('.search-form') as HTMLElement
385
- expect(searchForm.style.display).toBe('flex')
342
+ const dropdown = document.querySelector('data-grid-filter-dropdown')
343
+ expect(dropdown).not.toBeNull()
386
344
  })
387
345
  })
346
+ })
388
347
 
389
- it('should update findOptions when search is submitted', async () => {
348
+ describe('filter type routing', () => {
349
+ it('should render StringFilter for string filterConfig', async () => {
390
350
  await usingAsync(new Injector(), async (injector) => {
391
351
  const rootElement = document.getElementById('root') as HTMLDivElement
392
352
  const findOptions = createFindOptions()
@@ -394,152 +354,116 @@ describe('DataGridHeader', () => {
394
354
  initializeShadeRoot({
395
355
  injector,
396
356
  rootElement,
397
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
357
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
398
358
  })
399
359
 
400
- await sleepAsync(50)
401
-
402
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
403
- searchButton?.click()
404
-
405
- await sleepAsync(150)
406
-
407
- const input = document.querySelector('.search-form input') as HTMLInputElement
408
- input.value = 'test-search'
409
- input.dispatchEvent(new Event('input', { bubbles: true }))
360
+ await flushUpdates()
410
361
 
411
- const form = document.querySelector('.search-form') as HTMLFormElement
412
- form.dispatchEvent(new Event('submit', { bubbles: true }))
362
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
363
+ filterButton?.click()
413
364
 
414
- await sleepAsync(50)
365
+ await flushUpdates()
415
366
 
416
- const updatedOptions = findOptions.getValue()
417
- expect(updatedOptions.filter).toEqual({ name: { $regex: 'test-search' } })
367
+ const stringFilter = document.querySelector('data-grid-string-filter')
368
+ expect(stringFilter).not.toBeNull()
418
369
  })
419
370
  })
420
371
 
421
- it('should clear filter when clear button is clicked', async () => {
372
+ it('should render NumberFilter for number filterConfig', async () => {
422
373
  await usingAsync(new Injector(), async (injector) => {
423
374
  const rootElement = document.getElementById('root') as HTMLDivElement
424
- const findOptions = createFindOptions({ filter: { name: { $regex: 'existing' } } })
375
+ const findOptions = createFindOptions()
425
376
 
426
377
  initializeShadeRoot({
427
378
  injector,
428
379
  rootElement,
429
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
380
+ jsxElement: <DataGridHeader field="id" findOptions={findOptions} filterConfig={{ type: 'number' }} />,
430
381
  })
431
382
 
432
- await sleepAsync(50)
433
-
434
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
435
- searchButton?.click()
436
-
437
- await sleepAsync(150)
383
+ await flushUpdates()
438
384
 
439
- const clearButton = document.querySelector('.search-form button[type="reset"]') as HTMLButtonElement
440
- clearButton?.click()
385
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
386
+ filterButton?.click()
441
387
 
442
- await sleepAsync(50)
388
+ await flushUpdates()
443
389
 
444
- const updatedOptions = findOptions.getValue()
445
- expect(updatedOptions.filter?.name).toBeUndefined()
390
+ const numberFilter = document.querySelector('data-grid-number-filter')
391
+ expect(numberFilter).not.toBeNull()
446
392
  })
447
393
  })
448
394
 
449
- it('should preserve filters for other fields when submitting search', async () => {
395
+ it('should render BooleanFilter for boolean filterConfig', async () => {
450
396
  await usingAsync(new Injector(), async (injector) => {
451
397
  const rootElement = document.getElementById('root') as HTMLDivElement
452
- const findOptions = createFindOptions({ filter: { email: { $regex: 'existing' } } })
398
+ const findOptions = createFindOptions()
453
399
 
454
400
  initializeShadeRoot({
455
401
  injector,
456
402
  rootElement,
457
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
403
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'boolean' }} />,
458
404
  })
459
405
 
460
- await sleepAsync(50)
461
-
462
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
463
- searchButton?.click()
464
-
465
- await sleepAsync(150)
466
-
467
- const input = document.querySelector('.search-form input') as HTMLInputElement
468
- input.value = 'new-search'
469
- input.dispatchEvent(new Event('input', { bubbles: true }))
406
+ await flushUpdates()
470
407
 
471
- const form = document.querySelector('.search-form') as HTMLFormElement
472
- form.dispatchEvent(new Event('submit', { bubbles: true }))
408
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
409
+ filterButton?.click()
473
410
 
474
- await sleepAsync(50)
411
+ await flushUpdates()
475
412
 
476
- const updatedOptions = findOptions.getValue()
477
- expect(updatedOptions.filter).toEqual({
478
- email: { $regex: 'existing' },
479
- name: { $regex: 'new-search' },
480
- })
413
+ const booleanFilter = document.querySelector('data-grid-boolean-filter')
414
+ expect(booleanFilter).not.toBeNull()
481
415
  })
482
416
  })
483
417
 
484
- it('should preserve other findOptions properties when updating filter', async () => {
418
+ it('should render EnumFilter for enum filterConfig', async () => {
485
419
  await usingAsync(new Injector(), async (injector) => {
486
420
  const rootElement = document.getElementById('root') as HTMLDivElement
487
- const findOptions = createFindOptions({
488
- order: { id: 'ASC' },
489
- top: 10,
490
- skip: 20,
491
- })
421
+ const findOptions = createFindOptions()
492
422
 
493
423
  initializeShadeRoot({
494
424
  injector,
495
425
  rootElement,
496
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
426
+ jsxElement: (
427
+ <DataGridHeader
428
+ field="name"
429
+ findOptions={findOptions}
430
+ filterConfig={{ type: 'enum', values: [{ label: 'A', value: 'a' }] }}
431
+ />
432
+ ),
497
433
  })
498
434
 
499
- await sleepAsync(50)
435
+ await flushUpdates()
500
436
 
501
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
502
- searchButton?.click()
437
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
438
+ filterButton?.click()
503
439
 
504
- await sleepAsync(150)
440
+ await flushUpdates()
505
441
 
506
- const input = document.querySelector('.search-form input') as HTMLInputElement
507
- input.value = 'search-value'
508
- input.dispatchEvent(new Event('input', { bubbles: true }))
509
-
510
- const form = document.querySelector('.search-form') as HTMLFormElement
511
- form.dispatchEvent(new Event('submit', { bubbles: true }))
512
-
513
- await sleepAsync(50)
514
-
515
- const updatedOptions = findOptions.getValue()
516
- expect(updatedOptions.order).toEqual({ id: 'ASC' })
517
- expect(updatedOptions.top).toBe(10)
518
- expect(updatedOptions.skip).toBe(20)
519
- expect(updatedOptions.filter).toEqual({ name: { $regex: 'search-value' } })
442
+ const enumFilter = document.querySelector('data-grid-enum-filter')
443
+ expect(enumFilter).not.toBeNull()
520
444
  })
521
445
  })
522
446
 
523
- it('should show current filter value in search input', async () => {
447
+ it('should render DateFilter for date filterConfig', async () => {
524
448
  await usingAsync(new Injector(), async (injector) => {
525
449
  const rootElement = document.getElementById('root') as HTMLDivElement
526
- const findOptions = createFindOptions({ filter: { name: { $regex: 'current-filter' } } })
450
+ const findOptions = createFindOptions()
527
451
 
528
452
  initializeShadeRoot({
529
453
  injector,
530
454
  rootElement,
531
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
455
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'date' }} />,
532
456
  })
533
457
 
534
- await sleepAsync(50)
458
+ await flushUpdates()
535
459
 
536
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
537
- searchButton?.click()
460
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
461
+ filterButton?.click()
538
462
 
539
- await sleepAsync(150)
463
+ await flushUpdates()
540
464
 
541
- const input = document.querySelector('.search-form input') as HTMLInputElement
542
- expect(input.value).toBe('current-filter')
465
+ const dateFilter = document.querySelector('data-grid-date-filter')
466
+ expect(dateFilter).not.toBeNull()
543
467
  })
544
468
  })
545
469
  })
@@ -553,33 +477,25 @@ describe('DataGridHeader', () => {
553
477
  initializeShadeRoot({
554
478
  injector,
555
479
  rootElement,
556
- jsxElement: <DataGridHeader field="name" findOptions={findOptions} />,
480
+ jsxElement: <DataGridHeader field="name" findOptions={findOptions} filterConfig={{ type: 'string' }} />,
557
481
  })
558
482
 
559
- await sleepAsync(50)
483
+ await flushUpdates()
560
484
 
561
485
  const orderButton = document.querySelector('data-grid-order-button')?.querySelector('button')
562
486
  orderButton?.click()
563
487
 
564
- await sleepAsync(50)
565
-
566
- const searchButton = document.querySelector('data-grid-search-button')?.querySelector('button')
567
- searchButton?.click()
568
-
569
- await sleepAsync(150)
488
+ await flushUpdates()
570
489
 
571
- const input = document.querySelector('.search-form input') as HTMLInputElement
572
- input.value = 'filter-value'
573
- input.dispatchEvent(new Event('input', { bubbles: true }))
490
+ expect(findOptions.getValue().order).toEqual({ name: 'ASC' })
574
491
 
575
- const form = document.querySelector('.search-form') as HTMLFormElement
576
- form.dispatchEvent(new Event('submit', { bubbles: true }))
492
+ const filterButton = document.querySelector('data-grid-filter-button')?.querySelector('button')
493
+ filterButton?.click()
577
494
 
578
- await sleepAsync(50)
495
+ await flushUpdates()
579
496
 
580
- const updatedOptions = findOptions.getValue()
581
- expect(updatedOptions.order).toEqual({ name: 'ASC' })
582
- expect(updatedOptions.filter).toEqual({ name: { $regex: 'filter-value' } })
497
+ const dropdown = document.querySelector('data-grid-filter-dropdown')
498
+ expect(dropdown).not.toBeNull()
583
499
  })
584
500
  })
585
501
  })