@furystack/shades-common-components 14.0.0 → 15.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 +51 -0
- package/esm/components/accordion/accordion-item.d.ts.map +1 -1
- package/esm/components/accordion/accordion-item.js +6 -9
- package/esm/components/accordion/accordion-item.js.map +1 -1
- package/esm/components/accordion/accordion.d.ts +7 -0
- package/esm/components/accordion/accordion.d.ts.map +1 -1
- package/esm/components/accordion/accordion.js +4 -1
- package/esm/components/accordion/accordion.js.map +1 -1
- package/esm/components/accordion/accordion.spec.js +91 -50
- package/esm/components/accordion/accordion.spec.js.map +1 -1
- package/esm/components/carousel.js +1 -1
- package/esm/components/carousel.js.map +1 -1
- package/esm/components/chip.d.ts.map +1 -1
- package/esm/components/chip.js +4 -2
- package/esm/components/chip.js.map +1 -1
- package/esm/components/chip.spec.js +42 -0
- package/esm/components/chip.spec.js.map +1 -1
- package/esm/components/command-palette/index.d.ts.map +1 -1
- package/esm/components/command-palette/index.js +14 -1
- package/esm/components/command-palette/index.js.map +1 -1
- package/esm/components/command-palette/index.spec.js +78 -33
- package/esm/components/command-palette/index.spec.js.map +1 -1
- package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid-row.js +18 -2
- package/esm/components/data-grid/data-grid-row.js.map +1 -1
- package/esm/components/data-grid/data-grid.d.ts +7 -0
- package/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid.js +28 -10
- package/esm/components/data-grid/data-grid.js.map +1 -1
- package/esm/components/data-grid/data-grid.spec.js +114 -34
- package/esm/components/data-grid/data-grid.spec.js.map +1 -1
- package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
- package/esm/components/data-grid/selection-cell.js +1 -1
- package/esm/components/data-grid/selection-cell.js.map +1 -1
- package/esm/components/dialog.d.ts +11 -0
- package/esm/components/dialog.d.ts.map +1 -1
- package/esm/components/dialog.js +2 -2
- package/esm/components/dialog.js.map +1 -1
- package/esm/components/dialog.spec.js +54 -2
- package/esm/components/dialog.spec.js.map +1 -1
- package/esm/components/dropdown.d.ts.map +1 -1
- package/esm/components/dropdown.js +1 -1
- package/esm/components/dropdown.js.map +1 -1
- package/esm/components/dropdown.spec.js +8 -0
- package/esm/components/dropdown.spec.js.map +1 -1
- package/esm/components/image.d.ts.map +1 -1
- package/esm/components/image.js +15 -6
- package/esm/components/image.js.map +1 -1
- package/esm/components/image.spec.js +60 -0
- package/esm/components/image.spec.js.map +1 -1
- package/esm/components/inputs/checkbox.d.ts.map +1 -1
- package/esm/components/inputs/checkbox.js +1 -0
- package/esm/components/inputs/checkbox.js.map +1 -1
- package/esm/components/inputs/radio.d.ts.map +1 -1
- package/esm/components/inputs/radio.js +1 -0
- package/esm/components/inputs/radio.js.map +1 -1
- package/esm/components/inputs/slider.d.ts.map +1 -1
- package/esm/components/inputs/slider.js +1 -0
- package/esm/components/inputs/slider.js.map +1 -1
- package/esm/components/inputs/switch.d.ts.map +1 -1
- package/esm/components/inputs/switch.js +1 -0
- package/esm/components/inputs/switch.js.map +1 -1
- package/esm/components/list/list-item.d.ts.map +1 -1
- package/esm/components/list/list-item.js +21 -5
- package/esm/components/list/list-item.js.map +1 -1
- package/esm/components/list/list.d.ts +7 -0
- package/esm/components/list/list.d.ts.map +1 -1
- package/esm/components/list/list.js +28 -8
- package/esm/components/list/list.js.map +1 -1
- package/esm/components/list/list.spec.js +117 -23
- package/esm/components/list/list.spec.js.map +1 -1
- package/esm/components/markdown/markdown-display.d.ts.map +1 -1
- package/esm/components/markdown/markdown-display.js +11 -1
- package/esm/components/markdown/markdown-display.js.map +1 -1
- package/esm/components/markdown/markdown-display.spec.js +97 -0
- package/esm/components/markdown/markdown-display.spec.js.map +1 -1
- package/esm/components/markdown/markdown-editor.spec.js +87 -0
- package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
- package/esm/components/menu/menu.js +1 -1
- package/esm/components/menu/menu.js.map +1 -1
- package/esm/components/modal.d.ts +10 -0
- package/esm/components/modal.d.ts.map +1 -1
- package/esm/components/modal.js +24 -4
- package/esm/components/modal.js.map +1 -1
- package/esm/components/modal.spec.js +86 -1
- package/esm/components/modal.spec.js.map +1 -1
- package/esm/components/page-layout/index.js +1 -1
- package/esm/components/page-layout/index.js.map +1 -1
- package/esm/components/page-layout/index.spec.js +14 -0
- package/esm/components/page-layout/index.spec.js.map +1 -1
- package/esm/components/rating.d.ts.map +1 -1
- package/esm/components/rating.js +28 -21
- package/esm/components/rating.js.map +1 -1
- package/esm/components/rating.spec.js +151 -4
- package/esm/components/rating.spec.js.map +1 -1
- package/esm/components/suggest/index.d.ts.map +1 -1
- package/esm/components/suggest/index.js +14 -1
- package/esm/components/suggest/index.js.map +1 -1
- package/esm/components/suggest/index.spec.js +98 -43
- package/esm/components/suggest/index.spec.js.map +1 -1
- package/esm/components/tabs.d.ts.map +1 -1
- package/esm/components/tabs.js +4 -0
- package/esm/components/tabs.js.map +1 -1
- package/esm/components/tree/tree-item.d.ts.map +1 -1
- package/esm/components/tree/tree-item.js +18 -5
- package/esm/components/tree/tree-item.js.map +1 -1
- package/esm/components/tree/tree.d.ts +7 -0
- package/esm/components/tree/tree.d.ts.map +1 -1
- package/esm/components/tree/tree.js +12 -3
- package/esm/components/tree/tree.js.map +1 -1
- package/esm/components/tree/tree.spec.js +64 -2
- package/esm/components/tree/tree.spec.js.map +1 -1
- package/esm/services/collection-service.d.ts +9 -0
- package/esm/services/collection-service.d.ts.map +1 -1
- package/esm/services/collection-service.js +33 -11
- package/esm/services/collection-service.js.map +1 -1
- package/esm/services/collection-service.spec.js +33 -24
- package/esm/services/collection-service.spec.js.map +1 -1
- package/esm/services/css-variable-theme.d.ts +7 -0
- package/esm/services/css-variable-theme.d.ts.map +1 -1
- package/esm/services/css-variable-theme.js +23 -0
- package/esm/services/css-variable-theme.js.map +1 -1
- package/esm/services/css-variable-theme.spec.js +1 -0
- package/esm/services/css-variable-theme.spec.js.map +1 -1
- package/esm/services/list-service.d.ts +9 -0
- package/esm/services/list-service.d.ts.map +1 -1
- package/esm/services/list-service.js +13 -13
- package/esm/services/list-service.js.map +1 -1
- package/esm/services/list-service.spec.js +13 -33
- package/esm/services/list-service.spec.js.map +1 -1
- package/esm/services/theme-provider-service.d.ts +3 -0
- package/esm/services/theme-provider-service.d.ts.map +1 -1
- package/esm/services/theme-provider-service.js.map +1 -1
- package/esm/services/tree-service.d.ts.map +1 -1
- package/esm/services/tree-service.js +5 -9
- package/esm/services/tree-service.js.map +1 -1
- package/esm/services/tree-service.spec.js +12 -9
- package/esm/services/tree-service.spec.js.map +1 -1
- package/esm/themes/architect-theme.d.ts +1 -0
- package/esm/themes/architect-theme.d.ts.map +1 -1
- package/esm/themes/architect-theme.js +1 -0
- package/esm/themes/architect-theme.js.map +1 -1
- package/esm/themes/auditore-theme.d.ts +1 -0
- package/esm/themes/auditore-theme.d.ts.map +1 -1
- package/esm/themes/auditore-theme.js +1 -0
- package/esm/themes/auditore-theme.js.map +1 -1
- package/esm/themes/black-mesa-theme.d.ts +1 -0
- package/esm/themes/black-mesa-theme.d.ts.map +1 -1
- package/esm/themes/black-mesa-theme.js +1 -0
- package/esm/themes/black-mesa-theme.js.map +1 -1
- package/esm/themes/chieftain-theme.d.ts +1 -0
- package/esm/themes/chieftain-theme.d.ts.map +1 -1
- package/esm/themes/chieftain-theme.js +1 -0
- package/esm/themes/chieftain-theme.js.map +1 -1
- package/esm/themes/default-dark-theme.d.ts +1 -0
- package/esm/themes/default-dark-theme.d.ts.map +1 -1
- package/esm/themes/default-dark-theme.js +1 -0
- package/esm/themes/default-dark-theme.js.map +1 -1
- package/esm/themes/default-light-theme.d.ts +1 -0
- package/esm/themes/default-light-theme.d.ts.map +1 -1
- package/esm/themes/default-light-theme.js +1 -0
- package/esm/themes/default-light-theme.js.map +1 -1
- package/esm/themes/dragonborn-theme.d.ts +1 -0
- package/esm/themes/dragonborn-theme.d.ts.map +1 -1
- package/esm/themes/dragonborn-theme.js +1 -0
- package/esm/themes/dragonborn-theme.js.map +1 -1
- package/esm/themes/hawkins-theme.d.ts +1 -0
- package/esm/themes/hawkins-theme.d.ts.map +1 -1
- package/esm/themes/hawkins-theme.js +1 -0
- package/esm/themes/hawkins-theme.js.map +1 -1
- package/esm/themes/jedi-theme.d.ts +1 -0
- package/esm/themes/jedi-theme.d.ts.map +1 -1
- package/esm/themes/jedi-theme.js +1 -0
- package/esm/themes/jedi-theme.js.map +1 -1
- package/esm/themes/neon-runner-theme.d.ts +1 -0
- package/esm/themes/neon-runner-theme.d.ts.map +1 -1
- package/esm/themes/neon-runner-theme.js +1 -0
- package/esm/themes/neon-runner-theme.js.map +1 -1
- package/esm/themes/paladin-theme.d.ts +1 -0
- package/esm/themes/paladin-theme.d.ts.map +1 -1
- package/esm/themes/paladin-theme.js +1 -0
- package/esm/themes/paladin-theme.js.map +1 -1
- package/esm/themes/plumber-theme.d.ts +1 -0
- package/esm/themes/plumber-theme.d.ts.map +1 -1
- package/esm/themes/plumber-theme.js +1 -0
- package/esm/themes/plumber-theme.js.map +1 -1
- package/esm/themes/replicant-theme.d.ts +1 -0
- package/esm/themes/replicant-theme.d.ts.map +1 -1
- package/esm/themes/replicant-theme.js +1 -0
- package/esm/themes/replicant-theme.js.map +1 -1
- package/esm/themes/sandworm-theme.d.ts +1 -0
- package/esm/themes/sandworm-theme.d.ts.map +1 -1
- package/esm/themes/sandworm-theme.js +1 -0
- package/esm/themes/sandworm-theme.js.map +1 -1
- package/esm/themes/shadow-broker-theme.d.ts +1 -0
- package/esm/themes/shadow-broker-theme.d.ts.map +1 -1
- package/esm/themes/shadow-broker-theme.js +1 -0
- package/esm/themes/shadow-broker-theme.js.map +1 -1
- package/esm/themes/sith-theme.d.ts +1 -0
- package/esm/themes/sith-theme.d.ts.map +1 -1
- package/esm/themes/sith-theme.js +1 -0
- package/esm/themes/sith-theme.js.map +1 -1
- package/esm/themes/vault-dweller-theme.d.ts +1 -0
- package/esm/themes/vault-dweller-theme.d.ts.map +1 -1
- package/esm/themes/vault-dweller-theme.js +1 -0
- package/esm/themes/vault-dweller-theme.js.map +1 -1
- package/esm/themes/wild-hunt-theme.d.ts +1 -0
- package/esm/themes/wild-hunt-theme.d.ts.map +1 -1
- package/esm/themes/wild-hunt-theme.js +1 -0
- package/esm/themes/wild-hunt-theme.js.map +1 -1
- package/esm/themes/xenomorph-theme.d.ts +1 -0
- package/esm/themes/xenomorph-theme.d.ts.map +1 -1
- package/esm/themes/xenomorph-theme.js +1 -0
- package/esm/themes/xenomorph-theme.js.map +1 -1
- package/package.json +3 -3
- package/src/components/accordion/accordion-item.tsx +9 -14
- package/src/components/accordion/accordion.spec.tsx +134 -79
- package/src/components/accordion/accordion.tsx +13 -1
- package/src/components/carousel.tsx +1 -1
- package/src/components/chip.spec.tsx +64 -0
- package/src/components/chip.tsx +4 -1
- package/src/components/command-palette/index.spec.tsx +95 -33
- package/src/components/command-palette/index.tsx +15 -3
- package/src/components/data-grid/data-grid-row.tsx +20 -2
- package/src/components/data-grid/data-grid.spec.tsx +185 -57
- package/src/components/data-grid/data-grid.tsx +38 -13
- package/src/components/data-grid/selection-cell.tsx +1 -0
- package/src/components/dialog.spec.tsx +77 -2
- package/src/components/dialog.tsx +14 -1
- package/src/components/dropdown.spec.tsx +9 -0
- package/src/components/dropdown.tsx +1 -0
- package/src/components/image.spec.tsx +82 -0
- package/src/components/image.tsx +16 -7
- package/src/components/inputs/checkbox.tsx +1 -0
- package/src/components/inputs/radio.tsx +1 -0
- package/src/components/inputs/slider.tsx +1 -0
- package/src/components/inputs/switch.tsx +1 -0
- package/src/components/list/list-item.tsx +22 -4
- package/src/components/list/list.spec.tsx +165 -32
- package/src/components/list/list.tsx +37 -10
- package/src/components/markdown/markdown-display.spec.tsx +132 -0
- package/src/components/markdown/markdown-display.tsx +12 -1
- package/src/components/markdown/markdown-editor.spec.tsx +123 -0
- package/src/components/menu/menu.tsx +1 -1
- package/src/components/modal.spec.tsx +124 -1
- package/src/components/modal.tsx +41 -3
- package/src/components/page-layout/index.spec.tsx +20 -0
- package/src/components/page-layout/index.tsx +1 -1
- package/src/components/rating.spec.tsx +199 -4
- package/src/components/rating.tsx +28 -22
- package/src/components/suggest/index.spec.tsx +147 -43
- package/src/components/suggest/index.tsx +15 -2
- package/src/components/tabs.tsx +4 -0
- package/src/components/tree/tree-item.tsx +19 -4
- package/src/components/tree/tree.spec.tsx +101 -2
- package/src/components/tree/tree.tsx +21 -3
- package/src/services/collection-service.spec.ts +33 -24
- package/src/services/collection-service.ts +35 -13
- package/src/services/css-variable-theme.spec.ts +1 -0
- package/src/services/css-variable-theme.ts +25 -0
- package/src/services/list-service.spec.ts +13 -42
- package/src/services/list-service.ts +15 -13
- package/src/services/theme-provider-service.ts +2 -0
- package/src/services/tree-service.spec.ts +12 -9
- package/src/services/tree-service.ts +5 -8
- package/src/themes/architect-theme.ts +1 -0
- package/src/themes/auditore-theme.ts +1 -0
- package/src/themes/black-mesa-theme.ts +1 -0
- package/src/themes/chieftain-theme.ts +1 -0
- package/src/themes/default-dark-theme.ts +1 -0
- package/src/themes/default-light-theme.ts +1 -0
- package/src/themes/dragonborn-theme.ts +1 -0
- package/src/themes/hawkins-theme.ts +1 -0
- package/src/themes/jedi-theme.ts +1 -0
- package/src/themes/neon-runner-theme.ts +1 -0
- package/src/themes/paladin-theme.ts +1 -0
- package/src/themes/plumber-theme.ts +1 -0
- package/src/themes/replicant-theme.ts +1 -0
- package/src/themes/sandworm-theme.ts +1 -0
- package/src/themes/shadow-broker-theme.ts +1 -0
- package/src/themes/sith-theme.ts +1 -0
- package/src/themes/vault-dweller-theme.ts +1 -0
- package/src/themes/wild-hunt-theme.ts +1 -0
- package/src/themes/xenomorph-theme.ts +1 -0
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { Shade, createComponent } from '@furystack/shades'
|
|
2
2
|
import { cssVariableTheme } from '../../services/css-variable-theme.js'
|
|
3
3
|
|
|
4
|
+
let nextAccordionId = 0
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Props for the Accordion container component.
|
|
6
8
|
*/
|
|
7
9
|
export type AccordionProps = {
|
|
8
10
|
/** Visual variant of the accordion container */
|
|
9
11
|
variant?: 'outlined' | 'elevation'
|
|
12
|
+
/**
|
|
13
|
+
* Section name for spatial navigation scoping.
|
|
14
|
+
* Sets `data-nav-section` on the accordion so that SpatialNavigationService
|
|
15
|
+
* constrains arrow-key navigation within the accordion group.
|
|
16
|
+
* Auto-generated per instance when not provided.
|
|
17
|
+
*/
|
|
18
|
+
navSection?: string
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
/**
|
|
@@ -41,9 +50,12 @@ export const Accordion = Shade<AccordionProps>({
|
|
|
41
50
|
background: cssVariableTheme.background.paper,
|
|
42
51
|
},
|
|
43
52
|
},
|
|
44
|
-
render: ({ props, useHostProps, children }) => {
|
|
53
|
+
render: ({ props, useHostProps, children, useState }) => {
|
|
54
|
+
const [navSectionId] = useState('navSectionId', String(nextAccordionId++))
|
|
55
|
+
|
|
45
56
|
useHostProps({
|
|
46
57
|
'data-variant': props.variant || undefined,
|
|
58
|
+
'data-nav-section': props.navSection ?? `accordion-${navSectionId}`,
|
|
47
59
|
})
|
|
48
60
|
|
|
49
61
|
return <>{children}</>
|
|
@@ -200,7 +200,7 @@ export const Carousel = Shade<CarouselProps>({
|
|
|
200
200
|
'data-vertical': vertical ? '' : undefined,
|
|
201
201
|
role: 'region',
|
|
202
202
|
'aria-roledescription': 'carousel',
|
|
203
|
-
|
|
203
|
+
tabIndex: 0,
|
|
204
204
|
...(style ? { style: style as Record<string, string> } : {}),
|
|
205
205
|
})
|
|
206
206
|
|
|
@@ -140,6 +140,70 @@ describe('Chip', () => {
|
|
|
140
140
|
expect(chip.hasAttribute('data-clickable')).toBe(true)
|
|
141
141
|
})
|
|
142
142
|
|
|
143
|
+
it('should be focusable when clickable', async () => {
|
|
144
|
+
const el = (
|
|
145
|
+
<div>
|
|
146
|
+
<Chip clickable>Clickable</Chip>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
const chip = el.firstElementChild as JSX.Element
|
|
150
|
+
chip.updateComponent()
|
|
151
|
+
await flushUpdates()
|
|
152
|
+
expect(chip.getAttribute('tabindex')).toBe('0')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('should be focusable when onclick handler is provided', async () => {
|
|
156
|
+
const el = (
|
|
157
|
+
<div>
|
|
158
|
+
<Chip onclick={() => {}}>Click Handler</Chip>
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
const chip = el.firstElementChild as JSX.Element
|
|
162
|
+
chip.updateComponent()
|
|
163
|
+
await flushUpdates()
|
|
164
|
+
expect(chip.getAttribute('tabindex')).toBe('0')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should not be focusable when clickable but disabled', async () => {
|
|
168
|
+
const el = (
|
|
169
|
+
<div>
|
|
170
|
+
<Chip clickable disabled>
|
|
171
|
+
Disabled Clickable
|
|
172
|
+
</Chip>
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
175
|
+
const chip = el.firstElementChild as JSX.Element
|
|
176
|
+
chip.updateComponent()
|
|
177
|
+
await flushUpdates()
|
|
178
|
+
expect(chip.hasAttribute('tabindex')).toBe(false)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should not be focusable when not clickable', async () => {
|
|
182
|
+
const el = (
|
|
183
|
+
<div>
|
|
184
|
+
<Chip>Not Clickable</Chip>
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
const chip = el.firstElementChild as JSX.Element
|
|
188
|
+
chip.updateComponent()
|
|
189
|
+
await flushUpdates()
|
|
190
|
+
expect(chip.hasAttribute('tabindex')).toBe(false)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should have a focusable delete button', async () => {
|
|
194
|
+
const onDelete = vi.fn()
|
|
195
|
+
const el = (
|
|
196
|
+
<div>
|
|
197
|
+
<Chip onDelete={onDelete}>Deletable</Chip>
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
const chip = el.firstElementChild as JSX.Element
|
|
201
|
+
chip.updateComponent()
|
|
202
|
+
await flushUpdates()
|
|
203
|
+
const deleteBtn = chip.querySelector('.chip-delete') as HTMLElement
|
|
204
|
+
expect(deleteBtn.getAttribute('tabindex')).toBe('0')
|
|
205
|
+
})
|
|
206
|
+
|
|
143
207
|
it('should set CSS custom properties for palette color', async () => {
|
|
144
208
|
const el = (
|
|
145
209
|
<div>
|
package/src/components/chip.tsx
CHANGED
|
@@ -152,11 +152,13 @@ export const Chip = Shade<ChipProps>({
|
|
|
152
152
|
const { variant, color, size, disabled, clickable, onDelete, style, ...rest } = props
|
|
153
153
|
|
|
154
154
|
const colors = color ? paletteFullColors[color] : defaultColors
|
|
155
|
+
const isClickable = clickable || rest.onclick
|
|
155
156
|
useHostProps({
|
|
156
157
|
'data-variant': variant || undefined,
|
|
157
158
|
'data-size': size === 'small' ? 'small' : undefined,
|
|
158
159
|
'data-disabled': disabled ? '' : undefined,
|
|
159
|
-
'data-clickable':
|
|
160
|
+
'data-clickable': isClickable ? '' : undefined,
|
|
161
|
+
tabIndex: isClickable && !disabled ? 0 : undefined,
|
|
160
162
|
style: {
|
|
161
163
|
'--chip-color-main': colors.main,
|
|
162
164
|
'--chip-color-main-contrast': colors.mainContrast,
|
|
@@ -174,6 +176,7 @@ export const Chip = Shade<ChipProps>({
|
|
|
174
176
|
<span
|
|
175
177
|
className="chip-delete"
|
|
176
178
|
role="button"
|
|
179
|
+
tabIndex={0}
|
|
177
180
|
onclick={(ev: MouseEvent) => {
|
|
178
181
|
ev.stopPropagation()
|
|
179
182
|
onDelete(ev)
|
|
@@ -121,12 +121,16 @@ describe('CommandPalette', () => {
|
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
describe('keyboard navigation', () => {
|
|
124
|
-
const
|
|
125
|
-
const event = new KeyboardEvent('
|
|
124
|
+
const triggerKeydown = (input: HTMLInputElement, key: string) => {
|
|
125
|
+
const event = new KeyboardEvent('keydown', { key, bubbles: true })
|
|
126
126
|
Object.defineProperty(event, 'target', { value: input, writable: false })
|
|
127
127
|
input.dispatchEvent(event)
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
const triggerInput = (input: HTMLInputElement) => {
|
|
131
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
132
|
+
}
|
|
133
|
+
|
|
130
134
|
const getSuggestionItems = (commandPalette: HTMLElement) => {
|
|
131
135
|
const suggestionList = commandPalette.querySelector('shade-command-palette-suggestion-list') as HTMLElement
|
|
132
136
|
return suggestionList?.querySelectorAll('.suggestion-item') || []
|
|
@@ -155,13 +159,13 @@ describe('CommandPalette', () => {
|
|
|
155
159
|
|
|
156
160
|
// Open and trigger suggestions
|
|
157
161
|
input.value = 'test'
|
|
158
|
-
|
|
162
|
+
triggerInput(input)
|
|
159
163
|
|
|
160
164
|
await vi.advanceTimersByTimeAsync(300)
|
|
161
165
|
await flushUpdates()
|
|
162
166
|
|
|
163
167
|
// Press ArrowDown
|
|
164
|
-
|
|
168
|
+
triggerKeydown(input, 'ArrowDown')
|
|
165
169
|
await flushUpdates()
|
|
166
170
|
|
|
167
171
|
const suggestionItems = getSuggestionItems(commandPalette)
|
|
@@ -192,15 +196,15 @@ describe('CommandPalette', () => {
|
|
|
192
196
|
|
|
193
197
|
// Open and trigger suggestions
|
|
194
198
|
input.value = 'test'
|
|
195
|
-
|
|
199
|
+
triggerInput(input)
|
|
196
200
|
|
|
197
201
|
await vi.advanceTimersByTimeAsync(300)
|
|
198
202
|
await flushUpdates()
|
|
199
203
|
|
|
200
204
|
// Navigate down then up
|
|
201
|
-
|
|
205
|
+
triggerKeydown(input, 'ArrowDown')
|
|
202
206
|
await flushUpdates()
|
|
203
|
-
|
|
207
|
+
triggerKeydown(input, 'ArrowUp')
|
|
204
208
|
await flushUpdates()
|
|
205
209
|
|
|
206
210
|
const suggestionItems = getSuggestionItems(commandPalette)
|
|
@@ -226,15 +230,15 @@ describe('CommandPalette', () => {
|
|
|
226
230
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
227
231
|
|
|
228
232
|
input.value = 'test'
|
|
229
|
-
|
|
233
|
+
triggerInput(input)
|
|
230
234
|
|
|
231
235
|
await vi.advanceTimersByTimeAsync(300)
|
|
232
236
|
await flushUpdates()
|
|
233
237
|
|
|
234
238
|
// Press ArrowDown multiple times
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
triggerKeydown(input, 'ArrowDown')
|
|
240
|
+
triggerKeydown(input, 'ArrowDown')
|
|
241
|
+
triggerKeydown(input, 'ArrowDown')
|
|
238
242
|
await flushUpdates()
|
|
239
243
|
|
|
240
244
|
const suggestionItems = getSuggestionItems(commandPalette)
|
|
@@ -260,13 +264,13 @@ describe('CommandPalette', () => {
|
|
|
260
264
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
261
265
|
|
|
262
266
|
input.value = 'test'
|
|
263
|
-
|
|
267
|
+
triggerInput(input)
|
|
264
268
|
|
|
265
269
|
await vi.advanceTimersByTimeAsync(300)
|
|
266
270
|
await flushUpdates()
|
|
267
271
|
|
|
268
272
|
// Press ArrowUp when already at first item
|
|
269
|
-
|
|
273
|
+
triggerKeydown(input, 'ArrowUp')
|
|
270
274
|
await flushUpdates()
|
|
271
275
|
|
|
272
276
|
const suggestionItems = getSuggestionItems(commandPalette)
|
|
@@ -293,13 +297,13 @@ describe('CommandPalette', () => {
|
|
|
293
297
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
294
298
|
|
|
295
299
|
input.value = 'test'
|
|
296
|
-
|
|
300
|
+
triggerInput(input)
|
|
297
301
|
|
|
298
302
|
await vi.advanceTimersByTimeAsync(300)
|
|
299
303
|
await flushUpdates()
|
|
300
304
|
|
|
301
305
|
// Press Enter
|
|
302
|
-
|
|
306
|
+
triggerKeydown(input, 'Enter')
|
|
303
307
|
await flushUpdates()
|
|
304
308
|
|
|
305
309
|
expect(onSelected).toHaveBeenCalledTimes(1)
|
|
@@ -309,12 +313,16 @@ describe('CommandPalette', () => {
|
|
|
309
313
|
})
|
|
310
314
|
|
|
311
315
|
describe('selection', () => {
|
|
312
|
-
const
|
|
313
|
-
const event = new KeyboardEvent('
|
|
316
|
+
const triggerKeydown = (input: HTMLInputElement, key: string) => {
|
|
317
|
+
const event = new KeyboardEvent('keydown', { key, bubbles: true })
|
|
314
318
|
Object.defineProperty(event, 'target', { value: input, writable: false })
|
|
315
319
|
input.dispatchEvent(event)
|
|
316
320
|
}
|
|
317
321
|
|
|
322
|
+
const triggerInput = (input: HTMLInputElement) => {
|
|
323
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
324
|
+
}
|
|
325
|
+
|
|
318
326
|
const getSuggestionItems = (commandPalette: HTMLElement) => {
|
|
319
327
|
const suggestionList = commandPalette.querySelector('shade-command-palette-suggestion-list') as HTMLElement
|
|
320
328
|
return suggestionList?.querySelectorAll('.suggestion-item') || []
|
|
@@ -345,7 +353,7 @@ describe('CommandPalette', () => {
|
|
|
345
353
|
|
|
346
354
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
347
355
|
input.value = 'test'
|
|
348
|
-
|
|
356
|
+
triggerInput(input)
|
|
349
357
|
|
|
350
358
|
await vi.advanceTimersByTimeAsync(300)
|
|
351
359
|
await flushUpdates()
|
|
@@ -386,12 +394,13 @@ describe('CommandPalette', () => {
|
|
|
386
394
|
|
|
387
395
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
388
396
|
input.value = 'test'
|
|
389
|
-
|
|
397
|
+
triggerInput(input)
|
|
390
398
|
|
|
399
|
+
await vi.advanceTimersByTimeAsync(300)
|
|
391
400
|
await flushUpdates()
|
|
392
401
|
|
|
393
402
|
// Select via Enter
|
|
394
|
-
|
|
403
|
+
triggerKeydown(input, 'Enter')
|
|
395
404
|
await flushUpdates()
|
|
396
405
|
|
|
397
406
|
expect(commandPalette.hasAttribute('data-opened')).toBe(false)
|
|
@@ -400,10 +409,8 @@ describe('CommandPalette', () => {
|
|
|
400
409
|
})
|
|
401
410
|
|
|
402
411
|
describe('command providers', () => {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
Object.defineProperty(event, 'target', { value: input, writable: false })
|
|
406
|
-
input.dispatchEvent(event)
|
|
412
|
+
const triggerInput = (input: HTMLInputElement) => {
|
|
413
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
407
414
|
}
|
|
408
415
|
|
|
409
416
|
const getSuggestionItems = (commandPalette: HTMLElement) => {
|
|
@@ -430,7 +437,7 @@ describe('CommandPalette', () => {
|
|
|
430
437
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
431
438
|
|
|
432
439
|
input.value = 'search'
|
|
433
|
-
|
|
440
|
+
triggerInput(input)
|
|
434
441
|
|
|
435
442
|
await vi.advanceTimersByTimeAsync(300)
|
|
436
443
|
await flushUpdates()
|
|
@@ -459,7 +466,7 @@ describe('CommandPalette', () => {
|
|
|
459
466
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
460
467
|
|
|
461
468
|
input.value = 'search'
|
|
462
|
-
|
|
469
|
+
triggerInput(input)
|
|
463
470
|
|
|
464
471
|
await vi.advanceTimersByTimeAsync(300)
|
|
465
472
|
await flushUpdates()
|
|
@@ -493,7 +500,7 @@ describe('CommandPalette', () => {
|
|
|
493
500
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
494
501
|
|
|
495
502
|
input.value = 'search'
|
|
496
|
-
|
|
503
|
+
triggerInput(input)
|
|
497
504
|
|
|
498
505
|
await vi.advanceTimersByTimeAsync(300)
|
|
499
506
|
await flushUpdates()
|
|
@@ -585,9 +592,7 @@ describe('CommandPalette', () => {
|
|
|
585
592
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
586
593
|
|
|
587
594
|
input.value = 'test'
|
|
588
|
-
|
|
589
|
-
Object.defineProperty(event, 'target', { value: input, writable: false })
|
|
590
|
-
input.dispatchEvent(event)
|
|
595
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
591
596
|
|
|
592
597
|
await vi.advanceTimersByTimeAsync(260)
|
|
593
598
|
await flushUpdates()
|
|
@@ -602,6 +607,65 @@ describe('CommandPalette', () => {
|
|
|
602
607
|
})
|
|
603
608
|
})
|
|
604
609
|
|
|
610
|
+
describe('spatial navigation attributes', () => {
|
|
611
|
+
it('should have data-spatial-nav-target on the host element', async () => {
|
|
612
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
613
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
614
|
+
|
|
615
|
+
initializeShadeRoot({
|
|
616
|
+
injector,
|
|
617
|
+
rootElement,
|
|
618
|
+
jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
await flushUpdates()
|
|
622
|
+
|
|
623
|
+
const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
|
|
624
|
+
expect(commandPalette.hasAttribute('data-spatial-nav-target')).toBe(true)
|
|
625
|
+
})
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
it('should have tabIndex of -1 on the host element', async () => {
|
|
629
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
630
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
631
|
+
|
|
632
|
+
initializeShadeRoot({
|
|
633
|
+
injector,
|
|
634
|
+
rootElement,
|
|
635
|
+
jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
await flushUpdates()
|
|
639
|
+
|
|
640
|
+
const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
|
|
641
|
+
expect(commandPalette.tabIndex).toBe(-1)
|
|
642
|
+
})
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
it('should delegate focus to the inner input when the host is focused', async () => {
|
|
646
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
647
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
648
|
+
|
|
649
|
+
initializeShadeRoot({
|
|
650
|
+
injector,
|
|
651
|
+
rootElement,
|
|
652
|
+
jsxElement: <CommandPalette commandProviders={[]} defaultPrefix=">" />,
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
await flushUpdates()
|
|
656
|
+
|
|
657
|
+
const commandPalette = document.querySelector('shade-command-palette') as HTMLElement
|
|
658
|
+
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
659
|
+
|
|
660
|
+
commandPalette.dispatchEvent(new FocusEvent('focus', { bubbles: false }))
|
|
661
|
+
|
|
662
|
+
await flushUpdates()
|
|
663
|
+
|
|
664
|
+
expect(document.activeElement).toBe(input)
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
})
|
|
668
|
+
|
|
605
669
|
describe('click away', () => {
|
|
606
670
|
it('should close when clicking outside the component', async () => {
|
|
607
671
|
await usingAsync(new Injector(), async (injector) => {
|
|
@@ -681,9 +745,7 @@ describe('CommandPalette', () => {
|
|
|
681
745
|
|
|
682
746
|
const input = commandPalette.querySelector('input') as HTMLInputElement
|
|
683
747
|
input.value = 'test'
|
|
684
|
-
|
|
685
|
-
Object.defineProperty(event, 'target', { value: input, writable: false })
|
|
686
|
-
input.dispatchEvent(event)
|
|
748
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
687
749
|
|
|
688
750
|
await flushUpdates()
|
|
689
751
|
|
|
@@ -62,13 +62,25 @@ export const CommandPalette = Shade<CommandPaletteProps>({
|
|
|
62
62
|
useHostProps({
|
|
63
63
|
...(isLoading ? { 'data-loading': '' } : {}),
|
|
64
64
|
...(isOpenedAtRender ? { 'data-opened': '' } : {}),
|
|
65
|
+
tabIndex: -1,
|
|
66
|
+
'data-spatial-nav-target': '',
|
|
67
|
+
onfocus: (ev: FocusEvent) => {
|
|
68
|
+
const host = ev.currentTarget as HTMLElement
|
|
69
|
+
const input = host.querySelector('input')
|
|
70
|
+
if (input) {
|
|
71
|
+
input.focus()
|
|
72
|
+
}
|
|
73
|
+
},
|
|
65
74
|
})
|
|
66
75
|
|
|
67
76
|
return (
|
|
68
77
|
<div
|
|
69
78
|
ref={wrapperRef}
|
|
70
79
|
className="command-palette-wrapper"
|
|
71
|
-
|
|
80
|
+
onkeydown={(ev) => {
|
|
81
|
+
const hasSuggestions = manager.isOpened.getValue() && manager.currentSuggestions.getValue().length > 0
|
|
82
|
+
if (!hasSuggestions) return
|
|
83
|
+
|
|
72
84
|
if (ev.key === 'Enter') {
|
|
73
85
|
ev.preventDefault()
|
|
74
86
|
manager.selectSuggestion(injector)
|
|
@@ -84,11 +96,11 @@ export const CommandPalette = Shade<CommandPaletteProps>({
|
|
|
84
96
|
Math.min(manager.selectedIndex.getValue() + 1, manager.currentSuggestions.getValue().length - 1),
|
|
85
97
|
)
|
|
86
98
|
}
|
|
87
|
-
|
|
99
|
+
}}
|
|
100
|
+
oninput={(ev) => {
|
|
88
101
|
if (!manager.isOpened.getValue()) {
|
|
89
102
|
manager.isOpened.setValue(true)
|
|
90
103
|
}
|
|
91
|
-
|
|
92
104
|
void manager.getSuggestion({ injector, term: (ev.target as HTMLInputElement).value })
|
|
93
105
|
}}
|
|
94
106
|
>
|
|
@@ -75,7 +75,20 @@ export const DataGridRow: <T, Column extends string>(
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
useHostProps({
|
|
78
|
+
tabIndex: isFocused ? 0 : -1,
|
|
79
|
+
'data-spatial-nav-target': '',
|
|
78
80
|
'aria-selected': isSelected.toString(),
|
|
81
|
+
onpointerdown: () => {
|
|
82
|
+
service.setFocusAnchor()
|
|
83
|
+
},
|
|
84
|
+
onfocus: () => {
|
|
85
|
+
if (service.focusedEntry.getValue() !== entry) {
|
|
86
|
+
service.focusedEntry.setValue(entry)
|
|
87
|
+
}
|
|
88
|
+
if (!service.hasFocus.getValue()) {
|
|
89
|
+
service.hasFocus.setValue(true)
|
|
90
|
+
}
|
|
91
|
+
},
|
|
79
92
|
...(isSelected ? { 'data-selected': '' } : {}),
|
|
80
93
|
...(isFocused ? { 'data-focused': '' } : {}),
|
|
81
94
|
...(Object.keys(rowStyle).length > 0 ? { style: rowStyle } : {}),
|
|
@@ -89,6 +102,11 @@ export const DataGridRow: <T, Column extends string>(
|
|
|
89
102
|
if (!el) return
|
|
90
103
|
const hostEl = el.closest('shades-data-grid-row') as HTMLElement
|
|
91
104
|
if (!hostEl) return
|
|
105
|
+
|
|
106
|
+
if (document.activeElement !== hostEl) {
|
|
107
|
+
hostEl.focus({ preventScroll: true })
|
|
108
|
+
}
|
|
109
|
+
|
|
92
110
|
const scrollContainer = hostEl.closest('shade-data-grid') as HTMLElement
|
|
93
111
|
if (!scrollContainer) return
|
|
94
112
|
|
|
@@ -106,13 +124,13 @@ export const DataGridRow: <T, Column extends string>(
|
|
|
106
124
|
const scrollAdjustment = rowTopInContainer - headerHeight
|
|
107
125
|
scrollContainer.scrollTo({
|
|
108
126
|
top: scrollContainer.scrollTop + scrollAdjustment,
|
|
109
|
-
behavior: '
|
|
127
|
+
behavior: 'instant',
|
|
110
128
|
})
|
|
111
129
|
} else if (rowBottomInContainer > scrollContainer.clientHeight - footerHeight) {
|
|
112
130
|
const scrollAdjustment = rowBottomInContainer - (scrollContainer.clientHeight - footerHeight)
|
|
113
131
|
scrollContainer.scrollTo({
|
|
114
132
|
top: scrollContainer.scrollTop + scrollAdjustment,
|
|
115
|
-
behavior: '
|
|
133
|
+
behavior: 'instant',
|
|
116
134
|
})
|
|
117
135
|
}
|
|
118
136
|
})
|