@furystack/shades-common-components 12.3.0 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/esm/components/app-bar-link.spec.js +16 -19
- package/esm/components/app-bar-link.spec.js.map +1 -1
- package/esm/components/app-bar.spec.js +6 -4
- package/esm/components/app-bar.spec.js.map +1 -1
- package/esm/components/avatar.spec.js +9 -9
- package/esm/components/avatar.spec.js.map +1 -1
- package/esm/components/breadcrumb.spec.js +2 -2
- package/esm/components/breadcrumb.spec.js.map +1 -1
- package/esm/components/button-group.d.ts +32 -0
- package/esm/components/button-group.d.ts.map +1 -1
- package/esm/components/button-group.js +26 -2
- package/esm/components/button-group.js.map +1 -1
- package/esm/components/button-group.spec.js +127 -11
- package/esm/components/button-group.spec.js.map +1 -1
- package/esm/components/button.spec.js +4 -4
- package/esm/components/button.spec.js.map +1 -1
- package/esm/components/cache-view.spec.js +2 -3
- package/esm/components/cache-view.spec.js.map +1 -1
- package/esm/components/carousel.spec.js +47 -47
- package/esm/components/carousel.spec.js.map +1 -1
- package/esm/components/circular-progress.spec.js +2 -2
- package/esm/components/command-palette/command-palette-input.spec.js +23 -19
- package/esm/components/command-palette/command-palette-input.spec.js.map +1 -1
- package/esm/components/command-palette/command-palette-suggestion-list.spec.js +27 -27
- package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -1
- package/esm/components/command-palette/index.spec.js +64 -51
- package/esm/components/command-palette/index.spec.js.map +1 -1
- package/esm/components/context-menu/context-menu.spec.js +33 -33
- package/esm/components/context-menu/context-menu.spec.js.map +1 -1
- package/esm/components/data-grid/body.spec.js +13 -13
- package/esm/components/data-grid/body.spec.js.map +1 -1
- package/esm/components/data-grid/data-grid-row.spec.js +8 -8
- package/esm/components/data-grid/data-grid-row.spec.js.map +1 -1
- package/esm/components/data-grid/data-grid.d.ts +40 -2
- package/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/esm/components/data-grid/data-grid.js +7 -10
- package/esm/components/data-grid/data-grid.js.map +1 -1
- package/esm/components/data-grid/data-grid.spec.js +71 -28
- package/esm/components/data-grid/data-grid.spec.js.map +1 -1
- package/esm/components/data-grid/filters/boolean-filter.d.ts +12 -0
- package/esm/components/data-grid/filters/boolean-filter.d.ts.map +1 -0
- package/esm/components/data-grid/filters/boolean-filter.js +27 -0
- package/esm/components/data-grid/filters/boolean-filter.js.map +1 -0
- package/esm/components/data-grid/filters/boolean-filter.spec.d.ts +2 -0
- package/esm/components/data-grid/filters/boolean-filter.spec.d.ts.map +1 -0
- package/esm/components/data-grid/filters/boolean-filter.spec.js +114 -0
- package/esm/components/data-grid/filters/boolean-filter.spec.js.map +1 -0
- package/esm/components/data-grid/filters/date-filter.d.ts +12 -0
- package/esm/components/data-grid/filters/date-filter.d.ts.map +1 -0
- package/esm/components/data-grid/filters/date-filter.js +109 -0
- package/esm/components/data-grid/filters/date-filter.js.map +1 -0
- package/esm/components/data-grid/filters/date-filter.spec.d.ts +2 -0
- package/esm/components/data-grid/filters/date-filter.spec.d.ts.map +1 -0
- package/esm/components/data-grid/filters/date-filter.spec.js +145 -0
- package/esm/components/data-grid/filters/date-filter.spec.js.map +1 -0
- package/esm/components/data-grid/filters/enum-filter.d.ts +16 -0
- package/esm/components/data-grid/filters/enum-filter.d.ts.map +1 -0
- package/esm/components/data-grid/filters/enum-filter.js +72 -0
- package/esm/components/data-grid/filters/enum-filter.js.map +1 -0
- package/esm/components/data-grid/filters/enum-filter.spec.d.ts +2 -0
- package/esm/components/data-grid/filters/enum-filter.spec.d.ts.map +1 -0
- package/esm/components/data-grid/filters/enum-filter.spec.js +136 -0
- package/esm/components/data-grid/filters/enum-filter.spec.js.map +1 -0
- package/esm/components/data-grid/filters/filter-dropdown.d.ts +6 -0
- package/esm/components/data-grid/filters/filter-dropdown.d.ts.map +1 -0
- package/esm/components/data-grid/filters/filter-dropdown.js +41 -0
- package/esm/components/data-grid/filters/filter-dropdown.js.map +1 -0
- package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts +2 -0
- package/esm/components/data-grid/filters/filter-dropdown.spec.d.ts.map +1 -0
- package/esm/components/data-grid/filters/filter-dropdown.spec.js +69 -0
- package/esm/components/data-grid/filters/filter-dropdown.spec.js.map +1 -0
- package/esm/components/data-grid/filters/filter-styles.d.ts +24 -0
- package/esm/components/data-grid/filters/filter-styles.d.ts.map +1 -0
- package/esm/components/data-grid/filters/filter-styles.js +25 -0
- package/esm/components/data-grid/filters/filter-styles.js.map +1 -0
- package/esm/components/data-grid/filters/index.d.ts +7 -0
- package/esm/components/data-grid/filters/index.d.ts.map +1 -0
- package/esm/components/data-grid/filters/index.js +7 -0
- package/esm/components/data-grid/filters/index.js.map +1 -0
- package/esm/components/data-grid/filters/number-filter.d.ts +12 -0
- package/esm/components/data-grid/filters/number-filter.d.ts.map +1 -0
- package/esm/components/data-grid/filters/number-filter.js +65 -0
- package/esm/components/data-grid/filters/number-filter.js.map +1 -0
- package/esm/components/data-grid/filters/number-filter.spec.d.ts +2 -0
- package/esm/components/data-grid/filters/number-filter.spec.d.ts.map +1 -0
- package/esm/components/data-grid/filters/number-filter.spec.js +142 -0
- package/esm/components/data-grid/filters/number-filter.spec.js.map +1 -0
- package/esm/components/data-grid/filters/string-filter.d.ts +12 -0
- package/esm/components/data-grid/filters/string-filter.d.ts.map +1 -0
- package/esm/components/data-grid/filters/string-filter.js +63 -0
- package/esm/components/data-grid/filters/string-filter.js.map +1 -0
- package/esm/components/data-grid/filters/string-filter.spec.d.ts +2 -0
- package/esm/components/data-grid/filters/string-filter.spec.d.ts.map +1 -0
- package/esm/components/data-grid/filters/string-filter.spec.js +128 -0
- package/esm/components/data-grid/filters/string-filter.spec.js.map +1 -0
- package/esm/components/data-grid/footer.d.ts.map +1 -1
- package/esm/components/data-grid/footer.js +24 -9
- package/esm/components/data-grid/footer.js.map +1 -1
- package/esm/components/data-grid/footer.spec.js +38 -36
- package/esm/components/data-grid/footer.spec.js.map +1 -1
- package/esm/components/data-grid/header.d.ts +6 -9
- package/esm/components/data-grid/header.d.ts.map +1 -1
- package/esm/components/data-grid/header.js +51 -117
- package/esm/components/data-grid/header.js.map +1 -1
- package/esm/components/data-grid/header.spec.js +116 -187
- package/esm/components/data-grid/header.spec.js.map +1 -1
- package/esm/components/data-grid/index.d.ts +1 -0
- package/esm/components/data-grid/index.d.ts.map +1 -1
- package/esm/components/data-grid/index.js +1 -0
- package/esm/components/data-grid/index.js.map +1 -1
- package/esm/components/data-grid/selection-cell.spec.js +8 -8
- package/esm/components/data-grid/selection-cell.spec.js.map +1 -1
- package/esm/components/drawer/drawer-toggle-button.spec.js +22 -22
- package/esm/components/drawer/drawer-toggle-button.spec.js.map +1 -1
- package/esm/components/drawer/index.spec.js +36 -36
- package/esm/components/drawer/index.spec.js.map +1 -1
- package/esm/components/dropdown.spec.js +38 -30
- package/esm/components/dropdown.spec.js.map +1 -1
- package/esm/components/fab.spec.js +4 -4
- package/esm/components/fab.spec.js.map +1 -1
- package/esm/components/form.d.ts +5 -2
- package/esm/components/form.d.ts.map +1 -1
- package/esm/components/form.js +28 -6
- package/esm/components/form.js.map +1 -1
- package/esm/components/form.spec.js +227 -20
- package/esm/components/form.spec.js.map +1 -1
- package/esm/components/grid.spec.js +3 -3
- package/esm/components/grid.spec.js.map +1 -1
- package/esm/components/image.spec.js +55 -52
- package/esm/components/image.spec.js.map +1 -1
- package/esm/components/inputs/autocomplete.spec.js +7 -14
- package/esm/components/inputs/autocomplete.spec.js.map +1 -1
- package/esm/components/inputs/checkbox.spec.js +22 -22
- package/esm/components/inputs/checkbox.spec.js.map +1 -1
- package/esm/components/inputs/input-number.spec.js +47 -47
- package/esm/components/inputs/input-number.spec.js.map +1 -1
- package/esm/components/inputs/input.spec.js +53 -53
- package/esm/components/inputs/input.spec.js.map +1 -1
- package/esm/components/inputs/radio-group.spec.js +14 -14
- package/esm/components/inputs/radio-group.spec.js.map +1 -1
- package/esm/components/inputs/radio.spec.js +16 -16
- package/esm/components/inputs/radio.spec.js.map +1 -1
- package/esm/components/inputs/select.spec.js +74 -74
- package/esm/components/inputs/select.spec.js.map +1 -1
- package/esm/components/inputs/slider.spec.js +16 -16
- package/esm/components/inputs/slider.spec.js.map +1 -1
- package/esm/components/inputs/switch.spec.js +24 -24
- package/esm/components/inputs/switch.spec.js.map +1 -1
- package/esm/components/inputs/text-area.spec.js +17 -17
- package/esm/components/inputs/text-area.spec.js.map +1 -1
- package/esm/components/linear-progress.spec.js +2 -2
- package/esm/components/list/list.spec.js +36 -36
- package/esm/components/list/list.spec.js.map +1 -1
- package/esm/components/markdown/markdown-display.spec.js +15 -15
- package/esm/components/markdown/markdown-display.spec.js.map +1 -1
- package/esm/components/markdown/markdown-editor.spec.js +8 -8
- package/esm/components/markdown/markdown-editor.spec.js.map +1 -1
- package/esm/components/markdown/markdown-input.spec.js +17 -17
- package/esm/components/markdown/markdown-input.spec.js.map +1 -1
- package/esm/components/menu/menu.spec.js +28 -28
- package/esm/components/menu/menu.spec.js.map +1 -1
- package/esm/components/modal.spec.js +15 -18
- package/esm/components/modal.spec.js.map +1 -1
- package/esm/components/noty-list.spec.js +25 -23
- package/esm/components/noty-list.spec.js.map +1 -1
- package/esm/components/page-container/index.spec.js +16 -16
- package/esm/components/page-container/index.spec.js.map +1 -1
- package/esm/components/page-container/page-header.spec.js +16 -16
- package/esm/components/page-container/page-header.spec.js.map +1 -1
- package/esm/components/page-layout/index.spec.js +29 -29
- package/esm/components/page-layout/index.spec.js.map +1 -1
- package/esm/components/paper.spec.js +3 -3
- package/esm/components/paper.spec.js.map +1 -1
- package/esm/components/rating.spec.js +61 -61
- package/esm/components/rating.spec.js.map +1 -1
- package/esm/components/skeleton.spec.js +10 -6
- package/esm/components/skeleton.spec.js.map +1 -1
- package/esm/components/suggest/suggest-input.spec.js +4 -10
- package/esm/components/suggest/suggest-input.spec.js.map +1 -1
- package/esm/components/tabs.spec.js +30 -30
- package/esm/components/tabs.spec.js.map +1 -1
- package/esm/components/tree/tree.spec.js +27 -27
- package/esm/components/tree/tree.spec.js.map +1 -1
- package/esm/components/typography.spec.js +3 -3
- package/esm/components/typography.spec.js.map +1 -1
- package/esm/components/wizard/index.spec.js +5 -5
- package/esm/components/wizard/index.spec.js.map +1 -1
- package/esm/utils/promisify-animation.d.ts.map +1 -1
- package/esm/utils/promisify-animation.js +3 -0
- package/esm/utils/promisify-animation.js.map +1 -1
- package/package.json +2 -2
- package/src/components/app-bar-link.spec.tsx +16 -19
- package/src/components/app-bar.spec.tsx +6 -4
- package/src/components/avatar.spec.tsx +9 -9
- package/src/components/breadcrumb.spec.tsx +2 -2
- package/src/components/button-group.spec.tsx +155 -11
- package/src/components/button-group.tsx +49 -2
- package/src/components/button.spec.tsx +4 -4
- package/src/components/cache-view.spec.tsx +3 -3
- package/src/components/carousel.spec.tsx +47 -47
- package/src/components/circular-progress.spec.tsx +2 -2
- package/src/components/command-palette/command-palette-input.spec.tsx +23 -19
- package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +27 -27
- package/src/components/command-palette/index.spec.tsx +64 -51
- package/src/components/context-menu/context-menu.spec.tsx +33 -33
- package/src/components/data-grid/body.spec.tsx +13 -13
- package/src/components/data-grid/data-grid-row.spec.tsx +8 -8
- package/src/components/data-grid/data-grid.spec.tsx +106 -28
- package/src/components/data-grid/data-grid.tsx +44 -11
- package/src/components/data-grid/filters/boolean-filter.spec.tsx +142 -0
- package/src/components/data-grid/filters/boolean-filter.tsx +45 -0
- package/src/components/data-grid/filters/date-filter.spec.tsx +181 -0
- package/src/components/data-grid/filters/date-filter.tsx +162 -0
- package/src/components/data-grid/filters/enum-filter.spec.tsx +168 -0
- package/src/components/data-grid/filters/enum-filter.tsx +119 -0
- package/src/components/data-grid/filters/filter-dropdown.spec.tsx +89 -0
- package/src/components/data-grid/filters/filter-dropdown.tsx +60 -0
- package/src/components/data-grid/filters/filter-styles.ts +26 -0
- package/src/components/data-grid/filters/index.ts +6 -0
- package/src/components/data-grid/filters/number-filter.spec.tsx +174 -0
- package/src/components/data-grid/filters/number-filter.tsx +115 -0
- package/src/components/data-grid/filters/string-filter.spec.tsx +157 -0
- package/src/components/data-grid/filters/string-filter.tsx +112 -0
- package/src/components/data-grid/footer.spec.tsx +38 -36
- package/src/components/data-grid/footer.tsx +21 -8
- package/src/components/data-grid/header.spec.tsx +128 -212
- package/src/components/data-grid/header.tsx +95 -183
- package/src/components/data-grid/index.tsx +1 -0
- package/src/components/data-grid/selection-cell.spec.tsx +8 -8
- package/src/components/drawer/drawer-toggle-button.spec.tsx +22 -22
- package/src/components/drawer/index.spec.tsx +36 -36
- package/src/components/dropdown.spec.tsx +38 -30
- package/src/components/fab.spec.tsx +4 -4
- package/src/components/form.spec.tsx +329 -20
- package/src/components/form.tsx +31 -8
- package/src/components/grid.spec.tsx +3 -3
- package/src/components/image.spec.tsx +55 -52
- package/src/components/inputs/autocomplete.spec.tsx +7 -14
- package/src/components/inputs/checkbox.spec.tsx +22 -22
- package/src/components/inputs/input-number.spec.tsx +47 -47
- package/src/components/inputs/input.spec.tsx +53 -53
- package/src/components/inputs/radio-group.spec.tsx +14 -14
- package/src/components/inputs/radio.spec.tsx +16 -16
- package/src/components/inputs/select.spec.tsx +74 -74
- package/src/components/inputs/slider.spec.tsx +16 -16
- package/src/components/inputs/switch.spec.tsx +24 -24
- package/src/components/inputs/text-area.spec.tsx +17 -17
- package/src/components/linear-progress.spec.tsx +2 -2
- package/src/components/list/list.spec.tsx +36 -36
- package/src/components/markdown/markdown-display.spec.tsx +15 -15
- package/src/components/markdown/markdown-editor.spec.tsx +8 -8
- package/src/components/markdown/markdown-input.spec.tsx +17 -17
- package/src/components/menu/menu.spec.tsx +28 -28
- package/src/components/modal.spec.tsx +15 -18
- package/src/components/noty-list.spec.tsx +25 -23
- package/src/components/page-container/index.spec.tsx +16 -16
- package/src/components/page-container/page-header.spec.tsx +16 -16
- package/src/components/page-layout/index.spec.tsx +29 -29
- package/src/components/paper.spec.tsx +3 -3
- package/src/components/rating.spec.tsx +61 -61
- package/src/components/skeleton.spec.tsx +10 -6
- package/src/components/suggest/suggest-input.spec.tsx +4 -10
- package/src/components/tabs.spec.tsx +30 -30
- package/src/components/tree/tree.spec.tsx +27 -27
- package/src/components/typography.spec.tsx +3 -3
- package/src/components/wizard/index.spec.tsx +5 -5
- package/src/utils/promisify-animation.ts +3 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { usingAsync } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { FilterDropdown } from './filter-dropdown.js'
|
|
6
|
+
|
|
7
|
+
describe('FilterDropdown', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.body.innerHTML = ''
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should render children inside panel', async () => {
|
|
17
|
+
const onClose = vi.fn()
|
|
18
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
19
|
+
const rootElement = document.getElementById('root')!
|
|
20
|
+
initializeShadeRoot({
|
|
21
|
+
injector,
|
|
22
|
+
rootElement,
|
|
23
|
+
jsxElement: (
|
|
24
|
+
<FilterDropdown onClose={onClose}>
|
|
25
|
+
<span className="test-child">Hello</span>
|
|
26
|
+
</FilterDropdown>
|
|
27
|
+
),
|
|
28
|
+
})
|
|
29
|
+
await flushUpdates()
|
|
30
|
+
|
|
31
|
+
const dropdown = document.querySelector('data-grid-filter-dropdown')
|
|
32
|
+
expect(dropdown).not.toBeNull()
|
|
33
|
+
|
|
34
|
+
const panel = dropdown?.querySelector('.filter-dropdown-panel')
|
|
35
|
+
expect(panel).not.toBeNull()
|
|
36
|
+
|
|
37
|
+
const child = panel?.querySelector('.test-child')
|
|
38
|
+
expect(child).not.toBeNull()
|
|
39
|
+
expect(child?.textContent).toBe('Hello')
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should add visible class after animation frame', async () => {
|
|
44
|
+
const onClose = vi.fn()
|
|
45
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
46
|
+
const rootElement = document.getElementById('root')!
|
|
47
|
+
initializeShadeRoot({
|
|
48
|
+
injector,
|
|
49
|
+
rootElement,
|
|
50
|
+
jsxElement: (
|
|
51
|
+
<FilterDropdown onClose={onClose}>
|
|
52
|
+
<span>Content</span>
|
|
53
|
+
</FilterDropdown>
|
|
54
|
+
),
|
|
55
|
+
})
|
|
56
|
+
await flushUpdates()
|
|
57
|
+
await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
|
|
58
|
+
|
|
59
|
+
const panel = document.querySelector('.filter-dropdown-panel')
|
|
60
|
+
expect(panel?.classList.contains('visible')).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should stop click propagation on panel', async () => {
|
|
65
|
+
const onClose = vi.fn()
|
|
66
|
+
const outerClick = vi.fn()
|
|
67
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
68
|
+
const rootElement = document.getElementById('root')!
|
|
69
|
+
initializeShadeRoot({
|
|
70
|
+
injector,
|
|
71
|
+
rootElement,
|
|
72
|
+
jsxElement: (
|
|
73
|
+
<div onclick={outerClick}>
|
|
74
|
+
<FilterDropdown onClose={onClose}>
|
|
75
|
+
<span className="inner">Content</span>
|
|
76
|
+
</FilterDropdown>
|
|
77
|
+
</div>
|
|
78
|
+
),
|
|
79
|
+
})
|
|
80
|
+
await flushUpdates()
|
|
81
|
+
|
|
82
|
+
const panel = document.querySelector('.filter-dropdown-panel') as HTMLElement
|
|
83
|
+
panel?.click()
|
|
84
|
+
await flushUpdates()
|
|
85
|
+
|
|
86
|
+
expect(outerClick).not.toHaveBeenCalled()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ChildrenList } from '@furystack/shades'
|
|
2
|
+
import { createComponent, Shade } from '@furystack/shades'
|
|
3
|
+
import { ClickAwayService } from '../../../services/click-away-service.js'
|
|
4
|
+
import { buildTransition, cssVariableTheme } from '../../../services/css-variable-theme.js'
|
|
5
|
+
|
|
6
|
+
export type FilterDropdownProps = {
|
|
7
|
+
onClose: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FilterDropdown: (props: FilterDropdownProps, children: ChildrenList) => JSX.Element = Shade({
|
|
11
|
+
shadowDomName: 'data-grid-filter-dropdown',
|
|
12
|
+
css: {
|
|
13
|
+
display: 'block',
|
|
14
|
+
position: 'absolute',
|
|
15
|
+
top: '100%',
|
|
16
|
+
left: '0',
|
|
17
|
+
zIndex: '10',
|
|
18
|
+
'& .filter-dropdown-panel': {
|
|
19
|
+
background: cssVariableTheme.background.paper,
|
|
20
|
+
borderRadius: cssVariableTheme.shape.borderRadius.md,
|
|
21
|
+
boxShadow: cssVariableTheme.shadows.lg,
|
|
22
|
+
border: `1px solid ${cssVariableTheme.divider}`,
|
|
23
|
+
padding: cssVariableTheme.spacing.md,
|
|
24
|
+
opacity: '0',
|
|
25
|
+
transform: 'scale(0.95) translateY(-4px)',
|
|
26
|
+
transition: buildTransition(
|
|
27
|
+
['opacity', cssVariableTheme.transitions.duration.fast, 'ease-out'],
|
|
28
|
+
['transform', cssVariableTheme.transitions.duration.fast, 'ease-out'],
|
|
29
|
+
),
|
|
30
|
+
},
|
|
31
|
+
'& .filter-dropdown-panel.visible': {
|
|
32
|
+
opacity: '1',
|
|
33
|
+
transform: 'scale(1) translateY(0)',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
render: ({ props, children, useDisposable, useRef }) => {
|
|
37
|
+
const panelRef = useRef<HTMLDivElement>('panel')
|
|
38
|
+
|
|
39
|
+
useDisposable(
|
|
40
|
+
'clickAway',
|
|
41
|
+
() =>
|
|
42
|
+
new ClickAwayService(panelRef, () => {
|
|
43
|
+
props.onClose()
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
useDisposable('animateIn', () => {
|
|
48
|
+
const id = requestAnimationFrame(() => {
|
|
49
|
+
panelRef.current?.classList.add('visible')
|
|
50
|
+
})
|
|
51
|
+
return { [Symbol.dispose]: () => cancelAnimationFrame(id) }
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div ref={panelRef} className="filter-dropdown-panel" onclick={(ev: MouseEvent) => ev.stopPropagation()}>
|
|
56
|
+
{children}
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
},
|
|
60
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cssVariableTheme } from '../../../services/css-variable-theme.js'
|
|
2
|
+
|
|
3
|
+
export const filterBaseCss = {
|
|
4
|
+
display: 'block' as const,
|
|
5
|
+
'& .filter-row': {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
gap: '6px',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
marginBottom: '8px',
|
|
10
|
+
},
|
|
11
|
+
'& .filter-actions': {
|
|
12
|
+
display: 'flex',
|
|
13
|
+
justifyContent: 'flex-end',
|
|
14
|
+
gap: '4px',
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const filterInputCss = {
|
|
19
|
+
flex: '1',
|
|
20
|
+
padding: '4px 6px',
|
|
21
|
+
borderRadius: cssVariableTheme.shape.borderRadius.sm,
|
|
22
|
+
border: `1px solid ${cssVariableTheme.divider}`,
|
|
23
|
+
background: cssVariableTheme.background.default,
|
|
24
|
+
color: cssVariableTheme.text.primary,
|
|
25
|
+
fontSize: cssVariableTheme.typography.fontSize.xs,
|
|
26
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { ObservableValue, usingAsync } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import type { FilterableFindOptions } from '../data-grid.js'
|
|
6
|
+
import { NumberFilter } from './number-filter.js'
|
|
7
|
+
|
|
8
|
+
describe('NumberFilter', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
document.body.innerHTML = ''
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const createFindOptions = (options: Partial<FilterableFindOptions> = {}): ObservableValue<FilterableFindOptions> => {
|
|
18
|
+
return new ObservableValue<FilterableFindOptions>(options)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const renderNumberFilter = async (
|
|
22
|
+
findOptions: ObservableValue<FilterableFindOptions>,
|
|
23
|
+
field = 'level',
|
|
24
|
+
onClose = vi.fn(),
|
|
25
|
+
) => {
|
|
26
|
+
const injector = new Injector()
|
|
27
|
+
const rootElement = document.getElementById('root')!
|
|
28
|
+
initializeShadeRoot({
|
|
29
|
+
injector,
|
|
30
|
+
rootElement,
|
|
31
|
+
jsxElement: <NumberFilter field={field} findOptions={findOptions} onClose={onClose} />,
|
|
32
|
+
})
|
|
33
|
+
await flushUpdates()
|
|
34
|
+
return { injector, onClose }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it('should render operator segmented control and input', async () => {
|
|
38
|
+
const findOptions = createFindOptions()
|
|
39
|
+
await usingAsync((await renderNumberFilter(findOptions)).injector, async () => {
|
|
40
|
+
const control = document.querySelector('shade-segmented-control')
|
|
41
|
+
expect(control).not.toBeNull()
|
|
42
|
+
|
|
43
|
+
const input = document.querySelector('[data-testid="number-filter-value"]')
|
|
44
|
+
expect(input).not.toBeNull()
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should apply $eq filter on submit', async () => {
|
|
49
|
+
const findOptions = createFindOptions()
|
|
50
|
+
const { injector, onClose } = await renderNumberFilter(findOptions)
|
|
51
|
+
await usingAsync(injector, async () => {
|
|
52
|
+
const input = document.querySelector('[data-testid="number-filter-value"]') as HTMLInputElement
|
|
53
|
+
input.value = '42'
|
|
54
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
55
|
+
|
|
56
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
57
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
58
|
+
await flushUpdates()
|
|
59
|
+
|
|
60
|
+
expect(findOptions.getValue().filter).toEqual({ level: { $eq: 42 } })
|
|
61
|
+
expect(onClose).toHaveBeenCalled()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should apply $gt operator when selected', async () => {
|
|
66
|
+
const findOptions = createFindOptions()
|
|
67
|
+
const { injector, onClose } = await renderNumberFilter(findOptions)
|
|
68
|
+
await usingAsync(injector, async () => {
|
|
69
|
+
const gtButton = document.querySelector('shade-segmented-control button[data-value="$gt"]') as HTMLButtonElement
|
|
70
|
+
gtButton?.click()
|
|
71
|
+
await flushUpdates()
|
|
72
|
+
|
|
73
|
+
const input = document.querySelector('[data-testid="number-filter-value"]') as HTMLInputElement
|
|
74
|
+
input.value = '10'
|
|
75
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
76
|
+
|
|
77
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
78
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
79
|
+
await flushUpdates()
|
|
80
|
+
|
|
81
|
+
expect(findOptions.getValue().filter).toEqual({ level: { $gt: 10 } })
|
|
82
|
+
expect(onClose).toHaveBeenCalled()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('should apply $lte operator when selected', async () => {
|
|
87
|
+
const findOptions = createFindOptions()
|
|
88
|
+
const { injector, onClose } = await renderNumberFilter(findOptions)
|
|
89
|
+
await usingAsync(injector, async () => {
|
|
90
|
+
const lteButton = document.querySelector('shade-segmented-control button[data-value="$lte"]') as HTMLButtonElement
|
|
91
|
+
lteButton?.click()
|
|
92
|
+
await flushUpdates()
|
|
93
|
+
|
|
94
|
+
const input = document.querySelector('[data-testid="number-filter-value"]') as HTMLInputElement
|
|
95
|
+
input.value = '99.5'
|
|
96
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
97
|
+
|
|
98
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
99
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
100
|
+
await flushUpdates()
|
|
101
|
+
|
|
102
|
+
expect(findOptions.getValue().filter).toEqual({ level: { $lte: 99.5 } })
|
|
103
|
+
expect(onClose).toHaveBeenCalled()
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should clear filter when Clear button is clicked', async () => {
|
|
108
|
+
const findOptions = createFindOptions({ filter: { level: { $eq: 5 } } })
|
|
109
|
+
const { injector, onClose } = await renderNumberFilter(findOptions)
|
|
110
|
+
await usingAsync(injector, async () => {
|
|
111
|
+
const clearButton = Array.from(document.querySelectorAll('button')).find((b) => b.textContent?.trim() === 'Clear')
|
|
112
|
+
clearButton?.click()
|
|
113
|
+
await flushUpdates()
|
|
114
|
+
|
|
115
|
+
expect(findOptions.getValue().filter?.level).toBeUndefined()
|
|
116
|
+
expect(onClose).toHaveBeenCalled()
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should remove filter when submitting NaN value', async () => {
|
|
121
|
+
const findOptions = createFindOptions({ filter: { level: { $eq: 5 } } })
|
|
122
|
+
const { injector, onClose } = await renderNumberFilter(findOptions)
|
|
123
|
+
await usingAsync(injector, async () => {
|
|
124
|
+
const input = document.querySelector('[data-testid="number-filter-value"]') as HTMLInputElement
|
|
125
|
+
input.value = ''
|
|
126
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
127
|
+
|
|
128
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
129
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
130
|
+
await flushUpdates()
|
|
131
|
+
|
|
132
|
+
expect(findOptions.getValue().filter?.level).toBeUndefined()
|
|
133
|
+
expect(onClose).toHaveBeenCalled()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should preserve filters on other fields', async () => {
|
|
138
|
+
const findOptions = createFindOptions({ filter: { level: { $gt: 10 }, name: { $regex: 'keep' } } })
|
|
139
|
+
const { injector } = await renderNumberFilter(findOptions)
|
|
140
|
+
await usingAsync(injector, async () => {
|
|
141
|
+
const clearButton = Array.from(document.querySelectorAll('button')).find((b) => b.textContent?.trim() === 'Clear')
|
|
142
|
+
clearButton?.click()
|
|
143
|
+
await flushUpdates()
|
|
144
|
+
|
|
145
|
+
const updatedFilter = findOptions.getValue().filter
|
|
146
|
+
expect(updatedFilter?.level).toBeUndefined()
|
|
147
|
+
expect(updatedFilter?.name).toEqual({ $regex: 'keep' })
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should reset skip to 0 when applying filter', async () => {
|
|
152
|
+
const findOptions = createFindOptions({ skip: 20 })
|
|
153
|
+
const { injector } = await renderNumberFilter(findOptions)
|
|
154
|
+
await usingAsync(injector, async () => {
|
|
155
|
+
const input = document.querySelector('[data-testid="number-filter-value"]') as HTMLInputElement
|
|
156
|
+
input.value = '5'
|
|
157
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
158
|
+
|
|
159
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
160
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
161
|
+
await flushUpdates()
|
|
162
|
+
|
|
163
|
+
expect(findOptions.getValue().skip).toBe(0)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should show current filter value in input', async () => {
|
|
168
|
+
const findOptions = createFindOptions({ filter: { level: { $eq: 25 } } })
|
|
169
|
+
await usingAsync((await renderNumberFilter(findOptions)).injector, async () => {
|
|
170
|
+
const input = document.querySelector('[data-testid="number-filter-value"]') as HTMLInputElement
|
|
171
|
+
expect(input.value).toBe('25')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createComponent, Shade } from '@furystack/shades'
|
|
2
|
+
import type { ObservableValue } from '@furystack/utils'
|
|
3
|
+
import { Button } from '../../button.js'
|
|
4
|
+
import { SegmentedControl } from '../../button-group.js'
|
|
5
|
+
import { Icon } from '../../icons/icon.js'
|
|
6
|
+
import { close as closeIcon, search as searchIcon } from '../../icons/icon-definitions.js'
|
|
7
|
+
import type { FilterableFindOptions } from '../data-grid.js'
|
|
8
|
+
import { filterBaseCss, filterInputCss } from './filter-styles.js'
|
|
9
|
+
|
|
10
|
+
type NumberOperator = '$eq' | '$gt' | '$gte' | '$lt' | '$lte'
|
|
11
|
+
|
|
12
|
+
const operatorLabels: Record<NumberOperator, string> = {
|
|
13
|
+
$eq: '=',
|
|
14
|
+
$gt: '>',
|
|
15
|
+
$gte: '>=',
|
|
16
|
+
$lt: '<',
|
|
17
|
+
$lte: '<=',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const NumberFilter = Shade<{
|
|
21
|
+
field: string
|
|
22
|
+
findOptions: ObservableValue<FilterableFindOptions>
|
|
23
|
+
onClose: () => void
|
|
24
|
+
}>({
|
|
25
|
+
shadowDomName: 'data-grid-number-filter',
|
|
26
|
+
css: {
|
|
27
|
+
...filterBaseCss,
|
|
28
|
+
'& input': filterInputCss,
|
|
29
|
+
},
|
|
30
|
+
render: ({ props, useObservable, useState }) => {
|
|
31
|
+
const [findOptions, setFindOptions] = useObservable('findOptions', props.findOptions)
|
|
32
|
+
|
|
33
|
+
const currentFilter = findOptions.filter?.[props.field] as Record<string, number> | undefined
|
|
34
|
+
const currentOperator: NumberOperator = currentFilter
|
|
35
|
+
? ((Object.keys(currentFilter).find((k) => k in operatorLabels) as NumberOperator) ?? '$eq')
|
|
36
|
+
: '$eq'
|
|
37
|
+
const currentValue = currentFilter?.[currentOperator]
|
|
38
|
+
|
|
39
|
+
const applyFilter = (operator: NumberOperator, value: string) => {
|
|
40
|
+
const num = parseFloat(value)
|
|
41
|
+
const filter = { ...findOptions.filter }
|
|
42
|
+
if (isNaN(num)) {
|
|
43
|
+
delete filter[props.field]
|
|
44
|
+
} else {
|
|
45
|
+
filter[props.field] = { [operator]: num }
|
|
46
|
+
}
|
|
47
|
+
setFindOptions({ ...findOptions, filter, skip: 0 })
|
|
48
|
+
props.onClose()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const clearFilter = () => {
|
|
52
|
+
const filter = { ...findOptions.filter }
|
|
53
|
+
delete filter[props.field]
|
|
54
|
+
setFindOptions({ ...findOptions, filter, skip: 0 })
|
|
55
|
+
props.onClose()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [selectedOperator, setSelectedOperator] = useState<NumberOperator>('selectedOperator', currentOperator)
|
|
59
|
+
let inputValue = currentValue !== undefined ? currentValue.toString() : ''
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<form
|
|
63
|
+
onsubmit={(ev: Event) => {
|
|
64
|
+
ev.preventDefault()
|
|
65
|
+
applyFilter(selectedOperator, inputValue)
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
<div className="filter-row">
|
|
69
|
+
<SegmentedControl
|
|
70
|
+
size="small"
|
|
71
|
+
value={selectedOperator}
|
|
72
|
+
onValueChange={(v) => setSelectedOperator(v as NumberOperator)}
|
|
73
|
+
options={(Object.keys(operatorLabels) as NumberOperator[]).map((op) => ({
|
|
74
|
+
value: op,
|
|
75
|
+
label: operatorLabels[op],
|
|
76
|
+
}))}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="filter-row">
|
|
80
|
+
<input
|
|
81
|
+
data-testid="number-filter-value"
|
|
82
|
+
type="number"
|
|
83
|
+
step="any"
|
|
84
|
+
placeholder="Value..."
|
|
85
|
+
value={inputValue}
|
|
86
|
+
autofocus
|
|
87
|
+
oninput={(ev: Event) => {
|
|
88
|
+
inputValue = (ev.target as HTMLInputElement).value
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="filter-actions">
|
|
93
|
+
<Button
|
|
94
|
+
type="button"
|
|
95
|
+
variant="outlined"
|
|
96
|
+
size="small"
|
|
97
|
+
onclick={clearFilter}
|
|
98
|
+
startIcon={<Icon icon={closeIcon} size={14} />}
|
|
99
|
+
>
|
|
100
|
+
Clear
|
|
101
|
+
</Button>
|
|
102
|
+
<Button
|
|
103
|
+
type="submit"
|
|
104
|
+
variant="outlined"
|
|
105
|
+
size="small"
|
|
106
|
+
color="primary"
|
|
107
|
+
startIcon={<Icon icon={searchIcon} size={14} />}
|
|
108
|
+
>
|
|
109
|
+
Apply
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
</form>
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
})
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Injector } from '@furystack/inject'
|
|
2
|
+
import { createComponent, flushUpdates, initializeShadeRoot } from '@furystack/shades'
|
|
3
|
+
import { ObservableValue, usingAsync } from '@furystack/utils'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import type { FilterableFindOptions } from '../data-grid.js'
|
|
6
|
+
import { StringFilter } from './string-filter.js'
|
|
7
|
+
|
|
8
|
+
describe('StringFilter', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
document.body.innerHTML = ''
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const createFindOptions = (options: Partial<FilterableFindOptions> = {}): ObservableValue<FilterableFindOptions> => {
|
|
18
|
+
return new ObservableValue<FilterableFindOptions>(options)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const renderStringFilter = async (
|
|
22
|
+
findOptions: ObservableValue<FilterableFindOptions>,
|
|
23
|
+
field = 'name',
|
|
24
|
+
onClose = vi.fn(),
|
|
25
|
+
) => {
|
|
26
|
+
const injector = new Injector()
|
|
27
|
+
const rootElement = document.getElementById('root')!
|
|
28
|
+
initializeShadeRoot({
|
|
29
|
+
injector,
|
|
30
|
+
rootElement,
|
|
31
|
+
jsxElement: <StringFilter field={field} findOptions={findOptions} onClose={onClose} />,
|
|
32
|
+
})
|
|
33
|
+
await flushUpdates()
|
|
34
|
+
return { injector, onClose }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
it('should render operator segmented control and input', async () => {
|
|
38
|
+
const findOptions = createFindOptions()
|
|
39
|
+
await usingAsync((await renderStringFilter(findOptions)).injector, async () => {
|
|
40
|
+
const control = document.querySelector('shade-segmented-control')
|
|
41
|
+
expect(control).not.toBeNull()
|
|
42
|
+
|
|
43
|
+
const input = document.querySelector('[data-testid="string-filter-value"]')
|
|
44
|
+
expect(input).not.toBeNull()
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should apply $regex filter on submit', async () => {
|
|
49
|
+
const findOptions = createFindOptions()
|
|
50
|
+
const { injector, onClose } = await renderStringFilter(findOptions)
|
|
51
|
+
await usingAsync(injector, async () => {
|
|
52
|
+
const input = document.querySelector('[data-testid="string-filter-value"]') as HTMLInputElement
|
|
53
|
+
input.value = 'test-value'
|
|
54
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
55
|
+
|
|
56
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
57
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
58
|
+
await flushUpdates()
|
|
59
|
+
|
|
60
|
+
expect(findOptions.getValue().filter).toEqual({ name: { $regex: 'test-value' } })
|
|
61
|
+
expect(onClose).toHaveBeenCalled()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should clear filter when Clear button is clicked', async () => {
|
|
66
|
+
const findOptions = createFindOptions({ filter: { name: { $regex: 'existing' } } })
|
|
67
|
+
const { injector, onClose } = await renderStringFilter(findOptions)
|
|
68
|
+
await usingAsync(injector, async () => {
|
|
69
|
+
const clearButton = Array.from(document.querySelectorAll('button')).find((b) => b.textContent?.trim() === 'Clear')
|
|
70
|
+
clearButton?.click()
|
|
71
|
+
await flushUpdates()
|
|
72
|
+
|
|
73
|
+
expect(findOptions.getValue().filter?.name).toBeUndefined()
|
|
74
|
+
expect(onClose).toHaveBeenCalled()
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should remove filter when submitting empty value', async () => {
|
|
79
|
+
const findOptions = createFindOptions({ filter: { name: { $regex: 'existing' } } })
|
|
80
|
+
const { injector, onClose } = await renderStringFilter(findOptions)
|
|
81
|
+
await usingAsync(injector, async () => {
|
|
82
|
+
const input = document.querySelector('[data-testid="string-filter-value"]') as HTMLInputElement
|
|
83
|
+
input.value = ''
|
|
84
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
85
|
+
|
|
86
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
87
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
88
|
+
await flushUpdates()
|
|
89
|
+
|
|
90
|
+
expect(findOptions.getValue().filter?.name).toBeUndefined()
|
|
91
|
+
expect(onClose).toHaveBeenCalled()
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should preserve filters on other fields', async () => {
|
|
96
|
+
const findOptions = createFindOptions({ filter: { name: { $regex: 'old' }, email: { $regex: 'keep' } } })
|
|
97
|
+
const { injector } = await renderStringFilter(findOptions)
|
|
98
|
+
await usingAsync(injector, async () => {
|
|
99
|
+
const input = document.querySelector('[data-testid="string-filter-value"]') as HTMLInputElement
|
|
100
|
+
input.value = 'new-value'
|
|
101
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
102
|
+
|
|
103
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
104
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
105
|
+
await flushUpdates()
|
|
106
|
+
|
|
107
|
+
const updatedFilter = findOptions.getValue().filter
|
|
108
|
+
expect(updatedFilter?.name).toEqual({ $regex: 'new-value' })
|
|
109
|
+
expect(updatedFilter?.email).toEqual({ $regex: 'keep' })
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should apply selected operator', async () => {
|
|
114
|
+
const findOptions = createFindOptions()
|
|
115
|
+
const { injector, onClose } = await renderStringFilter(findOptions)
|
|
116
|
+
await usingAsync(injector, async () => {
|
|
117
|
+
const eqButton = document.querySelector('shade-segmented-control button[data-value="$eq"]') as HTMLButtonElement
|
|
118
|
+
eqButton?.click()
|
|
119
|
+
await flushUpdates()
|
|
120
|
+
|
|
121
|
+
const input = document.querySelector('[data-testid="string-filter-value"]') as HTMLInputElement
|
|
122
|
+
input.value = 'exact'
|
|
123
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
124
|
+
|
|
125
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
126
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
127
|
+
await flushUpdates()
|
|
128
|
+
|
|
129
|
+
expect(findOptions.getValue().filter).toEqual({ name: { $eq: 'exact' } })
|
|
130
|
+
expect(onClose).toHaveBeenCalled()
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should reset skip to 0 when applying filter', async () => {
|
|
135
|
+
const findOptions = createFindOptions({ skip: 20 })
|
|
136
|
+
const { injector } = await renderStringFilter(findOptions)
|
|
137
|
+
await usingAsync(injector, async () => {
|
|
138
|
+
const input = document.querySelector('[data-testid="string-filter-value"]') as HTMLInputElement
|
|
139
|
+
input.value = 'test'
|
|
140
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
141
|
+
|
|
142
|
+
const form = document.querySelector('form') as HTMLFormElement
|
|
143
|
+
form.dispatchEvent(new Event('submit', { bubbles: true }))
|
|
144
|
+
await flushUpdates()
|
|
145
|
+
|
|
146
|
+
expect(findOptions.getValue().skip).toBe(0)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should show current filter value in input', async () => {
|
|
151
|
+
const findOptions = createFindOptions({ filter: { name: { $regex: 'current-value' } } })
|
|
152
|
+
await usingAsync((await renderStringFilter(findOptions)).injector, async () => {
|
|
153
|
+
const input = document.querySelector('[data-testid="string-filter-value"]') as HTMLInputElement
|
|
154
|
+
expect(input.value).toBe('current-value')
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
})
|