@svgrid/grid 2.1.22 → 2.2.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/README.md +9 -2
- package/dist/DockNodeView.svelte +306 -0
- package/dist/DockNodeView.svelte.d.ts +7 -0
- package/dist/DockPopoutHost.svelte +17 -0
- package/dist/DockPopoutHost.svelte.d.ts +14 -0
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -178
- package/dist/GridMenus.svelte +83 -3
- package/dist/SvAutoComplete.svelte +5 -3
- package/dist/SvCarousel.svelte +141 -163
- package/dist/SvColorInput.svelte +187 -186
- package/dist/SvColorInput.svelte.d.ts +1 -1
- package/dist/SvComboBox.svelte +7 -3
- package/dist/SvCommand.svelte +28 -85
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvDateTimePicker.svelte +9 -3
- package/dist/SvDateTimePicker.svelte.d.ts +3 -0
- package/dist/SvDockLayout.svelte +244 -0
- package/dist/SvDockLayout.svelte.d.ts +77 -0
- package/dist/SvDockManager.svelte +922 -0
- package/dist/SvDockManager.svelte.d.ts +106 -0
- package/dist/SvDrawer.svelte +250 -228
- package/dist/SvDrawer.svelte.d.ts +4 -0
- package/dist/SvDropDownList.svelte +85 -13
- package/dist/SvDropDownList.svelte.d.ts +3 -0
- package/dist/SvField.svelte +21 -4
- package/dist/SvField.svelte.d.ts +1 -0
- package/dist/SvForm.svelte +201 -91
- package/dist/SvForm.svelte.d.ts +15 -22
- package/dist/SvGrid.controller.svelte.d.ts +32 -1
- package/dist/SvGrid.controller.svelte.js +156 -27
- package/dist/SvGrid.css +193 -5
- package/dist/SvGrid.svelte +3131 -2786
- package/dist/SvGrid.types.d.ts +452 -1
- package/dist/SvGridBoard.svelte +2339 -2339
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridDropdown.svelte +689 -682
- package/dist/SvGridSelect.svelte +1 -0
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +36 -14
- package/dist/SvMaskedInput.svelte +4 -3
- package/dist/SvMaskedInput.svelte.d.ts +1 -1
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +117 -165
- package/dist/SvMenuList.svelte.d.ts +2 -13
- package/dist/SvModal.svelte +12 -29
- package/dist/SvMultiSelect.svelte +1 -0
- package/dist/SvNumberInput.svelte +131 -130
- package/dist/SvNumberInput.svelte.d.ts +1 -1
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPagination.svelte +18 -18
- package/dist/SvPasswordInput.svelte +3 -2
- package/dist/SvPasswordInput.svelte.d.ts +1 -1
- package/dist/SvPhoneInput.svelte +3 -2
- package/dist/SvPhoneInput.svelte.d.ts +1 -1
- package/dist/SvPopover.svelte +139 -139
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvStepper.svelte +6 -9
- package/dist/SvTagsInput.svelte +3 -2
- package/dist/SvTagsInput.svelte.d.ts +1 -1
- package/dist/SvTooltip.svelte +127 -138
- package/dist/SvTour.svelte +204 -176
- package/dist/SvTree.svelte +377 -377
- package/dist/SvTreeSelect.svelte +230 -229
- package/dist/build-api.js +76 -54
- package/dist/cdn/svgrid.js +10913 -6450
- package/dist/cdn/svgrid.svelte-external.js +9628 -5481
- package/dist/chart-export.js +8 -8
- package/dist/columns.js +6 -0
- package/dist/conditional-formatting.d.ts +67 -3
- package/dist/conditional-formatting.js +124 -17
- package/dist/createCarousel.svelte.d.ts +72 -0
- package/dist/createCarousel.svelte.js +80 -0
- package/dist/createCommand.svelte.d.ts +62 -0
- package/dist/createCommand.svelte.js +109 -0
- package/dist/createForm.svelte.d.ts +62 -0
- package/dist/createForm.svelte.js +374 -0
- package/dist/createMenu.svelte.d.ts +88 -0
- package/dist/createMenu.svelte.js +153 -0
- package/dist/createOverlay.svelte.d.ts +38 -0
- package/dist/createOverlay.svelte.js +68 -0
- package/dist/createPagination.d.ts +74 -0
- package/dist/createPagination.js +71 -0
- package/dist/createPopoverSelect.svelte.js +6 -12
- package/dist/createStepper.d.ts +44 -0
- package/dist/createStepper.js +26 -0
- package/dist/createTooltip.svelte.d.ts +32 -0
- package/dist/createTooltip.svelte.js +61 -0
- package/dist/datetime/timezone.d.ts +36 -0
- package/dist/datetime/timezone.js +110 -0
- package/dist/dock-context.d.ts +52 -0
- package/dist/dock-context.js +1 -0
- package/dist/dock-manager-model.d.ts +136 -0
- package/dist/dock-manager-model.js +514 -0
- package/dist/dock-model.d.ts +105 -0
- package/dist/dock-model.js +288 -0
- package/dist/dock-popout.d.ts +30 -0
- package/dist/dock-popout.js +81 -0
- package/dist/editor-contract.d.ts +2 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +35 -2
- package/dist/filtering/excel-filters.d.ts +18 -1
- package/dist/filtering/excel-filters.js +56 -0
- package/dist/form-field.d.ts +73 -0
- package/dist/form-field.js +15 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.js +40 -4
- package/dist/list-nav.d.ts +18 -0
- package/dist/list-nav.js +30 -0
- package/dist/list-option.d.ts +2 -0
- package/dist/menu-item.d.ts +20 -0
- package/dist/menu-item.js +1 -0
- package/dist/menus.d.ts +5 -0
- package/dist/menus.js +68 -16
- package/dist/panel-resize.d.ts +25 -0
- package/dist/panel-resize.js +40 -0
- package/dist/popover.d.ts +29 -3
- package/dist/popover.js +22 -6
- package/dist/recurrence.d.ts +33 -7
- package/dist/recurrence.js +151 -13
- package/dist/row-drag.d.ts +8 -0
- package/dist/row-drag.js +11 -0
- package/dist/scheduler-ical.d.ts +30 -0
- package/dist/scheduler-ical.js +180 -0
- package/dist/scheduler-model.d.ts +269 -0
- package/dist/scheduler-model.js +602 -0
- package/dist/scheduler-view.svelte.d.ts +22 -0
- package/dist/scheduler-view.svelte.js +13 -0
- package/dist/spreadsheet.js +20 -12
- package/dist/ui-app.types.d.ts +3 -0
- package/package.json +14 -10
- package/src/DockNodeView.svelte +306 -0
- package/src/DockPopoutHost.svelte +17 -0
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -178
- package/src/GridMenus.svelte +83 -3
- package/src/SvAutoComplete.svelte +5 -3
- package/src/SvCarousel.svelte +141 -163
- package/src/SvCarousel.test.ts +68 -0
- package/src/SvColorInput.svelte +187 -186
- package/src/SvComboBox.svelte +7 -3
- package/src/SvCommand.svelte +28 -85
- package/src/SvCommand.test.ts +78 -0
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvDateTimePicker.svelte +9 -3
- package/src/SvDockLayout.svelte +244 -0
- package/src/SvDockManager.svelte +922 -0
- package/src/SvDrawer.svelte +250 -228
- package/src/SvDropDownList.svelte +85 -13
- package/src/SvField.svelte +21 -4
- package/src/SvForm.svelte +201 -91
- package/src/SvForm.test.ts +299 -0
- package/src/SvGrid.controller.svelte.ts +3158 -3034
- package/src/SvGrid.css +193 -5
- package/src/SvGrid.svelte +3131 -2786
- package/src/SvGrid.types.ts +1489 -1027
- package/src/SvGridBoard.svelte +2339 -2339
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridDropdown.svelte +689 -682
- package/src/SvGridSelect.svelte +1 -0
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +36 -14
- package/src/SvMaskedInput.svelte +4 -3
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -73
- package/src/SvMenuList.svelte +117 -165
- package/src/SvModal.svelte +12 -29
- package/src/SvModal.test.ts +82 -0
- package/src/SvMultiSelect.svelte +1 -0
- package/src/SvNumberInput.svelte +131 -130
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPagination.svelte +18 -18
- package/src/SvPasswordInput.svelte +3 -2
- package/src/SvPhoneInput.svelte +3 -2
- package/src/SvPopover.svelte +139 -139
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvStepper.svelte +6 -9
- package/src/SvTagsInput.svelte +3 -2
- package/src/SvTooltip.svelte +127 -138
- package/src/SvTooltip.test.ts +73 -0
- package/src/SvTour.svelte +204 -176
- package/src/SvTree.svelte +377 -377
- package/src/SvTreeSelect.svelte +230 -229
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/build-api.ts +870 -847
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/columns.ts +5 -0
- package/src/conditional-formatting.test.ts +104 -0
- package/src/conditional-formatting.ts +176 -19
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCarousel.svelte.ts +104 -0
- package/src/createCombobox.svelte.ts +179 -179
- package/src/createCommand.svelte.ts +125 -0
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +162 -162
- package/src/createForm.svelte.ts +333 -0
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createMenu.svelte.ts +202 -0
- package/src/createOverlay.svelte.ts +101 -0
- package/src/createPagination.test.ts +65 -0
- package/src/createPagination.ts +84 -0
- package/src/createPopoverSelect.svelte.ts +206 -210
- package/src/createStepper.test.ts +62 -0
- package/src/createStepper.ts +61 -0
- package/src/createTooltip.svelte.ts +86 -0
- package/src/createTree.svelte.ts +319 -319
- package/src/datetime/timezone.test.ts +69 -0
- package/src/datetime/timezone.ts +134 -0
- package/src/dock-context.ts +47 -0
- package/src/dock-manager-model.test.ts +275 -0
- package/src/dock-manager-model.ts +593 -0
- package/src/dock-model.test.ts +163 -0
- package/src/dock-model.ts +368 -0
- package/src/dock-popout.ts +94 -0
- package/src/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- package/src/editor-contract.ts +2 -0
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.test.ts +59 -0
- package/src/filter-operators.ts +35 -2
- package/src/filtering/excel-filters.test.ts +86 -1
- package/src/filtering/excel-filters.ts +65 -0
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +87 -0
- package/src/index.ts +623 -469
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -0
- package/src/list-nav.ts +29 -0
- package/src/list-option.ts +2 -0
- package/src/menu-item.ts +21 -0
- package/src/menus.test.ts +48 -0
- package/src/menus.ts +639 -582
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/panel-resize.ts +52 -0
- package/src/popover.bounds.test.ts +93 -0
- package/src/popover.ts +51 -7
- package/src/recurrence.test.ts +88 -1
- package/src/recurrence.ts +166 -15
- package/src/render-component.ts +28 -28
- package/src/row-drag.ts +10 -0
- package/src/scheduler-ical.test.ts +68 -0
- package/src/scheduler-ical.ts +180 -0
- package/src/scheduler-model.test.ts +546 -0
- package/src/scheduler-model.ts +853 -0
- package/src/scheduler-view.svelte.ts +33 -0
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -304
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +501 -501
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.charting.test.ts +527 -527
- package/src/svgrid.effect-driven-filter.svelte.test.ts +116 -0
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +159 -159
- package/src/ui-app.types.ts +3 -0
- package/src/virtual.test.ts +6 -4
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createOverlay - the HEADLESS core behind the dialog-type overlays (<SvModal>,
|
|
3
|
+
* <SvDrawer>): the open -> (focus-trap + scroll-lock + Escape/backdrop dismissal)
|
|
4
|
+
* -> restore lifecycle, wired to the shared a11y primitives so nested overlays
|
|
5
|
+
* close top-first via the layer stack. Owns no markup and no styles - the
|
|
6
|
+
* component renders the backdrop/panel and spreads `dialogProps()` for the ARIA.
|
|
7
|
+
*
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script lang="ts">
|
|
10
|
+
* import { createOverlay } from '@svgrid/grid'
|
|
11
|
+
* let dialogEl = $state<HTMLElement | null>(null)
|
|
12
|
+
* const overlay = createOverlay({
|
|
13
|
+
* open: () => open,
|
|
14
|
+
* getDialog: () => dialogEl,
|
|
15
|
+
* onClose: () => (open = false),
|
|
16
|
+
* })
|
|
17
|
+
* </script>
|
|
18
|
+
* {#if open}
|
|
19
|
+
* <div class="backdrop">
|
|
20
|
+
* <div bind:this={dialogEl} {...overlay.dialogProps({ labelledBy: titleId })}>…</div>
|
|
21
|
+
* </div>
|
|
22
|
+
* {/if}
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import { createFocusTrap } from './a11y/focus-trap'
|
|
26
|
+
import { lockScroll } from './a11y/scroll-lock'
|
|
27
|
+
import { createDismissableLayer } from './a11y/dismissable'
|
|
28
|
+
|
|
29
|
+
export type OverlayConfig = {
|
|
30
|
+
/** Whether the overlay is currently open. */
|
|
31
|
+
open: () => boolean
|
|
32
|
+
/** The dialog panel element (component owns `bind:this`, passes a getter). */
|
|
33
|
+
getDialog: () => HTMLElement | null
|
|
34
|
+
/** Close the overlay (set your `open` state false + notify). */
|
|
35
|
+
onClose: () => void
|
|
36
|
+
/** Allow Escape to close. Default true. */
|
|
37
|
+
closeOnEsc?: () => boolean
|
|
38
|
+
/** Allow an outside/backdrop click to close. Default true. */
|
|
39
|
+
closeOnBackdrop?: () => boolean
|
|
40
|
+
/** Run once each time the overlay opens - reset transient state (drag, resize,
|
|
41
|
+
* swipe offset) before the trap/lock/layer are wired. */
|
|
42
|
+
onOpen?: () => void
|
|
43
|
+
/** Trap focus inside the dialog while open. Default true. */
|
|
44
|
+
trapFocus?: () => boolean
|
|
45
|
+
/** Lock body scroll while open. Default true. */
|
|
46
|
+
scrollLock?: () => boolean
|
|
47
|
+
/** Element to focus first when the dialog opens (e.g. a search input).
|
|
48
|
+
* Defaults to the trap's first focusable. */
|
|
49
|
+
initialFocus?: () => HTMLElement | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type DialogProps = {
|
|
53
|
+
role: 'dialog'
|
|
54
|
+
'aria-modal': 'true'
|
|
55
|
+
'aria-labelledby': string | undefined
|
|
56
|
+
'aria-label': string | undefined
|
|
57
|
+
tabindex: -1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createOverlay(config: OverlayConfig) {
|
|
61
|
+
// While open: reset transient state, then hand focus, scroll-lock and
|
|
62
|
+
// Escape/backdrop dismissal to the shared primitives (one tested
|
|
63
|
+
// implementation; nested overlays close top-first via the layer stack).
|
|
64
|
+
$effect(() => {
|
|
65
|
+
if (!config.open() || !config.getDialog()) return
|
|
66
|
+
config.onOpen?.()
|
|
67
|
+
const el = config.getDialog()!
|
|
68
|
+
const trap = (config.trapFocus?.() ?? true)
|
|
69
|
+
? createFocusTrap(el, config.initialFocus ? { initialFocus: config.initialFocus } : undefined)
|
|
70
|
+
: null
|
|
71
|
+
trap?.activate()
|
|
72
|
+
const unlockScroll = (config.scrollLock?.() ?? true) ? lockScroll() : null
|
|
73
|
+
const layer = createDismissableLayer({
|
|
74
|
+
element: () => config.getDialog(),
|
|
75
|
+
onDismiss: (reason) => {
|
|
76
|
+
const allow = reason === 'escape' ? (config.closeOnEsc?.() ?? true) : (config.closeOnBackdrop?.() ?? true)
|
|
77
|
+
if (allow) config.onClose()
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
layer.activate()
|
|
81
|
+
return () => {
|
|
82
|
+
layer.release()
|
|
83
|
+
unlockScroll?.()
|
|
84
|
+
trap?.release()
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
/** Spread onto the dialog panel element. Pass `labelledBy` (an id, when there
|
|
90
|
+
* is a visible title) or `label` (an accessible name when there isn't). */
|
|
91
|
+
dialogProps: (opts?: { labelledBy?: string; label?: string }): DialogProps => ({
|
|
92
|
+
role: 'dialog',
|
|
93
|
+
'aria-modal': 'true',
|
|
94
|
+
'aria-labelledby': opts?.labelledBy,
|
|
95
|
+
'aria-label': opts?.label,
|
|
96
|
+
tabindex: -1,
|
|
97
|
+
}),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type Overlay = ReturnType<typeof createOverlay>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { createPagination } from './createPagination'
|
|
3
|
+
|
|
4
|
+
function make(over: Partial<{ page: number; pageCount: number; disabled: boolean }> = {}) {
|
|
5
|
+
let page = over.page ?? 5
|
|
6
|
+
const onChange = vi.fn((p: number) => (page = p))
|
|
7
|
+
const pg = createPagination({
|
|
8
|
+
page: () => page,
|
|
9
|
+
pageCount: () => over.pageCount ?? 20,
|
|
10
|
+
onChange,
|
|
11
|
+
disabled: () => over.disabled ?? false,
|
|
12
|
+
})
|
|
13
|
+
return { pg, onChange, getPage: () => page }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('createPagination', () => {
|
|
17
|
+
it('builds the page/ellipsis sequence around the current page', () => {
|
|
18
|
+
const { pg } = make({ page: 10, pageCount: 20 })
|
|
19
|
+
expect(pg.items[0]).toBe(1)
|
|
20
|
+
expect(pg.items).toContain(10)
|
|
21
|
+
expect(pg.items.some((i) => i === 'ellipsis-left')).toBe(true)
|
|
22
|
+
expect(pg.items.some((i) => i === 'ellipsis-right')).toBe(true)
|
|
23
|
+
expect(pg.items.at(-1)).toBe(20)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('clamps navigation to [1, pageCount] and only fires on a real change', () => {
|
|
27
|
+
const { pg, onChange } = make({ page: 1, pageCount: 20 })
|
|
28
|
+
pg.prev() // already at 1 -> clamped, no change
|
|
29
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
30
|
+
pg.next()
|
|
31
|
+
expect(onChange).toHaveBeenLastCalledWith(2)
|
|
32
|
+
pg.last()
|
|
33
|
+
expect(onChange).toHaveBeenLastCalledWith(20)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('go() clamps out-of-range targets', () => {
|
|
37
|
+
const { pg, onChange } = make({ page: 5, pageCount: 20 })
|
|
38
|
+
pg.go(999)
|
|
39
|
+
expect(onChange).toHaveBeenLastCalledWith(20)
|
|
40
|
+
pg.go(-3)
|
|
41
|
+
expect(onChange).toHaveBeenLastCalledWith(1)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('canPrev/canNext reflect the edges', () => {
|
|
45
|
+
expect(make({ page: 1, pageCount: 20 }).pg.canPrev()).toBe(false)
|
|
46
|
+
expect(make({ page: 1, pageCount: 20 }).pg.canNext()).toBe(true)
|
|
47
|
+
expect(make({ page: 20, pageCount: 20 }).pg.canNext()).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('is inert while disabled', () => {
|
|
51
|
+
const { pg, onChange } = make({ page: 5, pageCount: 20, disabled: true })
|
|
52
|
+
pg.next()
|
|
53
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
54
|
+
expect(pg.canPrev()).toBe(false)
|
|
55
|
+
expect(pg.canNext()).toBe(false)
|
|
56
|
+
expect(pg.pageButtonProps(3).disabled).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('marks the active page in prop-getters', () => {
|
|
60
|
+
const { pg } = make({ page: 5, pageCount: 20 })
|
|
61
|
+
expect(pg.isActive(5)).toBe(true)
|
|
62
|
+
expect(pg.pageButtonProps(5)['aria-current']).toBe('page')
|
|
63
|
+
expect(pg.pageButtonProps(6)['aria-current']).toBeUndefined()
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createPagination - the HEADLESS core behind <SvPagination>: the page/ellipsis
|
|
3
|
+
* sequence (via the pure `paginationRange`) plus navigation (first/prev/next/last
|
|
4
|
+
* with clamping) and prop-getters you spread onto your own markup. Pure getters,
|
|
5
|
+
* no runes - so it is directly unit-testable and framework-free.
|
|
6
|
+
*
|
|
7
|
+
* ```svelte
|
|
8
|
+
* <script lang="ts">
|
|
9
|
+
* import { createPagination } from '@svgrid/grid'
|
|
10
|
+
* const pg = createPagination({ page: () => page, pageCount: () => 20, onChange: (p) => (page = p) })
|
|
11
|
+
* </script>
|
|
12
|
+
* <nav {...pg.navProps()}>
|
|
13
|
+
* <button {...pg.prevProps()}>‹</button>
|
|
14
|
+
* {#each pg.items as it}
|
|
15
|
+
* {#if typeof it === 'number'}<button {...pg.pageButtonProps(it)}>{it}</button>{:else}…{/if}
|
|
16
|
+
* {/each}
|
|
17
|
+
* <button {...pg.nextProps()}>›</button>
|
|
18
|
+
* </nav>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { paginationRange, type PaginationItem } from './paginate'
|
|
22
|
+
|
|
23
|
+
export type PaginationConfig = {
|
|
24
|
+
/** 1-based current page. */
|
|
25
|
+
page: () => number
|
|
26
|
+
pageCount: () => number
|
|
27
|
+
onChange?: (page: number) => void
|
|
28
|
+
siblingCount?: () => number
|
|
29
|
+
boundaryCount?: () => number
|
|
30
|
+
disabled?: () => boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createPagination(config: PaginationConfig) {
|
|
34
|
+
const page = () => config.page()
|
|
35
|
+
const pageCount = () => config.pageCount()
|
|
36
|
+
const disabled = () => config.disabled?.() ?? false
|
|
37
|
+
|
|
38
|
+
function go(p: number) {
|
|
39
|
+
if (disabled()) return
|
|
40
|
+
const next = Math.min(Math.max(p, 1), pageCount())
|
|
41
|
+
if (next !== page()) config.onChange?.(next)
|
|
42
|
+
}
|
|
43
|
+
const first = () => go(1)
|
|
44
|
+
const prev = () => go(page() - 1)
|
|
45
|
+
const next = () => go(page() + 1)
|
|
46
|
+
const last = () => go(pageCount())
|
|
47
|
+
const canPrev = () => !disabled() && page() > 1
|
|
48
|
+
const canNext = () => !disabled() && page() < pageCount()
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
/** The page/ellipsis sequence to render, recomputed from the live inputs. */
|
|
52
|
+
get items(): PaginationItem[] {
|
|
53
|
+
return paginationRange({
|
|
54
|
+
page: page(),
|
|
55
|
+
pageCount: pageCount(),
|
|
56
|
+
siblingCount: config.siblingCount?.() ?? 1,
|
|
57
|
+
boundaryCount: config.boundaryCount?.() ?? 1,
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
go,
|
|
61
|
+
first,
|
|
62
|
+
prev,
|
|
63
|
+
next,
|
|
64
|
+
last,
|
|
65
|
+
canPrev,
|
|
66
|
+
canNext,
|
|
67
|
+
isActive: (p: number) => p === page(),
|
|
68
|
+
/** Spread onto the `<nav>` wrapper. */
|
|
69
|
+
navProps: (ariaLabel = 'Pagination') => ({ 'aria-label': ariaLabel }),
|
|
70
|
+
/** Spread onto a numbered page button. */
|
|
71
|
+
pageButtonProps: (p: number) => ({
|
|
72
|
+
'aria-current': (p === page() ? 'page' : undefined) as 'page' | undefined,
|
|
73
|
+
'aria-label': `Page ${p}`,
|
|
74
|
+
disabled: disabled(),
|
|
75
|
+
onclick: () => go(p),
|
|
76
|
+
}),
|
|
77
|
+
firstProps: () => ({ 'aria-label': 'First page', disabled: !canPrev(), onclick: first }),
|
|
78
|
+
prevProps: () => ({ 'aria-label': 'Previous page', disabled: !canPrev(), onclick: prev }),
|
|
79
|
+
nextProps: () => ({ 'aria-label': 'Next page', disabled: !canNext(), onclick: next }),
|
|
80
|
+
lastProps: () => ({ 'aria-label': 'Last page', disabled: !canNext(), onclick: last }),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type Pagination = ReturnType<typeof createPagination>
|