@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.
- package/CHANGELOG.md +66 -0
- package/esm/components/animations.spec.d.ts +2 -0
- package/esm/components/animations.spec.d.ts.map +1 -0
- package/esm/components/animations.spec.js +201 -0
- package/esm/components/animations.spec.js.map +1 -0
- package/esm/components/app-bar-link.js +21 -20
- package/esm/components/app-bar-link.js.map +1 -1
- package/esm/components/app-bar-link.spec.d.ts +2 -0
- package/esm/components/app-bar-link.spec.d.ts.map +1 -0
- package/esm/components/app-bar-link.spec.js +252 -0
- package/esm/components/app-bar-link.spec.js.map +1 -0
- package/esm/components/app-bar.js +21 -21
- package/esm/components/app-bar.js.map +1 -1
- package/esm/components/app-bar.spec.d.ts +2 -0
- package/esm/components/app-bar.spec.d.ts.map +1 -0
- package/esm/components/app-bar.spec.js +117 -0
- package/esm/components/app-bar.spec.js.map +1 -0
- package/esm/components/avatar.d.ts.map +1 -1
- package/esm/components/avatar.js +15 -19
- package/esm/components/avatar.js.map +1 -1
- package/esm/components/avatar.spec.d.ts +2 -0
- package/esm/components/avatar.spec.d.ts.map +1 -0
- package/esm/components/avatar.spec.js +114 -0
- package/esm/components/avatar.spec.js.map +1 -0
- package/esm/components/button.d.ts.map +1 -1
- package/esm/components/button.js +145 -156
- package/esm/components/button.js.map +1 -1
- package/esm/components/button.spec.d.ts +2 -0
- package/esm/components/button.spec.d.ts.map +1 -0
- package/esm/components/button.spec.js +155 -0
- package/esm/components/button.spec.js.map +1 -0
- package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
- package/esm/components/command-palette/command-palette-input.js +18 -16
- package/esm/components/command-palette/command-palette-input.js.map +1 -1
- package/esm/components/command-palette/command-palette-input.spec.d.ts +2 -0
- package/esm/components/command-palette/command-palette-input.spec.d.ts.map +1 -0
- package/esm/components/command-palette/command-palette-input.spec.js +233 -0
- package/esm/components/command-palette/command-palette-input.spec.js.map +1 -0
- package/esm/components/command-palette/command-palette-manager.spec.d.ts +2 -0
- package/esm/components/command-palette/command-palette-manager.spec.d.ts.map +1 -0
- package/esm/components/command-palette/command-palette-manager.spec.js +362 -0
- package/esm/components/command-palette/command-palette-manager.spec.js.map +1 -0
- package/esm/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.js +42 -46
- package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts +2 -0
- package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts.map +1 -0
- package/esm/components/command-palette/command-palette-suggestion-list.spec.js +376 -0
- package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -0
- package/esm/components/command-palette/index.d.ts.map +1 -1
- package/esm/components/command-palette/index.js +100 -110
- package/esm/components/command-palette/index.js.map +1 -1
- package/esm/components/command-palette/index.spec.d.ts +2 -0
- package/esm/components/command-palette/index.spec.d.ts.map +1 -0
- package/esm/components/command-palette/index.spec.js +509 -0
- package/esm/components/command-palette/index.spec.js.map +1 -0
- package/esm/components/data-grid/body.js +1 -1
- package/esm/components/data-grid/body.js.map +1 -1
- package/esm/components/data-grid/body.spec.d.ts +2 -0
- package/esm/components/data-grid/body.spec.d.ts.map +1 -0
- package/esm/components/data-grid/body.spec.js +228 -0
- package/esm/components/data-grid/body.spec.js.map +1 -0
- package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid-row.js +49 -73
- package/esm/components/data-grid/data-grid-row.js.map +1 -1
- package/esm/components/data-grid/data-grid-row.spec.d.ts +2 -0
- package/esm/components/data-grid/data-grid-row.spec.d.ts.map +1 -0
- package/esm/components/data-grid/data-grid-row.spec.js +296 -0
- package/esm/components/data-grid/data-grid-row.spec.js.map +1 -0
- package/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid.js +35 -28
- package/esm/components/data-grid/data-grid.js.map +1 -1
- package/esm/components/data-grid/data-grid.spec.d.ts +2 -0
- package/esm/components/data-grid/data-grid.spec.d.ts.map +1 -0
- package/esm/components/data-grid/data-grid.spec.js +544 -0
- package/esm/components/data-grid/data-grid.spec.js.map +1 -0
- package/esm/components/data-grid/footer.js +21 -15
- package/esm/components/data-grid/footer.js.map +1 -1
- package/esm/components/data-grid/footer.spec.d.ts +2 -0
- package/esm/components/data-grid/footer.spec.d.ts.map +1 -0
- package/esm/components/data-grid/footer.spec.js +264 -0
- package/esm/components/data-grid/footer.spec.js.map +1 -0
- package/esm/components/data-grid/header.d.ts.map +1 -1
- package/esm/components/data-grid/header.js +55 -33
- package/esm/components/data-grid/header.js.map +1 -1
- package/esm/components/data-grid/header.spec.d.ts +2 -0
- package/esm/components/data-grid/header.spec.d.ts.map +1 -0
- package/esm/components/data-grid/header.spec.js +421 -0
- package/esm/components/data-grid/header.spec.js.map +1 -0
- package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
- package/esm/components/data-grid/selection-cell.js +13 -6
- package/esm/components/data-grid/selection-cell.js.map +1 -1
- package/esm/components/data-grid/selection-cell.spec.d.ts +2 -0
- package/esm/components/data-grid/selection-cell.spec.d.ts.map +1 -0
- package/esm/components/data-grid/selection-cell.spec.js +118 -0
- package/esm/components/data-grid/selection-cell.spec.js.map +1 -0
- package/esm/components/fab.d.ts.map +1 -1
- package/esm/components/fab.js +10 -1
- package/esm/components/fab.js.map +1 -1
- package/esm/components/fab.spec.d.ts +2 -0
- package/esm/components/fab.spec.d.ts.map +1 -0
- package/esm/components/fab.spec.js +95 -0
- package/esm/components/fab.spec.js.map +1 -0
- package/esm/components/form.spec.d.ts +2 -0
- package/esm/components/form.spec.d.ts.map +1 -0
- package/esm/components/form.spec.js +314 -0
- package/esm/components/form.spec.js.map +1 -0
- package/esm/components/grid.d.ts.map +1 -1
- package/esm/components/grid.js +40 -37
- package/esm/components/grid.js.map +1 -1
- package/esm/components/grid.spec.d.ts +2 -0
- package/esm/components/grid.spec.d.ts.map +1 -0
- package/esm/components/grid.spec.js +316 -0
- package/esm/components/grid.spec.js.map +1 -0
- package/esm/components/inputs/autocomplete.spec.d.ts +2 -0
- package/esm/components/inputs/autocomplete.spec.d.ts.map +1 -0
- package/esm/components/inputs/autocomplete.spec.js +194 -0
- package/esm/components/inputs/autocomplete.spec.js.map +1 -0
- package/esm/components/inputs/input.d.ts.map +1 -1
- package/esm/components/inputs/input.js +141 -109
- package/esm/components/inputs/input.js.map +1 -1
- package/esm/components/inputs/input.spec.d.ts +2 -0
- package/esm/components/inputs/input.spec.d.ts.map +1 -0
- package/esm/components/inputs/input.spec.js +577 -0
- package/esm/components/inputs/input.spec.js.map +1 -0
- package/esm/components/inputs/text-area.d.ts.map +1 -1
- package/esm/components/inputs/text-area.js +54 -58
- package/esm/components/inputs/text-area.js.map +1 -1
- package/esm/components/inputs/text-area.spec.d.ts +2 -0
- package/esm/components/inputs/text-area.spec.d.ts.map +1 -0
- package/esm/components/inputs/text-area.spec.js +214 -0
- package/esm/components/inputs/text-area.spec.js.map +1 -0
- package/esm/components/loader.js +1 -1
- package/esm/components/loader.js.map +1 -1
- package/esm/components/loader.spec.d.ts +2 -0
- package/esm/components/loader.spec.d.ts.map +1 -0
- package/esm/components/loader.spec.js +251 -0
- package/esm/components/loader.spec.js.map +1 -0
- package/esm/components/modal.d.ts.map +1 -1
- package/esm/components/modal.js +11 -9
- package/esm/components/modal.js.map +1 -1
- package/esm/components/modal.spec.d.ts +2 -0
- package/esm/components/modal.spec.d.ts.map +1 -0
- package/esm/components/modal.spec.js +227 -0
- package/esm/components/modal.spec.js.map +1 -0
- package/esm/components/noty-list.d.ts.map +1 -1
- package/esm/components/noty-list.js +39 -40
- package/esm/components/noty-list.js.map +1 -1
- package/esm/components/noty-list.spec.d.ts +2 -0
- package/esm/components/noty-list.spec.d.ts.map +1 -0
- package/esm/components/noty-list.spec.js +486 -0
- package/esm/components/noty-list.spec.js.map +1 -0
- package/esm/components/paper.d.ts.map +1 -1
- package/esm/components/paper.js +15 -12
- package/esm/components/paper.js.map +1 -1
- package/esm/components/paper.spec.d.ts +2 -0
- package/esm/components/paper.spec.d.ts.map +1 -0
- package/esm/components/paper.spec.js +63 -0
- package/esm/components/paper.spec.js.map +1 -0
- package/esm/components/skeleton.js +1 -1
- package/esm/components/skeleton.js.map +1 -1
- package/esm/components/skeleton.spec.d.ts +2 -0
- package/esm/components/skeleton.spec.d.ts.map +1 -0
- package/esm/components/skeleton.spec.js +159 -0
- package/esm/components/skeleton.spec.js.map +1 -0
- package/esm/components/styles.spec.d.ts +2 -0
- package/esm/components/styles.spec.d.ts.map +1 -0
- package/esm/components/styles.spec.js +56 -0
- package/esm/components/styles.spec.js.map +1 -0
- package/esm/components/suggest/index.d.ts.map +1 -1
- package/esm/components/suggest/index.js +74 -83
- package/esm/components/suggest/index.js.map +1 -1
- package/esm/components/suggest/index.spec.d.ts +2 -0
- package/esm/components/suggest/index.spec.d.ts.map +1 -0
- package/esm/components/suggest/index.spec.js +515 -0
- package/esm/components/suggest/index.spec.js.map +1 -0
- package/esm/components/suggest/suggest-input.d.ts.map +1 -1
- package/esm/components/suggest/suggest-input.js +16 -17
- package/esm/components/suggest/suggest-input.js.map +1 -1
- package/esm/components/suggest/suggest-input.spec.d.ts +2 -0
- package/esm/components/suggest/suggest-input.spec.d.ts.map +1 -0
- package/esm/components/suggest/suggest-input.spec.js +138 -0
- package/esm/components/suggest/suggest-input.spec.js.map +1 -0
- package/esm/components/suggest/suggest-manager.spec.d.ts +2 -0
- package/esm/components/suggest/suggest-manager.spec.d.ts.map +1 -0
- package/esm/components/suggest/suggest-manager.spec.js +308 -0
- package/esm/components/suggest/suggest-manager.spec.js.map +1 -0
- package/esm/components/suggest/suggestion-list.d.ts.map +1 -1
- package/esm/components/suggest/suggestion-list.js +43 -48
- package/esm/components/suggest/suggestion-list.js.map +1 -1
- package/esm/components/suggest/suggestion-list.spec.d.ts +2 -0
- package/esm/components/suggest/suggestion-list.spec.d.ts.map +1 -0
- package/esm/components/suggest/suggestion-list.spec.js +252 -0
- package/esm/components/suggest/suggestion-list.spec.js.map +1 -0
- package/esm/components/tabs.d.ts.map +1 -1
- package/esm/components/tabs.js +32 -18
- package/esm/components/tabs.js.map +1 -1
- package/esm/components/tabs.spec.d.ts +2 -0
- package/esm/components/tabs.spec.d.ts.map +1 -0
- package/esm/components/tabs.spec.js +187 -0
- package/esm/components/tabs.spec.js.map +1 -0
- package/esm/components/wizard/index.d.ts.map +1 -1
- package/esm/components/wizard/index.js +10 -7
- package/esm/components/wizard/index.js.map +1 -1
- package/esm/components/wizard/index.spec.d.ts +2 -0
- package/esm/components/wizard/index.spec.d.ts.map +1 -0
- package/esm/components/wizard/index.spec.js +171 -0
- package/esm/components/wizard/index.spec.js.map +1 -0
- package/esm/services/collection-service.spec.js +391 -2
- package/esm/services/collection-service.spec.js.map +1 -1
- package/esm/services/css-variable-theme.d.ts.map +1 -1
- package/esm/services/css-variable-theme.js +21 -1
- package/esm/services/css-variable-theme.js.map +1 -1
- package/esm/services/css-variable-theme.spec.d.ts +2 -0
- package/esm/services/css-variable-theme.spec.d.ts.map +1 -0
- package/esm/services/css-variable-theme.spec.js +169 -0
- package/esm/services/css-variable-theme.spec.js.map +1 -0
- package/esm/services/default-palette.d.ts +4 -0
- package/esm/services/default-palette.d.ts.map +1 -1
- package/esm/services/default-palette.js +22 -0
- package/esm/services/default-palette.js.map +1 -1
- package/esm/services/theme-provider-service.d.ts +59 -1
- package/esm/services/theme-provider-service.d.ts.map +1 -1
- package/esm/services/theme-provider-service.js.map +1 -1
- package/esm/services/theme-provider-service.spec.d.ts +2 -0
- package/esm/services/theme-provider-service.spec.d.ts.map +1 -0
- package/esm/services/theme-provider-service.spec.js +166 -0
- package/esm/services/theme-provider-service.spec.js.map +1 -0
- package/package.json +2 -2
- package/src/components/animations.spec.ts +299 -0
- package/src/components/app-bar-link.spec.tsx +341 -0
- package/src/components/app-bar-link.tsx +21 -21
- package/src/components/app-bar.spec.tsx +142 -0
- package/src/components/app-bar.tsx +22 -22
- package/src/components/avatar.spec.tsx +146 -0
- package/src/components/avatar.tsx +17 -20
- package/src/components/button.spec.tsx +193 -0
- package/src/components/button.tsx +162 -197
- package/src/components/command-palette/command-palette-input.spec.tsx +320 -0
- package/src/components/command-palette/command-palette-input.tsx +19 -22
- package/src/components/command-palette/command-palette-manager.spec.ts +470 -0
- package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +499 -0
- package/src/components/command-palette/command-palette-suggestion-list.tsx +42 -46
- package/src/components/command-palette/index.spec.tsx +684 -0
- package/src/components/command-palette/index.tsx +107 -136
- package/src/components/data-grid/body.spec.tsx +340 -0
- package/src/components/data-grid/body.tsx +1 -1
- package/src/components/data-grid/data-grid-row.spec.tsx +382 -0
- package/src/components/data-grid/data-grid-row.tsx +50 -82
- package/src/components/data-grid/data-grid.spec.tsx +939 -0
- package/src/components/data-grid/data-grid.tsx +38 -35
- package/src/components/data-grid/footer.spec.tsx +344 -0
- package/src/components/data-grid/footer.tsx +19 -19
- package/src/components/data-grid/header.spec.tsx +563 -0
- package/src/components/data-grid/header.tsx +53 -44
- package/src/components/data-grid/selection-cell.spec.tsx +150 -0
- package/src/components/data-grid/selection-cell.tsx +12 -6
- package/src/components/fab.spec.tsx +108 -0
- package/src/components/fab.tsx +10 -1
- package/src/components/form.spec.tsx +481 -0
- package/src/components/grid.spec.tsx +334 -0
- package/src/components/grid.tsx +57 -63
- package/src/components/inputs/autocomplete.spec.tsx +258 -0
- package/src/components/inputs/input.spec.tsx +808 -0
- package/src/components/inputs/input.tsx +153 -139
- package/src/components/inputs/text-area.spec.tsx +285 -0
- package/src/components/inputs/text-area.tsx +53 -79
- package/src/components/loader.spec.tsx +346 -0
- package/src/components/loader.tsx +1 -1
- package/src/components/modal.spec.tsx +304 -0
- package/src/components/modal.tsx +11 -9
- package/src/components/noty-list.spec.tsx +631 -0
- package/src/components/noty-list.tsx +39 -50
- package/src/components/paper.spec.tsx +72 -0
- package/src/components/paper.tsx +15 -13
- package/src/components/skeleton.spec.tsx +219 -0
- package/src/components/skeleton.tsx +1 -1
- package/src/components/styles.spec.ts +70 -0
- package/src/components/suggest/index.spec.tsx +861 -0
- package/src/components/suggest/index.tsx +74 -101
- package/src/components/suggest/suggest-input.spec.tsx +181 -0
- package/src/components/suggest/suggest-input.tsx +16 -24
- package/src/components/suggest/suggest-manager.spec.ts +409 -0
- package/src/components/suggest/suggestion-list.spec.tsx +334 -0
- package/src/components/suggest/suggestion-list.tsx +43 -48
- package/src/components/tabs.spec.tsx +236 -0
- package/src/components/tabs.tsx +33 -21
- package/src/components/wizard/index.spec.tsx +224 -0
- package/src/components/wizard/index.tsx +10 -9
- package/src/services/collection-service.spec.ts +492 -3
- package/src/services/css-variable-theme.spec.ts +204 -0
- package/src/services/css-variable-theme.ts +21 -1
- package/src/services/default-palette.ts +22 -0
- package/src/services/theme-provider-service.spec.ts +195 -0
- package/src/services/theme-provider-service.ts +60 -2
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { CollectionService } from '../../services/collection-service.js'
|
|
6
|
+
import { DataGridRow } from './data-grid-row.js'
|
|
7
|
+
|
|
8
|
+
type TestEntry = { id: number; name: string }
|
|
9
|
+
|
|
10
|
+
describe('DataGridRow', () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
13
|
+
// Mock scrollTo for jsdom
|
|
14
|
+
Element.prototype.scrollTo = vi.fn()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
document.body.innerHTML = ''
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const renderRow = async (props: {
|
|
22
|
+
entry: TestEntry
|
|
23
|
+
service: CollectionService<TestEntry>
|
|
24
|
+
columns?: Array<'id' | 'name'>
|
|
25
|
+
onRowClick?: (row: TestEntry, event: MouseEvent) => void
|
|
26
|
+
onRowDoubleClick?: (row: TestEntry, event: MouseEvent) => void
|
|
27
|
+
focusedRowStyle?: Partial<CSSStyleDeclaration>
|
|
28
|
+
selectedRowStyle?: Partial<CSSStyleDeclaration>
|
|
29
|
+
unfocusedRowStyle?: Partial<CSSStyleDeclaration>
|
|
30
|
+
unselectedRowStyle?: Partial<CSSStyleDeclaration>
|
|
31
|
+
rowComponents?: Record<string, (entry: TestEntry) => JSX.Element>
|
|
32
|
+
}) => {
|
|
33
|
+
const injector = new Injector()
|
|
34
|
+
const root = document.getElementById('root')!
|
|
35
|
+
|
|
36
|
+
initializeShadeRoot({
|
|
37
|
+
injector,
|
|
38
|
+
rootElement: root,
|
|
39
|
+
jsxElement: (
|
|
40
|
+
<div className="shade-grid-wrapper" style={{ overflow: 'auto', height: '200px' }}>
|
|
41
|
+
<table>
|
|
42
|
+
<thead>
|
|
43
|
+
<tr>
|
|
44
|
+
<th>ID</th>
|
|
45
|
+
<th>Name</th>
|
|
46
|
+
</tr>
|
|
47
|
+
</thead>
|
|
48
|
+
<tbody>
|
|
49
|
+
<DataGridRow<TestEntry, 'id' | 'name'>
|
|
50
|
+
entry={props.entry}
|
|
51
|
+
service={props.service}
|
|
52
|
+
columns={props.columns ?? ['id', 'name']}
|
|
53
|
+
onRowClick={props.onRowClick}
|
|
54
|
+
onRowDoubleClick={props.onRowDoubleClick}
|
|
55
|
+
focusedRowStyle={props.focusedRowStyle}
|
|
56
|
+
selectedRowStyle={props.selectedRowStyle}
|
|
57
|
+
unfocusedRowStyle={props.unfocusedRowStyle}
|
|
58
|
+
unselectedRowStyle={props.unselectedRowStyle}
|
|
59
|
+
rowComponents={props.rowComponents}
|
|
60
|
+
/>
|
|
61
|
+
</tbody>
|
|
62
|
+
</table>
|
|
63
|
+
</div>
|
|
64
|
+
),
|
|
65
|
+
})
|
|
66
|
+
await sleepAsync(50)
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
injector,
|
|
70
|
+
getRow: () => root.querySelector('shades-data-grid-row'),
|
|
71
|
+
getCells: () => root.querySelectorAll('td'),
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('rendering', () => {
|
|
76
|
+
it('should render as a table row element', async () => {
|
|
77
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
78
|
+
const entry = { id: 1, name: 'Test' }
|
|
79
|
+
const { getRow } = await renderRow({ entry, service })
|
|
80
|
+
|
|
81
|
+
const row = getRow()
|
|
82
|
+
expect(row).toBeTruthy()
|
|
83
|
+
expect(row?.tagName.toLowerCase()).toBe('shades-data-grid-row')
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should render a cell for each column', async () => {
|
|
88
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
89
|
+
const entry = { id: 1, name: 'Test' }
|
|
90
|
+
const { getCells } = await renderRow({ entry, service, columns: ['id', 'name'] })
|
|
91
|
+
|
|
92
|
+
expect(getCells().length).toBe(2)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should render entry property values in cells', async () => {
|
|
97
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
98
|
+
const entry = { id: 42, name: 'Test Entry' }
|
|
99
|
+
const { getCells } = await renderRow({ entry, service })
|
|
100
|
+
|
|
101
|
+
const cells = getCells()
|
|
102
|
+
expect(cells[0]?.textContent).toBe('42')
|
|
103
|
+
expect(cells[1]?.textContent).toBe('Test Entry')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should use custom row components when provided', async () => {
|
|
108
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
109
|
+
const entry = { id: 1, name: 'Custom' }
|
|
110
|
+
const { getCells } = await renderRow({
|
|
111
|
+
entry,
|
|
112
|
+
service,
|
|
113
|
+
rowComponents: {
|
|
114
|
+
id: (e: TestEntry) => <span data-testid="custom-id">ID: {e.id}</span>,
|
|
115
|
+
name: (e: TestEntry) => <strong data-testid="custom-name">{e.name}</strong>,
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const cells = getCells()
|
|
120
|
+
expect(cells[0]?.querySelector('[data-testid="custom-id"]')?.textContent).toContain('ID: 1')
|
|
121
|
+
expect(cells[1]?.querySelector('[data-testid="custom-name"]')?.textContent).toBe('Custom')
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('selection state', () => {
|
|
127
|
+
it('should not have selected class when entry is not selected', async () => {
|
|
128
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
129
|
+
const entry = { id: 1, name: 'Test' }
|
|
130
|
+
const { getRow } = await renderRow({ entry, service })
|
|
131
|
+
|
|
132
|
+
const row = getRow()
|
|
133
|
+
expect(row?.classList.contains('selected')).toBe(false)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should have selected class when entry is in selection', async () => {
|
|
138
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
139
|
+
const entry = { id: 1, name: 'Test' }
|
|
140
|
+
service.selection.setValue([entry])
|
|
141
|
+
const { getRow } = await renderRow({ entry, service })
|
|
142
|
+
|
|
143
|
+
const row = getRow()
|
|
144
|
+
expect(row?.classList.contains('selected')).toBe(true)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should update selected class when selection changes', async () => {
|
|
149
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
150
|
+
const entry = { id: 1, name: 'Test' }
|
|
151
|
+
const { getRow } = await renderRow({ entry, service })
|
|
152
|
+
|
|
153
|
+
const row = getRow()
|
|
154
|
+
expect(row?.classList.contains('selected')).toBe(false)
|
|
155
|
+
|
|
156
|
+
service.selection.setValue([entry])
|
|
157
|
+
await sleepAsync(50)
|
|
158
|
+
expect(row?.classList.contains('selected')).toBe(true)
|
|
159
|
+
|
|
160
|
+
service.selection.setValue([])
|
|
161
|
+
await sleepAsync(50)
|
|
162
|
+
expect(row?.classList.contains('selected')).toBe(false)
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('should set aria-selected attribute based on selection', async () => {
|
|
167
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
168
|
+
const entry = { id: 1, name: 'Test' }
|
|
169
|
+
const { getRow } = await renderRow({ entry, service })
|
|
170
|
+
|
|
171
|
+
const row = getRow()
|
|
172
|
+
expect(row?.getAttribute('aria-selected')).toBe('false')
|
|
173
|
+
|
|
174
|
+
service.selection.setValue([entry])
|
|
175
|
+
await sleepAsync(50)
|
|
176
|
+
expect(row?.getAttribute('aria-selected')).toBe('true')
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should apply selectedRowStyle when entry is selected', async () => {
|
|
181
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
182
|
+
const entry = { id: 1, name: 'Test' }
|
|
183
|
+
service.selection.setValue([entry])
|
|
184
|
+
const { getRow } = await renderRow({
|
|
185
|
+
entry,
|
|
186
|
+
service,
|
|
187
|
+
selectedRowStyle: { backgroundColor: 'rgb(255, 0, 0)' },
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
const row = getRow() as HTMLElement | null
|
|
191
|
+
expect(row?.style.backgroundColor).toBe('rgb(255, 0, 0)')
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('should apply unselectedRowStyle when entry is not selected', async () => {
|
|
196
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
197
|
+
const entry = { id: 1, name: 'Test' }
|
|
198
|
+
const { getRow } = await renderRow({
|
|
199
|
+
entry,
|
|
200
|
+
service,
|
|
201
|
+
unselectedRowStyle: { backgroundColor: 'rgb(0, 255, 0)' },
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const row = getRow() as HTMLElement | null
|
|
205
|
+
expect(row?.style.backgroundColor).toBe('rgb(0, 255, 0)')
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
describe('focus state', () => {
|
|
211
|
+
it('should not have focused class when entry is not focused', async () => {
|
|
212
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
213
|
+
const entry = { id: 1, name: 'Test' }
|
|
214
|
+
const { getRow } = await renderRow({ entry, service })
|
|
215
|
+
|
|
216
|
+
const row = getRow()
|
|
217
|
+
expect(row?.classList.contains('focused')).toBe(false)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should have focused class when entry is focused', async () => {
|
|
222
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
223
|
+
const entry = { id: 1, name: 'Test' }
|
|
224
|
+
service.focusedEntry.setValue(entry)
|
|
225
|
+
const { getRow } = await renderRow({ entry, service })
|
|
226
|
+
|
|
227
|
+
const row = getRow()
|
|
228
|
+
expect(row?.classList.contains('focused')).toBe(true)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('should update focused class when focus changes', async () => {
|
|
233
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
234
|
+
const entry = { id: 1, name: 'Test' }
|
|
235
|
+
const { getRow } = await renderRow({ entry, service })
|
|
236
|
+
|
|
237
|
+
const row = getRow()
|
|
238
|
+
expect(row?.classList.contains('focused')).toBe(false)
|
|
239
|
+
|
|
240
|
+
service.focusedEntry.setValue(entry)
|
|
241
|
+
await sleepAsync(50)
|
|
242
|
+
expect(row?.classList.contains('focused')).toBe(true)
|
|
243
|
+
|
|
244
|
+
service.focusedEntry.setValue(undefined)
|
|
245
|
+
await sleepAsync(50)
|
|
246
|
+
expect(row?.classList.contains('focused')).toBe(false)
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should apply focusedRowStyle when entry is focused', async () => {
|
|
251
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
252
|
+
const entry = { id: 1, name: 'Test' }
|
|
253
|
+
service.focusedEntry.setValue(entry)
|
|
254
|
+
const { getRow } = await renderRow({
|
|
255
|
+
entry,
|
|
256
|
+
service,
|
|
257
|
+
focusedRowStyle: { fontWeight: 'bold' },
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const row = getRow() as HTMLElement | null
|
|
261
|
+
expect(row?.style.fontWeight).toBe('bold')
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should apply unfocusedRowStyle when entry is not focused', async () => {
|
|
266
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
267
|
+
const entry = { id: 1, name: 'Test' }
|
|
268
|
+
const { getRow } = await renderRow({
|
|
269
|
+
entry,
|
|
270
|
+
service,
|
|
271
|
+
unfocusedRowStyle: { opacity: '0.8' },
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const row = getRow() as HTMLElement | null
|
|
275
|
+
expect(row?.style.opacity).toBe('0.8')
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('should not have focused class when different entry is focused', async () => {
|
|
280
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
281
|
+
const entry = { id: 1, name: 'Test' }
|
|
282
|
+
const otherEntry = { id: 2, name: 'Other' }
|
|
283
|
+
service.focusedEntry.setValue(otherEntry)
|
|
284
|
+
const { getRow } = await renderRow({ entry, service })
|
|
285
|
+
|
|
286
|
+
const row = getRow()
|
|
287
|
+
expect(row?.classList.contains('focused')).toBe(false)
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
describe('click handlers', () => {
|
|
293
|
+
it('should call onRowClick when cell is clicked', async () => {
|
|
294
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
295
|
+
const entry = { id: 1, name: 'Test' }
|
|
296
|
+
const onRowClick = vi.fn()
|
|
297
|
+
const { getCells } = await renderRow({ entry, service, onRowClick })
|
|
298
|
+
|
|
299
|
+
const cell = getCells()[0]
|
|
300
|
+
cell.click()
|
|
301
|
+
|
|
302
|
+
expect(onRowClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('should call onRowDoubleClick when cell is double-clicked', async () => {
|
|
307
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
308
|
+
const entry = { id: 1, name: 'Test' }
|
|
309
|
+
const onRowDoubleClick = vi.fn()
|
|
310
|
+
const { getCells } = await renderRow({ entry, service, onRowDoubleClick })
|
|
311
|
+
|
|
312
|
+
const cell = getCells()[0]
|
|
313
|
+
const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
|
|
314
|
+
cell.dispatchEvent(dblClickEvent)
|
|
315
|
+
|
|
316
|
+
expect(onRowDoubleClick).toHaveBeenCalledWith(entry, expect.any(MouseEvent))
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('should not throw when onRowClick is not provided', async () => {
|
|
321
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
322
|
+
const entry = { id: 1, name: 'Test' }
|
|
323
|
+
const { getCells } = await renderRow({ entry, service })
|
|
324
|
+
|
|
325
|
+
const cell = getCells()[0]
|
|
326
|
+
expect(() => cell.click()).not.toThrow()
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('should not throw when onRowDoubleClick is not provided', async () => {
|
|
331
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
332
|
+
const entry = { id: 1, name: 'Test' }
|
|
333
|
+
const { getCells } = await renderRow({ entry, service })
|
|
334
|
+
|
|
335
|
+
const cell = getCells()[0]
|
|
336
|
+
const dblClickEvent = new MouseEvent('dblclick', { bubbles: true })
|
|
337
|
+
expect(() => cell.dispatchEvent(dblClickEvent)).not.toThrow()
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
describe('combined selection and focus states', () => {
|
|
343
|
+
it('should have both selected and focused classes when applicable', async () => {
|
|
344
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
345
|
+
const entry = { id: 1, name: 'Test' }
|
|
346
|
+
service.selection.setValue([entry])
|
|
347
|
+
service.focusedEntry.setValue(entry)
|
|
348
|
+
const { getRow } = await renderRow({ entry, service })
|
|
349
|
+
|
|
350
|
+
const row = getRow()
|
|
351
|
+
expect(row?.classList.contains('selected')).toBe(true)
|
|
352
|
+
expect(row?.classList.contains('focused')).toBe(true)
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('should be selected but not focused', async () => {
|
|
357
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
358
|
+
const entry = { id: 1, name: 'Test' }
|
|
359
|
+
const otherEntry = { id: 2, name: 'Other' }
|
|
360
|
+
service.selection.setValue([entry])
|
|
361
|
+
service.focusedEntry.setValue(otherEntry)
|
|
362
|
+
const { getRow } = await renderRow({ entry, service })
|
|
363
|
+
|
|
364
|
+
const row = getRow()
|
|
365
|
+
expect(row?.classList.contains('selected')).toBe(true)
|
|
366
|
+
expect(row?.classList.contains('focused')).toBe(false)
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('should be focused but not selected', async () => {
|
|
371
|
+
await usingAsync(new CollectionService<TestEntry>(), async (service) => {
|
|
372
|
+
const entry = { id: 1, name: 'Test' }
|
|
373
|
+
service.focusedEntry.setValue(entry)
|
|
374
|
+
const { getRow } = await renderRow({ entry, service })
|
|
375
|
+
|
|
376
|
+
const row = getRow()
|
|
377
|
+
expect(row?.classList.contains('selected')).toBe(false)
|
|
378
|
+
expect(row?.classList.contains('focused')).toBe(true)
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChildrenList } from '@furystack/shades'
|
|
2
2
|
import { attachStyles, createComponent, Shade } from '@furystack/shades'
|
|
3
3
|
import type { CollectionService } from '../../services/collection-service.js'
|
|
4
|
+
import { cssVariableTheme } from '../../services/css-variable-theme.js'
|
|
4
5
|
import type { DataRowCells } from './data-grid.js'
|
|
5
6
|
|
|
6
7
|
export interface DataGridRowProps<T, Column extends string> {
|
|
@@ -21,44 +22,55 @@ export const DataGridRow: <T, Column extends string>(
|
|
|
21
22
|
children: ChildrenList,
|
|
22
23
|
) => JSX.Element<any> = Shade({
|
|
23
24
|
shadowDomName: 'shades-data-grid-row',
|
|
24
|
-
|
|
25
|
+
css: {
|
|
26
|
+
display: 'table-row',
|
|
27
|
+
cursor: 'default',
|
|
28
|
+
userSelect: 'none',
|
|
29
|
+
transition: 'background-color 0.15s ease, box-shadow 0.15s ease-in-out, transform 0.15s ease-in-out',
|
|
30
|
+
borderLeft: '3px solid transparent',
|
|
31
|
+
'&:not(.selected):hover': {
|
|
32
|
+
backgroundColor: 'rgba(128, 128, 128, 0.08)',
|
|
33
|
+
},
|
|
34
|
+
'&.selected': {
|
|
35
|
+
backgroundColor: 'rgba(128, 128, 128, 0.15)',
|
|
36
|
+
borderLeft: `3px solid ${cssVariableTheme.palette.primary.main}`,
|
|
37
|
+
},
|
|
38
|
+
'&.focused': {
|
|
39
|
+
boxShadow: `0 0 0 2px ${cssVariableTheme.palette.primary.main} inset, 0 2px 8px 0px rgba(0,0,0,0.15)`,
|
|
40
|
+
fontWeight: '500',
|
|
41
|
+
transform: 'scale(1.002)',
|
|
42
|
+
},
|
|
43
|
+
'& td': {
|
|
44
|
+
padding: '0.75em 1.2em',
|
|
45
|
+
borderBottom: '1px solid rgba(128, 128, 128, 0.1)',
|
|
46
|
+
verticalAlign: 'middle',
|
|
47
|
+
fontSize: '0.875rem',
|
|
48
|
+
lineHeight: '1.5',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
25
51
|
render: ({ props, element, useObservable }) => {
|
|
26
52
|
const { entry, rowComponents, columns, service } = props
|
|
27
53
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
element.setAttribute('aria-selected', 'true')
|
|
38
|
-
} else {
|
|
39
|
-
element.classList.remove('selected')
|
|
40
|
-
attachStyles(element, {
|
|
41
|
-
style: props.unselectedRowStyle || {
|
|
42
|
-
backgroundColor: 'transparent',
|
|
43
|
-
borderLeft: '3px solid transparent',
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
element.setAttribute('aria-selected', 'false')
|
|
54
|
+
const updateSelectionState = (selection: unknown[]) => {
|
|
55
|
+
const isSelected = selection.includes(entry)
|
|
56
|
+
element.classList.toggle('selected', isSelected)
|
|
57
|
+
element.setAttribute('aria-selected', isSelected.toString())
|
|
58
|
+
|
|
59
|
+
if (props.selectedRowStyle && isSelected) {
|
|
60
|
+
attachStyles(element, { style: props.selectedRowStyle })
|
|
61
|
+
} else if (props.unselectedRowStyle && !isSelected) {
|
|
62
|
+
attachStyles(element, { style: props.unselectedRowStyle })
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
style: props.focusedRowStyle || {
|
|
54
|
-
boxShadow: `0 0 0 2px var(--shades-theme-palette-primary-main) inset, 0 2px 8px 0px rgba(0,0,0,0.15)`,
|
|
55
|
-
transition: 'box-shadow 0.15s ease-in-out, transform 0.15s ease-in-out',
|
|
56
|
-
fontWeight: '500',
|
|
57
|
-
transform: 'scale(1.002)',
|
|
58
|
-
},
|
|
59
|
-
})
|
|
66
|
+
const updateFocusState = (focusedEntry?: unknown) => {
|
|
67
|
+
const isFocused = focusedEntry === entry
|
|
68
|
+
element.classList.toggle('focused', isFocused)
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
if (isFocused) {
|
|
71
|
+
if (props.focusedRowStyle) {
|
|
72
|
+
attachStyles(element, { style: props.focusedRowStyle })
|
|
73
|
+
}
|
|
62
74
|
|
|
63
75
|
const headerHeight = element.closest('table')?.querySelector('th')?.getBoundingClientRect().height || 42
|
|
64
76
|
|
|
@@ -77,69 +89,25 @@ export const DataGridRow: <T, Column extends string>(
|
|
|
77
89
|
if (desiredMaxTop > visibleMaxTop) {
|
|
78
90
|
parent.scrollTo({ top: desiredMaxTop - visibleMaxTop, behavior: 'smooth' })
|
|
79
91
|
}
|
|
80
|
-
} else {
|
|
81
|
-
element.
|
|
82
|
-
attachStyles(element, {
|
|
83
|
-
style: props.unfocusedRowStyle || {
|
|
84
|
-
boxShadow: 'none',
|
|
85
|
-
fontWeight: 'inherit',
|
|
86
|
-
transform: 'scale(1)',
|
|
87
|
-
},
|
|
88
|
-
})
|
|
92
|
+
} else if (props.unfocusedRowStyle) {
|
|
93
|
+
attachStyles(element, { style: props.unfocusedRowStyle })
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
const [selection] = useObservable('isSelected', service.selection, {
|
|
93
|
-
onChange:
|
|
98
|
+
onChange: updateSelectionState,
|
|
94
99
|
})
|
|
95
|
-
|
|
100
|
+
updateSelectionState(selection)
|
|
96
101
|
|
|
97
102
|
const [focus] = useObservable('focus', service.focusedEntry, {
|
|
98
|
-
onChange:
|
|
103
|
+
onChange: updateFocusState,
|
|
99
104
|
})
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
element.style.display = 'table-row'
|
|
103
|
-
element.style.cursor = 'default'
|
|
104
|
-
element.style.userSelect = 'none'
|
|
105
|
-
element.style.transition = 'background-color 0.15s ease'
|
|
106
|
-
|
|
107
|
-
if (selection?.includes(entry)) {
|
|
108
|
-
element.setAttribute('aria-selected', 'true')
|
|
109
|
-
element.classList.add('selected')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (focus === entry) {
|
|
113
|
-
element.classList.add('focused')
|
|
114
|
-
}
|
|
115
|
-
element.setAttribute('aria-selected', selection?.includes(entry).toString() || 'false')
|
|
116
|
-
|
|
117
|
-
// Add hover effect
|
|
118
|
-
element.onmouseenter = () => {
|
|
119
|
-
if (!selection?.includes(entry)) {
|
|
120
|
-
element.style.backgroundColor = 'rgba(128, 128, 128, 0.08)'
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
element.onmouseleave = () => {
|
|
124
|
-
if (!selection?.includes(entry)) {
|
|
125
|
-
element.style.backgroundColor = 'transparent'
|
|
126
|
-
}
|
|
127
|
-
}
|
|
105
|
+
updateFocusState(focus)
|
|
128
106
|
|
|
129
107
|
return (
|
|
130
108
|
<>
|
|
131
109
|
{columns.map((column) => (
|
|
132
|
-
<td
|
|
133
|
-
style={{
|
|
134
|
-
padding: '0.75em 1.2em',
|
|
135
|
-
borderBottom: '1px solid rgba(128, 128, 128, 0.1)',
|
|
136
|
-
verticalAlign: 'middle',
|
|
137
|
-
fontSize: '0.875rem',
|
|
138
|
-
lineHeight: '1.5',
|
|
139
|
-
}}
|
|
140
|
-
onclick={(ev) => props.onRowClick?.(entry, ev)}
|
|
141
|
-
ondblclick={(ev) => props.onRowDoubleClick?.(entry, ev)}
|
|
142
|
-
>
|
|
110
|
+
<td onclick={(ev) => props.onRowClick?.(entry, ev)} ondblclick={(ev) => props.onRowDoubleClick?.(entry, ev)}>
|
|
143
111
|
{rowComponents?.[column]?.(entry, { selection, focus }) ||
|
|
144
112
|
rowComponents?.default?.(entry, { selection, focus }) || (
|
|
145
113
|
<span>
|