@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.
Files changed (295) hide show
  1. package/README.md +9 -2
  2. package/dist/DockNodeView.svelte +306 -0
  3. package/dist/DockNodeView.svelte.d.ts +7 -0
  4. package/dist/DockPopoutHost.svelte +17 -0
  5. package/dist/DockPopoutHost.svelte.d.ts +14 -0
  6. package/dist/FlexRender.svelte +96 -96
  7. package/dist/GridFooter.svelte +178 -178
  8. package/dist/GridMenus.svelte +83 -3
  9. package/dist/SvAutoComplete.svelte +5 -3
  10. package/dist/SvCarousel.svelte +141 -163
  11. package/dist/SvColorInput.svelte +187 -186
  12. package/dist/SvColorInput.svelte.d.ts +1 -1
  13. package/dist/SvComboBox.svelte +7 -3
  14. package/dist/SvCommand.svelte +28 -85
  15. package/dist/SvContextMenu.svelte +116 -116
  16. package/dist/SvCountryInput.svelte +1 -1
  17. package/dist/SvDateRangeInput.svelte +1 -1
  18. package/dist/SvDateTimePicker.svelte +9 -3
  19. package/dist/SvDateTimePicker.svelte.d.ts +3 -0
  20. package/dist/SvDockLayout.svelte +244 -0
  21. package/dist/SvDockLayout.svelte.d.ts +77 -0
  22. package/dist/SvDockManager.svelte +922 -0
  23. package/dist/SvDockManager.svelte.d.ts +106 -0
  24. package/dist/SvDrawer.svelte +250 -228
  25. package/dist/SvDrawer.svelte.d.ts +4 -0
  26. package/dist/SvDropDownList.svelte +85 -13
  27. package/dist/SvDropDownList.svelte.d.ts +3 -0
  28. package/dist/SvField.svelte +21 -4
  29. package/dist/SvField.svelte.d.ts +1 -0
  30. package/dist/SvForm.svelte +201 -91
  31. package/dist/SvForm.svelte.d.ts +15 -22
  32. package/dist/SvGrid.controller.svelte.d.ts +32 -1
  33. package/dist/SvGrid.controller.svelte.js +156 -27
  34. package/dist/SvGrid.css +193 -5
  35. package/dist/SvGrid.svelte +3131 -2786
  36. package/dist/SvGrid.types.d.ts +452 -1
  37. package/dist/SvGridBoard.svelte +2339 -2339
  38. package/dist/SvGridChart.svelte +1724 -1724
  39. package/dist/SvGridChartPanel.svelte +485 -485
  40. package/dist/SvGridDropdown.svelte +689 -682
  41. package/dist/SvGridSelect.svelte +1 -0
  42. package/dist/SvGroupCell.svelte +109 -109
  43. package/dist/SvListBox.svelte +36 -14
  44. package/dist/SvMaskedInput.svelte +4 -3
  45. package/dist/SvMaskedInput.svelte.d.ts +1 -1
  46. package/dist/SvMenu.svelte +124 -124
  47. package/dist/SvMenuList.svelte +117 -165
  48. package/dist/SvMenuList.svelte.d.ts +2 -13
  49. package/dist/SvModal.svelte +12 -29
  50. package/dist/SvMultiSelect.svelte +1 -0
  51. package/dist/SvNumberInput.svelte +131 -130
  52. package/dist/SvNumberInput.svelte.d.ts +1 -1
  53. package/dist/SvOtpInput.svelte +158 -158
  54. package/dist/SvPagination.svelte +18 -18
  55. package/dist/SvPasswordInput.svelte +3 -2
  56. package/dist/SvPasswordInput.svelte.d.ts +1 -1
  57. package/dist/SvPhoneInput.svelte +3 -2
  58. package/dist/SvPhoneInput.svelte.d.ts +1 -1
  59. package/dist/SvPopover.svelte +139 -139
  60. package/dist/SvRowGroupPanel.svelte +170 -170
  61. package/dist/SvScrollArea.svelte +61 -61
  62. package/dist/SvStepper.svelte +6 -9
  63. package/dist/SvTagsInput.svelte +3 -2
  64. package/dist/SvTagsInput.svelte.d.ts +1 -1
  65. package/dist/SvTooltip.svelte +127 -138
  66. package/dist/SvTour.svelte +204 -176
  67. package/dist/SvTree.svelte +377 -377
  68. package/dist/SvTreeSelect.svelte +230 -229
  69. package/dist/build-api.js +76 -54
  70. package/dist/cdn/svgrid.js +10913 -6450
  71. package/dist/cdn/svgrid.svelte-external.js +9628 -5481
  72. package/dist/chart-export.js +8 -8
  73. package/dist/columns.js +6 -0
  74. package/dist/conditional-formatting.d.ts +67 -3
  75. package/dist/conditional-formatting.js +124 -17
  76. package/dist/createCarousel.svelte.d.ts +72 -0
  77. package/dist/createCarousel.svelte.js +80 -0
  78. package/dist/createCommand.svelte.d.ts +62 -0
  79. package/dist/createCommand.svelte.js +109 -0
  80. package/dist/createForm.svelte.d.ts +62 -0
  81. package/dist/createForm.svelte.js +374 -0
  82. package/dist/createMenu.svelte.d.ts +88 -0
  83. package/dist/createMenu.svelte.js +153 -0
  84. package/dist/createOverlay.svelte.d.ts +38 -0
  85. package/dist/createOverlay.svelte.js +68 -0
  86. package/dist/createPagination.d.ts +74 -0
  87. package/dist/createPagination.js +71 -0
  88. package/dist/createPopoverSelect.svelte.js +6 -12
  89. package/dist/createStepper.d.ts +44 -0
  90. package/dist/createStepper.js +26 -0
  91. package/dist/createTooltip.svelte.d.ts +32 -0
  92. package/dist/createTooltip.svelte.js +61 -0
  93. package/dist/datetime/timezone.d.ts +36 -0
  94. package/dist/datetime/timezone.js +110 -0
  95. package/dist/dock-context.d.ts +52 -0
  96. package/dist/dock-context.js +1 -0
  97. package/dist/dock-manager-model.d.ts +136 -0
  98. package/dist/dock-manager-model.js +514 -0
  99. package/dist/dock-model.d.ts +105 -0
  100. package/dist/dock-model.js +288 -0
  101. package/dist/dock-popout.d.ts +30 -0
  102. package/dist/dock-popout.js +81 -0
  103. package/dist/editor-contract.d.ts +2 -0
  104. package/dist/filter-operators.d.ts +6 -0
  105. package/dist/filter-operators.js +35 -2
  106. package/dist/filtering/excel-filters.d.ts +18 -1
  107. package/dist/filtering/excel-filters.js +56 -0
  108. package/dist/form-field.d.ts +73 -0
  109. package/dist/form-field.js +15 -0
  110. package/dist/index.d.ts +25 -5
  111. package/dist/index.js +40 -4
  112. package/dist/list-nav.d.ts +18 -0
  113. package/dist/list-nav.js +30 -0
  114. package/dist/list-option.d.ts +2 -0
  115. package/dist/menu-item.d.ts +20 -0
  116. package/dist/menu-item.js +1 -0
  117. package/dist/menus.d.ts +5 -0
  118. package/dist/menus.js +68 -16
  119. package/dist/panel-resize.d.ts +25 -0
  120. package/dist/panel-resize.js +40 -0
  121. package/dist/popover.d.ts +29 -3
  122. package/dist/popover.js +22 -6
  123. package/dist/recurrence.d.ts +33 -7
  124. package/dist/recurrence.js +151 -13
  125. package/dist/row-drag.d.ts +8 -0
  126. package/dist/row-drag.js +11 -0
  127. package/dist/scheduler-ical.d.ts +30 -0
  128. package/dist/scheduler-ical.js +180 -0
  129. package/dist/scheduler-model.d.ts +269 -0
  130. package/dist/scheduler-model.js +602 -0
  131. package/dist/scheduler-view.svelte.d.ts +22 -0
  132. package/dist/scheduler-view.svelte.js +13 -0
  133. package/dist/spreadsheet.js +20 -12
  134. package/dist/ui-app.types.d.ts +3 -0
  135. package/package.json +14 -10
  136. package/src/DockNodeView.svelte +306 -0
  137. package/src/DockPopoutHost.svelte +17 -0
  138. package/src/FlexRender.svelte +96 -96
  139. package/src/GridFooter.svelte +178 -178
  140. package/src/GridMenus.svelte +83 -3
  141. package/src/SvAutoComplete.svelte +5 -3
  142. package/src/SvCarousel.svelte +141 -163
  143. package/src/SvCarousel.test.ts +68 -0
  144. package/src/SvColorInput.svelte +187 -186
  145. package/src/SvComboBox.svelte +7 -3
  146. package/src/SvCommand.svelte +28 -85
  147. package/src/SvCommand.test.ts +78 -0
  148. package/src/SvContextMenu.svelte +116 -116
  149. package/src/SvCountryInput.svelte +1 -1
  150. package/src/SvDateRangeInput.svelte +1 -1
  151. package/src/SvDateTimePicker.svelte +9 -3
  152. package/src/SvDockLayout.svelte +244 -0
  153. package/src/SvDockManager.svelte +922 -0
  154. package/src/SvDrawer.svelte +250 -228
  155. package/src/SvDropDownList.svelte +85 -13
  156. package/src/SvField.svelte +21 -4
  157. package/src/SvForm.svelte +201 -91
  158. package/src/SvForm.test.ts +299 -0
  159. package/src/SvGrid.controller.svelte.ts +3158 -3034
  160. package/src/SvGrid.css +193 -5
  161. package/src/SvGrid.svelte +3131 -2786
  162. package/src/SvGrid.types.ts +1489 -1027
  163. package/src/SvGridBoard.svelte +2339 -2339
  164. package/src/SvGridChart.svelte +1724 -1724
  165. package/src/SvGridChartPanel.svelte +485 -485
  166. package/src/SvGridDropdown.svelte +689 -682
  167. package/src/SvGridSelect.svelte +1 -0
  168. package/src/SvGroupCell.svelte +109 -109
  169. package/src/SvListBox.svelte +36 -14
  170. package/src/SvMaskedInput.svelte +4 -3
  171. package/src/SvMenu.svelte +124 -124
  172. package/src/SvMenu.test.ts +97 -73
  173. package/src/SvMenuList.svelte +117 -165
  174. package/src/SvModal.svelte +12 -29
  175. package/src/SvModal.test.ts +82 -0
  176. package/src/SvMultiSelect.svelte +1 -0
  177. package/src/SvNumberInput.svelte +131 -130
  178. package/src/SvOtpInput.svelte +158 -158
  179. package/src/SvPagination.svelte +18 -18
  180. package/src/SvPasswordInput.svelte +3 -2
  181. package/src/SvPhoneInput.svelte +3 -2
  182. package/src/SvPopover.svelte +139 -139
  183. package/src/SvRowGroupPanel.svelte +170 -170
  184. package/src/SvScrollArea.svelte +61 -61
  185. package/src/SvStepper.svelte +6 -9
  186. package/src/SvTagsInput.svelte +3 -2
  187. package/src/SvTooltip.svelte +127 -138
  188. package/src/SvTooltip.test.ts +73 -0
  189. package/src/SvTour.svelte +204 -176
  190. package/src/SvTree.svelte +377 -377
  191. package/src/SvTreeSelect.svelte +230 -229
  192. package/src/a11y.contract.test.ts +49 -49
  193. package/src/a11y.test.ts +59 -59
  194. package/src/a11y.ts +61 -61
  195. package/src/build-api.ts +870 -847
  196. package/src/cell-formatting.ts +169 -169
  197. package/src/cell-render.ts +469 -469
  198. package/src/chart-export.ts +201 -201
  199. package/src/chart.ts +2296 -2296
  200. package/src/collaboration.test.ts +104 -104
  201. package/src/collaboration.ts +167 -167
  202. package/src/columns.ts +5 -0
  203. package/src/conditional-formatting.test.ts +104 -0
  204. package/src/conditional-formatting.ts +176 -19
  205. package/src/core.performance.test.ts +30 -30
  206. package/src/core.ts +1111 -1111
  207. package/src/createAutocomplete.svelte.ts +132 -132
  208. package/src/createCarousel.svelte.ts +104 -0
  209. package/src/createCombobox.svelte.ts +179 -179
  210. package/src/createCommand.svelte.ts +125 -0
  211. package/src/createCountryInput.svelte.ts +157 -157
  212. package/src/createDropdownList.svelte.ts +162 -162
  213. package/src/createForm.svelte.ts +333 -0
  214. package/src/createGrid.svelte.ts +42 -42
  215. package/src/createGrid.test.ts +10 -10
  216. package/src/createGridState.svelte.ts +17 -17
  217. package/src/createMenu.svelte.ts +202 -0
  218. package/src/createOverlay.svelte.ts +101 -0
  219. package/src/createPagination.test.ts +65 -0
  220. package/src/createPagination.ts +84 -0
  221. package/src/createPopoverSelect.svelte.ts +206 -210
  222. package/src/createStepper.test.ts +62 -0
  223. package/src/createStepper.ts +61 -0
  224. package/src/createTooltip.svelte.ts +86 -0
  225. package/src/createTree.svelte.ts +319 -319
  226. package/src/datetime/timezone.test.ts +69 -0
  227. package/src/datetime/timezone.ts +134 -0
  228. package/src/dock-context.ts +47 -0
  229. package/src/dock-manager-model.test.ts +275 -0
  230. package/src/dock-manager-model.ts +593 -0
  231. package/src/dock-model.test.ts +163 -0
  232. package/src/dock-model.ts +368 -0
  233. package/src/dock-popout.ts +94 -0
  234. package/src/editing.test.ts +859 -859
  235. package/src/editing.ts +675 -675
  236. package/src/editor-contract.ts +2 -0
  237. package/src/export-data-api.test.ts +126 -126
  238. package/src/export-format.test.ts +107 -107
  239. package/src/export-format.ts +598 -598
  240. package/src/filter-operators.test.ts +59 -0
  241. package/src/filter-operators.ts +35 -2
  242. package/src/filtering/excel-filters.test.ts +86 -1
  243. package/src/filtering/excel-filters.ts +65 -0
  244. package/src/flex-render.ts +3 -3
  245. package/src/form-field.ts +87 -0
  246. package/src/index.ts +623 -469
  247. package/src/keyboard.test.ts +59 -59
  248. package/src/keyboard.ts +97 -97
  249. package/src/list-nav.test.ts +49 -0
  250. package/src/list-nav.ts +29 -0
  251. package/src/list-option.ts +2 -0
  252. package/src/menu-item.ts +21 -0
  253. package/src/menus.test.ts +48 -0
  254. package/src/menus.ts +639 -582
  255. package/src/merge-objects.ts +48 -48
  256. package/src/overlays.test.ts +90 -90
  257. package/src/panel-resize.ts +52 -0
  258. package/src/popover.bounds.test.ts +93 -0
  259. package/src/popover.ts +51 -7
  260. package/src/recurrence.test.ts +88 -1
  261. package/src/recurrence.ts +166 -15
  262. package/src/render-component.ts +28 -28
  263. package/src/row-drag.ts +10 -0
  264. package/src/scheduler-ical.test.ts +68 -0
  265. package/src/scheduler-ical.ts +180 -0
  266. package/src/scheduler-model.test.ts +546 -0
  267. package/src/scheduler-model.ts +853 -0
  268. package/src/scheduler-view.svelte.ts +33 -0
  269. package/src/selection.test.ts +754 -754
  270. package/src/selection.ts +600 -600
  271. package/src/server-data-source.test.ts +289 -289
  272. package/src/server-data-source.ts +413 -413
  273. package/src/sparkline.test.ts +68 -68
  274. package/src/sparkline.ts +169 -169
  275. package/src/spreadsheet.test.ts +489 -489
  276. package/src/spreadsheet.ts +312 -304
  277. package/src/static-functions.ts +11 -11
  278. package/src/subscribe.ts +38 -38
  279. package/src/svgrid-wrapper.types.ts +501 -501
  280. package/src/svgrid.behavior.test.ts +706 -706
  281. package/src/svgrid.charting.test.ts +527 -527
  282. package/src/svgrid.effect-driven-filter.svelte.test.ts +116 -0
  283. package/src/svgrid.features.test.ts +157 -157
  284. package/src/svgrid.new-features.wrapper.test.ts +251 -251
  285. package/src/svgrid.wrapper.test.ts +40 -40
  286. package/src/test-setup.ts +62 -62
  287. package/src/themes/index.ts +159 -159
  288. package/src/ui-app.types.ts +3 -0
  289. package/src/virtual.test.ts +6 -4
  290. package/src/virtualization/column-virtualizer.test.ts +27 -27
  291. package/src/virtualization/column-virtualizer.ts +30 -30
  292. package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
  293. package/src/virtualization/types.ts +30 -30
  294. package/src/virtualization/virtualizer.test.ts +47 -47
  295. 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>