@opendata-ai/openchart-vanilla 8.0.0-rc.9 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/{chunk-SPILUE5S.js → chunk-2NAQRP66.js} +2 -2
  2. package/dist/{chunk-IZQBT6QM.js → chunk-6BUCYCLD.js} +7283 -5881
  3. package/dist/chunk-6BUCYCLD.js.map +1 -0
  4. package/dist/chunk-DCODCLWI.js +234 -0
  5. package/dist/chunk-DCODCLWI.js.map +1 -0
  6. package/dist/{chunk-QW7ZB6NE.js → chunk-EKAUQAAX.js} +49 -45
  7. package/dist/chunk-EKAUQAAX.js.map +1 -0
  8. package/dist/chunk-RM5XS6NR.js +113 -0
  9. package/dist/chunk-RM5XS6NR.js.map +1 -0
  10. package/dist/chunk-T32UHMZA.js +282 -0
  11. package/dist/chunk-T32UHMZA.js.map +1 -0
  12. package/dist/export-gif.d.ts +113 -0
  13. package/dist/export-gif.js +21 -0
  14. package/dist/export-gif.js.map +1 -0
  15. package/dist/index.d.ts +309 -27
  16. package/dist/index.js +2020 -836
  17. package/dist/index.js.map +1 -1
  18. package/dist/{mount-CUKQLeqD.d.ts → mount-CL-nA0f-.d.ts} +85 -2
  19. package/dist/simulation-worker.js +83 -2
  20. package/dist/static.d.ts +2 -0
  21. package/dist/static.js +8 -69
  22. package/dist/static.js.map +1 -1
  23. package/dist/story/index.d.ts +25 -9
  24. package/dist/story/index.js +9 -7
  25. package/dist/story/index.js.map +1 -1
  26. package/dist/styles.css +1 -1
  27. package/dist/styles.css.map +1 -1
  28. package/package.json +14 -6
  29. package/dist/chunk-IZQBT6QM.js.map +0 -1
  30. package/dist/chunk-QW7ZB6NE.js.map +0 -1
  31. package/src/__test-fixtures__/dom.ts +0 -70
  32. package/src/__test-fixtures__/specs.ts +0 -214
  33. package/src/__tests__/animation.test.ts +0 -358
  34. package/src/__tests__/annotations-render.test.ts +0 -201
  35. package/src/__tests__/compound-labels.test.ts +0 -122
  36. package/src/__tests__/continuous-legend.test.ts +0 -91
  37. package/src/__tests__/crosshair.test.ts +0 -130
  38. package/src/__tests__/edit-events.test.ts +0 -1477
  39. package/src/__tests__/editable-prop.test.ts +0 -174
  40. package/src/__tests__/events.test.ts +0 -387
  41. package/src/__tests__/export.test.ts +0 -241
  42. package/src/__tests__/font-race.test.ts +0 -199
  43. package/src/__tests__/gradient-ids.test.ts +0 -159
  44. package/src/__tests__/gradient-utils.test.ts +0 -125
  45. package/src/__tests__/invert.test.ts +0 -187
  46. package/src/__tests__/known-bugs-render.test.ts +0 -183
  47. package/src/__tests__/map.test.ts +0 -636
  48. package/src/__tests__/mount.test.ts +0 -357
  49. package/src/__tests__/pattern-fills.test.ts +0 -105
  50. package/src/__tests__/resize-timing.test.ts +0 -218
  51. package/src/__tests__/sankey.test.ts +0 -133
  52. package/src/__tests__/selection-events.test.ts +0 -1062
  53. package/src/__tests__/series-search.test.ts +0 -311
  54. package/src/__tests__/static.test.ts +0 -141
  55. package/src/__tests__/svg-renderer.test.ts +0 -895
  56. package/src/__tests__/table-keyboard.test.ts +0 -361
  57. package/src/__tests__/table-mount.test.ts +0 -639
  58. package/src/__tests__/tilemap.test.ts +0 -158
  59. package/src/__tests__/tooltip.test.ts +0 -337
  60. package/src/__tests__/transition.test.ts +0 -1792
  61. package/src/__tests__/update-scrolly-matrix.test.ts +0 -563
  62. package/src/__tests__/you-draw-it.test.ts +0 -273
  63. package/src/animation-vars.ts +0 -42
  64. package/src/animation.ts +0 -76
  65. package/src/barlist-mount.ts +0 -383
  66. package/src/barlist-renderer.ts +0 -267
  67. package/src/dom-helpers.ts +0 -17
  68. package/src/export.ts +0 -482
  69. package/src/gradient-utils.ts +0 -138
  70. package/src/graph/__tests__/canvas-renderer.test.ts +0 -713
  71. package/src/graph/__tests__/graph-mount.test.ts +0 -213
  72. package/src/graph/__tests__/interaction.test.ts +0 -205
  73. package/src/graph/__tests__/keyboard.test.ts +0 -653
  74. package/src/graph/__tests__/search.test.ts +0 -88
  75. package/src/graph/__tests__/simulation.test.ts +0 -233
  76. package/src/graph/__tests__/spatial-index.test.ts +0 -142
  77. package/src/graph/__tests__/zoom.test.ts +0 -203
  78. package/src/graph/canvas-renderer.ts +0 -747
  79. package/src/graph/interaction.ts +0 -367
  80. package/src/graph/keyboard.ts +0 -208
  81. package/src/graph/search.ts +0 -50
  82. package/src/graph/simulation-worker-url.ts +0 -12
  83. package/src/graph/simulation-worker.ts +0 -278
  84. package/src/graph/simulation.ts +0 -422
  85. package/src/graph/spatial-index.ts +0 -121
  86. package/src/graph/types.ts +0 -47
  87. package/src/graph/worker-protocol.ts +0 -70
  88. package/src/graph/zoom.ts +0 -134
  89. package/src/graph-mount.ts +0 -888
  90. package/src/index.ts +0 -76
  91. package/src/interactions/chart-events.ts +0 -139
  92. package/src/interactions/crosshair.ts +0 -233
  93. package/src/interactions/drag-handler.ts +0 -175
  94. package/src/interactions/editing-drags.ts +0 -649
  95. package/src/interactions/index.ts +0 -27
  96. package/src/interactions/invert.ts +0 -54
  97. package/src/interactions/keyboard-nav.ts +0 -111
  98. package/src/interactions/legend-interaction.ts +0 -46
  99. package/src/interactions/selection.ts +0 -286
  100. package/src/interactions/tooltip-events.ts +0 -72
  101. package/src/map-camera.ts +0 -111
  102. package/src/map-mount.ts +0 -797
  103. package/src/map-renderer.ts +0 -476
  104. package/src/map-transition.ts +0 -99
  105. package/src/measure-text.ts +0 -134
  106. package/src/mount.ts +0 -1434
  107. package/src/pattern-utils.ts +0 -110
  108. package/src/renderers/annotations.ts +0 -388
  109. package/src/renderers/axes.ts +0 -351
  110. package/src/renderers/brand.ts +0 -87
  111. package/src/renderers/chrome.ts +0 -196
  112. package/src/renderers/endpoint-labels.ts +0 -150
  113. package/src/renderers/legend.ts +0 -312
  114. package/src/renderers/marks.ts +0 -561
  115. package/src/renderers/metrics.ts +0 -63
  116. package/src/renderers/svg-dom.ts +0 -40
  117. package/src/renderers/table-cells.ts +0 -446
  118. package/src/resize-observer.ts +0 -46
  119. package/src/sankey-mount.ts +0 -622
  120. package/src/sankey-renderer.ts +0 -614
  121. package/src/series-search.ts +0 -392
  122. package/src/static.ts +0 -251
  123. package/src/story/__tests__/camera-math.test.ts +0 -145
  124. package/src/story/__tests__/create-chart-story.test.ts +0 -317
  125. package/src/story/__tests__/progress-math.test.ts +0 -95
  126. package/src/story/__tests__/scroll-driver.test.ts +0 -130
  127. package/src/story/__tests__/tween.test.ts +0 -159
  128. package/src/story/camera-math.ts +0 -125
  129. package/src/story/create-chart-story.ts +0 -353
  130. package/src/story/crossfade.ts +0 -70
  131. package/src/story/index.ts +0 -39
  132. package/src/story/progress-math.ts +0 -85
  133. package/src/story/resolve-camera-target.ts +0 -119
  134. package/src/story/scroll-driver.ts +0 -206
  135. package/src/story/story-camera.ts +0 -49
  136. package/src/story/tween.ts +0 -112
  137. package/src/story/types.ts +0 -76
  138. package/src/svg-ids.ts +0 -23
  139. package/src/svg-renderer.ts +0 -383
  140. package/src/table-keyboard.ts +0 -266
  141. package/src/table-mount.ts +0 -567
  142. package/src/table-renderer.ts +0 -414
  143. package/src/text-edit-overlay.ts +0 -389
  144. package/src/theme-tokens.ts +0 -51
  145. package/src/tilemap-mount.ts +0 -511
  146. package/src/tilemap-renderer.ts +0 -396
  147. package/src/tooltip.ts +0 -177
  148. package/src/transition.ts +0 -2816
  149. package/src/you-draw-it.ts +0 -480
  150. /package/dist/{chunk-SPILUE5S.js.map → chunk-2NAQRP66.js.map} +0 -0
@@ -1,567 +0,0 @@
1
- /**
2
- * Table mount API: the main entry point for vanilla JS table usage.
3
- *
4
- * createTable() takes a container, spec, and options, compiles the table,
5
- * renders it as HTML, sets up responsive resizing, sort/search/pagination
6
- * interactivity, and returns a TableInstance with update/resize/export/destroy.
7
- *
8
- * Supports both controlled and uncontrolled modes:
9
- * - Uncontrolled (default): manages sort/search/page internally
10
- * - Controlled: reads state from externalState, fires onStateChange
11
- */
12
-
13
- import type {
14
- CompileTableOptions,
15
- DarkMode,
16
- SortState,
17
- TableLayout,
18
- TableSpec,
19
- ThemeConfig,
20
- } from '@opendata-ai/openchart-core';
21
- import { getBreakpoint } from '@opendata-ai/openchart-core';
22
- import { compileTable } from '@opendata-ai/openchart-engine';
23
- import { setupTableAnimationCleanup } from './animation';
24
- import { observeResize } from './resize-observer';
25
- import { attachKeyboardNav } from './table-keyboard';
26
- import { renderTable } from './table-renderer';
27
-
28
- // ---------------------------------------------------------------------------
29
- // Types
30
- // ---------------------------------------------------------------------------
31
-
32
- export interface TableState {
33
- sort: SortState | null;
34
- search: string;
35
- page: number;
36
- }
37
-
38
- export interface TableMountOptions {
39
- theme?: ThemeConfig;
40
- darkMode?: DarkMode;
41
- responsive?: boolean;
42
- /** Show the tryOpenData.ai watermark. Defaults to true. */
43
- watermark?: boolean;
44
- onRowClick?: (row: Record<string, unknown>) => void;
45
- onStateChange?: (state: TableState) => void;
46
- externalState?: { sort?: SortState | null; search?: string; page?: number };
47
- }
48
-
49
- export interface TableInstance {
50
- update(spec: TableSpec): void;
51
- resize(): void;
52
- export(format: 'csv'): string;
53
- getState(): TableState;
54
- setState(partial: Partial<TableState>): void;
55
- destroy(): void;
56
- }
57
-
58
- // ---------------------------------------------------------------------------
59
- // Dark mode resolution
60
- // ---------------------------------------------------------------------------
61
-
62
- function resolveDarkMode(mode?: DarkMode): boolean {
63
- if (mode === 'force') return true;
64
- if (mode === 'off' || mode === undefined) return false;
65
- if (typeof window !== 'undefined' && window.matchMedia) {
66
- return window.matchMedia('(prefers-color-scheme: dark)').matches;
67
- }
68
- return false;
69
- }
70
-
71
- // ---------------------------------------------------------------------------
72
- // CSV export
73
- // ---------------------------------------------------------------------------
74
-
75
- function csvEscape(value: string): string {
76
- if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
77
- return `"${value.replace(/"/g, '""')}"`;
78
- }
79
- return value;
80
- }
81
-
82
- // ---------------------------------------------------------------------------
83
- // Sort cycling
84
- // ---------------------------------------------------------------------------
85
-
86
- /**
87
- * Cycle sort state: clicking same column cycles none -> asc -> desc -> none.
88
- * Clicking a different column resets to asc.
89
- */
90
- function cycleSort(current: SortState | null, column: string): SortState | null {
91
- if (!current || current.column !== column) {
92
- return { column, direction: 'asc' };
93
- }
94
- if (current.direction === 'asc') {
95
- return { column, direction: 'desc' };
96
- }
97
- // desc -> none
98
- return null;
99
- }
100
-
101
- // ---------------------------------------------------------------------------
102
- // Main API
103
- // ---------------------------------------------------------------------------
104
-
105
- /**
106
- * Create a table instance from a spec and mount it into a container.
107
- *
108
- * @param container - The DOM element to render into.
109
- * @param spec - The table spec.
110
- * @param options - Mount options.
111
- * @returns A TableInstance with update/resize/export/destroy methods.
112
- */
113
- export function createTable(
114
- container: HTMLElement,
115
- spec: TableSpec,
116
- options?: TableMountOptions,
117
- ): TableInstance {
118
- let currentSpec = spec;
119
- let currentLayout: TableLayout;
120
- let wrapperElement: HTMLElement | null = null;
121
- let disconnectResize: (() => void) | null = null;
122
- let cleanupKeyboard: (() => void) | null = null;
123
- let cleanupAnimations: (() => void) | null = null;
124
- let isFirstRender = true;
125
- let destroyed = false;
126
-
127
- // Internal state (used in uncontrolled mode)
128
- const internalState: TableState = {
129
- sort: null,
130
- search: '',
131
- page: 0,
132
- };
133
-
134
- // Debounce timers
135
- let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;
136
- let resizeDebounceTimer: ReturnType<typeof setTimeout> | null = null;
137
-
138
- const isControlled = options?.externalState !== undefined;
139
-
140
- function getState(): TableState {
141
- if (isControlled && options?.externalState) {
142
- return {
143
- sort: options.externalState.sort ?? null,
144
- search: options.externalState.search ?? '',
145
- page: options.externalState.page ?? 0,
146
- };
147
- }
148
- return { ...internalState };
149
- }
150
-
151
- function updateState(partial: Partial<TableState>): void {
152
- if (isControlled) {
153
- // In controlled mode, notify parent
154
- const current = getState();
155
- const next: TableState = {
156
- sort: partial.sort !== undefined ? partial.sort : current.sort,
157
- search: partial.search !== undefined ? partial.search : current.search,
158
- page: partial.page !== undefined ? partial.page : current.page,
159
- };
160
- options?.onStateChange?.(next);
161
- } else {
162
- // In uncontrolled mode, update internal state
163
- if (partial.sort !== undefined) internalState.sort = partial.sort;
164
- if (partial.search !== undefined) internalState.search = partial.search;
165
- if (partial.page !== undefined) internalState.page = partial.page;
166
- options?.onStateChange?.({ ...internalState });
167
- }
168
- }
169
-
170
- function compile(): TableLayout {
171
- const state = getState();
172
- const darkMode = resolveDarkMode(options?.darkMode);
173
- const { width } = getContainerDimensions();
174
-
175
- const compileOpts: CompileTableOptions = {
176
- width,
177
- height: 600,
178
- theme: options?.theme,
179
- darkMode,
180
- watermark: options?.watermark,
181
- sort: state.sort ?? undefined,
182
- search: state.search || undefined,
183
- page: state.page,
184
- };
185
-
186
- return compileTable(currentSpec, compileOpts);
187
- }
188
-
189
- function getContainerDimensions(): { width: number; height: number } {
190
- const rect = container.getBoundingClientRect();
191
- return {
192
- width: Math.max(rect.width || 600, 100),
193
- height: Math.max(rect.height || 400, 100),
194
- };
195
- }
196
-
197
- /**
198
- * Announce a message to screen readers via the live region.
199
- */
200
- function announce(message: string): void {
201
- if (!wrapperElement) return;
202
- const liveRegion = wrapperElement.querySelector('.oc-table-live-region');
203
- if (liveRegion) {
204
- liveRegion.textContent = message;
205
- }
206
- }
207
-
208
- /**
209
- * Apply responsive breakpoint class based on container width.
210
- * Only auto-adds compact mode at small sizes. Never removes compact
211
- * if the spec explicitly requests it (layout.compact === true).
212
- */
213
- function applyBreakpointClass(): void {
214
- if (!wrapperElement) return;
215
- const { width } = getContainerDimensions();
216
- const bp = getBreakpoint(width);
217
-
218
- if (bp === 'compact' || bp === 'medium') {
219
- wrapperElement.classList.add('oc-table--compact');
220
- } else if (!currentLayout?.compact) {
221
- // Only remove compact if the spec didn't explicitly request it
222
- wrapperElement.classList.remove('oc-table--compact');
223
- }
224
- }
225
-
226
- function render(): void {
227
- if (destroyed) return;
228
-
229
- try {
230
- // Cancel any in-flight animations before re-rendering
231
- if (cleanupAnimations) {
232
- cleanupAnimations();
233
- cleanupAnimations = null;
234
- }
235
-
236
- // Clean up previous keyboard nav
237
- if (cleanupKeyboard) {
238
- cleanupKeyboard();
239
- cleanupKeyboard = null;
240
- }
241
-
242
- // Clean up previous render
243
- if (wrapperElement?.parentNode) {
244
- wrapperElement.parentNode.removeChild(wrapperElement);
245
- wrapperElement = null;
246
- }
247
-
248
- currentLayout = compile();
249
- const shouldAnimate = isFirstRender && !!currentLayout.animation?.enter;
250
- wrapperElement = renderTable(currentLayout, container, { animate: shouldAnimate });
251
-
252
- // Set up animation cleanup on first animated render
253
- if (shouldAnimate && wrapperElement) {
254
- cleanupAnimations = setupTableAnimationCleanup(wrapperElement);
255
- }
256
- if (isFirstRender) {
257
- isFirstRender = false;
258
- }
259
-
260
- // Apply dark mode class
261
- const isDark = resolveDarkMode(options?.darkMode);
262
- if (isDark) {
263
- container.classList.add('oc-dark');
264
- } else {
265
- container.classList.remove('oc-dark');
266
- }
267
-
268
- // Apply responsive breakpoint
269
- applyBreakpointClass();
270
-
271
- // Add clickable class if onRowClick is provided
272
- if (options?.onRowClick) {
273
- wrapperElement.classList.add('oc-table--clickable');
274
- }
275
-
276
- // Wire up event handlers
277
- wireEvents();
278
-
279
- // Wire up keyboard navigation
280
- if (wrapperElement) {
281
- cleanupKeyboard = attachKeyboardNav({
282
- wrapper: wrapperElement,
283
- onSort: (columnKey: string) => {
284
- const state = getState();
285
- const newSort = cycleSort(state.sort, columnKey);
286
- updateState({ sort: newSort, page: 0 });
287
-
288
- // Announce sort change
289
- if (newSort) {
290
- const dir = newSort.direction === 'asc' ? 'ascending' : 'descending';
291
- announce(`Sorted by ${columnKey} ${dir}`);
292
- } else {
293
- announce('Sort cleared');
294
- }
295
-
296
- if (!isControlled) {
297
- rerender();
298
- }
299
- },
300
- onClearSearch: () => {
301
- updateState({ search: '', page: 0 });
302
- if (!isControlled) {
303
- rerender();
304
- }
305
- },
306
- onAnnounce: announce,
307
- });
308
- }
309
- } catch (err) {
310
- console.error('[viz] Table render failed:', err);
311
- }
312
- }
313
-
314
- function wireEvents(): void {
315
- if (!wrapperElement) return;
316
-
317
- // Sort click handlers on thead buttons
318
- const sortBtns = wrapperElement.querySelectorAll('[data-sort-column]');
319
- for (const btn of sortBtns) {
320
- btn.addEventListener('click', handleSortClick);
321
- }
322
-
323
- // Search input
324
- const searchInput = wrapperElement.querySelector(
325
- '.oc-table-search input',
326
- ) as HTMLInputElement | null;
327
- if (searchInput) {
328
- searchInput.addEventListener('input', handleSearchInput);
329
- }
330
-
331
- // Pagination buttons
332
- const pageButtons = wrapperElement.querySelectorAll('[data-page-action]');
333
- for (const btn of pageButtons) {
334
- btn.addEventListener('click', handlePageClick);
335
- }
336
-
337
- // Row click
338
- if (options?.onRowClick) {
339
- const rows = wrapperElement.querySelectorAll('tbody tr');
340
- for (const row of rows) {
341
- row.addEventListener('click', handleRowClick);
342
- }
343
- }
344
- }
345
-
346
- function handleSortClick(e: Event): void {
347
- const btn = e.currentTarget as HTMLElement;
348
- const column = btn.getAttribute('data-sort-column');
349
- if (!column) return;
350
-
351
- const state = getState();
352
- const newSort = cycleSort(state.sort, column);
353
-
354
- updateState({ sort: newSort, page: 0 });
355
-
356
- // Announce sort change for screen readers
357
- if (newSort) {
358
- const dir = newSort.direction === 'asc' ? 'ascending' : 'descending';
359
- announce(`Sorted by ${column} ${dir}`);
360
- } else {
361
- announce('Sort cleared');
362
- }
363
-
364
- if (!isControlled) {
365
- rerender();
366
- }
367
- }
368
-
369
- function handleSearchInput(e: Event): void {
370
- const input = e.target as HTMLInputElement;
371
- const query = input.value;
372
-
373
- if (searchDebounceTimer !== null) {
374
- clearTimeout(searchDebounceTimer);
375
- }
376
-
377
- searchDebounceTimer = setTimeout(() => {
378
- searchDebounceTimer = null;
379
- updateState({ search: query, page: 0 });
380
-
381
- if (!isControlled) {
382
- rerender();
383
- // Announce search result count
384
- const rowCount = currentLayout?.rows?.length ?? 0;
385
- if (query) {
386
- announce(`${rowCount} result${rowCount !== 1 ? 's' : ''} found`);
387
- }
388
- }
389
- }, 200);
390
- }
391
-
392
- function handlePageClick(e: Event): void {
393
- const btn = e.currentTarget as HTMLElement;
394
- const action = btn.getAttribute('data-page-action');
395
- const state = getState();
396
-
397
- if (action === 'prev' && state.page > 0) {
398
- updateState({ page: state.page - 1 });
399
- } else if (action === 'next') {
400
- updateState({ page: state.page + 1 });
401
- }
402
-
403
- if (!isControlled) {
404
- rerender();
405
- }
406
- }
407
-
408
- function handleRowClick(e: Event): void {
409
- const tr = e.currentTarget as HTMLElement;
410
- const rowId = tr.getAttribute('data-row-id');
411
- if (!rowId || !currentLayout) return;
412
-
413
- const row = currentLayout.rows.find((r) => r.id === rowId);
414
- if (row) {
415
- options?.onRowClick?.(row.data);
416
- }
417
- }
418
-
419
- /**
420
- * Re-render the table, preserving search input focus across the DOM rebuild.
421
- */
422
- function rerender(): void {
423
- if (destroyed) return;
424
-
425
- // Capture current search input state before re-render
426
- const searchInput = wrapperElement?.querySelector(
427
- '.oc-table-search input',
428
- ) as HTMLInputElement | null;
429
- const hadFocus = searchInput && document.activeElement === searchInput;
430
- const selectionStart = searchInput?.selectionStart ?? 0;
431
- const selectionEnd = searchInput?.selectionEnd ?? 0;
432
-
433
- render();
434
-
435
- // Restore search focus after re-render
436
- if (hadFocus) {
437
- const newInput = wrapperElement?.querySelector(
438
- '.oc-table-search input',
439
- ) as HTMLInputElement | null;
440
- if (newInput) {
441
- newInput.focus();
442
- newInput.setSelectionRange(selectionStart, selectionEnd);
443
- }
444
- }
445
- }
446
-
447
- function update(newSpec: TableSpec): void {
448
- if (destroyed) return;
449
- currentSpec = newSpec;
450
- render();
451
- }
452
-
453
- function resize(): void {
454
- if (destroyed) return;
455
- if (cleanupAnimations) return; // Skip resize during entrance animation
456
- render();
457
- }
458
-
459
- function doExport(format: 'csv'): string {
460
- if (format !== 'csv') {
461
- throw new Error(`Unsupported export format: ${format}`);
462
- }
463
-
464
- // Export all filtered/sorted data (not just current page)
465
- // Re-compile without pagination
466
- const state = getState();
467
- const darkMode = resolveDarkMode(options?.darkMode);
468
- const { width } = getContainerDimensions();
469
-
470
- const fullLayout = compileTable(currentSpec, {
471
- width,
472
- height: 600,
473
- theme: options?.theme,
474
- darkMode,
475
- sort: state.sort ?? undefined,
476
- search: state.search || undefined,
477
- // No page/pageSize: get all rows
478
- });
479
-
480
- const headers = fullLayout.columns.map((c) => c.label);
481
- const csvRows = [headers.map(csvEscape).join(',')];
482
-
483
- for (const row of fullLayout.rows) {
484
- const values = row.cells.map((cell) => csvEscape(cell.formattedValue));
485
- csvRows.push(values.join(','));
486
- }
487
-
488
- return csvRows.join('\n');
489
- }
490
-
491
- function setState(partial: Partial<TableState>): void {
492
- if (destroyed) return;
493
-
494
- // In controlled mode compile() reads from options.externalState, so
495
- // updating internalState alone would be ignored. Push the new values onto
496
- // externalState (the source getState() reads) so the render reflects them.
497
- if (isControlled && options?.externalState) {
498
- if (partial.sort !== undefined) options.externalState.sort = partial.sort;
499
- if (partial.search !== undefined) options.externalState.search = partial.search;
500
- if (partial.page !== undefined) options.externalState.page = partial.page;
501
- } else {
502
- if (partial.sort !== undefined) internalState.sort = partial.sort;
503
- if (partial.search !== undefined) internalState.search = partial.search;
504
- if (partial.page !== undefined) internalState.page = partial.page;
505
- }
506
-
507
- render();
508
- }
509
-
510
- function destroy(): void {
511
- if (destroyed) return;
512
- destroyed = true;
513
-
514
- if (cleanupAnimations) {
515
- cleanupAnimations();
516
- cleanupAnimations = null;
517
- }
518
- if (cleanupKeyboard) {
519
- cleanupKeyboard();
520
- cleanupKeyboard = null;
521
- }
522
- if (searchDebounceTimer !== null) {
523
- clearTimeout(searchDebounceTimer);
524
- searchDebounceTimer = null;
525
- }
526
- if (resizeDebounceTimer !== null) {
527
- clearTimeout(resizeDebounceTimer);
528
- resizeDebounceTimer = null;
529
- }
530
- if (disconnectResize) {
531
- disconnectResize();
532
- disconnectResize = null;
533
- }
534
- if (wrapperElement?.parentNode) {
535
- wrapperElement.parentNode.removeChild(wrapperElement);
536
- wrapperElement = null;
537
- }
538
- container.classList.remove('oc-dark');
539
- }
540
-
541
- // Initial render
542
- render();
543
-
544
- // Set up responsive resize with breakpoint detection
545
- if (options?.responsive !== false) {
546
- disconnectResize = observeResize(container, () => {
547
- if (resizeDebounceTimer !== null) {
548
- clearTimeout(resizeDebounceTimer);
549
- }
550
- resizeDebounceTimer = setTimeout(() => {
551
- resizeDebounceTimer = null;
552
- // Update breakpoint class without full re-render when possible
553
- applyBreakpointClass();
554
- resize();
555
- }, 100);
556
- });
557
- }
558
-
559
- return {
560
- update,
561
- resize,
562
- export: doExport,
563
- getState,
564
- setState,
565
- destroy,
566
- };
567
- }