@furystack/shades-common-components 12.0.0 → 12.1.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 (49) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +26 -0
  3. package/esm/components/cache-view.d.ts +46 -0
  4. package/esm/components/cache-view.d.ts.map +1 -0
  5. package/esm/components/cache-view.js +65 -0
  6. package/esm/components/cache-view.js.map +1 -0
  7. package/esm/components/cache-view.spec.d.ts +2 -0
  8. package/esm/components/cache-view.spec.d.ts.map +1 -0
  9. package/esm/components/cache-view.spec.js +183 -0
  10. package/esm/components/cache-view.spec.js.map +1 -0
  11. package/esm/components/command-palette/command-palette-input.spec.js +148 -148
  12. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
  13. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +258 -258
  14. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
  15. package/esm/components/context-menu/context-menu-manager.spec.js +211 -217
  16. package/esm/components/context-menu/context-menu-manager.spec.js.map +1 -1
  17. package/esm/components/data-grid/body.spec.js +173 -173
  18. package/esm/components/data-grid/body.spec.js.map +1 -1
  19. package/esm/components/data-grid/data-grid.spec.js +39 -130
  20. package/esm/components/data-grid/data-grid.spec.js.map +1 -1
  21. package/esm/components/index.d.ts +1 -0
  22. package/esm/components/index.d.ts.map +1 -1
  23. package/esm/components/index.js +1 -0
  24. package/esm/components/index.js.map +1 -1
  25. package/esm/components/skeleton.d.ts.map +1 -1
  26. package/esm/components/skeleton.js +2 -11
  27. package/esm/components/skeleton.js.map +1 -1
  28. package/esm/components/skeleton.spec.js +6 -55
  29. package/esm/components/skeleton.spec.js.map +1 -1
  30. package/esm/services/click-away-service.spec.js +14 -12
  31. package/esm/services/click-away-service.spec.js.map +1 -1
  32. package/esm/services/list-service.spec.js +170 -141
  33. package/esm/services/list-service.spec.js.map +1 -1
  34. package/esm/services/tree-service.spec.js +190 -159
  35. package/esm/services/tree-service.spec.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/components/cache-view.spec.tsx +210 -0
  38. package/src/components/cache-view.tsx +103 -0
  39. package/src/components/command-palette/command-palette-input.spec.tsx +183 -194
  40. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +303 -321
  41. package/src/components/context-menu/context-menu-manager.spec.ts +213 -258
  42. package/src/components/data-grid/body.spec.tsx +266 -276
  43. package/src/components/data-grid/data-grid.spec.tsx +137 -232
  44. package/src/components/index.ts +1 -0
  45. package/src/components/skeleton.spec.tsx +6 -73
  46. package/src/components/skeleton.tsx +2 -11
  47. package/src/services/click-away-service.spec.ts +14 -16
  48. package/src/services/list-service.spec.ts +170 -172
  49. package/src/services/tree-service.spec.ts +191 -207
@@ -18,337 +18,327 @@ describe('DataGridBody', () => {
18
18
 
19
19
  it('should render default empty component when no data', async () => {
20
20
  await usingAsync(new Injector(), async (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
- ),
21
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
22
+ const rootElement = document.getElementById('root') as HTMLDivElement
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 -')
32
39
  })
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
40
  })
42
41
  })
43
42
 
44
43
  it('should render custom empty component when provided and no data', async () => {
45
44
  await usingAsync(new Injector(), async (injector) => {
46
- const rootElement = document.getElementById('root') as HTMLDivElement
47
- const service = new CollectionService<TestEntry>()
48
-
49
- initializeShadeRoot({
50
- injector,
51
- rootElement,
52
- jsxElement: (
53
- <table>
54
- <DataGridBody<TestEntry, 'id' | 'name'>
55
- service={service}
56
- columns={['id', 'name']}
57
- emptyComponent={<div data-testid="custom-empty">Custom Empty State</div>}
58
- />
59
- </table>
60
- ),
45
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
46
+ const rootElement = document.getElementById('root') as HTMLDivElement
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')
61
68
  })
62
-
63
- await sleepAsync(50)
64
-
65
- const body = document.querySelector('tbody[is="shade-data-grid-body"]')
66
- expect(body).not.toBeNull()
67
- expect(body?.querySelector('[data-testid="custom-empty"]')).not.toBeNull()
68
- expect(body?.textContent).toContain('Custom Empty State')
69
-
70
- service[Symbol.dispose]()
71
69
  })
72
70
  })
73
71
 
74
72
  it('should render rows for each data entry', async () => {
75
73
  await usingAsync(new Injector(), async (injector) => {
76
- const rootElement = document.getElementById('root') as HTMLDivElement
77
- const service = new CollectionService<TestEntry>()
78
-
79
- service.data.setValue({
80
- count: 2,
81
- entries: [
82
- { id: 1, name: 'First' },
83
- { id: 2, name: 'Second' },
84
- ],
85
- })
86
-
87
- initializeShadeRoot({
88
- injector,
89
- rootElement,
90
- jsxElement: (
91
- <table>
92
- <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
93
- </table>
94
- ),
74
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
75
+ const rootElement = document.getElementById('root') as HTMLDivElement
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)
95
102
  })
96
-
97
- await sleepAsync(50)
98
-
99
- const body = document.querySelector('tbody[is="shade-data-grid-body"]')
100
- expect(body).not.toBeNull()
101
-
102
- const rows = body?.querySelectorAll('shades-data-grid-row')
103
- expect(rows?.length).toBe(2)
104
-
105
- service[Symbol.dispose]()
106
103
  })
107
104
  })
108
105
 
109
106
  it('should render cell content from entry properties', async () => {
110
107
  await usingAsync(new Injector(), async (injector) => {
111
- const rootElement = document.getElementById('root') as HTMLDivElement
112
- const service = new CollectionService<TestEntry>()
113
-
114
- service.data.setValue({
115
- count: 1,
116
- entries: [{ id: 42, name: 'Test Entry' }],
117
- })
118
-
119
- initializeShadeRoot({
120
- injector,
121
- rootElement,
122
- jsxElement: (
123
- <table>
124
- <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
125
- </table>
126
- ),
108
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
109
+ const rootElement = document.getElementById('root') as HTMLDivElement
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')
127
134
  })
128
-
129
- await sleepAsync(50)
130
-
131
- const body = document.querySelector('tbody[is="shade-data-grid-body"]')
132
- const cells = body?.querySelectorAll('td')
133
-
134
- expect(cells?.length).toBe(2)
135
- expect(cells?.[0]?.textContent).toBe('42')
136
- expect(cells?.[1]?.textContent).toBe('Test Entry')
137
-
138
- service[Symbol.dispose]()
139
135
  })
140
136
  })
141
137
 
142
138
  it('should re-render when data observable changes', async () => {
143
139
  await usingAsync(new Injector(), async (injector) => {
144
- const rootElement = document.getElementById('root') as HTMLDivElement
145
- const service = new CollectionService<TestEntry>()
146
-
147
- initializeShadeRoot({
148
- injector,
149
- rootElement,
150
- jsxElement: (
151
- <table>
152
- <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
153
- </table>
154
- ),
155
- })
156
-
157
- await sleepAsync(50)
158
-
159
- let body = document.querySelector('tbody[is="shade-data-grid-body"]')
160
- expect(body?.textContent).toContain('- No Data -')
161
-
162
- service.data.setValue({
163
- count: 1,
164
- entries: [{ id: 1, name: 'New Entry' }],
140
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
141
+ const rootElement = document.getElementById('root') as HTMLDivElement
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)
165
168
  })
166
-
167
- await sleepAsync(50)
168
-
169
- body = document.querySelector('tbody[is="shade-data-grid-body"]')
170
- const rows = body?.querySelectorAll('shades-data-grid-row')
171
- expect(rows?.length).toBe(1)
172
-
173
- service[Symbol.dispose]()
174
169
  })
175
170
  })
176
171
 
177
172
  it('should call onRowClick callback when row is clicked', async () => {
178
173
  await usingAsync(new Injector(), async (injector) => {
179
- const rootElement = document.getElementById('root') as HTMLDivElement
180
- const service = new CollectionService<TestEntry>()
181
- const onRowClick = vi.fn()
182
-
183
- const entry = { id: 1, name: 'Clickable' }
184
- service.data.setValue({
185
- count: 1,
186
- entries: [entry],
174
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
175
+ const rootElement = document.getElementById('root') as HTMLDivElement
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'>
190
+ service={service}
191
+ columns={['id', 'name']}
192
+ onRowClick={onRowClick}
193
+ />
194
+ </table>
195
+ ),
196
+ })
197
+
198
+ await sleepAsync(50)
199
+
200
+ const cell = document.querySelector('td') as HTMLTableCellElement
201
+ cell.click()
202
+
203
+ expect(onRowClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
187
204
  })
188
-
189
- initializeShadeRoot({
190
- injector,
191
- rootElement,
192
- jsxElement: (
193
- <table>
194
- <DataGridBody<TestEntry, 'id' | 'name'>
195
- service={service}
196
- columns={['id', 'name']}
197
- onRowClick={onRowClick}
198
- />
199
- </table>
200
- ),
201
- })
202
-
203
- await sleepAsync(50)
204
-
205
- const cell = document.querySelector('td') as HTMLTableCellElement
206
- cell.click()
207
-
208
- expect(onRowClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
209
-
210
- service[Symbol.dispose]()
211
205
  })
212
206
  })
213
207
 
214
208
  it('should call onRowDoubleClick callback when row is double-clicked', async () => {
215
209
  await usingAsync(new Injector(), async (injector) => {
216
- const rootElement = document.getElementById('root') as HTMLDivElement
217
- const service = new CollectionService<TestEntry>()
218
- const onRowDoubleClick = vi.fn()
219
-
220
- const entry = { id: 1, name: 'DoubleClickable' }
221
- service.data.setValue({
222
- count: 1,
223
- entries: [entry],
210
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
211
+ const rootElement = document.getElementById('root') as HTMLDivElement
212
+ const onRowDoubleClick = vi.fn()
213
+
214
+ const entry = { id: 1, name: 'DoubleClickable' }
215
+ service.data.setValue({
216
+ count: 1,
217
+ entries: [entry],
218
+ })
219
+
220
+ initializeShadeRoot({
221
+ injector,
222
+ rootElement,
223
+ jsxElement: (
224
+ <table>
225
+ <DataGridBody<TestEntry, 'id' | 'name'>
226
+ service={service}
227
+ columns={['id', 'name']}
228
+ onRowDoubleClick={onRowDoubleClick}
229
+ />
230
+ </table>
231
+ ),
232
+ })
233
+
234
+ await sleepAsync(50)
235
+
236
+ const cell = document.querySelector('td') as HTMLTableCellElement
237
+ const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
238
+ cell.dispatchEvent(dblClickEvent)
239
+
240
+ expect(onRowDoubleClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
224
241
  })
225
-
226
- initializeShadeRoot({
227
- injector,
228
- rootElement,
229
- jsxElement: (
230
- <table>
231
- <DataGridBody<TestEntry, 'id' | 'name'>
232
- service={service}
233
- columns={['id', 'name']}
234
- onRowDoubleClick={onRowDoubleClick}
235
- />
236
- </table>
237
- ),
238
- })
239
-
240
- await sleepAsync(50)
241
-
242
- const cell = document.querySelector('td') as HTMLTableCellElement
243
- const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
244
- cell.dispatchEvent(dblClickEvent)
245
-
246
- expect(onRowDoubleClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
247
-
248
- service[Symbol.dispose]()
249
242
  })
250
243
  })
251
244
 
252
245
  it('should use custom row components when provided', async () => {
253
246
  await usingAsync(new Injector(), async (injector) => {
254
- const rootElement = document.getElementById('root') as HTMLDivElement
255
- const service = new CollectionService<TestEntry>()
256
-
257
- service.data.setValue({
258
- count: 1,
259
- entries: [{ id: 1, name: 'Custom' }],
247
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
248
+ const rootElement = document.getElementById('root') as HTMLDivElement
249
+
250
+ service.data.setValue({
251
+ count: 1,
252
+ entries: [{ id: 1, name: 'Custom' }],
253
+ })
254
+
255
+ initializeShadeRoot({
256
+ injector,
257
+ rootElement,
258
+ jsxElement: (
259
+ <table>
260
+ <DataGridBody<TestEntry, 'id' | 'name'>
261
+ service={service}
262
+ columns={['id', 'name']}
263
+ rowComponents={{
264
+ id: (entry) => <span data-testid="custom-id">ID: {entry.id}</span>,
265
+ name: (entry) => <strong data-testid="custom-name">{entry.name}</strong>,
266
+ }}
267
+ />
268
+ </table>
269
+ ),
270
+ })
271
+
272
+ await sleepAsync(50)
273
+
274
+ const customId = document.querySelector('[data-testid="custom-id"]')
275
+ const customName = document.querySelector('[data-testid="custom-name"]')
276
+
277
+ expect(customId?.textContent).toContain('ID: 1')
278
+ expect(customName?.textContent).toBe('Custom')
260
279
  })
261
-
262
- initializeShadeRoot({
263
- injector,
264
- rootElement,
265
- jsxElement: (
266
- <table>
267
- <DataGridBody<TestEntry, 'id' | 'name'>
268
- service={service}
269
- columns={['id', 'name']}
270
- rowComponents={{
271
- id: (entry) => <span data-testid="custom-id">ID: {entry.id}</span>,
272
- name: (entry) => <strong data-testid="custom-name">{entry.name}</strong>,
273
- }}
274
- />
275
- </table>
276
- ),
277
- })
278
-
279
- await sleepAsync(50)
280
-
281
- const customId = document.querySelector('[data-testid="custom-id"]')
282
- const customName = document.querySelector('[data-testid="custom-name"]')
283
-
284
- expect(customId?.textContent).toContain('ID: 1')
285
- expect(customName?.textContent).toBe('Custom')
286
-
287
- service[Symbol.dispose]()
288
280
  })
289
281
  })
290
282
 
291
283
  it('should use default row component when column-specific one is not provided', async () => {
292
284
  await usingAsync(new Injector(), async (injector) => {
293
- const rootElement = document.getElementById('root') as HTMLDivElement
294
- const service = new CollectionService<TestEntry>()
295
-
296
- service.data.setValue({
297
- count: 1,
298
- entries: [{ id: 1, name: 'Default' }],
285
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
286
+ const rootElement = document.getElementById('root') as HTMLDivElement
287
+
288
+ service.data.setValue({
289
+ count: 1,
290
+ entries: [{ id: 1, name: 'Default' }],
291
+ })
292
+
293
+ initializeShadeRoot({
294
+ injector,
295
+ rootElement,
296
+ jsxElement: (
297
+ <table>
298
+ <DataGridBody<TestEntry, 'id' | 'name'>
299
+ service={service}
300
+ columns={['id', 'name']}
301
+ rowComponents={{
302
+ default: (entry) => <em data-testid="default-cell">{JSON.stringify(entry)}</em>,
303
+ }}
304
+ />
305
+ </table>
306
+ ),
307
+ })
308
+
309
+ await sleepAsync(50)
310
+
311
+ const defaultCells = document.querySelectorAll('[data-testid="default-cell"]')
312
+ expect(defaultCells.length).toBe(2)
299
313
  })
300
-
301
- initializeShadeRoot({
302
- injector,
303
- rootElement,
304
- jsxElement: (
305
- <table>
306
- <DataGridBody<TestEntry, 'id' | 'name'>
307
- service={service}
308
- columns={['id', 'name']}
309
- rowComponents={{
310
- default: (entry) => <em data-testid="default-cell">{JSON.stringify(entry)}</em>,
311
- }}
312
- />
313
- </table>
314
- ),
315
- })
316
-
317
- await sleepAsync(50)
318
-
319
- const defaultCells = document.querySelectorAll('[data-testid="default-cell"]')
320
- expect(defaultCells.length).toBe(2)
321
-
322
- service[Symbol.dispose]()
323
314
  })
324
315
  })
325
316
 
326
317
  it('should render with empty entries array', async () => {
327
318
  await usingAsync(new Injector(), async (injector) => {
328
- const rootElement = document.getElementById('root') as HTMLDivElement
329
- const service = new CollectionService<TestEntry>()
330
-
331
- service.data.setValue({
332
- count: 0,
333
- entries: [],
334
- })
335
-
336
- initializeShadeRoot({
337
- injector,
338
- rootElement,
339
- jsxElement: (
340
- <table>
341
- <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
342
- </table>
343
- ),
319
+ await usingAsync(new CollectionService<TestEntry>(), async (service) => {
320
+ const rootElement = document.getElementById('root') as HTMLDivElement
321
+
322
+ service.data.setValue({
323
+ count: 0,
324
+ entries: [],
325
+ })
326
+
327
+ initializeShadeRoot({
328
+ injector,
329
+ rootElement,
330
+ jsxElement: (
331
+ <table>
332
+ <DataGridBody<TestEntry, 'id' | 'name'> service={service} columns={['id', 'name']} />
333
+ </table>
334
+ ),
335
+ })
336
+
337
+ await sleepAsync(50)
338
+
339
+ const body = document.querySelector('tbody[is="shade-data-grid-body"]')
340
+ expect(body?.textContent).toContain('- No Data -')
344
341
  })
345
-
346
- await sleepAsync(50)
347
-
348
- const body = document.querySelector('tbody[is="shade-data-grid-body"]')
349
- expect(body?.textContent).toContain('- No Data -')
350
-
351
- service[Symbol.dispose]()
352
342
  })
353
343
  })
354
344
  })