@svgrid/grid 2.1.6 → 2.1.8

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 (94) hide show
  1. package/dist/SvCarousel.svelte +35 -6
  2. package/dist/SvCarousel.svelte.d.ts +3 -0
  3. package/dist/SvColorInput.svelte +0 -1
  4. package/dist/SvContextMenu.svelte +26 -6
  5. package/dist/SvDrawer.svelte +5 -3
  6. package/dist/SvGrid.controller.svelte.d.ts +85 -2
  7. package/dist/SvGrid.controller.svelte.js +494 -1
  8. package/dist/SvGrid.css +142 -0
  9. package/dist/SvGrid.svelte +49 -27
  10. package/dist/SvGrid.types.d.ts +78 -0
  11. package/dist/SvGridBoard.svelte +71 -13
  12. package/dist/SvGridChart.svelte +41 -7
  13. package/dist/SvGridChart.svelte.d.ts +4 -0
  14. package/dist/SvGridChartPanel.svelte +485 -0
  15. package/dist/SvGridChartPanel.svelte.d.ts +28 -0
  16. package/dist/SvGridDropdown.svelte +18 -2
  17. package/dist/SvGroupCell.svelte +5 -5
  18. package/dist/SvMenu.svelte +25 -2
  19. package/dist/SvMenu.svelte.d.ts +3 -0
  20. package/dist/SvMenuList.svelte +31 -9
  21. package/dist/SvMenuList.svelte.d.ts +9 -1
  22. package/dist/SvNumberInput.svelte +1 -1
  23. package/dist/SvOtpInput.svelte +3 -2
  24. package/dist/SvPopover.svelte +13 -1
  25. package/dist/SvRowGroupPanel.svelte +23 -0
  26. package/dist/SvScrollArea.svelte +13 -2
  27. package/dist/SvScrollArea.svelte.d.ts +2 -0
  28. package/dist/SvTooltip.svelte +27 -2
  29. package/dist/SvTooltip.svelte.d.ts +3 -2
  30. package/dist/SvTour.svelte +30 -5
  31. package/dist/SvTour.svelte.d.ts +3 -0
  32. package/dist/SvTree.svelte +7 -3
  33. package/dist/SvTreeSelect.svelte +16 -9
  34. package/dist/build-api.js +51 -0
  35. package/dist/cdn/svgrid.js +17394 -16052
  36. package/dist/cdn/svgrid.svelte-external.js +16784 -15644
  37. package/dist/chart-export.d.ts +21 -0
  38. package/dist/chart-export.js +52 -11
  39. package/dist/chart.d.ts +30 -0
  40. package/dist/chart.js +53 -13
  41. package/dist/createAutocomplete.svelte.d.ts +6 -0
  42. package/dist/createAutocomplete.svelte.js +10 -0
  43. package/dist/createCombobox.svelte.d.ts +1 -0
  44. package/dist/createCombobox.svelte.js +1 -0
  45. package/dist/createCountryInput.svelte.d.ts +5 -0
  46. package/dist/createCountryInput.svelte.js +11 -0
  47. package/dist/createDropdownList.svelte.d.ts +4 -0
  48. package/dist/createDropdownList.svelte.js +7 -0
  49. package/dist/createPopoverSelect.svelte.d.ts +5 -3
  50. package/dist/createPopoverSelect.svelte.js +5 -3
  51. package/dist/createTree.svelte.d.ts +1 -0
  52. package/dist/createTree.svelte.js +15 -2
  53. package/dist/index.d.ts +4 -2
  54. package/dist/index.js +3 -2
  55. package/dist/svgrid-wrapper.types.d.ts +58 -0
  56. package/package.json +1 -1
  57. package/src/SvCarousel.svelte +35 -6
  58. package/src/SvColorInput.svelte +0 -1
  59. package/src/SvContextMenu.svelte +26 -6
  60. package/src/SvDrawer.svelte +5 -3
  61. package/src/SvGrid.controller.svelte.ts +481 -0
  62. package/src/SvGrid.css +142 -0
  63. package/src/SvGrid.svelte +49 -27
  64. package/src/SvGrid.types.ts +83 -0
  65. package/src/SvGridBoard.svelte +71 -13
  66. package/src/SvGridChart.svelte +41 -7
  67. package/src/SvGridChartPanel.svelte +485 -0
  68. package/src/SvGridDropdown.svelte +18 -2
  69. package/src/SvGroupCell.svelte +5 -5
  70. package/src/SvMenu.svelte +25 -2
  71. package/src/SvMenuList.svelte +31 -9
  72. package/src/SvNumberInput.svelte +1 -1
  73. package/src/SvOtpInput.svelte +3 -2
  74. package/src/SvPopover.svelte +13 -1
  75. package/src/SvRowGroupPanel.svelte +23 -0
  76. package/src/SvScrollArea.svelte +13 -2
  77. package/src/SvTooltip.svelte +27 -2
  78. package/src/SvTour.svelte +30 -5
  79. package/src/SvTree.svelte +7 -3
  80. package/src/SvTreeSelect.svelte +16 -9
  81. package/src/build-api.ts +49 -0
  82. package/src/chart-export.ts +57 -0
  83. package/src/chart.ts +70 -13
  84. package/src/createAutocomplete.svelte.ts +11 -0
  85. package/src/createCombobox.svelte.ts +1 -0
  86. package/src/createCountryInput.svelte.ts +12 -0
  87. package/src/createDropdownList.svelte.ts +8 -0
  88. package/src/createPopoverSelect.svelte.ts +5 -3
  89. package/src/createTree.svelte.ts +15 -2
  90. package/src/index.ts +6 -0
  91. package/src/overlays.test.ts +5 -0
  92. package/src/svgrid-wrapper.types.ts +62 -0
  93. package/src/svgrid.charting.test.ts +527 -0
  94. package/src/test-setup.ts +22 -0
@@ -12,6 +12,7 @@
12
12
  * ```
13
13
  */
14
14
  import type { Snippet } from 'svelte'
15
+ import { type EditorDir } from './editor-contract'
15
16
 
16
17
  type Props = {
17
18
  count: number
@@ -24,6 +25,8 @@
24
25
  arrows?: boolean
25
26
  dots?: boolean
26
27
  ariaLabel?: string
28
+ /** Text direction (rtl mirrors the prev/next arrows + slide direction). */
29
+ dir?: EditorDir
27
30
  }
28
31
 
29
32
  let {
@@ -35,8 +38,12 @@
35
38
  arrows = true,
36
39
  dots = true,
37
40
  ariaLabel = 'Carousel',
41
+ dir,
38
42
  }: Props = $props()
39
43
 
44
+ const resolvedDir = $derived(dir === 'ltr' || dir === 'rtl' ? dir : undefined)
45
+ const rtl = $derived(resolvedDir === 'rtl')
46
+
40
47
  function go(i: number) {
41
48
  if (count === 0) return
42
49
  current = loop ? (i + count) % count : Math.max(0, Math.min(i, count - 1))
@@ -44,10 +51,13 @@
44
51
  const next = () => go(current + 1)
45
52
  const prev = () => go(current - 1)
46
53
 
47
- // Autoplay, paused while hovered/focused.
54
+ // Autoplay, paused while hovered/focused, or stopped via the persistent
55
+ // play/pause toggle (WCAG 2.2.2: auto-advancing content needs a way to stop
56
+ // it that doesn't depend on hover/focus).
48
57
  let paused = $state(false)
58
+ let userPaused = $state(false)
49
59
  $effect(() => {
50
- if (!autoplay || paused || count < 2) return
60
+ if (!autoplay || paused || userPaused || count < 2) return
51
61
  const t = setInterval(next, autoplay)
52
62
  return () => clearInterval(t)
53
63
  })
@@ -82,9 +92,9 @@
82
92
  >
83
93
  <!-- svelte-ignore a11y_no_static_element_interactions -->
84
94
  <div class="sv-carousel__viewport" onpointerdown={down} onpointermove={move} onpointerup={up} onpointercancel={up}>
85
- <div class="sv-carousel__track" class:is-dragging={dragging} style:transform={`translateX(calc(${-current * 100}% + ${dx}px))`}>
95
+ <div class="sv-carousel__track" class:is-dragging={dragging} style:transform={`translateX(calc(${(rtl ? current : -current) * 100}% + ${dx}px))`}>
86
96
  {#each Array(count) as _, i (i)}
87
- <div class="sv-carousel__slide" role="group" aria-roledescription="slide" aria-label={`${i + 1} of ${count}`} aria-hidden={i !== current}>
97
+ <div class="sv-carousel__slide" role="group" aria-roledescription="slide" aria-label={`${i + 1} of ${count}`} aria-hidden={i !== current} inert={i !== current}>
88
98
  {@render slide?.(i)}
89
99
  </div>
90
100
  {/each}
@@ -92,8 +102,8 @@
92
102
  </div>
93
103
 
94
104
  {#if arrows && count > 1}
95
- <button type="button" class="sv-carousel__arrow is-prev" aria-label="Previous slide" onclick={prev} disabled={!loop && current === 0}>‹</button>
96
- <button type="button" class="sv-carousel__arrow is-next" aria-label="Next slide" onclick={next} disabled={!loop && current === count - 1}>›</button>
105
+ <button type="button" class="sv-carousel__arrow is-prev" aria-label={rtl ? 'Next slide' : 'Previous slide'} onclick={rtl ? next : prev} disabled={rtl ? (!loop && current === count - 1) : (!loop && current === 0)}>{rtl ? '›' : '‹'}</button>
106
+ <button type="button" class="sv-carousel__arrow is-next" aria-label={rtl ? 'Previous slide' : 'Next slide'} onclick={rtl ? prev : next} disabled={rtl ? (!loop && current === 0) : (!loop && current === count - 1)}>{rtl ? '‹' : '›'}</button>
97
107
  {/if}
98
108
 
99
109
  {#if dots && count > 1}
@@ -103,6 +113,16 @@
103
113
  {/each}
104
114
  </div>
105
115
  {/if}
116
+
117
+ {#if autoplay > 0}
118
+ <button
119
+ type="button"
120
+ class="sv-carousel__playpause"
121
+ aria-label={userPaused ? 'Play autoplay' : 'Pause autoplay'}
122
+ aria-pressed={userPaused}
123
+ onclick={() => (userPaused = !userPaused)}
124
+ >{userPaused ? '▶' : '⏸'}</button>
125
+ {/if}
106
126
  </div>
107
127
 
108
128
  <style>
@@ -131,4 +151,13 @@
131
151
  background: color-mix(in srgb, var(--sg-fg, #0f172a) 30%, var(--sg-bg, #fff)); transition: background 0.15s, width 0.15s;
132
152
  }
133
153
  .sv-carousel__dot.is-active { width: 20px; border-radius: 4px; background: var(--sg-accent, #2563eb); }
154
+
155
+ .sv-carousel__playpause {
156
+ position: absolute; top: 10px; inset-inline-end: 10px; z-index: 2;
157
+ width: 28px; height: 28px; display: grid; place-items: center; border-radius: 50%;
158
+ background: color-mix(in srgb, var(--sg-bg, #fff) 82%, transparent); color: var(--sg-fg, #0f172a);
159
+ border: 1px solid var(--sg-border, #e2e8f0); cursor: pointer; font-size: 12px; line-height: 1;
160
+ box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.3);
161
+ }
162
+ .sv-carousel__playpause:hover { background: var(--sg-bg, #fff); }
134
163
  </style>
@@ -11,6 +11,7 @@
11
11
  * ```
12
12
  */
13
13
  import type { Snippet } from 'svelte';
14
+ import { type EditorDir } from './editor-contract';
14
15
  type Props = {
15
16
  count: number;
16
17
  slide?: Snippet<[number]>;
@@ -22,6 +23,8 @@ type Props = {
22
23
  arrows?: boolean;
23
24
  dots?: boolean;
24
25
  ariaLabel?: string;
26
+ /** Text direction (rtl mirrors the prev/next arrows + slide direction). */
27
+ dir?: EditorDir;
25
28
  };
26
29
  declare const SvCarousel: import("svelte").Component<Props, {}, "current">;
27
30
  type SvCarousel = ReturnType<typeof SvCarousel>;
@@ -84,7 +84,6 @@
84
84
  const layer = createDismissableLayer({
85
85
  element: () => [triggerEl, panelEl],
86
86
  onDismiss: () => col.popover.close(),
87
- closeOnEscape: false,
88
87
  })
89
88
  layer.activate()
90
89
  return () => {
@@ -18,6 +18,7 @@
18
18
  import type { Snippet } from 'svelte'
19
19
  import { portalToBody, popIn } from './popover'
20
20
  import { createDismissableLayer } from './a11y/dismissable'
21
+ import { createFocusTrap } from './a11y/focus-trap'
21
22
  import SvMenuList, { type MenuItem } from './SvMenuList.svelte'
22
23
 
23
24
  type Props = {
@@ -33,6 +34,7 @@
33
34
  let open = $state(false)
34
35
  let pos = $state({ x: 0, y: 0 })
35
36
  let panelEl = $state<HTMLDivElement | null>(null)
37
+ let zoneEl = $state<HTMLDivElement | null>(null)
36
38
 
37
39
  const MENU_W = 200
38
40
  function openAt(clientX: number, clientY: number) {
@@ -50,9 +52,24 @@
50
52
  openAt(e.clientX, e.clientY)
51
53
  }
52
54
 
55
+ // Keyboard equivalent of a right-click: the ContextMenu key, or Shift+F10.
56
+ // Anchors at the focused zone's own position instead of the (nonexistent)
57
+ // pointer coordinates.
58
+ function onZoneKeydown(e: KeyboardEvent) {
59
+ if (disabled) return
60
+ const isMenuKey = e.key === 'ContextMenu' || (e.key === 'F10' && e.shiftKey)
61
+ if (!isMenuKey) return
62
+ e.preventDefault()
63
+ const rect = zoneEl?.getBoundingClientRect()
64
+ openAt(rect ? rect.left : 0, rect ? rect.bottom : 0)
65
+ }
66
+
53
67
  $effect(() => {
54
- if (!open) return
55
- queueMicrotask(() => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])')?.focus())
68
+ if (!open || !panelEl) return
69
+ const trap = createFocusTrap(panelEl, {
70
+ initialFocus: () => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])') ?? null,
71
+ })
72
+ trap.activate()
56
73
  const layer = createDismissableLayer({
57
74
  element: () => panelEl,
58
75
  onDismiss: () => (open = false),
@@ -61,12 +78,12 @@
61
78
  // Reposition-close on scroll (a moved anchor would leave the menu stranded).
62
79
  const onScroll = () => (open = false)
63
80
  window.addEventListener('scroll', onScroll, true)
64
- return () => { layer.release(); window.removeEventListener('scroll', onScroll, true) }
81
+ return () => { layer.release(); trap.release(); window.removeEventListener('scroll', onScroll, true) }
65
82
  })
66
83
  </script>
67
84
 
68
- <!-- svelte-ignore a11y_no_static_element_interactions -->
69
- <div class="sv-ctx__zone" oncontextmenu={onContextMenu}>
85
+ <!-- svelte-ignore a11y_no_static_element_interactions, a11y_no_noninteractive_tabindex -->
86
+ <div class="sv-ctx__zone" bind:this={zoneEl} tabindex={disabled ? -1 : 0} oncontextmenu={onContextMenu} onkeydown={onZoneKeydown}>
70
87
  {@render children?.()}
71
88
  </div>
72
89
 
@@ -86,7 +103,10 @@
86
103
  {/if}
87
104
 
88
105
  <style>
89
- .sv-ctx__zone { display: contents; }
106
+ /* display:contents generates no box and per spec cannot receive focus - it
107
+ must be a real box for the keyboard-open path (tabindex) to work at all. */
108
+ .sv-ctx__zone { display: block; }
109
+ .sv-ctx__zone:focus-visible { outline: 2px solid var(--sg-accent, #6366f1); outline-offset: 1px; }
90
110
  :global(.sv-ctx) {
91
111
  z-index: 2147483646; min-width: 200px;
92
112
  background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
@@ -171,12 +171,14 @@
171
171
  box-shadow: 0 24px 64px -16px rgba(15, 23, 42, 0.5);
172
172
  overflow: hidden;
173
173
  }
174
- /* Left / right: full height, fixed width, slide along X. */
174
+ /* Left / right: full height, fixed width, slide along X. `side` is a stated
175
+ physical edge (like SvToaster's position prop), not a text-flow-relative
176
+ "start/end" - so it stays physical left/right regardless of page dir. */
175
177
  :global(.sv-drawer--right), :global(.sv-drawer--left) {
176
178
  top: 0; bottom: 0; width: var(--sv-drawer-size); max-width: 100vw;
177
179
  }
178
- :global(.sv-drawer--right) { inset-inline-end: 0; border-inline-start: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-right 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
179
- :global(.sv-drawer--left) { inset-inline-start: 0; border-inline-end: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-left 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
180
+ :global(.sv-drawer--right) { right: 0; border-left: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-right 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
181
+ :global(.sv-drawer--left) { left: 0; border-right: 1px solid var(--sg-border, #e2e8f0); animation: sv-drawer-in-left 0.22s cubic-bezier(0.16, 1, 0.3, 1) both; }
180
182
  /* Top / bottom: full width, fixed height, slide along Y. */
181
183
  :global(.sv-drawer--top), :global(.sv-drawer--bottom) {
182
184
  left: 0; right: 0; height: var(--sv-drawer-size); max-height: 100vh;
@@ -1,7 +1,7 @@
1
- import { type CellEditorOption, type Column, type ColumnDef, type Row, type RowData, type TableFeatures } from "./index";
1
+ import { type CellEditorOption, type Column, type ColumnDef, type Row, type RowData, type TableFeatures, type ChartType, type ChartSpec, type ChartValueFormat } from "./index";
2
2
  import "./sv-grid-scrollbar";
3
3
  import { type ColumnStat } from "./conditional-formatting";
4
- import type { Props, SelectionRange, CellEditState, FilterOperator, MenuPosition, ContextMenuTarget } from "./SvGrid.types";
4
+ import type { Props, SelectionRange, CellEditState, FilterOperator, MenuPosition, ContextMenuTarget, ChartingConfig } from "./SvGrid.types";
5
5
  import { operatorOption, operatorsForColumn, defaultOperatorFor, operatorLabelFor } from "./filter-operators";
6
6
  import { type FacetBucket, isBucketableColumn, buildBuckets, isInBucket } from "./facet-buckets";
7
7
  import { getColumnBaseValue, isGroupRow, toolPanelHeaderLabel, formatSummaryNumeric, getColumnAlign, getPinnedCellValue, getColumnAccessorValue, columnDefMatchesId } from "./cell-values";
@@ -257,6 +257,89 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
257
257
  readonly toolPanelHeaderLabel: typeof toolPanelHeaderLabel;
258
258
  readonly toggleColumnVisibleInPanel: (columnId: string) => void;
259
259
  readonly moveColumnInPanel: (columnId: string, dir: -1 | 1) => void;
260
+ readonly chartingEnabled: boolean;
261
+ readonly chartingConfig: {
262
+ position: "right" | "bottom";
263
+ defaultOpen: boolean;
264
+ defaultType: ChartType;
265
+ height: number;
266
+ width: number;
267
+ crossFilter: boolean;
268
+ } | null;
269
+ readonly chartCfg: ChartingConfig<TData> | null;
270
+ readonly chartIsCustom: boolean;
271
+ chartPanelOpen: boolean;
272
+ chartType: ChartType;
273
+ chartReduce: "sum" | "avg" | "count";
274
+ chartDimensionId: string | null;
275
+ chartMeasureId: string | null;
276
+ chartSeriesId: string | null;
277
+ chartStacked: boolean;
278
+ chartDataLabels: boolean;
279
+ chartLogScale: boolean;
280
+ chartTimeAxis: boolean;
281
+ chartValueFormat: ChartValueFormat;
282
+ readonly chartDimensionIsDate: boolean;
283
+ chartSize: number | null;
284
+ chartFloating: boolean;
285
+ chartMaximized: boolean;
286
+ chartFloatRect: {
287
+ x: number;
288
+ y: number;
289
+ w: number;
290
+ h: number;
291
+ } | null;
292
+ readonly chartableColumns: {
293
+ dims: {
294
+ id: string;
295
+ field: string;
296
+ label: string;
297
+ }[];
298
+ measures: {
299
+ id: string;
300
+ field: string;
301
+ label: string;
302
+ }[];
303
+ };
304
+ readonly chartSpec: ChartSpec | null;
305
+ applyChartCrossFilter: (category: string) => void;
306
+ clearChartCrossFilter: () => void;
307
+ readonly charts: {
308
+ id: string;
309
+ title: string;
310
+ }[];
311
+ activeChartIndex: number;
312
+ addChart(): void;
313
+ removeChart(index?: number): void;
314
+ renameChart(index: number, title: string): void;
315
+ getChartsState(): {
316
+ title: string;
317
+ type: ChartType;
318
+ dimension: string | null;
319
+ series: string | null;
320
+ measure: string | null;
321
+ reduce: "sum" | "avg" | "count";
322
+ stacked: boolean | null;
323
+ dataLabels: boolean | null;
324
+ logScale: boolean | null;
325
+ timeAxis: boolean | null;
326
+ valueFormat: ChartValueFormat | null;
327
+ }[];
328
+ applyChartsState(list: ReadonlyArray<Record<string, unknown>>, active?: number): void;
329
+ chartAiHandler: ((prompt: string) => Promise<Record<string, unknown> | null>) | null;
330
+ applyChartConfig(config: {
331
+ open?: boolean;
332
+ type?: ChartType;
333
+ dimension?: string | null;
334
+ series?: string | null;
335
+ measure?: string | null;
336
+ reduce?: "sum" | "avg" | "count";
337
+ stacked?: boolean;
338
+ dataLabels?: boolean;
339
+ logScale?: boolean;
340
+ timeAxis?: boolean;
341
+ valueFormat?: ChartValueFormat;
342
+ }): void;
260
343
  readonly toggleGroupInPanel: (columnId: string) => void;
261
344
  lastSortingSerialized: string;
262
345
  lastFiltersSerialized: string;