@zvndev/yable-react 0.1.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/dist/index.js ADDED
@@ -0,0 +1,4835 @@
1
+ import { canCellEnterEditMode, functionalUpdate, createTable, getFirstKeyboardCell, getResolvedFocusedCell, detectCellChanges } from '@zvndev/yable-core';
2
+ export { CommitError, aggregationFns, createColumnHelper, createLocale, en, filterFns, functionalUpdate, getDefaultLocale, resetLocale, setDefaultLocale, sortingFns } from '@zvndev/yable-core';
3
+ import React3, { createContext, useCallback, useMemo, useState, useRef, useEffect, useContext } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { getInitialRowDragState, moveRow } from '@zvndev/yable-core/features/rowDragging';
6
+
7
+ // src/index.ts
8
+ function shallowEqual(a, b) {
9
+ if (a === b) return true;
10
+ const keysA = Object.keys(a);
11
+ const keysB = Object.keys(b);
12
+ if (keysA.length !== keysB.length) return false;
13
+ for (const key of keysA) {
14
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
15
+ if (a[key] !== b[key]) return false;
16
+ }
17
+ return true;
18
+ }
19
+ function useTable(options) {
20
+ const [state, setState] = useState(() => ({
21
+ sorting: [],
22
+ columnFilters: [],
23
+ globalFilter: "",
24
+ pagination: { pageIndex: 0, pageSize: 10 },
25
+ rowSelection: {},
26
+ columnVisibility: {},
27
+ columnOrder: [],
28
+ columnPinning: { left: [], right: [] },
29
+ columnSizing: {},
30
+ columnSizingInfo: {
31
+ startOffset: null,
32
+ startSize: null,
33
+ deltaOffset: null,
34
+ deltaPercentage: null,
35
+ isResizingColumn: false,
36
+ columnSizingStart: []
37
+ },
38
+ expanded: {},
39
+ rowPinning: { top: [], bottom: [] },
40
+ grouping: [],
41
+ editing: { activeCell: void 0, pendingValues: {} },
42
+ commits: { cells: {}, nextOpId: 1 },
43
+ keyboardNavigation: { focusedCell: null },
44
+ undoRedo: { undoStack: [], redoStack: [], maxSize: 50 },
45
+ fillHandle: { isDragging: false },
46
+ formulas: { enabled: false, formulas: {}, computedValues: {}, errors: {} },
47
+ rowDrag: { draggingRowId: null, overRowId: null, dropPosition: null },
48
+ pivot: {
49
+ enabled: false,
50
+ config: { rowFields: [], columnFields: [], valueFields: [] },
51
+ expandedRowGroups: {},
52
+ expandedColumnGroups: {}
53
+ },
54
+ ...options.initialState,
55
+ ...options.state
56
+ }));
57
+ const stateRef = useRef(state);
58
+ stateRef.current = state;
59
+ const prevOptionsRef = useRef(options);
60
+ const stableOptions = useMemo(() => {
61
+ if (shallowEqual(prevOptionsRef.current, options)) {
62
+ return prevOptionsRef.current;
63
+ }
64
+ prevOptionsRef.current = options;
65
+ return options;
66
+ }, [options]);
67
+ const resolvedState = useMemo(
68
+ () => ({
69
+ ...state,
70
+ ...stableOptions.state
71
+ }),
72
+ [state, stableOptions.state]
73
+ );
74
+ const onStateChange = useCallback(
75
+ (updater) => {
76
+ if (stableOptions.onStateChange) {
77
+ stableOptions.onStateChange(updater);
78
+ } else {
79
+ setState((prev) => functionalUpdate(updater, prev));
80
+ }
81
+ },
82
+ [stableOptions.onStateChange]
83
+ );
84
+ const resolvedOptions = useMemo(
85
+ () => ({
86
+ ...stableOptions,
87
+ state: resolvedState,
88
+ onStateChange
89
+ }),
90
+ [stableOptions, resolvedState, onStateChange]
91
+ );
92
+ const tableRef = useRef(null);
93
+ if (!tableRef.current) {
94
+ tableRef.current = createTable(resolvedOptions);
95
+ } else {
96
+ tableRef.current.setOptions((prev) => ({
97
+ ...prev,
98
+ ...resolvedOptions,
99
+ state: resolvedState,
100
+ onStateChange
101
+ }));
102
+ }
103
+ useEffect(() => {
104
+ return () => {
105
+ if (tableRef.current) {
106
+ tableRef.current.events.removeAllListeners();
107
+ }
108
+ };
109
+ }, []);
110
+ return tableRef.current;
111
+ }
112
+ var EMPTY_RESULT = {
113
+ virtualRows: [],
114
+ totalHeight: 0,
115
+ startIndex: 0,
116
+ endIndex: 0
117
+ };
118
+ function useVirtualization({
119
+ containerRef,
120
+ totalRows,
121
+ rowHeight = 40,
122
+ overscan = 5,
123
+ estimateRowHeight: _estimateRowHeight,
124
+ pretextHeights,
125
+ pretextPrefixSums
126
+ }) {
127
+ const hasPretextHeights = !!(pretextHeights && pretextPrefixSums && pretextHeights.length >= totalRows);
128
+ const isFixedHeight = typeof rowHeight === "number" && !hasPretextHeights;
129
+ const heightCacheRef = useRef(/* @__PURE__ */ new Map());
130
+ const getRowHeight = useCallback(
131
+ (index) => {
132
+ if (hasPretextHeights) return pretextHeights[index];
133
+ if (isFixedHeight) return rowHeight;
134
+ const cached = heightCacheRef.current.get(index);
135
+ if (cached !== void 0) return cached;
136
+ const height = rowHeight(index);
137
+ heightCacheRef.current.set(index, height);
138
+ return height;
139
+ },
140
+ [rowHeight, isFixedHeight, hasPretextHeights, pretextHeights]
141
+ );
142
+ const [scrollState, setScrollState] = useState({ scrollTop: 0, containerHeight: 0 });
143
+ const rafRef = useRef(null);
144
+ useEffect(() => {
145
+ const container = containerRef.current;
146
+ if (!container) return;
147
+ setScrollState({
148
+ scrollTop: container.scrollTop,
149
+ containerHeight: container.clientHeight
150
+ });
151
+ const handleScroll = () => {
152
+ if (rafRef.current !== null) return;
153
+ rafRef.current = requestAnimationFrame(() => {
154
+ rafRef.current = null;
155
+ const el = containerRef.current;
156
+ if (!el) return;
157
+ setScrollState({
158
+ scrollTop: el.scrollTop,
159
+ containerHeight: el.clientHeight
160
+ });
161
+ });
162
+ };
163
+ container.addEventListener("scroll", handleScroll, { passive: true });
164
+ let resizeObserver;
165
+ if (typeof ResizeObserver !== "undefined") {
166
+ resizeObserver = new ResizeObserver(() => {
167
+ const el = containerRef.current;
168
+ if (!el) return;
169
+ setScrollState((prev) => {
170
+ const newHeight = el.clientHeight;
171
+ if (prev.containerHeight === newHeight) return prev;
172
+ return { ...prev, containerHeight: newHeight };
173
+ });
174
+ });
175
+ resizeObserver.observe(container);
176
+ }
177
+ return () => {
178
+ container.removeEventListener("scroll", handleScroll);
179
+ if (rafRef.current !== null) {
180
+ cancelAnimationFrame(rafRef.current);
181
+ rafRef.current = null;
182
+ }
183
+ if (resizeObserver) {
184
+ resizeObserver.disconnect();
185
+ }
186
+ };
187
+ }, [containerRef]);
188
+ useEffect(() => {
189
+ if (!isFixedHeight) {
190
+ heightCacheRef.current.clear();
191
+ }
192
+ }, [totalRows, isFixedHeight]);
193
+ const scrollTo = useCallback(
194
+ (index) => {
195
+ const container = containerRef.current;
196
+ if (!container || totalRows === 0) return;
197
+ const clampedIndex = Math.max(0, Math.min(index, totalRows - 1));
198
+ if (hasPretextHeights && pretextPrefixSums) {
199
+ container.scrollTop = pretextPrefixSums[clampedIndex];
200
+ } else if (isFixedHeight) {
201
+ container.scrollTop = clampedIndex * rowHeight;
202
+ } else {
203
+ let offset = 0;
204
+ for (let i = 0; i < clampedIndex; i++) {
205
+ offset += getRowHeight(i);
206
+ }
207
+ container.scrollTop = offset;
208
+ }
209
+ },
210
+ [containerRef, totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]
211
+ );
212
+ const totalHeight = useMemo(() => {
213
+ if (totalRows === 0) return 0;
214
+ if (hasPretextHeights && pretextPrefixSums) return pretextPrefixSums[totalRows];
215
+ if (isFixedHeight) return totalRows * rowHeight;
216
+ let total = 0;
217
+ for (let i = 0; i < totalRows; i++) {
218
+ total += getRowHeight(i);
219
+ }
220
+ return total;
221
+ }, [totalRows, rowHeight, isFixedHeight, getRowHeight, hasPretextHeights, pretextPrefixSums]);
222
+ const { scrollTop, containerHeight } = scrollState;
223
+ if (totalRows === 0) {
224
+ return { ...EMPTY_RESULT, scrollTo };
225
+ }
226
+ if (containerHeight === 0) {
227
+ return {
228
+ virtualRows: [],
229
+ totalHeight,
230
+ startIndex: 0,
231
+ endIndex: 0,
232
+ scrollTo
233
+ };
234
+ }
235
+ let startIndex = 0;
236
+ let endIndex = 0;
237
+ if (hasPretextHeights && pretextPrefixSums) {
238
+ let lo = 0;
239
+ let hi = totalRows - 1;
240
+ while (lo < hi) {
241
+ const mid = lo + hi >>> 1;
242
+ if (pretextPrefixSums[mid + 1] <= scrollTop) {
243
+ lo = mid + 1;
244
+ } else {
245
+ hi = mid;
246
+ }
247
+ }
248
+ startIndex = lo;
249
+ const bottomEdge = scrollTop + containerHeight;
250
+ lo = startIndex;
251
+ hi = totalRows - 1;
252
+ while (lo < hi) {
253
+ const mid = lo + hi + 1 >>> 1;
254
+ if (pretextPrefixSums[mid] < bottomEdge) {
255
+ lo = mid;
256
+ } else {
257
+ hi = mid - 1;
258
+ }
259
+ }
260
+ endIndex = lo;
261
+ } else if (isFixedHeight) {
262
+ const fixedH = rowHeight;
263
+ startIndex = Math.floor(scrollTop / fixedH);
264
+ endIndex = Math.min(
265
+ totalRows - 1,
266
+ Math.ceil((scrollTop + containerHeight) / fixedH) - 1
267
+ );
268
+ } else {
269
+ let accum = 0;
270
+ let foundStart = false;
271
+ for (let i = 0; i < totalRows; i++) {
272
+ const h = getRowHeight(i);
273
+ if (!foundStart && accum + h > scrollTop) {
274
+ startIndex = i;
275
+ foundStart = true;
276
+ }
277
+ if (foundStart && accum >= scrollTop + containerHeight) {
278
+ endIndex = i - 1;
279
+ break;
280
+ }
281
+ accum += h;
282
+ if (i === totalRows - 1) {
283
+ endIndex = i;
284
+ }
285
+ }
286
+ if (!foundStart) {
287
+ startIndex = 0;
288
+ endIndex = 0;
289
+ }
290
+ }
291
+ const overscanStart = Math.max(0, startIndex - overscan);
292
+ const overscanEnd = Math.min(totalRows - 1, endIndex + overscan);
293
+ const virtualRows = [];
294
+ if (hasPretextHeights && pretextPrefixSums) {
295
+ for (let i = overscanStart; i <= overscanEnd; i++) {
296
+ virtualRows.push({
297
+ index: i,
298
+ start: pretextPrefixSums[i],
299
+ size: pretextHeights[i]
300
+ });
301
+ }
302
+ } else if (isFixedHeight) {
303
+ const fixedH = rowHeight;
304
+ for (let i = overscanStart; i <= overscanEnd; i++) {
305
+ virtualRows.push({
306
+ index: i,
307
+ start: i * fixedH,
308
+ size: fixedH
309
+ });
310
+ }
311
+ } else {
312
+ let offset = 0;
313
+ for (let i = 0; i < overscanStart; i++) {
314
+ offset += getRowHeight(i);
315
+ }
316
+ for (let i = overscanStart; i <= overscanEnd; i++) {
317
+ const h = getRowHeight(i);
318
+ virtualRows.push({
319
+ index: i,
320
+ start: offset,
321
+ size: h
322
+ });
323
+ offset += h;
324
+ }
325
+ }
326
+ return {
327
+ virtualRows,
328
+ totalHeight,
329
+ startIndex: overscanStart,
330
+ endIndex: overscanEnd,
331
+ scrollTo
332
+ };
333
+ }
334
+ var pretextPromise = null;
335
+ function loadPretext() {
336
+ if (pretextPromise) return pretextPromise;
337
+ pretextPromise = import('@chenglou/pretext').then((mod) => mod).catch(() => null);
338
+ return pretextPromise;
339
+ }
340
+ function usePretextMeasurement({
341
+ data,
342
+ columns,
343
+ getCellText,
344
+ minRowHeight = 32,
345
+ enabled = true
346
+ }) {
347
+ const [pretext, setPretext] = useState(null);
348
+ const [ready, setReady] = useState(false);
349
+ const prepareTimeRef = useRef(0);
350
+ const layoutTimeRef = useRef(0);
351
+ const columnWidthsKey = columns.map((c) => `${c.columnId}:${c.width}`).join("|");
352
+ useEffect(() => {
353
+ if (!enabled) return;
354
+ let cancelled = false;
355
+ loadPretext().then((mod) => {
356
+ if (!cancelled && mod) setPretext(mod);
357
+ });
358
+ return () => {
359
+ cancelled = true;
360
+ };
361
+ }, [enabled]);
362
+ const preparedCacheRef = useRef(/* @__PURE__ */ new Map());
363
+ const preparedCells = useMemo(() => {
364
+ if (!pretext || !enabled || data.length === 0 || columns.length === 0) return null;
365
+ const cache = preparedCacheRef.current;
366
+ const start = performance.now();
367
+ const result = [];
368
+ for (let r = 0; r < data.length; r++) {
369
+ for (const col of columns) {
370
+ if (col.fixedHeight) continue;
371
+ const text = getCellText(data[r], col.columnId);
372
+ if (!text) continue;
373
+ const key = `${text}|${col.font}`;
374
+ let prepared = cache.get(key);
375
+ if (!prepared) {
376
+ prepared = pretext.prepare(text, col.font);
377
+ cache.set(key, prepared);
378
+ }
379
+ result.push({ rowIndex: r, columnId: col.columnId, prepared });
380
+ }
381
+ }
382
+ prepareTimeRef.current = performance.now() - start;
383
+ return result;
384
+ }, [pretext, enabled, data, columns.map((c) => `${c.columnId}:${c.font}:${c.fixedHeight ? "F" : "T"}`).join("|")]);
385
+ const measurement = useMemo(() => {
386
+ if (!pretext || !preparedCells) {
387
+ return { rowHeights: null, prefixSums: null, totalHeight: 0 };
388
+ }
389
+ const start = performance.now();
390
+ const rowHeights = new Float64Array(data.length).fill(minRowHeight);
391
+ const colMap = /* @__PURE__ */ new Map();
392
+ for (const col of columns) colMap.set(col.columnId, col);
393
+ let fixedFloor = 0;
394
+ for (const col of columns) {
395
+ if (col.fixedHeight) {
396
+ const h = col.lineHeight + (col.padding ?? 0);
397
+ if (h > fixedFloor) fixedFloor = h;
398
+ }
399
+ }
400
+ if (fixedFloor > 0) {
401
+ for (let r = 0; r < data.length; r++) {
402
+ if (fixedFloor > rowHeights[r]) rowHeights[r] = fixedFloor;
403
+ }
404
+ }
405
+ for (const { rowIndex, columnId, prepared } of preparedCells) {
406
+ const col = colMap.get(columnId);
407
+ if (!col) continue;
408
+ const result = pretext.layout(prepared, col.width, col.lineHeight);
409
+ const cellHeight = result.height + (col.padding ?? 16);
410
+ if (cellHeight > rowHeights[rowIndex]) {
411
+ rowHeights[rowIndex] = cellHeight;
412
+ }
413
+ }
414
+ const prefixSums = new Float64Array(data.length + 1);
415
+ for (let i = 0; i < data.length; i++) {
416
+ prefixSums[i + 1] = prefixSums[i] + rowHeights[i];
417
+ }
418
+ layoutTimeRef.current = performance.now() - start;
419
+ return {
420
+ rowHeights,
421
+ prefixSums,
422
+ totalHeight: prefixSums[data.length]
423
+ };
424
+ }, [pretext, preparedCells, columnWidthsKey, minRowHeight, data.length]);
425
+ useEffect(() => {
426
+ if (measurement.rowHeights) setReady(true);
427
+ }, [measurement.rowHeights]);
428
+ return {
429
+ rowHeights: measurement.rowHeights,
430
+ prefixSums: measurement.prefixSums,
431
+ totalHeight: measurement.totalHeight,
432
+ ready,
433
+ prepareTimeMs: prepareTimeRef.current,
434
+ layoutTimeMs: layoutTimeRef.current
435
+ };
436
+ }
437
+ var measureRecipe = {
438
+ font: '500 12px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
439
+ lineHeight: 20,
440
+ padding: 20
441
+ };
442
+ function CellBadge({
443
+ context,
444
+ variant = "default",
445
+ appearance = "soft",
446
+ className
447
+ }) {
448
+ const value = context.getValue();
449
+ if (value == null || value === "") return null;
450
+ const classNames = [
451
+ "yable-cell-badge",
452
+ `yable-cell-badge--${variant}`,
453
+ `yable-cell-badge--${appearance}`,
454
+ className
455
+ ].filter(Boolean).join(" ");
456
+ return /* @__PURE__ */ jsx("span", { className: classNames, children: String(value) });
457
+ }
458
+ CellBadge.displayName = "CellBadge";
459
+ var measureRecipe2 = {
460
+ font: '400 13px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
461
+ lineHeight: 20,
462
+ padding: 20
463
+ };
464
+ function CellCurrency({
465
+ context,
466
+ currency = "USD",
467
+ locale = "en-US",
468
+ decimals,
469
+ colorize = false,
470
+ className
471
+ }) {
472
+ const raw = context.getValue();
473
+ const num = typeof raw === "number" ? raw : Number(raw);
474
+ const formatter = useMemo(
475
+ () => new Intl.NumberFormat(locale, {
476
+ style: "currency",
477
+ currency,
478
+ minimumFractionDigits: decimals ?? 0,
479
+ maximumFractionDigits: decimals ?? 0
480
+ }),
481
+ [locale, currency, decimals]
482
+ );
483
+ if (raw == null || isNaN(num)) return null;
484
+ const classNames = [
485
+ "yable-cell-currency",
486
+ colorize && num > 0 && "yable-cell-currency--positive",
487
+ colorize && num < 0 && "yable-cell-currency--negative",
488
+ className
489
+ ].filter(Boolean).join(" ");
490
+ return /* @__PURE__ */ jsx("span", { className: classNames, children: formatter.format(num) });
491
+ }
492
+ CellCurrency.displayName = "CellCurrency";
493
+ var measureRecipe3 = {
494
+ font: '500 12px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
495
+ lineHeight: 20,
496
+ padding: 20
497
+ };
498
+ var DEFAULT_COLOR_MAP = {
499
+ active: "success",
500
+ inactive: "danger",
501
+ enabled: "success",
502
+ disabled: "danger",
503
+ published: "success",
504
+ draft: "warning",
505
+ review: "info",
506
+ pending: "warning",
507
+ archived: "default",
508
+ error: "danger",
509
+ failed: "danger",
510
+ success: "success",
511
+ complete: "success",
512
+ completed: "success",
513
+ cancelled: "danger",
514
+ canceled: "danger",
515
+ true: "success",
516
+ false: "danger"
517
+ };
518
+ function CellStatus({
519
+ context,
520
+ colorMap,
521
+ className
522
+ }) {
523
+ const raw = context.getValue();
524
+ if (raw == null) return null;
525
+ const label = String(raw);
526
+ const normalizedKey = label.toLowerCase().trim();
527
+ const color = colorMap?.[normalizedKey] ?? colorMap?.[label] ?? DEFAULT_COLOR_MAP[normalizedKey] ?? "default";
528
+ const classNames = [
529
+ "yable-cell-status",
530
+ `yable-cell-status--${color}`,
531
+ className
532
+ ].filter(Boolean).join(" ");
533
+ return /* @__PURE__ */ jsx("span", { className: classNames, children: label });
534
+ }
535
+ CellStatus.displayName = "CellStatus";
536
+ var measureRecipe4 = {
537
+ font: '400 13px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
538
+ lineHeight: 20,
539
+ padding: 20
540
+ };
541
+ function CellNumeric({
542
+ context,
543
+ locale = "en-US",
544
+ unit,
545
+ decimals,
546
+ colorize = false,
547
+ className
548
+ }) {
549
+ const raw = context.getValue();
550
+ const num = typeof raw === "number" ? raw : Number(raw);
551
+ const formatter = useMemo(
552
+ () => new Intl.NumberFormat(locale, {
553
+ minimumFractionDigits: decimals,
554
+ maximumFractionDigits: decimals
555
+ }),
556
+ [locale, decimals]
557
+ );
558
+ if (raw == null || isNaN(num)) return null;
559
+ const classNames = [
560
+ "yable-cell-numeric",
561
+ colorize && num > 0 && "yable-cell-numeric--positive",
562
+ colorize && num < 0 && "yable-cell-numeric--negative",
563
+ className
564
+ ].filter(Boolean).join(" ");
565
+ return /* @__PURE__ */ jsxs("span", { className: classNames, children: [
566
+ formatter.format(num),
567
+ unit && /* @__PURE__ */ jsx("span", { className: "yable-cell-numeric__unit", children: unit })
568
+ ] });
569
+ }
570
+ CellNumeric.displayName = "CellNumeric";
571
+ var measureRecipe5 = {
572
+ font: '400 13px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
573
+ lineHeight: 20,
574
+ padding: 20,
575
+ fixedHeight: true
576
+ };
577
+ function CellRating({
578
+ context,
579
+ max = 5,
580
+ character = "\u2605",
581
+ emptyCharacter = "\u2606",
582
+ className
583
+ }) {
584
+ const raw = context.getValue();
585
+ const num = typeof raw === "number" ? raw : Number(raw);
586
+ if (raw == null || isNaN(num)) return null;
587
+ const filled = Math.min(Math.max(Math.round(num), 0), max);
588
+ return /* @__PURE__ */ jsxs("span", { className: `yable-cell-rating ${className ?? ""}`, "aria-label": `${filled} of ${max}`, children: [
589
+ /* @__PURE__ */ jsx("span", { className: "yable-cell-rating__filled", children: character.repeat(filled) }),
590
+ /* @__PURE__ */ jsx("span", { className: "yable-cell-rating__empty", children: emptyCharacter.repeat(max - filled) })
591
+ ] });
592
+ }
593
+ CellRating.displayName = "CellRating";
594
+ var measureRecipe6 = {
595
+ font: '500 12px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
596
+ lineHeight: 20,
597
+ padding: 20
598
+ };
599
+ function CellBoolean({
600
+ context,
601
+ trueLabel = "Active",
602
+ falseLabel = "Inactive",
603
+ mode = "dot",
604
+ className
605
+ }) {
606
+ const raw = context.getValue();
607
+ const bool = Boolean(raw);
608
+ const label = bool ? trueLabel : falseLabel;
609
+ const variant = bool ? "success" : "danger";
610
+ const classNames = [
611
+ "yable-cell-boolean",
612
+ `yable-cell-boolean--${variant}`,
613
+ `yable-cell-boolean--${mode}`,
614
+ className
615
+ ].filter(Boolean).join(" ");
616
+ return /* @__PURE__ */ jsx("span", { className: classNames, children: label });
617
+ }
618
+ CellBoolean.displayName = "CellBoolean";
619
+ var measureRecipe7 = {
620
+ font: '400 12px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
621
+ lineHeight: 20,
622
+ padding: 20,
623
+ fixedHeight: true
624
+ };
625
+ function CellProgress({
626
+ context,
627
+ max = 100,
628
+ variant = "accent",
629
+ showLabel = true,
630
+ className
631
+ }) {
632
+ const raw = context.getValue();
633
+ const num = typeof raw === "number" ? raw : Number(raw);
634
+ if (raw == null || isNaN(num)) return null;
635
+ const pct = Math.min(Math.max(num / max * 100, 0), 100);
636
+ const classNames = [
637
+ "yable-cell-progress",
638
+ `yable-cell-progress--${variant}`,
639
+ className
640
+ ].filter(Boolean).join(" ");
641
+ return /* @__PURE__ */ jsxs("span", { className: classNames, children: [
642
+ /* @__PURE__ */ jsx("span", { className: "yable-cell-progress__track", children: /* @__PURE__ */ jsx(
643
+ "span",
644
+ {
645
+ className: "yable-cell-progress__fill",
646
+ style: { width: `${pct}%` }
647
+ }
648
+ ) }),
649
+ showLabel && /* @__PURE__ */ jsxs("span", { className: "yable-cell-progress__label", children: [
650
+ Math.round(pct),
651
+ "%"
652
+ ] })
653
+ ] });
654
+ }
655
+ CellProgress.displayName = "CellProgress";
656
+ var measureRecipe8 = {
657
+ font: '400 13px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
658
+ lineHeight: 20,
659
+ padding: 20
660
+ };
661
+ var PRESETS = {
662
+ short: { month: "numeric", day: "numeric", year: "2-digit" },
663
+ medium: { month: "short", day: "numeric", year: "numeric" },
664
+ long: { month: "long", day: "numeric", year: "numeric" }
665
+ };
666
+ var rtf = typeof Intl !== "undefined" && Intl.RelativeTimeFormat ? new Intl.RelativeTimeFormat("en", { numeric: "auto" }) : null;
667
+ function formatRelative(date) {
668
+ if (!rtf) return date.toLocaleDateString();
669
+ const diffMs = date.getTime() - Date.now();
670
+ const diffDays = Math.round(diffMs / (1e3 * 60 * 60 * 24));
671
+ if (Math.abs(diffDays) < 1) return "today";
672
+ if (Math.abs(diffDays) < 30) return rtf.format(diffDays, "day");
673
+ if (Math.abs(diffDays) < 365) return rtf.format(Math.round(diffDays / 30), "month");
674
+ return rtf.format(Math.round(diffDays / 365), "year");
675
+ }
676
+ function CellDate({
677
+ context,
678
+ format = "medium",
679
+ locale = "en-US",
680
+ className
681
+ }) {
682
+ const raw = context.getValue();
683
+ if (raw == null) return null;
684
+ const date = raw instanceof Date ? raw : new Date(String(raw));
685
+ if (isNaN(date.getTime())) return /* @__PURE__ */ jsx("span", { className: "yable-cell-date", children: String(raw) });
686
+ const formatter = useMemo(() => {
687
+ if (typeof format === "string" && format !== "relative") {
688
+ return new Intl.DateTimeFormat(locale, {
689
+ ...PRESETS[format],
690
+ timeZone: "UTC"
691
+ });
692
+ }
693
+ if (typeof format === "object") {
694
+ return new Intl.DateTimeFormat(locale, { ...format, timeZone: "UTC" });
695
+ }
696
+ return null;
697
+ }, [format, locale]);
698
+ const formatted = format === "relative" ? formatRelative(date) : formatter ? formatter.format(date) : date.toLocaleDateString(locale);
699
+ return /* @__PURE__ */ jsx("span", { className: `yable-cell-date ${className ?? ""}`, title: date.toISOString(), children: formatted });
700
+ }
701
+ CellDate.displayName = "CellDate";
702
+ var measureRecipe9 = {
703
+ font: '400 13px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
704
+ lineHeight: 20,
705
+ padding: 20
706
+ };
707
+ function isSafeUrl(url) {
708
+ const normalized = String(url).toLowerCase().trim();
709
+ return !normalized.startsWith("javascript:") && !normalized.startsWith("data:text/html") && !normalized.startsWith("vbscript:");
710
+ }
711
+ function CellLink({
712
+ context,
713
+ href,
714
+ external = false,
715
+ className
716
+ }) {
717
+ const raw = context.getValue();
718
+ if (raw == null || raw === "") return null;
719
+ const label = String(raw);
720
+ const url = typeof href === "function" ? href(raw) : href ?? label;
721
+ const safeUrl = isSafeUrl(url) ? url : void 0;
722
+ if (!safeUrl) {
723
+ return /* @__PURE__ */ jsx("span", { className: `yable-cell-link ${className ?? ""}`, children: label });
724
+ }
725
+ return /* @__PURE__ */ jsxs(
726
+ "a",
727
+ {
728
+ href: safeUrl,
729
+ className: `yable-cell-link ${className ?? ""}`,
730
+ target: external ? "_blank" : void 0,
731
+ rel: external ? "noopener noreferrer" : void 0,
732
+ children: [
733
+ label,
734
+ external && /* @__PURE__ */ jsx("span", { className: "yable-cell-link__icon", "aria-hidden": true, children: "\u2197" })
735
+ ]
736
+ }
737
+ );
738
+ }
739
+ CellLink.displayName = "CellLink";
740
+ var CELL_TYPE_MAP = {
741
+ badge: CellBadge,
742
+ currency: CellCurrency,
743
+ status: CellStatus,
744
+ numeric: CellNumeric,
745
+ rating: CellRating,
746
+ boolean: CellBoolean,
747
+ progress: CellProgress,
748
+ date: CellDate,
749
+ link: CellLink
750
+ };
751
+ var MEASURE_RECIPE_MAP = {
752
+ badge: measureRecipe,
753
+ currency: measureRecipe2,
754
+ status: measureRecipe3,
755
+ numeric: measureRecipe4,
756
+ rating: measureRecipe5,
757
+ boolean: measureRecipe6,
758
+ progress: measureRecipe7,
759
+ date: measureRecipe8,
760
+ link: measureRecipe9
761
+ };
762
+ function resolveCellType(cellType, context, props) {
763
+ const Component = CELL_TYPE_MAP[cellType];
764
+ if (!Component) return context.renderValue();
765
+ return /* @__PURE__ */ jsx(Component, { context, ...props });
766
+ }
767
+ function getMeasureRecipeForCellType(cellType) {
768
+ return MEASURE_RECIPE_MAP[cellType];
769
+ }
770
+ function getRegisteredCellTypes() {
771
+ return Object.keys(CELL_TYPE_MAP);
772
+ }
773
+
774
+ // src/hooks/useAutoMeasurements.ts
775
+ var NON_DATA_COLUMN_IDS = /* @__PURE__ */ new Set(["select", "expand", "drag", "actions"]);
776
+ function getColumnId(col) {
777
+ const id = col.id ?? col.accessorKey;
778
+ return typeof id === "string" ? id : void 0;
779
+ }
780
+ function defaultShouldMeasure(col) {
781
+ const id = getColumnId(col);
782
+ if (!id) return false;
783
+ if (id.startsWith("_")) return false;
784
+ if (NON_DATA_COLUMN_IDS.has(id)) return false;
785
+ return true;
786
+ }
787
+ function defaultGetColumnWidth(col) {
788
+ return col.size ?? 150;
789
+ }
790
+ function resolveMeasureRecipe(column, defaultRecipe) {
791
+ const explicit = column.measureRecipe;
792
+ if (explicit) return explicit;
793
+ const cellType = column.cellType;
794
+ if (cellType) {
795
+ const fromCellType = getMeasureRecipeForCellType(cellType);
796
+ if (fromCellType) return fromCellType;
797
+ }
798
+ return defaultRecipe;
799
+ }
800
+ function useAutoMeasurements({
801
+ columns,
802
+ defaultRecipe,
803
+ getColumnWidth = defaultGetColumnWidth,
804
+ shouldMeasureColumn = defaultShouldMeasure
805
+ }) {
806
+ const widthKey = columns.map((c) => `${getColumnId(c) ?? "?"}:${getColumnWidth(c)}`).join("|");
807
+ return useMemo(() => {
808
+ const result = [];
809
+ for (const col of columns) {
810
+ const id = getColumnId(col);
811
+ if (!id) continue;
812
+ if (!shouldMeasureColumn(col)) continue;
813
+ const recipe = resolveMeasureRecipe(col, defaultRecipe);
814
+ result.push({
815
+ columnId: id,
816
+ width: getColumnWidth(col),
817
+ font: recipe.font,
818
+ lineHeight: recipe.lineHeight,
819
+ padding: recipe.padding,
820
+ fixedHeight: recipe.fixedHeight
821
+ });
822
+ }
823
+ return result;
824
+ }, [widthKey, columns, defaultRecipe, shouldMeasureColumn, getColumnWidth]);
825
+ }
826
+ var DEFAULT_TEXT_RECIPE = {
827
+ font: '400 13px -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
828
+ lineHeight: 20,
829
+ padding: 20
830
+ };
831
+ function useTableRowHeights({
832
+ data,
833
+ columns,
834
+ defaultRecipe = DEFAULT_TEXT_RECIPE,
835
+ getColumnWidth,
836
+ minRowHeight = 36,
837
+ enabled = true
838
+ }) {
839
+ const measurements = useAutoMeasurements({
840
+ columns,
841
+ defaultRecipe,
842
+ getColumnWidth
843
+ });
844
+ const accessorMap = useMemo(() => {
845
+ const map = /* @__PURE__ */ new Map();
846
+ for (const col of columns) {
847
+ const id = col.id ?? col.accessorKey;
848
+ if (typeof id !== "string") continue;
849
+ const accessorKey = col.accessorKey;
850
+ const accessorFn = col.accessorFn;
851
+ if (typeof accessorFn === "function") {
852
+ map.set(id, accessorFn);
853
+ } else if (typeof accessorKey === "string") {
854
+ map.set(id, (row) => row[accessorKey]);
855
+ } else {
856
+ map.set(id, (row) => row[id]);
857
+ }
858
+ }
859
+ return map;
860
+ }, [columns]);
861
+ const getCellText = useMemo(
862
+ () => (row, columnId) => {
863
+ const get = accessorMap.get(columnId);
864
+ if (!get) return "";
865
+ const value = get(row);
866
+ if (value == null) return "";
867
+ return typeof value === "string" ? value : String(value);
868
+ },
869
+ [accessorMap]
870
+ );
871
+ const result = usePretextMeasurement({
872
+ data,
873
+ columns: measurements,
874
+ getCellText,
875
+ minRowHeight,
876
+ enabled
877
+ });
878
+ return { ...result, measurements };
879
+ }
880
+ var TableContext = createContext(null);
881
+ var TableProvider = TableContext.Provider;
882
+ function useTableContext() {
883
+ const ctx = useContext(TableContext);
884
+ if (!ctx) {
885
+ throw new Error(
886
+ "[yable] useTableContext must be used within a <Table> component. Did you forget to pass the `table` prop?"
887
+ );
888
+ }
889
+ return ctx;
890
+ }
891
+ function SortIndicator({ direction, index }) {
892
+ return /* @__PURE__ */ jsxs(
893
+ "span",
894
+ {
895
+ className: "yable-sort-indicator",
896
+ "data-active": direction ? "true" : void 0,
897
+ "data-direction": direction || void 0,
898
+ "aria-hidden": "true",
899
+ children: [
900
+ direction ? /* @__PURE__ */ jsx(
901
+ "svg",
902
+ {
903
+ width: "14",
904
+ height: "14",
905
+ viewBox: "0 0 14 14",
906
+ fill: "none",
907
+ xmlns: "http://www.w3.org/2000/svg",
908
+ children: /* @__PURE__ */ jsx(
909
+ "path",
910
+ {
911
+ d: "M7 3L11 8H3L7 3Z",
912
+ fill: "currentColor"
913
+ }
914
+ )
915
+ }
916
+ ) : /* @__PURE__ */ jsxs(
917
+ "svg",
918
+ {
919
+ width: "14",
920
+ height: "14",
921
+ viewBox: "0 0 14 14",
922
+ fill: "none",
923
+ xmlns: "http://www.w3.org/2000/svg",
924
+ style: { opacity: 0.3 },
925
+ children: [
926
+ /* @__PURE__ */ jsx("path", { d: "M7 3L11 7H3L7 3Z", fill: "currentColor" }),
927
+ /* @__PURE__ */ jsx("path", { d: "M7 11L3 7H11L7 11Z", fill: "currentColor" })
928
+ ]
929
+ }
930
+ ),
931
+ index != null && index >= 0 && /* @__PURE__ */ jsx("span", { className: "yable-sort-badge", children: index + 1 })
932
+ ]
933
+ }
934
+ );
935
+ }
936
+ var DRAG_MIME = "application/yable-column";
937
+ function TableHeader({
938
+ table
939
+ }) {
940
+ const headerGroups = table.getHeaderGroups();
941
+ return /* @__PURE__ */ jsx("thead", { className: "yable-thead", children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx("tr", { className: "yable-header-row", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx(HeaderCell, { header, table }, header.id)) }, headerGroup.id)) });
942
+ }
943
+ function HeaderCell({
944
+ header,
945
+ table
946
+ }) {
947
+ const column = header.column;
948
+ const canSort = column.getCanSort();
949
+ const sortDirection = column.getIsSorted();
950
+ const sortIndex = column.getSortIndex();
951
+ const canResize = column.getCanResize();
952
+ const canReorder = column.getCanReorder() && !header.isPlaceholder;
953
+ const headerContent = header.isPlaceholder ? null : typeof column.columnDef.header === "function" ? column.columnDef.header(header.getContext()) : column.columnDef.header ?? header.id;
954
+ const style = useMemo(() => {
955
+ const s = {
956
+ width: header.getSize(),
957
+ minWidth: column.columnDef.minSize,
958
+ maxWidth: column.columnDef.maxSize
959
+ };
960
+ const pinned2 = column.getIsPinned();
961
+ if (pinned2) {
962
+ s.position = "sticky";
963
+ if (pinned2 === "left") {
964
+ s.left = header.getStart("left");
965
+ } else {
966
+ s.right = header.getStart("right");
967
+ }
968
+ }
969
+ return s;
970
+ }, [header, column]);
971
+ const pinned = column.getIsPinned();
972
+ const lastResizeEndRef = useRef(0);
973
+ const startResize = useCallback(
974
+ (e) => {
975
+ e.stopPropagation();
976
+ const onEnd = () => {
977
+ lastResizeEndRef.current = Date.now();
978
+ document.removeEventListener("mouseup", onEnd, true);
979
+ document.removeEventListener("touchend", onEnd, true);
980
+ };
981
+ document.addEventListener("mouseup", onEnd, true);
982
+ document.addEventListener("touchend", onEnd, true);
983
+ const handler = header.getResizeHandler();
984
+ if (handler) handler(e.nativeEvent);
985
+ },
986
+ [header]
987
+ );
988
+ const handleResizeClick = useCallback((e) => {
989
+ e.stopPropagation();
990
+ }, []);
991
+ const [dragOver, setDragOver] = useState(null);
992
+ const handleDragStart = useCallback(
993
+ (e) => {
994
+ if (!canReorder) return;
995
+ const target = e.target;
996
+ if (target && target.closest(".yable-resize-handle")) {
997
+ e.preventDefault();
998
+ return;
999
+ }
1000
+ e.stopPropagation();
1001
+ e.dataTransfer.effectAllowed = "move";
1002
+ try {
1003
+ e.dataTransfer.setData(DRAG_MIME, column.id);
1004
+ e.dataTransfer.setData("text/plain", column.id);
1005
+ } catch {
1006
+ }
1007
+ },
1008
+ [canReorder, column.id]
1009
+ );
1010
+ const handleDragOver = useCallback(
1011
+ (e) => {
1012
+ if (!canReorder) return;
1013
+ const types = e.dataTransfer.types;
1014
+ let isYableDrag = false;
1015
+ for (let i = 0; i < types.length; i++) {
1016
+ if (types[i] === DRAG_MIME) {
1017
+ isYableDrag = true;
1018
+ break;
1019
+ }
1020
+ }
1021
+ if (!isYableDrag) return;
1022
+ e.preventDefault();
1023
+ e.dataTransfer.dropEffect = "move";
1024
+ const rect = e.currentTarget.getBoundingClientRect();
1025
+ const midpoint = rect.left + rect.width / 2;
1026
+ setDragOver(e.clientX < midpoint ? "left" : "right");
1027
+ },
1028
+ [canReorder]
1029
+ );
1030
+ const handleDragLeave = useCallback(
1031
+ (e) => {
1032
+ const next = e.relatedTarget;
1033
+ if (next && e.currentTarget.contains(next)) return;
1034
+ setDragOver(null);
1035
+ },
1036
+ []
1037
+ );
1038
+ const handleDragEnd = useCallback(() => {
1039
+ setDragOver(null);
1040
+ }, []);
1041
+ const handleDrop = useCallback(
1042
+ (e) => {
1043
+ if (!canReorder) return;
1044
+ e.preventDefault();
1045
+ e.stopPropagation();
1046
+ const sourceId = e.dataTransfer.getData(DRAG_MIME);
1047
+ const rect = e.currentTarget.getBoundingClientRect();
1048
+ const insertAfter = e.clientX >= rect.left + rect.width / 2;
1049
+ setDragOver(null);
1050
+ if (!sourceId || sourceId === column.id) return;
1051
+ const state = table.getState();
1052
+ const allLeafs = table.getAllLeafColumns();
1053
+ const baseOrder = state.columnOrder && state.columnOrder.length > 0 ? state.columnOrder : allLeafs.map((c) => c.id);
1054
+ const next = [];
1055
+ const seen = /* @__PURE__ */ new Set();
1056
+ for (const id of baseOrder) {
1057
+ if (allLeafs.some((c) => c.id === id)) {
1058
+ next.push(id);
1059
+ seen.add(id);
1060
+ }
1061
+ }
1062
+ for (const c of allLeafs) {
1063
+ if (!seen.has(c.id)) {
1064
+ next.push(c.id);
1065
+ seen.add(c.id);
1066
+ }
1067
+ }
1068
+ const fromIdx = next.indexOf(sourceId);
1069
+ if (fromIdx === -1) return;
1070
+ next.splice(fromIdx, 1);
1071
+ let toIdx = next.indexOf(column.id);
1072
+ if (toIdx === -1) return;
1073
+ if (insertAfter) toIdx += 1;
1074
+ next.splice(toIdx, 0, sourceId);
1075
+ table.setColumnOrder(next);
1076
+ },
1077
+ [canReorder, column.id, table]
1078
+ );
1079
+ const handleHeaderClick = useCallback(
1080
+ (e) => {
1081
+ if (!canSort) return;
1082
+ if (Date.now() - lastResizeEndRef.current < 250) return;
1083
+ const handler = column.getToggleSortingHandler();
1084
+ if (handler) handler(e);
1085
+ },
1086
+ [canSort, column]
1087
+ );
1088
+ return /* @__PURE__ */ jsxs(
1089
+ "th",
1090
+ {
1091
+ className: "yable-th",
1092
+ style,
1093
+ "data-sortable": canSort || void 0,
1094
+ "data-pinned": pinned || void 0,
1095
+ "data-reorderable": canReorder || void 0,
1096
+ "data-drag-over": dragOver || void 0,
1097
+ "aria-sort": sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : canSort ? "none" : void 0,
1098
+ role: "columnheader",
1099
+ colSpan: header.colSpan,
1100
+ draggable: canReorder || void 0,
1101
+ onClick: handleHeaderClick,
1102
+ onDragStart: canReorder ? handleDragStart : void 0,
1103
+ onDragOver: canReorder ? handleDragOver : void 0,
1104
+ onDragLeave: canReorder ? handleDragLeave : void 0,
1105
+ onDragEnd: canReorder ? handleDragEnd : void 0,
1106
+ onDrop: canReorder ? handleDrop : void 0,
1107
+ children: [
1108
+ /* @__PURE__ */ jsxs("div", { className: "yable-th-content", children: [
1109
+ /* @__PURE__ */ jsx("span", { children: headerContent }),
1110
+ canSort && /* @__PURE__ */ jsx(
1111
+ SortIndicator,
1112
+ {
1113
+ direction: sortDirection,
1114
+ index: sortIndex > 0 ? sortIndex : void 0
1115
+ }
1116
+ )
1117
+ ] }),
1118
+ canResize && /* @__PURE__ */ jsx(
1119
+ "div",
1120
+ {
1121
+ className: "yable-resize-handle",
1122
+ "data-resizing": column.getIsResizing() || void 0,
1123
+ onMouseDown: startResize,
1124
+ onTouchStart: startResize,
1125
+ onClick: handleResizeClick,
1126
+ draggable: false,
1127
+ onDragStart: (e) => e.preventDefault()
1128
+ }
1129
+ )
1130
+ ]
1131
+ }
1132
+ );
1133
+ }
1134
+ function CellStatusBadge(props) {
1135
+ if (props.status === "error") {
1136
+ return /* @__PURE__ */ jsxs(
1137
+ "span",
1138
+ {
1139
+ className: "yable-cell-status-badge yable-cell-status-badge--error",
1140
+ role: "status",
1141
+ "aria-label": `Save failed: ${props.message ?? "unknown error"}`,
1142
+ title: props.message,
1143
+ children: [
1144
+ /* @__PURE__ */ jsx(
1145
+ "button",
1146
+ {
1147
+ type: "button",
1148
+ className: "yable-cell-status-badge__retry",
1149
+ onClick: (e) => {
1150
+ e.stopPropagation();
1151
+ props.onRetry();
1152
+ },
1153
+ "aria-label": "Retry save",
1154
+ children: "\u21BB"
1155
+ }
1156
+ ),
1157
+ /* @__PURE__ */ jsx(
1158
+ "button",
1159
+ {
1160
+ type: "button",
1161
+ className: "yable-cell-status-badge__dismiss",
1162
+ onClick: (e) => {
1163
+ e.stopPropagation();
1164
+ props.onDismiss();
1165
+ },
1166
+ "aria-label": "Dismiss error",
1167
+ children: "\xD7"
1168
+ }
1169
+ )
1170
+ ]
1171
+ }
1172
+ );
1173
+ }
1174
+ return /* @__PURE__ */ jsxs(
1175
+ "span",
1176
+ {
1177
+ className: "yable-cell-status-badge yable-cell-status-badge--conflict",
1178
+ role: "status",
1179
+ "aria-label": `Conflict: server has ${String(props.conflictWith)}`,
1180
+ title: `Server value: ${String(props.conflictWith)}`,
1181
+ children: [
1182
+ /* @__PURE__ */ jsx(
1183
+ "button",
1184
+ {
1185
+ type: "button",
1186
+ className: "yable-cell-status-badge__accept-mine",
1187
+ onClick: (e) => {
1188
+ e.stopPropagation();
1189
+ props.onRetry();
1190
+ },
1191
+ "aria-label": "Keep my change",
1192
+ children: "\u2713"
1193
+ }
1194
+ ),
1195
+ /* @__PURE__ */ jsx(
1196
+ "button",
1197
+ {
1198
+ type: "button",
1199
+ className: "yable-cell-status-badge__accept-theirs",
1200
+ onClick: (e) => {
1201
+ e.stopPropagation();
1202
+ props.onDismiss();
1203
+ },
1204
+ "aria-label": "Accept server value",
1205
+ children: "\u2717"
1206
+ }
1207
+ )
1208
+ ]
1209
+ }
1210
+ );
1211
+ }
1212
+ function TableCell({
1213
+ cell,
1214
+ table,
1215
+ rowIndex,
1216
+ columnIndex,
1217
+ isFocused,
1218
+ isTabStop
1219
+ }) {
1220
+ const column = cell.column;
1221
+ const isEditing = cell.getIsEditing();
1222
+ const isAlwaysEditable = cell.getIsAlwaysEditable();
1223
+ const pinned = column.getIsPinned();
1224
+ const keyboardNavigationEnabled = table.options.enableKeyboardNavigation !== false;
1225
+ const style = {
1226
+ width: column.getSize(),
1227
+ minWidth: column.columnDef.minSize,
1228
+ maxWidth: column.columnDef.maxSize
1229
+ };
1230
+ if (pinned) {
1231
+ style.position = "sticky";
1232
+ if (pinned === "left") {
1233
+ style.left = column.getStart("left");
1234
+ } else {
1235
+ style.right = column.getStart("right");
1236
+ }
1237
+ }
1238
+ const cellStatus = table.getCellStatus(cell.row.id, column.id);
1239
+ const cellErrorMessage = table.getCellErrorMessage(cell.row.id, column.id);
1240
+ const cellConflictWith = table.getCellConflictWith(cell.row.id, column.id);
1241
+ const overrideValue = cellStatus !== "idle" ? table.getCellRenderValue(cell.row.id, column.id) : void 0;
1242
+ let content;
1243
+ const cellDef = column.columnDef.cell;
1244
+ const cellType = column.columnDef.cellType;
1245
+ if (typeof cellDef === "function") {
1246
+ const ctx = cell.getContext();
1247
+ if (overrideValue !== void 0) {
1248
+ const overriddenCtx = {
1249
+ ...ctx,
1250
+ getValue: () => overrideValue,
1251
+ renderValue: () => overrideValue
1252
+ };
1253
+ content = cellDef(overriddenCtx);
1254
+ } else {
1255
+ content = cellDef(ctx);
1256
+ }
1257
+ } else if (cellType && !(isEditing || isAlwaysEditable)) {
1258
+ content = resolveCellType(cellType, cell.getContext(), column.columnDef.cellTypeProps);
1259
+ } else {
1260
+ content = overrideValue !== void 0 ? overrideValue : cell.renderValue();
1261
+ }
1262
+ const handleClick = useCallback(
1263
+ (e) => {
1264
+ table.setFocusedCell({ rowIndex, columnIndex });
1265
+ const clickTarget = e.target;
1266
+ if (!isInteractiveClickTarget(clickTarget)) {
1267
+ const currentTarget = e.currentTarget;
1268
+ currentTarget.focus({ preventScroll: true });
1269
+ }
1270
+ table.events.emit("cell:click", {
1271
+ cell,
1272
+ row: cell.row,
1273
+ column: cell.column,
1274
+ event: e.nativeEvent
1275
+ });
1276
+ if (canCellEnterEditMode(table, cell.row, column) && !isAlwaysEditable && !isEditing) {
1277
+ table.startEditing(cell.row.id, column.id);
1278
+ }
1279
+ },
1280
+ [cell, column, columnIndex, isAlwaysEditable, isEditing, rowIndex, table]
1281
+ );
1282
+ const handleDoubleClick = useCallback(
1283
+ (e) => {
1284
+ table.events.emit("cell:dblclick", {
1285
+ cell,
1286
+ row: cell.row,
1287
+ column: cell.column,
1288
+ event: e.nativeEvent
1289
+ });
1290
+ },
1291
+ [table, cell]
1292
+ );
1293
+ const handleContextMenu = useCallback(
1294
+ (e) => {
1295
+ table.events.emit("cell:contextmenu", {
1296
+ cell,
1297
+ row: cell.row,
1298
+ column: cell.column,
1299
+ event: e.nativeEvent
1300
+ });
1301
+ },
1302
+ [table, cell]
1303
+ );
1304
+ const handleFocus = useCallback(() => {
1305
+ if (!keyboardNavigationEnabled) return;
1306
+ table.setFocusedCell({ rowIndex, columnIndex });
1307
+ }, [columnIndex, keyboardNavigationEnabled, rowIndex, table]);
1308
+ const classNames = [
1309
+ "yable-td",
1310
+ isFocused && "yable-cell--focused"
1311
+ ].filter(Boolean).join(" ");
1312
+ return /* @__PURE__ */ jsxs(
1313
+ "td",
1314
+ {
1315
+ className: classNames,
1316
+ style,
1317
+ "data-editing": isEditing || void 0,
1318
+ "data-focused": isFocused || void 0,
1319
+ "data-pinned": pinned || void 0,
1320
+ "data-cell-status": cellStatus !== "idle" ? cellStatus : void 0,
1321
+ "data-column-id": column.id,
1322
+ "data-row-index": rowIndex,
1323
+ "data-column-index": columnIndex,
1324
+ "aria-rowindex": rowIndex + 1,
1325
+ "aria-colindex": columnIndex + 1,
1326
+ role: "gridcell",
1327
+ tabIndex: keyboardNavigationEnabled ? isTabStop ? 0 : -1 : void 0,
1328
+ onClick: handleClick,
1329
+ onDoubleClick: handleDoubleClick,
1330
+ onContextMenu: handleContextMenu,
1331
+ onFocus: handleFocus,
1332
+ children: [
1333
+ content,
1334
+ cellStatus === "error" && /* @__PURE__ */ jsx(
1335
+ CellStatusBadge,
1336
+ {
1337
+ status: "error",
1338
+ message: cellErrorMessage,
1339
+ onRetry: () => void table.retryCommit(cell.row.id, column.id),
1340
+ onDismiss: () => table.dismissCommit(cell.row.id, column.id)
1341
+ }
1342
+ ),
1343
+ cellStatus === "conflict" && /* @__PURE__ */ jsx(
1344
+ CellStatusBadge,
1345
+ {
1346
+ status: "conflict",
1347
+ conflictWith: cellConflictWith,
1348
+ onRetry: () => void table.retryCommit(cell.row.id, column.id),
1349
+ onDismiss: () => table.dismissCommit(cell.row.id, column.id)
1350
+ }
1351
+ )
1352
+ ]
1353
+ }
1354
+ );
1355
+ }
1356
+ function isInteractiveClickTarget(element) {
1357
+ if (!element) return false;
1358
+ const interactive = element.closest(
1359
+ 'input, textarea, select, button, a[href], [contenteditable="true"]'
1360
+ );
1361
+ return interactive !== null;
1362
+ }
1363
+ var ErrorBoundary = class extends React3.Component {
1364
+ constructor(props) {
1365
+ super(props);
1366
+ this.state = { hasError: false, error: null };
1367
+ }
1368
+ static getDerivedStateFromError(error) {
1369
+ return { hasError: true, error };
1370
+ }
1371
+ componentDidCatch(error, errorInfo) {
1372
+ console.error("[yable] Rendering error caught by ErrorBoundary:", error, errorInfo);
1373
+ this.props.onError?.(error, errorInfo);
1374
+ }
1375
+ componentDidUpdate(prevProps) {
1376
+ if (this.state.hasError && this.props.resetKeys) {
1377
+ const prevKeys = prevProps.resetKeys ?? [];
1378
+ const nextKeys = this.props.resetKeys;
1379
+ const changed = prevKeys.length !== nextKeys.length || prevKeys.some((key, i) => !Object.is(key, nextKeys[i]));
1380
+ if (changed) {
1381
+ this.setState({ hasError: false, error: null });
1382
+ }
1383
+ }
1384
+ }
1385
+ render() {
1386
+ if (this.state.hasError) {
1387
+ if (this.props.fallback !== void 0) {
1388
+ return this.props.fallback;
1389
+ }
1390
+ return /* @__PURE__ */ jsx(
1391
+ "span",
1392
+ {
1393
+ className: "yable-error-cell",
1394
+ title: this.state.error?.message ?? "Render error",
1395
+ style: {
1396
+ color: "#dc2626",
1397
+ border: "1px solid #dc2626",
1398
+ borderRadius: 2,
1399
+ padding: "2px 4px",
1400
+ fontSize: "0.75em"
1401
+ },
1402
+ children: "Error"
1403
+ }
1404
+ );
1405
+ }
1406
+ return this.props.children;
1407
+ }
1408
+ };
1409
+ var CellErrorBoundary = class extends React3.Component {
1410
+ constructor(props) {
1411
+ super(props);
1412
+ this.state = { hasError: false, error: null };
1413
+ }
1414
+ static getDerivedStateFromError(error) {
1415
+ return { hasError: true, error };
1416
+ }
1417
+ componentDidCatch(error, errorInfo) {
1418
+ console.error("[yable] Cell rendering error:", error, errorInfo);
1419
+ this.props.onError?.(error, errorInfo);
1420
+ }
1421
+ componentDidUpdate(prevProps) {
1422
+ if (this.state.hasError && this.props.resetKeys) {
1423
+ const prevKeys = prevProps.resetKeys ?? [];
1424
+ const nextKeys = this.props.resetKeys;
1425
+ const changed = prevKeys.length !== nextKeys.length || prevKeys.some((key, i) => !Object.is(key, nextKeys[i]));
1426
+ if (changed) {
1427
+ this.setState({ hasError: false, error: null });
1428
+ }
1429
+ }
1430
+ }
1431
+ render() {
1432
+ if (this.state.hasError) {
1433
+ return /* @__PURE__ */ jsx(
1434
+ "span",
1435
+ {
1436
+ className: "yable-error-cell",
1437
+ title: this.state.error?.message ?? "Render error",
1438
+ style: {
1439
+ color: "#dc2626",
1440
+ border: "1px solid #dc2626",
1441
+ borderRadius: 2,
1442
+ padding: "2px 4px",
1443
+ fontSize: "0.75em"
1444
+ },
1445
+ children: "Error"
1446
+ }
1447
+ );
1448
+ }
1449
+ return this.props.children;
1450
+ }
1451
+ };
1452
+ function TableBody({
1453
+ table,
1454
+ clickableRows
1455
+ }) {
1456
+ const rows = table.getRowModel().rows;
1457
+ const visibleColumns = table.getVisibleLeafColumns();
1458
+ const activeCell = table.getState().editing.activeCell;
1459
+ const focusedCell = table.getFocusedCell();
1460
+ const options = table.options;
1461
+ const enableVirtualization = options.enableVirtualization ?? false;
1462
+ const scrollContainerRef = useRef(null);
1463
+ const rowHeight = options.rowHeight ?? 40;
1464
+ const overscan = options.overscan ?? 5;
1465
+ const estimateRowHeight = options.estimateRowHeight;
1466
+ const pretextHeights = options.pretextHeights ?? null;
1467
+ const pretextPrefixSums = options.pretextPrefixSums ?? null;
1468
+ const { virtualRows, totalHeight } = useVirtualization({
1469
+ containerRef: scrollContainerRef,
1470
+ totalRows: rows.length,
1471
+ rowHeight,
1472
+ overscan,
1473
+ estimateRowHeight,
1474
+ pretextHeights,
1475
+ pretextPrefixSums
1476
+ });
1477
+ if (!enableVirtualization) {
1478
+ return /* @__PURE__ */ jsx("tbody", { className: "yable-tbody", children: rows.map((row, rowIndex) => /* @__PURE__ */ jsx(
1479
+ MemoizedTableRow,
1480
+ {
1481
+ row,
1482
+ table,
1483
+ rowIndex,
1484
+ visibleColumns,
1485
+ isSelected: row.getIsSelected(),
1486
+ isExpanded: row.getIsExpanded(),
1487
+ activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1488
+ focusedColumnIndex: focusedCell?.rowIndex === rowIndex ? focusedCell.columnIndex : null,
1489
+ hasFocusedCell: focusedCell !== null,
1490
+ clickable: clickableRows
1491
+ },
1492
+ row.id
1493
+ )) });
1494
+ }
1495
+ const hasPretextData = !!(pretextHeights && pretextPrefixSums);
1496
+ const fixedRowHeight = typeof rowHeight === "number" && !hasPretextData ? rowHeight : void 0;
1497
+ const containerHeight = hasPretextData ? Math.min(totalHeight, 800) : fixedRowHeight ? Math.min(totalHeight, fixedRowHeight * 20) : 600;
1498
+ return /* @__PURE__ */ jsx("tbody", { className: "yable-tbody", children: /* @__PURE__ */ jsx("tr", { style: { height: 0, padding: 0, border: "none" }, children: /* @__PURE__ */ jsx(
1499
+ "td",
1500
+ {
1501
+ style: { height: 0, padding: 0, border: "none" },
1502
+ colSpan: visibleColumns.length,
1503
+ children: /* @__PURE__ */ jsx(
1504
+ "div",
1505
+ {
1506
+ ref: scrollContainerRef,
1507
+ className: "yable-virtual-scroll-container",
1508
+ style: {
1509
+ overflowY: "auto",
1510
+ height: containerHeight,
1511
+ position: "relative"
1512
+ },
1513
+ children: /* @__PURE__ */ jsx(
1514
+ "div",
1515
+ {
1516
+ className: "yable-virtual-spacer",
1517
+ style: { height: totalHeight, position: "relative" },
1518
+ children: /* @__PURE__ */ jsx(
1519
+ "table",
1520
+ {
1521
+ style: {
1522
+ position: "absolute",
1523
+ top: 0,
1524
+ left: 0,
1525
+ width: "100%",
1526
+ tableLayout: "fixed",
1527
+ borderCollapse: "collapse"
1528
+ },
1529
+ children: /* @__PURE__ */ jsx("tbody", { children: virtualRows.map((vRow) => {
1530
+ const row = rows[vRow.index];
1531
+ if (!row) return null;
1532
+ return /* @__PURE__ */ jsx(
1533
+ MemoizedTableRow,
1534
+ {
1535
+ row,
1536
+ table,
1537
+ rowIndex: vRow.index,
1538
+ visibleColumns,
1539
+ isSelected: row.getIsSelected(),
1540
+ isExpanded: row.getIsExpanded(),
1541
+ activeColumnId: activeCell?.rowId === row.id ? activeCell.columnId : void 0,
1542
+ focusedColumnIndex: focusedCell?.rowIndex === vRow.index ? focusedCell.columnIndex : null,
1543
+ hasFocusedCell: focusedCell !== null,
1544
+ clickable: clickableRows,
1545
+ virtualStyle: {
1546
+ position: "absolute",
1547
+ top: 0,
1548
+ left: 0,
1549
+ width: "100%",
1550
+ height: vRow.size,
1551
+ transform: `translateY(${vRow.start}px)`
1552
+ }
1553
+ },
1554
+ row.id
1555
+ );
1556
+ }) })
1557
+ }
1558
+ )
1559
+ }
1560
+ )
1561
+ }
1562
+ )
1563
+ }
1564
+ ) }) });
1565
+ }
1566
+ function TableRowInner({
1567
+ row,
1568
+ table,
1569
+ rowIndex,
1570
+ visibleColumns,
1571
+ isSelected,
1572
+ isExpanded,
1573
+ activeColumnId,
1574
+ focusedColumnIndex,
1575
+ hasFocusedCell,
1576
+ clickable,
1577
+ virtualStyle
1578
+ }) {
1579
+ const visibleCells = row.getVisibleCells();
1580
+ const handleClick = useCallback(
1581
+ (e) => {
1582
+ if (clickable) {
1583
+ table.events.emit("row:click", {
1584
+ row,
1585
+ event: e.nativeEvent
1586
+ });
1587
+ }
1588
+ },
1589
+ [clickable, table.events, row]
1590
+ );
1591
+ const handleDoubleClick = useCallback(
1592
+ (e) => {
1593
+ table.events.emit("row:dblclick", {
1594
+ row,
1595
+ event: e.nativeEvent
1596
+ });
1597
+ },
1598
+ [table.events, row]
1599
+ );
1600
+ const handleContextMenu = useCallback(
1601
+ (e) => {
1602
+ table.events.emit("row:contextmenu", {
1603
+ row,
1604
+ event: e.nativeEvent
1605
+ });
1606
+ },
1607
+ [table.events, row]
1608
+ );
1609
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1610
+ /* @__PURE__ */ jsx(
1611
+ "tr",
1612
+ {
1613
+ className: "yable-tr",
1614
+ style: virtualStyle,
1615
+ "data-selected": isSelected || void 0,
1616
+ "data-expanded": isExpanded || void 0,
1617
+ "data-clickable": clickable || void 0,
1618
+ "data-row-id": row.id,
1619
+ "data-row-index": rowIndex,
1620
+ onClick: handleClick,
1621
+ onDoubleClick: handleDoubleClick,
1622
+ onContextMenu: handleContextMenu,
1623
+ children: visibleCells.map((cell, columnIndex) => {
1624
+ const isFocused = focusedColumnIndex === columnIndex;
1625
+ const isTabStop = isFocused || !hasFocusedCell && rowIndex === 0 && columnIndex === 0;
1626
+ return /* @__PURE__ */ jsx(
1627
+ CellErrorBoundary,
1628
+ {
1629
+ resetKeys: [cell.getValue(), activeColumnId === cell.column.id, isFocused],
1630
+ children: /* @__PURE__ */ jsx(
1631
+ TableCell,
1632
+ {
1633
+ cell,
1634
+ table,
1635
+ rowIndex,
1636
+ columnIndex,
1637
+ isFocused,
1638
+ isTabStop
1639
+ }
1640
+ )
1641
+ },
1642
+ cell.id
1643
+ );
1644
+ })
1645
+ }
1646
+ ),
1647
+ isExpanded && /* @__PURE__ */ jsx("tr", { className: "yable-expand-row", children: /* @__PURE__ */ jsx("td", { className: "yable-td", colSpan: visibleColumns.length, children: typeof row._renderExpanded === "function" ? row._renderExpanded() : null }) })
1648
+ ] });
1649
+ }
1650
+ function areRowPropsEqual(prev, next) {
1651
+ if (prev.row.id !== next.row.id) return false;
1652
+ if (prev.rowIndex !== next.rowIndex) return false;
1653
+ if (prev.visibleColumns !== next.visibleColumns) return false;
1654
+ if (prev.row.original !== next.row.original) return false;
1655
+ if (prev.isSelected !== next.isSelected) return false;
1656
+ if (prev.isExpanded !== next.isExpanded) return false;
1657
+ if (prev.clickable !== next.clickable) return false;
1658
+ if (prev.activeColumnId !== next.activeColumnId) return false;
1659
+ if (prev.focusedColumnIndex !== next.focusedColumnIndex) return false;
1660
+ if (prev.hasFocusedCell !== next.hasFocusedCell) return false;
1661
+ if (prev.virtualStyle !== next.virtualStyle) {
1662
+ if (!prev.virtualStyle || !next.virtualStyle) return false;
1663
+ if (prev.virtualStyle.transform !== next.virtualStyle.transform) return false;
1664
+ if (prev.virtualStyle.height !== next.virtualStyle.height) return false;
1665
+ }
1666
+ if (prev.table !== next.table) return false;
1667
+ return true;
1668
+ }
1669
+ var MemoizedTableRow = React3.memo(TableRowInner, areRowPropsEqual);
1670
+ function TableFooter({
1671
+ table
1672
+ }) {
1673
+ const footerGroups = table.getFooterGroups();
1674
+ if (!footerGroups.length) return null;
1675
+ return /* @__PURE__ */ jsx("tfoot", { className: "yable-tfoot", children: footerGroups.map((footerGroup) => /* @__PURE__ */ jsx("tr", { className: "yable-tr", children: footerGroup.headers.map((header) => {
1676
+ const footerDef = header.column.columnDef.footer;
1677
+ const content = header.isPlaceholder ? null : typeof footerDef === "function" ? footerDef(header.getContext()) : footerDef ?? null;
1678
+ return /* @__PURE__ */ jsx("td", { className: "yable-td", colSpan: header.colSpan, children: content }, header.id);
1679
+ }) }, footerGroup.id)) });
1680
+ }
1681
+ function Spinner() {
1682
+ return /* @__PURE__ */ jsx("div", { className: "yable-overlay-spinner", "aria-hidden": "true", children: /* @__PURE__ */ jsxs(
1683
+ "svg",
1684
+ {
1685
+ width: "32",
1686
+ height: "32",
1687
+ viewBox: "0 0 32 32",
1688
+ fill: "none",
1689
+ xmlns: "http://www.w3.org/2000/svg",
1690
+ children: [
1691
+ /* @__PURE__ */ jsx(
1692
+ "circle",
1693
+ {
1694
+ cx: "16",
1695
+ cy: "16",
1696
+ r: "13",
1697
+ stroke: "currentColor",
1698
+ strokeOpacity: "0.1",
1699
+ strokeWidth: "3"
1700
+ }
1701
+ ),
1702
+ /* @__PURE__ */ jsx(
1703
+ "path",
1704
+ {
1705
+ d: "M16 3a13 13 0 0 1 13 13",
1706
+ stroke: "currentColor",
1707
+ strokeWidth: "3",
1708
+ strokeLinecap: "round"
1709
+ }
1710
+ )
1711
+ ]
1712
+ }
1713
+ ) });
1714
+ }
1715
+ function LoadingOverlay({
1716
+ loading,
1717
+ loadingComponent,
1718
+ loadingText = "Loading..."
1719
+ }) {
1720
+ if (!loading) return null;
1721
+ return /* @__PURE__ */ jsx(
1722
+ "div",
1723
+ {
1724
+ className: "yable-overlay-loading",
1725
+ role: "alert",
1726
+ "aria-busy": "true",
1727
+ "aria-label": loadingText,
1728
+ children: /* @__PURE__ */ jsx("div", { className: "yable-overlay-loading-content", children: loadingComponent ?? /* @__PURE__ */ jsxs(Fragment, { children: [
1729
+ /* @__PURE__ */ jsx(Spinner, {}),
1730
+ loadingText && /* @__PURE__ */ jsx("span", { className: "yable-overlay-loading-text", children: loadingText })
1731
+ ] }) })
1732
+ }
1733
+ );
1734
+ }
1735
+ function DefaultEmptyIcon() {
1736
+ return /* @__PURE__ */ jsxs(
1737
+ "svg",
1738
+ {
1739
+ className: "yable-overlay-empty-icon",
1740
+ width: "56",
1741
+ height: "56",
1742
+ viewBox: "0 0 56 56",
1743
+ fill: "none",
1744
+ xmlns: "http://www.w3.org/2000/svg",
1745
+ "aria-hidden": "true",
1746
+ children: [
1747
+ /* @__PURE__ */ jsx(
1748
+ "path",
1749
+ {
1750
+ d: "M10 22L28 14L46 22V38L28 46L10 38V22Z",
1751
+ stroke: "currentColor",
1752
+ strokeWidth: "1.5",
1753
+ strokeLinejoin: "round",
1754
+ strokeOpacity: "0.25"
1755
+ }
1756
+ ),
1757
+ /* @__PURE__ */ jsx(
1758
+ "path",
1759
+ {
1760
+ d: "M28 14V30",
1761
+ stroke: "currentColor",
1762
+ strokeWidth: "1.5",
1763
+ strokeOpacity: "0.15"
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ jsx(
1767
+ "path",
1768
+ {
1769
+ d: "M10 22L28 30L46 22",
1770
+ stroke: "currentColor",
1771
+ strokeWidth: "1.5",
1772
+ strokeLinejoin: "round",
1773
+ strokeOpacity: "0.2"
1774
+ }
1775
+ ),
1776
+ /* @__PURE__ */ jsx(
1777
+ "path",
1778
+ {
1779
+ d: "M4 20L28 10L52 20",
1780
+ stroke: "currentColor",
1781
+ strokeWidth: "1.5",
1782
+ strokeLinecap: "round",
1783
+ strokeLinejoin: "round",
1784
+ strokeOpacity: "0.12"
1785
+ }
1786
+ )
1787
+ ]
1788
+ }
1789
+ );
1790
+ }
1791
+ function DefaultFilteredIcon() {
1792
+ return /* @__PURE__ */ jsxs(
1793
+ "svg",
1794
+ {
1795
+ className: "yable-overlay-empty-icon",
1796
+ width: "56",
1797
+ height: "56",
1798
+ viewBox: "0 0 56 56",
1799
+ fill: "none",
1800
+ xmlns: "http://www.w3.org/2000/svg",
1801
+ "aria-hidden": "true",
1802
+ children: [
1803
+ /* @__PURE__ */ jsx(
1804
+ "circle",
1805
+ {
1806
+ cx: "25",
1807
+ cy: "25",
1808
+ r: "13",
1809
+ stroke: "currentColor",
1810
+ strokeWidth: "1.5",
1811
+ strokeOpacity: "0.3"
1812
+ }
1813
+ ),
1814
+ /* @__PURE__ */ jsx(
1815
+ "path",
1816
+ {
1817
+ d: "M34 34L46 46",
1818
+ stroke: "currentColor",
1819
+ strokeWidth: "2",
1820
+ strokeLinecap: "round",
1821
+ strokeOpacity: "0.25"
1822
+ }
1823
+ ),
1824
+ /* @__PURE__ */ jsx(
1825
+ "path",
1826
+ {
1827
+ d: "M20.5 20.5L29.5 29.5M29.5 20.5L20.5 29.5",
1828
+ stroke: "currentColor",
1829
+ strokeWidth: "1.5",
1830
+ strokeLinecap: "round",
1831
+ strokeOpacity: "0.25"
1832
+ }
1833
+ )
1834
+ ]
1835
+ }
1836
+ );
1837
+ }
1838
+ function NoRowsOverlay({
1839
+ emptyComponent,
1840
+ emptyIcon,
1841
+ emptyMessage,
1842
+ emptyDetail,
1843
+ isFiltered = false
1844
+ }) {
1845
+ if (emptyComponent) {
1846
+ return /* @__PURE__ */ jsx("div", { className: "yable-overlay-empty", children: emptyComponent });
1847
+ }
1848
+ const defaultMessage = isFiltered ? "No results found" : "No data";
1849
+ const defaultDetail = isFiltered ? "Try adjusting your search or filter criteria." : "There are no rows to display.";
1850
+ const icon = emptyIcon ?? (isFiltered ? /* @__PURE__ */ jsx(DefaultFilteredIcon, {}) : /* @__PURE__ */ jsx(DefaultEmptyIcon, {}));
1851
+ return /* @__PURE__ */ jsxs("div", { className: "yable-overlay-empty", role: "status", children: [
1852
+ /* @__PURE__ */ jsx("div", { className: "yable-overlay-empty-icon-wrapper", children: icon }),
1853
+ /* @__PURE__ */ jsx("div", { className: "yable-overlay-empty-message", children: emptyMessage ?? defaultMessage }),
1854
+ /* @__PURE__ */ jsx("div", { className: "yable-overlay-empty-detail", children: emptyDetail ?? defaultDetail })
1855
+ ] });
1856
+ }
1857
+ function StatusBarPanelComponent({
1858
+ label,
1859
+ value,
1860
+ icon,
1861
+ title
1862
+ }) {
1863
+ const displayValue = typeof value === "number" ? value.toLocaleString() : value;
1864
+ return /* @__PURE__ */ jsxs("div", { className: "yable-status-panel", title, children: [
1865
+ icon && /* @__PURE__ */ jsx("span", { className: "yable-status-panel-icon", children: icon }),
1866
+ label && /* @__PURE__ */ jsxs("span", { className: "yable-status-panel-label", children: [
1867
+ label,
1868
+ ":"
1869
+ ] }),
1870
+ /* @__PURE__ */ jsx("span", { className: "yable-status-panel-value", children: displayValue })
1871
+ ] });
1872
+ }
1873
+ function StatusDivider() {
1874
+ return /* @__PURE__ */ jsx("span", { className: "yable-status-bar-divider", "aria-hidden": "true" });
1875
+ }
1876
+ function PanelGroup({ children }) {
1877
+ const items = React3.Children.toArray(children).filter(Boolean);
1878
+ return /* @__PURE__ */ jsx(Fragment, { children: items.map((child, i) => /* @__PURE__ */ jsxs(React3.Fragment, { children: [
1879
+ i > 0 && /* @__PURE__ */ jsx(StatusDivider, {}),
1880
+ child
1881
+ ] }, i)) });
1882
+ }
1883
+ function RowCountIcon() {
1884
+ return /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: [
1885
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "10", height: "2.5", rx: "0.5", fill: "currentColor", opacity: "0.5" }),
1886
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "4.75", width: "10", height: "2.5", rx: "0.5", fill: "currentColor", opacity: "0.35" }),
1887
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "8.5", width: "10", height: "2.5", rx: "0.5", fill: "currentColor", opacity: "0.2" })
1888
+ ] });
1889
+ }
1890
+ function FilterIcon() {
1891
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1 2.5h10M3 6h6M4.5 9.5h3", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }) });
1892
+ }
1893
+ function SelectedIcon() {
1894
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2.5 6.5L5 9L9.5 3", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" }) });
1895
+ }
1896
+ function StatusBar({
1897
+ table,
1898
+ panels
1899
+ }) {
1900
+ const totalRows = table.getCoreRowModel().rows.length;
1901
+ const filteredRows = table.getFilteredRowModel().rows.length;
1902
+ const selectedRows = Object.keys(table.getState().rowSelection).length;
1903
+ const isFiltered = table.getState().globalFilter || table.getState().columnFilters.length > 0;
1904
+ if (panels && panels.length > 0) {
1905
+ const leftPanels = panels.filter((p) => p.align === "left" || !p.align);
1906
+ const centerPanels = panels.filter((p) => p.align === "center");
1907
+ const rightPanels = panels.filter((p) => p.align === "right");
1908
+ return /* @__PURE__ */ jsxs("div", { className: "yable-status-bar", role: "status", "aria-label": "Table status", children: [
1909
+ /* @__PURE__ */ jsx("div", { className: "yable-status-bar-section yable-status-bar-left", children: /* @__PURE__ */ jsx(PanelGroup, { children: leftPanels.map(
1910
+ (panel) => panel.component ? /* @__PURE__ */ jsx(panel.component, { table }, panel.id) : /* @__PURE__ */ jsx(
1911
+ StatusBarPanelComponent,
1912
+ {
1913
+ label: panel.label,
1914
+ value: ""
1915
+ },
1916
+ panel.id
1917
+ )
1918
+ ) }) }),
1919
+ centerPanels.length > 0 && /* @__PURE__ */ jsx("div", { className: "yable-status-bar-section yable-status-bar-center", children: /* @__PURE__ */ jsx(PanelGroup, { children: centerPanels.map(
1920
+ (panel) => panel.component ? /* @__PURE__ */ jsx(panel.component, { table }, panel.id) : null
1921
+ ) }) }),
1922
+ /* @__PURE__ */ jsx("div", { className: "yable-status-bar-section yable-status-bar-right", children: /* @__PURE__ */ jsx(PanelGroup, { children: rightPanels.map(
1923
+ (panel) => panel.component ? /* @__PURE__ */ jsx(panel.component, { table }, panel.id) : null
1924
+ ) }) })
1925
+ ] });
1926
+ }
1927
+ return /* @__PURE__ */ jsxs("div", { className: "yable-status-bar", role: "status", "aria-label": "Table status", children: [
1928
+ /* @__PURE__ */ jsx("div", { className: "yable-status-bar-section yable-status-bar-left", children: /* @__PURE__ */ jsxs(PanelGroup, { children: [
1929
+ /* @__PURE__ */ jsx(
1930
+ StatusBarPanelComponent,
1931
+ {
1932
+ label: "Total",
1933
+ value: totalRows,
1934
+ icon: /* @__PURE__ */ jsx(RowCountIcon, {})
1935
+ }
1936
+ ),
1937
+ isFiltered && /* @__PURE__ */ jsx(
1938
+ StatusBarPanelComponent,
1939
+ {
1940
+ label: "Filtered",
1941
+ value: filteredRows,
1942
+ icon: /* @__PURE__ */ jsx(FilterIcon, {})
1943
+ }
1944
+ )
1945
+ ] }) }),
1946
+ /* @__PURE__ */ jsx("div", { className: "yable-status-bar-section yable-status-bar-right", children: /* @__PURE__ */ jsx(PanelGroup, { children: selectedRows > 0 && /* @__PURE__ */ jsx(
1947
+ StatusBarPanelComponent,
1948
+ {
1949
+ label: "Selected",
1950
+ value: selectedRows,
1951
+ icon: /* @__PURE__ */ jsx(SelectedIcon, {})
1952
+ }
1953
+ ) }) })
1954
+ ] });
1955
+ }
1956
+ function SearchIcon() {
1957
+ return /* @__PURE__ */ jsxs("svg", { className: "yable-sidebar-search-icon", width: "13", height: "13", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: [
1958
+ /* @__PURE__ */ jsx("circle", { cx: "6.25", cy: "6.25", r: "4.25", stroke: "currentColor", strokeWidth: "1.5" }),
1959
+ /* @__PURE__ */ jsx("path", { d: "M9.5 9.5L12.5 12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
1960
+ ] });
1961
+ }
1962
+ function VisibilityIcon({ visible }) {
1963
+ if (visible) {
1964
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: [
1965
+ /* @__PURE__ */ jsx("path", { d: "M1 7s2.5-4 6-4 6 4 6 4-2.5 4-6 4-6-4-6-4z", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round" }),
1966
+ /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "2", stroke: "currentColor", strokeWidth: "1.2" })
1967
+ ] });
1968
+ }
1969
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: [
1970
+ /* @__PURE__ */ jsx("path", { d: "M1 7s2.5-4 6-4 6 4 6 4-2.5 4-6 4-6-4-6-4z", stroke: "currentColor", strokeWidth: "1.2", strokeLinejoin: "round", opacity: "0.3" }),
1971
+ /* @__PURE__ */ jsx("line", { x1: "2", y1: "2", x2: "12", y2: "12", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", opacity: "0.5" })
1972
+ ] });
1973
+ }
1974
+ function ColumnsPanel({
1975
+ table
1976
+ }) {
1977
+ const [search, setSearch] = useState("");
1978
+ const [draggedId, setDraggedId] = useState(null);
1979
+ const columns = table.getAllLeafColumns();
1980
+ const visibleCount = columns.filter((c) => c.getIsVisible()).length;
1981
+ const filteredColumns = search ? columns.filter((col) => {
1982
+ const header = typeof col.columnDef.header === "string" ? col.columnDef.header : col.id;
1983
+ return header.toLowerCase().includes(search.toLowerCase());
1984
+ }) : columns;
1985
+ const handleToggleAll = useCallback(
1986
+ (visible) => {
1987
+ table.toggleAllColumnsVisible(visible);
1988
+ },
1989
+ [table]
1990
+ );
1991
+ const handleDragStart = useCallback((e, columnId) => {
1992
+ setDraggedId(columnId);
1993
+ e.dataTransfer.effectAllowed = "move";
1994
+ e.dataTransfer.setData("text/plain", columnId);
1995
+ }, []);
1996
+ const handleDragOver = useCallback((e) => {
1997
+ e.preventDefault();
1998
+ e.dataTransfer.dropEffect = "move";
1999
+ }, []);
2000
+ const handleDragEnd = useCallback(() => {
2001
+ setDraggedId(null);
2002
+ }, []);
2003
+ const handleDrop = useCallback(
2004
+ (e, targetId) => {
2005
+ e.preventDefault();
2006
+ if (!draggedId || draggedId === targetId) return;
2007
+ const currentOrder = table.getState().columnOrder.length > 0 ? table.getState().columnOrder : columns.map((c) => c.id);
2008
+ const fromIndex = currentOrder.indexOf(draggedId);
2009
+ const toIndex = currentOrder.indexOf(targetId);
2010
+ if (fromIndex === -1 || toIndex === -1) return;
2011
+ const newOrder = [...currentOrder];
2012
+ newOrder.splice(fromIndex, 1);
2013
+ newOrder.splice(toIndex, 0, draggedId);
2014
+ table.setColumnOrder(newOrder);
2015
+ setDraggedId(null);
2016
+ },
2017
+ [draggedId, table, columns]
2018
+ );
2019
+ return /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-panel yable-sidebar-columns", children: [
2020
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-panel-search", children: [
2021
+ /* @__PURE__ */ jsx(SearchIcon, {}),
2022
+ /* @__PURE__ */ jsx(
2023
+ "input",
2024
+ {
2025
+ type: "text",
2026
+ className: "yable-sidebar-search-input",
2027
+ placeholder: "Search columns...",
2028
+ value: search,
2029
+ onChange: (e) => setSearch(e.target.value),
2030
+ "aria-label": "Search columns"
2031
+ }
2032
+ )
2033
+ ] }),
2034
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-panel-actions", children: [
2035
+ /* @__PURE__ */ jsx(
2036
+ "button",
2037
+ {
2038
+ type: "button",
2039
+ className: "yable-sidebar-action-btn",
2040
+ onClick: () => handleToggleAll(true),
2041
+ children: "Show all"
2042
+ }
2043
+ ),
2044
+ /* @__PURE__ */ jsx(
2045
+ "button",
2046
+ {
2047
+ type: "button",
2048
+ className: "yable-sidebar-action-btn",
2049
+ onClick: () => handleToggleAll(false),
2050
+ children: "Hide all"
2051
+ }
2052
+ ),
2053
+ /* @__PURE__ */ jsxs("span", { className: "yable-sidebar-column-count", "aria-live": "polite", children: [
2054
+ visibleCount,
2055
+ "/",
2056
+ columns.length
2057
+ ] })
2058
+ ] }),
2059
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-column-list", role: "listbox", "aria-label": "Columns", children: [
2060
+ filteredColumns.map((column) => {
2061
+ const header = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
2062
+ const isVisible = column.getIsVisible();
2063
+ return /* @__PURE__ */ jsxs(
2064
+ "div",
2065
+ {
2066
+ className: `yable-sidebar-column-item${draggedId === column.id ? " yable-sidebar-column-item--dragging" : ""}${!isVisible ? " yable-sidebar-column-item--hidden" : ""}`,
2067
+ draggable: true,
2068
+ onDragStart: (e) => handleDragStart(e, column.id),
2069
+ onDragOver: handleDragOver,
2070
+ onDragEnd: handleDragEnd,
2071
+ onDrop: (e) => handleDrop(e, column.id),
2072
+ role: "option",
2073
+ "aria-selected": isVisible,
2074
+ children: [
2075
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-drag-handle", "aria-hidden": "true", children: /* @__PURE__ */ jsxs("svg", { width: "8", height: "14", viewBox: "0 0 8 14", fill: "currentColor", children: [
2076
+ /* @__PURE__ */ jsx("circle", { cx: "2", cy: "2", r: "1.2" }),
2077
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "2", r: "1.2" }),
2078
+ /* @__PURE__ */ jsx("circle", { cx: "2", cy: "7", r: "1.2" }),
2079
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "7", r: "1.2" }),
2080
+ /* @__PURE__ */ jsx("circle", { cx: "2", cy: "12", r: "1.2" }),
2081
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "12", r: "1.2" })
2082
+ ] }) }),
2083
+ /* @__PURE__ */ jsxs("label", { className: "yable-sidebar-column-label", children: [
2084
+ /* @__PURE__ */ jsx(
2085
+ "input",
2086
+ {
2087
+ type: "checkbox",
2088
+ className: "yable-checkbox",
2089
+ checked: isVisible,
2090
+ onChange: (e) => column.toggleVisibility(e.target.checked)
2091
+ }
2092
+ ),
2093
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-column-name", children: header })
2094
+ ] }),
2095
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-column-visibility", "aria-hidden": "true", children: /* @__PURE__ */ jsx(VisibilityIcon, { visible: isVisible }) })
2096
+ ]
2097
+ },
2098
+ column.id
2099
+ );
2100
+ }),
2101
+ filteredColumns.length === 0 && search && /* @__PURE__ */ jsx("div", { className: "yable-sidebar-empty", children: /* @__PURE__ */ jsxs("span", { children: [
2102
+ "No columns match \u201C",
2103
+ search,
2104
+ "\u201D"
2105
+ ] }) })
2106
+ ] })
2107
+ ] });
2108
+ }
2109
+ function RemoveIcon() {
2110
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 3l6 6M9 3L3 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) });
2111
+ }
2112
+ function GlobalSearchIcon() {
2113
+ return /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: [
2114
+ /* @__PURE__ */ jsx("circle", { cx: "5.5", cy: "5.5", r: "3.5", stroke: "currentColor", strokeWidth: "1.2" }),
2115
+ /* @__PURE__ */ jsx("path", { d: "M8 8L10.5 10.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })
2116
+ ] });
2117
+ }
2118
+ function ColumnFilterIcon() {
2119
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1.5 2h9L7 6v3.5L5 11V6L1.5 2z", stroke: "currentColor", strokeWidth: "1.1", strokeLinejoin: "round" }) });
2120
+ }
2121
+ function FiltersPanel({
2122
+ table
2123
+ }) {
2124
+ const columnFilters = table.getState().columnFilters;
2125
+ const globalFilter = table.getState().globalFilter;
2126
+ const hasFilters = columnFilters.length > 0 || Boolean(globalFilter);
2127
+ const filterCount = columnFilters.length + (globalFilter ? 1 : 0);
2128
+ const handleRemoveColumnFilter = useCallback(
2129
+ (columnId) => {
2130
+ table.setColumnFilters(
2131
+ (prev) => prev.filter((f) => f.id !== columnId)
2132
+ );
2133
+ },
2134
+ [table]
2135
+ );
2136
+ const handleClearAll = useCallback(() => {
2137
+ table.resetColumnFilters(true);
2138
+ table.resetGlobalFilter(true);
2139
+ }, [table]);
2140
+ return /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-panel yable-sidebar-filters", children: [
2141
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-panel-header", children: [
2142
+ /* @__PURE__ */ jsxs("span", { className: "yable-sidebar-panel-title", children: [
2143
+ "Active Filters",
2144
+ hasFilters && /* @__PURE__ */ jsx("span", { className: "yable-sidebar-filter-badge", children: filterCount })
2145
+ ] }),
2146
+ hasFilters && /* @__PURE__ */ jsx(
2147
+ "button",
2148
+ {
2149
+ type: "button",
2150
+ className: "yable-sidebar-action-btn yable-sidebar-action-btn--danger",
2151
+ onClick: handleClearAll,
2152
+ children: "Clear all"
2153
+ }
2154
+ )
2155
+ ] }),
2156
+ !hasFilters && /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-empty", children: [
2157
+ /* @__PURE__ */ jsxs("svg", { width: "40", height: "40", viewBox: "0 0 40 40", fill: "none", "aria-hidden": "true", children: [
2158
+ /* @__PURE__ */ jsx(
2159
+ "path",
2160
+ {
2161
+ d: "M5 10h30M10 20h20M15 30h10",
2162
+ stroke: "currentColor",
2163
+ strokeWidth: "1.5",
2164
+ strokeLinecap: "round",
2165
+ strokeOpacity: "0.2"
2166
+ }
2167
+ ),
2168
+ /* @__PURE__ */ jsx("circle", { cx: "32", cy: "30", r: "6", stroke: "currentColor", strokeWidth: "1.5", strokeOpacity: "0.15" }),
2169
+ /* @__PURE__ */ jsx("path", { d: "M30 28l4 4M34 28l-4 4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeOpacity: "0.15" })
2170
+ ] }),
2171
+ /* @__PURE__ */ jsx("span", { children: "No active filters" }),
2172
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-empty-hint", children: "Filter columns using the header menu or the search bar." })
2173
+ ] }),
2174
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-filter-list", role: "list", children: [
2175
+ globalFilter && /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-filter-item", role: "listitem", children: [
2176
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-filter-type-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(GlobalSearchIcon, {}) }),
2177
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-filter-info", children: [
2178
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-filter-column", children: "Global Search" }),
2179
+ /* @__PURE__ */ jsxs("span", { className: "yable-sidebar-filter-value", children: [
2180
+ "\u201C",
2181
+ String(globalFilter),
2182
+ "\u201D"
2183
+ ] })
2184
+ ] }),
2185
+ /* @__PURE__ */ jsx(
2186
+ "button",
2187
+ {
2188
+ type: "button",
2189
+ className: "yable-sidebar-filter-remove",
2190
+ onClick: () => table.resetGlobalFilter(true),
2191
+ "aria-label": "Remove global filter",
2192
+ title: "Remove filter",
2193
+ children: /* @__PURE__ */ jsx(RemoveIcon, {})
2194
+ }
2195
+ )
2196
+ ] }),
2197
+ columnFilters.map((filter) => {
2198
+ const column = table.getColumn(filter.id);
2199
+ const headerName = column ? typeof column.columnDef.header === "string" ? column.columnDef.header : column.id : filter.id;
2200
+ return /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-filter-item", role: "listitem", children: [
2201
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-filter-type-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(ColumnFilterIcon, {}) }),
2202
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-filter-info", children: [
2203
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-filter-column", children: headerName }),
2204
+ /* @__PURE__ */ jsx("span", { className: "yable-sidebar-filter-value", children: String(filter.value) })
2205
+ ] }),
2206
+ /* @__PURE__ */ jsx(
2207
+ "button",
2208
+ {
2209
+ type: "button",
2210
+ className: "yable-sidebar-filter-remove",
2211
+ onClick: () => handleRemoveColumnFilter(filter.id),
2212
+ "aria-label": `Remove filter for ${headerName}`,
2213
+ title: "Remove filter",
2214
+ children: /* @__PURE__ */ jsx(RemoveIcon, {})
2215
+ }
2216
+ )
2217
+ ] }, filter.id);
2218
+ })
2219
+ ] })
2220
+ ] });
2221
+ }
2222
+ function Sidebar({
2223
+ table,
2224
+ open,
2225
+ onClose,
2226
+ panels,
2227
+ activePanel,
2228
+ onPanelChange
2229
+ }) {
2230
+ const sidebarRef = useRef(null);
2231
+ const handleKeyDown = useCallback(
2232
+ (e) => {
2233
+ if (e.key === "Escape" && open) {
2234
+ onClose();
2235
+ }
2236
+ },
2237
+ [open, onClose]
2238
+ );
2239
+ useEffect(() => {
2240
+ document.addEventListener("keydown", handleKeyDown);
2241
+ return () => document.removeEventListener("keydown", handleKeyDown);
2242
+ }, [handleKeyDown]);
2243
+ const panelId = `yable-sidebar-panel-${activePanel}`;
2244
+ const tabId = (panel) => `yable-sidebar-tab-${panel}`;
2245
+ return /* @__PURE__ */ jsxs(
2246
+ "div",
2247
+ {
2248
+ ref: sidebarRef,
2249
+ className: `yable-sidebar${open ? " yable-sidebar--open" : ""}`,
2250
+ role: "complementary",
2251
+ "aria-label": "Table tools",
2252
+ children: [
2253
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-header", children: [
2254
+ /* @__PURE__ */ jsxs("div", { className: "yable-sidebar-tabs", role: "tablist", "aria-label": "Tool panels", children: [
2255
+ panels.includes("columns") && /* @__PURE__ */ jsxs(
2256
+ "button",
2257
+ {
2258
+ type: "button",
2259
+ id: tabId("columns"),
2260
+ className: `yable-sidebar-tab${activePanel === "columns" ? " yable-sidebar-tab--active" : ""}`,
2261
+ role: "tab",
2262
+ "aria-selected": activePanel === "columns",
2263
+ "aria-controls": panelId,
2264
+ onClick: () => onPanelChange("columns"),
2265
+ children: [
2266
+ /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: [
2267
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "5", height: "12", rx: "1", stroke: "currentColor", strokeWidth: "1.2" }),
2268
+ /* @__PURE__ */ jsx("rect", { x: "8", y: "1", width: "5", height: "12", rx: "1", stroke: "currentColor", strokeWidth: "1.2" })
2269
+ ] }),
2270
+ /* @__PURE__ */ jsx("span", { children: "Columns" })
2271
+ ]
2272
+ }
2273
+ ),
2274
+ panels.includes("filters") && /* @__PURE__ */ jsxs(
2275
+ "button",
2276
+ {
2277
+ type: "button",
2278
+ id: tabId("filters"),
2279
+ className: `yable-sidebar-tab${activePanel === "filters" ? " yable-sidebar-tab--active" : ""}`,
2280
+ role: "tab",
2281
+ "aria-selected": activePanel === "filters",
2282
+ "aria-controls": panelId,
2283
+ onClick: () => onPanelChange("filters"),
2284
+ children: [
2285
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1 3h12M3 7h8M5 11h4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }) }),
2286
+ /* @__PURE__ */ jsx("span", { children: "Filters" })
2287
+ ]
2288
+ }
2289
+ )
2290
+ ] }),
2291
+ /* @__PURE__ */ jsx(
2292
+ "button",
2293
+ {
2294
+ type: "button",
2295
+ className: "yable-sidebar-close",
2296
+ onClick: onClose,
2297
+ "aria-label": "Close sidebar",
2298
+ title: "Close (Esc)",
2299
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l6 6M10 4L4 10", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
2300
+ }
2301
+ )
2302
+ ] }),
2303
+ /* @__PURE__ */ jsxs(
2304
+ "div",
2305
+ {
2306
+ id: panelId,
2307
+ className: "yable-sidebar-content",
2308
+ role: "tabpanel",
2309
+ "aria-labelledby": tabId(activePanel),
2310
+ children: [
2311
+ activePanel === "columns" && /* @__PURE__ */ jsx(ColumnsPanel, { table }),
2312
+ activePanel === "filters" && /* @__PURE__ */ jsx(FiltersPanel, { table })
2313
+ ]
2314
+ }
2315
+ )
2316
+ ]
2317
+ }
2318
+ );
2319
+ }
2320
+ function ContextMenuItem({ item, onClose }) {
2321
+ const [submenuOpen, setSubmenuOpen] = useState(false);
2322
+ const itemRef = useRef(null);
2323
+ const submenuRef = useRef(null);
2324
+ const timerRef = useRef(void 0);
2325
+ useEffect(() => {
2326
+ return () => clearTimeout(timerRef.current);
2327
+ }, []);
2328
+ if (item.separator) {
2329
+ return /* @__PURE__ */ jsx("div", { className: "yable-ctx-separator", role: "separator" });
2330
+ }
2331
+ const hasChildren = item.children && item.children.length > 0;
2332
+ const classes = [
2333
+ "yable-ctx-item",
2334
+ item.disabled && "yable-ctx-item--disabled",
2335
+ item.danger && "yable-ctx-item--danger",
2336
+ hasChildren && "yable-ctx-item--has-submenu",
2337
+ submenuOpen && "yable-ctx-item--submenu-open"
2338
+ ].filter(Boolean).join(" ");
2339
+ const handleClick = () => {
2340
+ if (item.disabled) return;
2341
+ if (hasChildren) return;
2342
+ item.action?.();
2343
+ onClose();
2344
+ };
2345
+ const handleKeyDown = (e) => {
2346
+ if (e.key === "Enter" || e.key === " ") {
2347
+ e.preventDefault();
2348
+ handleClick();
2349
+ }
2350
+ if (e.key === "ArrowRight" && hasChildren) {
2351
+ setSubmenuOpen(true);
2352
+ }
2353
+ if (e.key === "ArrowLeft" && submenuOpen) {
2354
+ setSubmenuOpen(false);
2355
+ }
2356
+ };
2357
+ const handleMouseEnter = () => {
2358
+ clearTimeout(timerRef.current);
2359
+ if (hasChildren) {
2360
+ timerRef.current = setTimeout(() => setSubmenuOpen(true), 150);
2361
+ }
2362
+ };
2363
+ const handleMouseLeave = () => {
2364
+ clearTimeout(timerRef.current);
2365
+ timerRef.current = setTimeout(() => setSubmenuOpen(false), 200);
2366
+ };
2367
+ return /* @__PURE__ */ jsxs(
2368
+ "div",
2369
+ {
2370
+ ref: itemRef,
2371
+ className: classes,
2372
+ role: "menuitem",
2373
+ tabIndex: item.disabled ? -1 : 0,
2374
+ "aria-disabled": item.disabled,
2375
+ "aria-haspopup": hasChildren ? "menu" : void 0,
2376
+ "aria-expanded": hasChildren ? submenuOpen : void 0,
2377
+ onClick: handleClick,
2378
+ onKeyDown: handleKeyDown,
2379
+ onMouseEnter: handleMouseEnter,
2380
+ onMouseLeave: handleMouseLeave,
2381
+ children: [
2382
+ /* @__PURE__ */ jsx("span", { className: "yable-ctx-item-icon", "aria-hidden": "true", children: item.icon ?? null }),
2383
+ /* @__PURE__ */ jsx("span", { className: "yable-ctx-item-label", children: item.label }),
2384
+ item.shortcut && /* @__PURE__ */ jsx("kbd", { className: "yable-ctx-item-shortcut", children: item.shortcut }),
2385
+ hasChildren && /* @__PURE__ */ jsx("span", { className: "yable-ctx-item-arrow", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M3.5 2L6.5 5L3.5 8", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
2386
+ hasChildren && submenuOpen && /* @__PURE__ */ jsx(
2387
+ "div",
2388
+ {
2389
+ ref: submenuRef,
2390
+ className: "yable-ctx-menu yable-ctx-submenu yable-ctx-menu--animated",
2391
+ role: "menu",
2392
+ onMouseEnter: () => clearTimeout(timerRef.current),
2393
+ onMouseLeave: handleMouseLeave,
2394
+ children: item.children.map((child) => /* @__PURE__ */ jsx(ContextMenuItem, { item: child, onClose }, child.id))
2395
+ }
2396
+ )
2397
+ ]
2398
+ }
2399
+ );
2400
+ }
2401
+ function ContextMenu({
2402
+ x,
2403
+ y,
2404
+ onClose,
2405
+ table,
2406
+ customItems
2407
+ }) {
2408
+ const menuRef = useRef(null);
2409
+ useEffect(() => {
2410
+ if (!menuRef.current) return;
2411
+ const rect = menuRef.current.getBoundingClientRect();
2412
+ const vw = window.innerWidth;
2413
+ const vh = window.innerHeight;
2414
+ if (rect.right > vw - 8) {
2415
+ menuRef.current.style.left = `${x - rect.width}px`;
2416
+ }
2417
+ if (rect.bottom > vh - 8) {
2418
+ menuRef.current.style.top = `${y - rect.height}px`;
2419
+ }
2420
+ }, [x, y]);
2421
+ useEffect(() => {
2422
+ const firstItem = menuRef.current?.querySelector('[role="menuitem"]');
2423
+ firstItem?.focus();
2424
+ }, []);
2425
+ const handleKeyDown = useCallback(
2426
+ (e) => {
2427
+ if (e.key === "Escape") {
2428
+ onClose();
2429
+ return;
2430
+ }
2431
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2432
+ e.preventDefault();
2433
+ const items2 = menuRef.current?.querySelectorAll('[role="menuitem"]:not([aria-disabled="true"])');
2434
+ if (!items2 || items2.length === 0) return;
2435
+ const currentIndex = Array.from(items2).findIndex(
2436
+ (el) => el === document.activeElement
2437
+ );
2438
+ let nextIndex;
2439
+ if (e.key === "ArrowDown") {
2440
+ nextIndex = currentIndex < items2.length - 1 ? currentIndex + 1 : 0;
2441
+ } else {
2442
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : items2.length - 1;
2443
+ }
2444
+ items2[nextIndex]?.focus();
2445
+ }
2446
+ },
2447
+ [onClose]
2448
+ );
2449
+ const defaultItems = [
2450
+ {
2451
+ id: "copy",
2452
+ label: "Copy",
2453
+ shortcut: "\u2318C",
2454
+ action: () => {
2455
+ try {
2456
+ const text = table.copyToClipboard();
2457
+ navigator.clipboard?.writeText(text);
2458
+ } catch {
2459
+ }
2460
+ }
2461
+ },
2462
+ {
2463
+ id: "cut",
2464
+ label: "Cut",
2465
+ shortcut: "\u2318X",
2466
+ action: () => {
2467
+ try {
2468
+ const text = table.cutCells();
2469
+ navigator.clipboard?.writeText(text);
2470
+ } catch {
2471
+ }
2472
+ }
2473
+ },
2474
+ {
2475
+ id: "paste",
2476
+ label: "Paste",
2477
+ shortcut: "\u2318V",
2478
+ action: () => {
2479
+ navigator.clipboard?.readText().then((text) => {
2480
+ const editing = table.getState().editing;
2481
+ if (editing?.activeCell) {
2482
+ table.pasteFromClipboard(
2483
+ text,
2484
+ editing.activeCell.rowId,
2485
+ editing.activeCell.columnId
2486
+ );
2487
+ }
2488
+ }).catch(() => {
2489
+ });
2490
+ }
2491
+ },
2492
+ { id: "sep1", label: "", separator: true },
2493
+ {
2494
+ id: "sort",
2495
+ label: "Sort",
2496
+ children: [
2497
+ {
2498
+ id: "sort-asc",
2499
+ label: "Sort Ascending",
2500
+ action: () => {
2501
+ table.setSorting([]);
2502
+ }
2503
+ },
2504
+ {
2505
+ id: "sort-desc",
2506
+ label: "Sort Descending",
2507
+ action: () => {
2508
+ table.setSorting([]);
2509
+ }
2510
+ },
2511
+ {
2512
+ id: "sort-clear",
2513
+ label: "Clear Sort",
2514
+ action: () => {
2515
+ table.resetSorting(true);
2516
+ }
2517
+ }
2518
+ ]
2519
+ },
2520
+ { id: "sep2", label: "", separator: true },
2521
+ {
2522
+ id: "export",
2523
+ label: "Export",
2524
+ children: [
2525
+ {
2526
+ id: "export-csv",
2527
+ label: "Export as CSV",
2528
+ action: () => {
2529
+ const csv = table.exportData({ format: "csv", allRows: true });
2530
+ const blob = new Blob([csv], { type: "text/csv" });
2531
+ const url = URL.createObjectURL(blob);
2532
+ const a = document.createElement("a");
2533
+ a.href = url;
2534
+ a.download = "table-export.csv";
2535
+ a.click();
2536
+ URL.revokeObjectURL(url);
2537
+ }
2538
+ },
2539
+ {
2540
+ id: "export-json",
2541
+ label: "Export as JSON",
2542
+ action: () => {
2543
+ const json = table.exportData({ format: "json", allRows: true });
2544
+ const blob = new Blob([json], { type: "application/json" });
2545
+ const url = URL.createObjectURL(blob);
2546
+ const a = document.createElement("a");
2547
+ a.href = url;
2548
+ a.download = "table-export.json";
2549
+ a.click();
2550
+ URL.revokeObjectURL(url);
2551
+ }
2552
+ }
2553
+ ]
2554
+ }
2555
+ ];
2556
+ const items = customItems ? [...defaultItems, { id: "sep-custom", label: "", separator: true }, ...customItems] : defaultItems;
2557
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2558
+ /* @__PURE__ */ jsx(
2559
+ "div",
2560
+ {
2561
+ className: "yable-ctx-backdrop",
2562
+ onClick: onClose,
2563
+ onContextMenu: (e) => {
2564
+ e.preventDefault();
2565
+ onClose();
2566
+ },
2567
+ style: {
2568
+ position: "fixed",
2569
+ inset: 0,
2570
+ zIndex: 9998
2571
+ },
2572
+ "aria-hidden": "true"
2573
+ }
2574
+ ),
2575
+ /* @__PURE__ */ jsx(
2576
+ "div",
2577
+ {
2578
+ ref: menuRef,
2579
+ className: "yable-ctx-menu yable-ctx-menu--animated",
2580
+ role: "menu",
2581
+ "aria-label": "Context menu",
2582
+ style: {
2583
+ position: "fixed",
2584
+ left: x,
2585
+ top: y,
2586
+ zIndex: 9999
2587
+ },
2588
+ onKeyDown: handleKeyDown,
2589
+ onClick: (e) => e.stopPropagation(),
2590
+ children: items.map((item) => /* @__PURE__ */ jsx(ContextMenuItem, { item, onClose }, item.id))
2591
+ }
2592
+ )
2593
+ ] });
2594
+ }
2595
+ function useContextMenu() {
2596
+ const [isOpen, setIsOpen] = useState(false);
2597
+ const [x, setX] = useState(0);
2598
+ const [y, setY] = useState(0);
2599
+ const [targetTable, setTargetTable] = useState(null);
2600
+ const open = useCallback(
2601
+ (clientX, clientY, table) => {
2602
+ setX(clientX);
2603
+ setY(clientY);
2604
+ setTargetTable(table);
2605
+ setIsOpen(true);
2606
+ },
2607
+ []
2608
+ );
2609
+ const close = useCallback(() => {
2610
+ setIsOpen(false);
2611
+ setTargetTable(null);
2612
+ }, []);
2613
+ useEffect(() => {
2614
+ if (!isOpen) return;
2615
+ const handleKeyDown = (e) => {
2616
+ if (e.key === "Escape") close();
2617
+ };
2618
+ const handleClick = () => close();
2619
+ document.addEventListener("keydown", handleKeyDown);
2620
+ document.addEventListener("click", handleClick);
2621
+ return () => {
2622
+ document.removeEventListener("keydown", handleKeyDown);
2623
+ document.removeEventListener("click", handleClick);
2624
+ };
2625
+ }, [isOpen, close]);
2626
+ return { isOpen, x, y, targetTable, open, close };
2627
+ }
2628
+ function useKeyboardNavigation(table, options = {}) {
2629
+ const {
2630
+ enabled = true,
2631
+ containerRef
2632
+ } = options;
2633
+ const focusedCell = table.getFocusedCell();
2634
+ const activeCell = table.getState().editing.activeCell;
2635
+ const focusedRowIndex = focusedCell?.rowIndex ?? null;
2636
+ const focusedColumnIndex = focusedCell?.columnIndex ?? null;
2637
+ const focusCellElement = useCallback(
2638
+ (container, cell) => {
2639
+ const element = getCellElement(container, cell);
2640
+ if (!element) return false;
2641
+ element.scrollIntoView({
2642
+ block: "nearest",
2643
+ inline: "nearest"
2644
+ });
2645
+ if (document.activeElement !== element) {
2646
+ element.focus({ preventScroll: true });
2647
+ }
2648
+ return true;
2649
+ },
2650
+ []
2651
+ );
2652
+ const handleKeyDown = useCallback(
2653
+ (event) => {
2654
+ if (!enabled || table.options.enableKeyboardNavigation === false) return;
2655
+ const target = event.target;
2656
+ if (isEditableTarget(target)) return;
2657
+ const ctrlKey = event.ctrlKey || event.metaKey;
2658
+ const currentFocusedCell = table.getFocusedCell() ?? getFirstKeyboardCell(table);
2659
+ switch (event.key) {
2660
+ case "ArrowUp":
2661
+ event.preventDefault();
2662
+ table.moveFocus({ type: "arrow", direction: "up", ctrlKey });
2663
+ return;
2664
+ case "ArrowDown":
2665
+ event.preventDefault();
2666
+ table.moveFocus({ type: "arrow", direction: "down", ctrlKey });
2667
+ return;
2668
+ case "ArrowLeft":
2669
+ event.preventDefault();
2670
+ table.moveFocus({ type: "arrow", direction: "left", ctrlKey });
2671
+ return;
2672
+ case "ArrowRight":
2673
+ event.preventDefault();
2674
+ table.moveFocus({ type: "arrow", direction: "right", ctrlKey });
2675
+ return;
2676
+ case "Tab":
2677
+ event.preventDefault();
2678
+ table.moveFocus({ type: "tab", shiftKey: event.shiftKey });
2679
+ return;
2680
+ case "Home":
2681
+ event.preventDefault();
2682
+ table.moveFocus({ type: "home", ctrlKey });
2683
+ return;
2684
+ case "End":
2685
+ event.preventDefault();
2686
+ table.moveFocus({ type: "end", ctrlKey });
2687
+ return;
2688
+ case "PageUp":
2689
+ event.preventDefault();
2690
+ table.moveFocus({
2691
+ type: "page",
2692
+ direction: "up",
2693
+ pageSize: getVisiblePageSize(containerRef?.current, table)
2694
+ });
2695
+ return;
2696
+ case "PageDown":
2697
+ event.preventDefault();
2698
+ table.moveFocus({
2699
+ type: "page",
2700
+ direction: "down",
2701
+ pageSize: getVisiblePageSize(containerRef?.current, table)
2702
+ });
2703
+ return;
2704
+ case "F2": {
2705
+ const resolved = getResolvedFocusedCell(table, currentFocusedCell);
2706
+ if (!resolved) return;
2707
+ if (!canCellEnterEditMode(table, resolved.row, resolved.column)) return;
2708
+ event.preventDefault();
2709
+ table.startEditing(resolved.row.id, resolved.column.id);
2710
+ return;
2711
+ }
2712
+ case "Enter":
2713
+ if (!activeCell) return;
2714
+ event.preventDefault();
2715
+ table.commitEdit();
2716
+ return;
2717
+ case "Escape":
2718
+ if (!activeCell) return;
2719
+ event.preventDefault();
2720
+ table.cancelEdit();
2721
+ return;
2722
+ }
2723
+ },
2724
+ [activeCell, containerRef, enabled, table]
2725
+ );
2726
+ useEffect(() => {
2727
+ if (!enabled || table.options.enableKeyboardNavigation === false) return;
2728
+ const container = containerRef?.current;
2729
+ if (!container) return;
2730
+ container.addEventListener("keydown", handleKeyDown);
2731
+ return () => {
2732
+ container.removeEventListener("keydown", handleKeyDown);
2733
+ };
2734
+ }, [containerRef, enabled, handleKeyDown, table.options.enableKeyboardNavigation]);
2735
+ useEffect(() => {
2736
+ if (!enabled || table.options.enableKeyboardNavigation === false) return;
2737
+ if (focusedRowIndex === null || focusedColumnIndex === null) return;
2738
+ if (activeCell) return;
2739
+ const container = containerRef?.current;
2740
+ if (!container) return;
2741
+ const nextFocusedCell = {
2742
+ rowIndex: focusedRowIndex,
2743
+ columnIndex: focusedColumnIndex
2744
+ };
2745
+ if (focusCellElement(container, nextFocusedCell)) {
2746
+ return;
2747
+ }
2748
+ const virtualContainer = getVirtualScrollContainer(container);
2749
+ if (!virtualContainer) return;
2750
+ scrollVirtualRowIntoView(virtualContainer, table, nextFocusedCell.rowIndex);
2751
+ const rafId = requestAnimationFrame(() => {
2752
+ focusCellElement(container, nextFocusedCell);
2753
+ });
2754
+ return () => cancelAnimationFrame(rafId);
2755
+ }, [
2756
+ activeCell,
2757
+ containerRef,
2758
+ enabled,
2759
+ focusCellElement,
2760
+ focusedColumnIndex,
2761
+ focusedRowIndex,
2762
+ table
2763
+ ]);
2764
+ }
2765
+ function getCellElement(container, cell) {
2766
+ return container.querySelector(
2767
+ `[data-row-index="${cell.rowIndex}"][data-column-index="${cell.columnIndex}"]`
2768
+ );
2769
+ }
2770
+ function getVirtualScrollContainer(container) {
2771
+ return container.querySelector(".yable-virtual-scroll-container");
2772
+ }
2773
+ function getEstimatedRowHeight(table, rowIndex) {
2774
+ const rowHeight = table.options.rowHeight;
2775
+ if (typeof rowHeight === "function") {
2776
+ return rowHeight(rowIndex);
2777
+ }
2778
+ if (typeof rowHeight === "number") {
2779
+ return rowHeight;
2780
+ }
2781
+ return table.options.estimateRowHeight ?? 40;
2782
+ }
2783
+ function getEstimatedRowOffset(table, rowIndex) {
2784
+ const rowHeight = table.options.rowHeight;
2785
+ if (typeof rowHeight === "function") {
2786
+ let offset = 0;
2787
+ for (let index = 0; index < rowIndex; index++) {
2788
+ offset += rowHeight(index);
2789
+ }
2790
+ return offset;
2791
+ }
2792
+ const estimatedRowHeight = getEstimatedRowHeight(table, rowIndex);
2793
+ return rowIndex * estimatedRowHeight;
2794
+ }
2795
+ function scrollVirtualRowIntoView(container, table, rowIndex) {
2796
+ const rowTop = getEstimatedRowOffset(table, rowIndex);
2797
+ const rowHeight = getEstimatedRowHeight(table, rowIndex);
2798
+ const rowBottom = rowTop + rowHeight;
2799
+ if (rowTop < container.scrollTop) {
2800
+ container.scrollTop = rowTop;
2801
+ return;
2802
+ }
2803
+ const viewportBottom = container.scrollTop + container.clientHeight;
2804
+ if (rowBottom > viewportBottom) {
2805
+ container.scrollTop = rowBottom - container.clientHeight;
2806
+ }
2807
+ }
2808
+ function getVisiblePageSize(container, table) {
2809
+ if (!container) return 10;
2810
+ const scrollContainer = getVirtualScrollContainer(container) ?? container;
2811
+ const estimatedRowHeight = getEstimatedRowHeight(
2812
+ table,
2813
+ table.getFocusedCell()?.rowIndex ?? 0
2814
+ );
2815
+ if (estimatedRowHeight <= 0) return 10;
2816
+ return Math.max(1, Math.floor(scrollContainer.clientHeight / estimatedRowHeight));
2817
+ }
2818
+ function isEditableTarget(element) {
2819
+ if (!element) return false;
2820
+ const tagName = element.tagName.toLowerCase();
2821
+ if (tagName === "input" || tagName === "textarea" || tagName === "select") {
2822
+ return true;
2823
+ }
2824
+ return element.isContentEditable;
2825
+ }
2826
+ function Table({
2827
+ table,
2828
+ stickyHeader,
2829
+ striped,
2830
+ bordered,
2831
+ compact,
2832
+ theme,
2833
+ clickableRows,
2834
+ footer,
2835
+ loading,
2836
+ loadingComponent,
2837
+ loadingText,
2838
+ emptyMessage = "No data",
2839
+ emptyComponent,
2840
+ emptyIcon,
2841
+ emptyDetail,
2842
+ renderEmpty,
2843
+ renderLoading,
2844
+ children,
2845
+ className,
2846
+ direction,
2847
+ statusBar,
2848
+ statusBarPanels,
2849
+ sidebar,
2850
+ sidebarPanels = ["columns", "filters"],
2851
+ defaultSidebarPanel,
2852
+ ...rest
2853
+ }) {
2854
+ const [sidebarOpen, setSidebarOpen] = useState(false);
2855
+ const [sidebarPanel, setSidebarPanel] = useState(
2856
+ defaultSidebarPanel ?? "columns"
2857
+ );
2858
+ const containerRef = useRef(null);
2859
+ const isRtl = direction === "rtl";
2860
+ const classNames = [
2861
+ "yable",
2862
+ theme && `yable-theme-${theme}`,
2863
+ stickyHeader && "yable--sticky-header",
2864
+ striped && "yable--striped",
2865
+ bordered && "yable--bordered",
2866
+ compact && "yable--compact",
2867
+ loading && "yable-loading",
2868
+ isRtl && "yable--rtl",
2869
+ sidebarOpen && "yable--sidebar-open",
2870
+ className
2871
+ ].filter(Boolean).join(" ");
2872
+ const rows = table.getRowModel().rows;
2873
+ const hasGlobalFilter = Boolean(table.getState().globalFilter);
2874
+ const hasColumnFilters = table.getState().columnFilters.length > 0;
2875
+ const isFiltered = hasGlobalFilter || hasColumnFilters;
2876
+ const contextMenu = useContextMenu();
2877
+ useKeyboardNavigation(table, { containerRef });
2878
+ const handleContextMenu = useCallback(
2879
+ (e) => {
2880
+ e.preventDefault();
2881
+ contextMenu.open(e.clientX, e.clientY, table);
2882
+ },
2883
+ [contextMenu, table]
2884
+ );
2885
+ return /* @__PURE__ */ jsx(TableProvider, { value: table, children: /* @__PURE__ */ jsxs(
2886
+ "div",
2887
+ {
2888
+ ref: containerRef,
2889
+ className: classNames,
2890
+ "data-theme": theme,
2891
+ dir: direction,
2892
+ role: "grid",
2893
+ "aria-rowcount": table.getRowModel().rows.length,
2894
+ "aria-colcount": table.getVisibleLeafColumns().length,
2895
+ onContextMenu: handleContextMenu,
2896
+ ...rest,
2897
+ children: [
2898
+ /* @__PURE__ */ jsxs("div", { className: "yable-main", children: [
2899
+ /* @__PURE__ */ jsxs("table", { className: "yable-table", children: [
2900
+ /* @__PURE__ */ jsx(TableHeader, { table }),
2901
+ /* @__PURE__ */ jsx(
2902
+ TableBody,
2903
+ {
2904
+ table,
2905
+ clickableRows
2906
+ }
2907
+ ),
2908
+ footer && /* @__PURE__ */ jsx(TableFooter, { table })
2909
+ ] }),
2910
+ /* @__PURE__ */ jsx(
2911
+ LoadingOverlay,
2912
+ {
2913
+ loading,
2914
+ loadingComponent: renderLoading ? renderLoading() : loadingComponent,
2915
+ loadingText
2916
+ }
2917
+ ),
2918
+ !loading && rows.length === 0 && (renderEmpty ? renderEmpty() : /* @__PURE__ */ jsx(
2919
+ NoRowsOverlay,
2920
+ {
2921
+ emptyComponent,
2922
+ emptyIcon,
2923
+ emptyMessage,
2924
+ emptyDetail,
2925
+ isFiltered
2926
+ }
2927
+ ))
2928
+ ] }),
2929
+ sidebar && /* @__PURE__ */ jsx(
2930
+ Sidebar,
2931
+ {
2932
+ table,
2933
+ open: sidebarOpen,
2934
+ onClose: () => setSidebarOpen(false),
2935
+ panels: sidebarPanels,
2936
+ activePanel: sidebarPanel,
2937
+ onPanelChange: setSidebarPanel
2938
+ }
2939
+ ),
2940
+ statusBar && /* @__PURE__ */ jsx(StatusBar, { table, panels: statusBarPanels }),
2941
+ children,
2942
+ contextMenu.isOpen && /* @__PURE__ */ jsx(
2943
+ ContextMenu,
2944
+ {
2945
+ x: contextMenu.x,
2946
+ y: contextMenu.y,
2947
+ onClose: contextMenu.close,
2948
+ table
2949
+ }
2950
+ )
2951
+ ]
2952
+ }
2953
+ ) });
2954
+ }
2955
+ function ChevronLeftIcon() {
2956
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M8.5 3L4.5 7L8.5 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
2957
+ }
2958
+ function ChevronRightIcon() {
2959
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M5.5 3L9.5 7L5.5 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) });
2960
+ }
2961
+ function ChevronFirstIcon() {
2962
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: [
2963
+ /* @__PURE__ */ jsx("path", { d: "M9.5 3L5.5 7L9.5 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
2964
+ /* @__PURE__ */ jsx("path", { d: "M4.5 3V11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
2965
+ ] });
2966
+ }
2967
+ function ChevronLastIcon() {
2968
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": "true", children: [
2969
+ /* @__PURE__ */ jsx("path", { d: "M4.5 3L8.5 7L4.5 11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
2970
+ /* @__PURE__ */ jsx("path", { d: "M9.5 3V11", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
2971
+ ] });
2972
+ }
2973
+ function SelectChevronIcon() {
2974
+ return /* @__PURE__ */ jsx("svg", { className: "yable-pagination-select-icon", width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2.5 4L5 6.5L7.5 4", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) });
2975
+ }
2976
+ function Pagination({
2977
+ table,
2978
+ showPageSize = true,
2979
+ pageSizes = [10, 20, 50, 100],
2980
+ showInfo = true,
2981
+ showFirstLast = true
2982
+ }) {
2983
+ const { pageIndex, pageSize } = table.getState().pagination;
2984
+ const pageCount = table.getPageCount();
2985
+ const totalRows = table.getPrePaginationRowModel().rows.length;
2986
+ const from = pageIndex * pageSize + 1;
2987
+ const to = Math.min((pageIndex + 1) * pageSize, totalRows);
2988
+ const canPrev = table.getCanPreviousPage();
2989
+ const canNext = table.getCanNextPage();
2990
+ return /* @__PURE__ */ jsxs("nav", { className: "yable-pagination", role: "navigation", "aria-label": "Table pagination", children: [
2991
+ showInfo && /* @__PURE__ */ jsxs("div", { className: "yable-pagination-info", children: [
2992
+ /* @__PURE__ */ jsx("span", { className: "yable-pagination-info-text", children: totalRows > 0 ? `${from}\u2013${to} of ${totalRows}` : "No results" }),
2993
+ showPageSize && /* @__PURE__ */ jsxs("div", { className: "yable-pagination-select-wrapper", children: [
2994
+ /* @__PURE__ */ jsx(
2995
+ "select",
2996
+ {
2997
+ className: "yable-pagination-select",
2998
+ value: pageSize,
2999
+ onChange: (e) => {
3000
+ table.setPageSize(Number(e.target.value));
3001
+ },
3002
+ "aria-label": "Rows per page",
3003
+ children: pageSizes.map((size) => /* @__PURE__ */ jsxs("option", { value: size, children: [
3004
+ size,
3005
+ " rows"
3006
+ ] }, size))
3007
+ }
3008
+ ),
3009
+ /* @__PURE__ */ jsx(SelectChevronIcon, {})
3010
+ ] })
3011
+ ] }),
3012
+ /* @__PURE__ */ jsxs("div", { className: "yable-pagination-pages", children: [
3013
+ showFirstLast && /* @__PURE__ */ jsx(
3014
+ "button",
3015
+ {
3016
+ type: "button",
3017
+ className: "yable-pagination-btn yable-pagination-btn--nav",
3018
+ onClick: () => table.setPageIndex(0),
3019
+ disabled: !canPrev,
3020
+ "aria-label": "First page",
3021
+ title: "First page",
3022
+ children: /* @__PURE__ */ jsx(ChevronFirstIcon, {})
3023
+ }
3024
+ ),
3025
+ /* @__PURE__ */ jsx(
3026
+ "button",
3027
+ {
3028
+ type: "button",
3029
+ className: "yable-pagination-btn yable-pagination-btn--nav",
3030
+ onClick: () => table.previousPage(),
3031
+ disabled: !canPrev,
3032
+ "aria-label": "Previous page",
3033
+ title: "Previous page",
3034
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
3035
+ }
3036
+ ),
3037
+ getPageNumbers(pageIndex, pageCount).map(
3038
+ (page, i) => page === -1 ? /* @__PURE__ */ jsx(
3039
+ "span",
3040
+ {
3041
+ className: "yable-pagination-ellipsis",
3042
+ "aria-hidden": "true",
3043
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: [
3044
+ /* @__PURE__ */ jsx("circle", { cx: "3", cy: "7", r: "1", fill: "currentColor" }),
3045
+ /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "1", fill: "currentColor" }),
3046
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "7", r: "1", fill: "currentColor" })
3047
+ ] })
3048
+ },
3049
+ `ellipsis-${i}`
3050
+ ) : /* @__PURE__ */ jsx(
3051
+ "button",
3052
+ {
3053
+ type: "button",
3054
+ className: `yable-pagination-btn yable-pagination-btn--page${page === pageIndex ? " yable-pagination-btn--active" : ""}`,
3055
+ "data-active": page === pageIndex ? "true" : void 0,
3056
+ onClick: () => table.setPageIndex(page),
3057
+ "aria-label": `Page ${page + 1}`,
3058
+ "aria-current": page === pageIndex ? "page" : void 0,
3059
+ children: page + 1
3060
+ },
3061
+ page
3062
+ )
3063
+ ),
3064
+ /* @__PURE__ */ jsx(
3065
+ "button",
3066
+ {
3067
+ type: "button",
3068
+ className: "yable-pagination-btn yable-pagination-btn--nav",
3069
+ onClick: () => table.nextPage(),
3070
+ disabled: !canNext,
3071
+ "aria-label": "Next page",
3072
+ title: "Next page",
3073
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
3074
+ }
3075
+ ),
3076
+ showFirstLast && /* @__PURE__ */ jsx(
3077
+ "button",
3078
+ {
3079
+ type: "button",
3080
+ className: "yable-pagination-btn yable-pagination-btn--nav",
3081
+ onClick: () => table.setPageIndex(pageCount - 1),
3082
+ disabled: !canNext,
3083
+ "aria-label": "Last page",
3084
+ title: "Last page",
3085
+ children: /* @__PURE__ */ jsx(ChevronLastIcon, {})
3086
+ }
3087
+ )
3088
+ ] })
3089
+ ] });
3090
+ }
3091
+ function getPageNumbers(current, total) {
3092
+ if (total <= 7) {
3093
+ return Array.from({ length: total }, (_, i) => i);
3094
+ }
3095
+ const pages = [];
3096
+ pages.push(0);
3097
+ if (current > 3) {
3098
+ pages.push(-1);
3099
+ }
3100
+ const start = Math.max(1, current - 1);
3101
+ const end = Math.min(total - 2, current + 1);
3102
+ for (let i = start; i <= end; i++) {
3103
+ pages.push(i);
3104
+ }
3105
+ if (current < total - 4) {
3106
+ pages.push(-1);
3107
+ }
3108
+ pages.push(total - 1);
3109
+ return pages;
3110
+ }
3111
+ function SearchIcon2() {
3112
+ return /* @__PURE__ */ jsxs(
3113
+ "svg",
3114
+ {
3115
+ className: "yable-global-filter-icon",
3116
+ width: "14",
3117
+ height: "14",
3118
+ viewBox: "0 0 14 14",
3119
+ fill: "none",
3120
+ "aria-hidden": "true",
3121
+ children: [
3122
+ /* @__PURE__ */ jsx("circle", { cx: "6.25", cy: "6.25", r: "4.25", stroke: "currentColor", strokeWidth: "1.5" }),
3123
+ /* @__PURE__ */ jsx("path", { d: "M9.5 9.5L12.5 12.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
3124
+ ]
3125
+ }
3126
+ );
3127
+ }
3128
+ function ClearIcon() {
3129
+ return /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 3l6 6M9 3L3 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) });
3130
+ }
3131
+ function GlobalFilter({
3132
+ table,
3133
+ placeholder = "Search...",
3134
+ debounce = 300,
3135
+ className
3136
+ }) {
3137
+ const [value, setValue] = useState(
3138
+ table.getState().globalFilter ?? ""
3139
+ );
3140
+ const timerRef = useRef(void 0);
3141
+ const inputRef = useRef(null);
3142
+ const handleChange = useCallback(
3143
+ (e) => {
3144
+ const newValue = e.target.value;
3145
+ setValue(newValue);
3146
+ if (debounce > 0) {
3147
+ clearTimeout(timerRef.current);
3148
+ timerRef.current = setTimeout(() => {
3149
+ table.setGlobalFilter(newValue);
3150
+ }, debounce);
3151
+ } else {
3152
+ table.setGlobalFilter(newValue);
3153
+ }
3154
+ },
3155
+ [table, debounce]
3156
+ );
3157
+ const handleClear = useCallback(() => {
3158
+ setValue("");
3159
+ table.setGlobalFilter("");
3160
+ clearTimeout(timerRef.current);
3161
+ inputRef.current?.focus();
3162
+ }, [table]);
3163
+ const handleKeyDown = useCallback(
3164
+ (e) => {
3165
+ if (e.key === "Escape" && value) {
3166
+ e.preventDefault();
3167
+ handleClear();
3168
+ }
3169
+ },
3170
+ [value, handleClear]
3171
+ );
3172
+ useEffect(() => {
3173
+ return () => clearTimeout(timerRef.current);
3174
+ }, []);
3175
+ useEffect(() => {
3176
+ const externalValue = table.getState().globalFilter ?? "";
3177
+ if (externalValue !== value) {
3178
+ setValue(externalValue);
3179
+ }
3180
+ }, [table.getState().globalFilter]);
3181
+ const hasValue = value.length > 0;
3182
+ return /* @__PURE__ */ jsxs("div", { className: `yable-global-filter${hasValue ? " yable-global-filter--has-value" : ""}${className ? ` ${className}` : ""}`, children: [
3183
+ /* @__PURE__ */ jsx(SearchIcon2, {}),
3184
+ /* @__PURE__ */ jsx(
3185
+ "input",
3186
+ {
3187
+ ref: inputRef,
3188
+ type: "text",
3189
+ className: "yable-global-filter-input",
3190
+ value,
3191
+ onChange: handleChange,
3192
+ onKeyDown: handleKeyDown,
3193
+ placeholder,
3194
+ "aria-label": "Search table",
3195
+ role: "searchbox"
3196
+ }
3197
+ ),
3198
+ hasValue && /* @__PURE__ */ jsx(
3199
+ "button",
3200
+ {
3201
+ type: "button",
3202
+ className: "yable-global-filter-clear",
3203
+ onClick: handleClear,
3204
+ "aria-label": "Clear search",
3205
+ tabIndex: -1,
3206
+ children: /* @__PURE__ */ jsx(ClearIcon, {})
3207
+ }
3208
+ )
3209
+ ] });
3210
+ }
3211
+ function CellInput({
3212
+ context,
3213
+ type = "text",
3214
+ placeholder,
3215
+ inline = false,
3216
+ autoFocus,
3217
+ className
3218
+ }) {
3219
+ const { table, row, column, cell } = context;
3220
+ const inputRef = useRef(null);
3221
+ const isEditing = cell.getIsEditing();
3222
+ const isAlwaysEditable = cell.getIsAlwaysEditable();
3223
+ const pending = table.getPendingValue(row.id, column.id);
3224
+ const currentValue = pending !== void 0 ? pending : cell.getValue();
3225
+ const handleChange = (e) => {
3226
+ const val = type === "number" ? Number(e.target.value) : e.target.value;
3227
+ table.setPendingValue(row.id, column.id, val);
3228
+ };
3229
+ const handleBlur = () => {
3230
+ if (isEditing && !isAlwaysEditable) {
3231
+ table.commitEdit();
3232
+ }
3233
+ };
3234
+ const handleKeyDown = (e) => {
3235
+ if (e.key === "Enter") {
3236
+ if (isEditing && !isAlwaysEditable) {
3237
+ table.commitEdit();
3238
+ }
3239
+ } else if (e.key === "Escape") {
3240
+ if (isEditing && !isAlwaysEditable) {
3241
+ table.cancelEdit();
3242
+ }
3243
+ }
3244
+ };
3245
+ useEffect(() => {
3246
+ if ((isEditing || autoFocus) && inputRef.current) {
3247
+ inputRef.current.focus();
3248
+ inputRef.current.select();
3249
+ }
3250
+ }, [isEditing, autoFocus]);
3251
+ const classNames = [
3252
+ "yable-input",
3253
+ inline && "yable-input--inline",
3254
+ className
3255
+ ].filter(Boolean).join(" ");
3256
+ return /* @__PURE__ */ jsx(
3257
+ "input",
3258
+ {
3259
+ ref: inputRef,
3260
+ type,
3261
+ className: classNames,
3262
+ value: String(currentValue ?? ""),
3263
+ onChange: handleChange,
3264
+ onBlur: handleBlur,
3265
+ onKeyDown: handleKeyDown,
3266
+ placeholder
3267
+ }
3268
+ );
3269
+ }
3270
+ function CellSelect({
3271
+ context,
3272
+ options,
3273
+ placeholder,
3274
+ className
3275
+ }) {
3276
+ const { table, row, column, cell } = context;
3277
+ const isEditing = cell.getIsEditing();
3278
+ const isAlwaysEditable = cell.getIsAlwaysEditable();
3279
+ const pending = table.getPendingValue(row.id, column.id);
3280
+ const currentValue = pending !== void 0 ? pending : cell.getValue();
3281
+ const handleChange = (e) => {
3282
+ table.setPendingValue(row.id, column.id, e.target.value);
3283
+ if (isEditing && !isAlwaysEditable) {
3284
+ setTimeout(() => table.commitEdit(), 0);
3285
+ }
3286
+ };
3287
+ return /* @__PURE__ */ jsxs(
3288
+ "select",
3289
+ {
3290
+ className: `yable-select ${className ?? ""}`,
3291
+ value: String(currentValue ?? ""),
3292
+ onChange: handleChange,
3293
+ children: [
3294
+ placeholder && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }),
3295
+ options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
3296
+ ]
3297
+ }
3298
+ );
3299
+ }
3300
+ function CellCheckbox({
3301
+ context,
3302
+ className
3303
+ }) {
3304
+ const { table, row, column, cell } = context;
3305
+ const pending = table.getPendingValue(row.id, column.id);
3306
+ const currentValue = pending !== void 0 ? pending : cell.getValue();
3307
+ const handleChange = (e) => {
3308
+ table.setPendingValue(row.id, column.id, e.target.checked);
3309
+ };
3310
+ return /* @__PURE__ */ jsx(
3311
+ "input",
3312
+ {
3313
+ type: "checkbox",
3314
+ className: `yable-checkbox ${className ?? ""}`,
3315
+ checked: Boolean(currentValue),
3316
+ onChange: handleChange
3317
+ }
3318
+ );
3319
+ }
3320
+ function CellToggle({
3321
+ context,
3322
+ className
3323
+ }) {
3324
+ const { table, row, column, cell } = context;
3325
+ const pending = table.getPendingValue(row.id, column.id);
3326
+ const currentValue = pending !== void 0 ? pending : cell.getValue();
3327
+ const handleChange = (e) => {
3328
+ table.setPendingValue(row.id, column.id, e.target.checked);
3329
+ };
3330
+ return /* @__PURE__ */ jsx(
3331
+ "input",
3332
+ {
3333
+ type: "checkbox",
3334
+ role: "switch",
3335
+ className: `yable-toggle ${className ?? ""}`,
3336
+ checked: Boolean(currentValue),
3337
+ onChange: handleChange,
3338
+ "aria-checked": Boolean(currentValue)
3339
+ }
3340
+ );
3341
+ }
3342
+ function CellDatePicker({
3343
+ context,
3344
+ type = "date",
3345
+ className
3346
+ }) {
3347
+ const { table, row, column, cell } = context;
3348
+ const inputRef = useRef(null);
3349
+ const isEditing = cell.getIsEditing();
3350
+ const isAlwaysEditable = cell.getIsAlwaysEditable();
3351
+ const pending = table.getPendingValue(row.id, column.id);
3352
+ const currentValue = pending !== void 0 ? pending : cell.getValue();
3353
+ const formattedValue = formatDateValue(currentValue, type);
3354
+ const handleChange = (e) => {
3355
+ table.setPendingValue(row.id, column.id, e.target.value);
3356
+ };
3357
+ const handleBlur = () => {
3358
+ if (isEditing && !isAlwaysEditable) {
3359
+ table.commitEdit();
3360
+ }
3361
+ };
3362
+ useEffect(() => {
3363
+ if (isEditing && inputRef.current) {
3364
+ inputRef.current.focus();
3365
+ }
3366
+ }, [isEditing]);
3367
+ return /* @__PURE__ */ jsx(
3368
+ "input",
3369
+ {
3370
+ ref: inputRef,
3371
+ type,
3372
+ className: `yable-input ${className ?? ""}`,
3373
+ value: formattedValue,
3374
+ onChange: handleChange,
3375
+ onBlur: handleBlur
3376
+ }
3377
+ );
3378
+ }
3379
+ function formatDateValue(value, type) {
3380
+ if (!value) return "";
3381
+ if (typeof value === "string") return value;
3382
+ if (value instanceof Date) {
3383
+ if (type === "date") {
3384
+ return value.toISOString().split("T")[0];
3385
+ }
3386
+ if (type === "datetime-local") {
3387
+ return value.toISOString().slice(0, 16);
3388
+ }
3389
+ if (type === "time") {
3390
+ return value.toISOString().slice(11, 16);
3391
+ }
3392
+ }
3393
+ return String(value);
3394
+ }
3395
+ function useClipboard(table, options = {}) {
3396
+ const {
3397
+ enabled = true,
3398
+ containerRef,
3399
+ onCopy,
3400
+ onCut,
3401
+ onPaste,
3402
+ ...clipboardOptions
3403
+ } = options;
3404
+ const handleCopy = useCallback(
3405
+ (e) => {
3406
+ if (isEditableTarget2(e.target)) return;
3407
+ e.preventDefault();
3408
+ const text = table.copyToClipboard(clipboardOptions);
3409
+ if (e.clipboardData) {
3410
+ e.clipboardData.setData("text/plain", text);
3411
+ }
3412
+ onCopy?.(text);
3413
+ },
3414
+ [table, clipboardOptions, onCopy]
3415
+ );
3416
+ const handleCut = useCallback(
3417
+ (e) => {
3418
+ if (isEditableTarget2(e.target)) return;
3419
+ e.preventDefault();
3420
+ const text = table.cutCells(clipboardOptions);
3421
+ if (e.clipboardData) {
3422
+ e.clipboardData.setData("text/plain", text);
3423
+ }
3424
+ onCut?.(text);
3425
+ },
3426
+ [table, clipboardOptions, onCut]
3427
+ );
3428
+ const handlePaste = useCallback(
3429
+ (e) => {
3430
+ if (isEditableTarget2(e.target)) return;
3431
+ e.preventDefault();
3432
+ const text = e.clipboardData?.getData("text/plain") ?? "";
3433
+ if (!text) return;
3434
+ const state = table.getState();
3435
+ let targetRowId;
3436
+ let targetColumnId;
3437
+ if (state.editing?.activeCell) {
3438
+ targetRowId = state.editing.activeCell.rowId;
3439
+ targetColumnId = state.editing.activeCell.columnId;
3440
+ } else {
3441
+ const selectedRowIds = Object.keys(state.rowSelection || {}).filter(
3442
+ (id) => state.rowSelection[id]
3443
+ );
3444
+ if (selectedRowIds.length > 0) {
3445
+ targetRowId = selectedRowIds[0];
3446
+ } else {
3447
+ const rows = table.getRowModel().rows;
3448
+ if (rows.length > 0) {
3449
+ targetRowId = rows[0].id;
3450
+ }
3451
+ }
3452
+ const columns = table.getVisibleLeafColumns();
3453
+ if (columns.length > 0) {
3454
+ targetColumnId = columns[0].id;
3455
+ }
3456
+ }
3457
+ if (targetRowId && targetColumnId) {
3458
+ table.pasteFromClipboard(text, targetRowId, targetColumnId, clipboardOptions);
3459
+ onPaste?.(text);
3460
+ }
3461
+ },
3462
+ [table, clipboardOptions, onPaste]
3463
+ );
3464
+ useEffect(() => {
3465
+ if (!enabled) return;
3466
+ const container = containerRef?.current ?? document;
3467
+ container.addEventListener("copy", handleCopy);
3468
+ container.addEventListener("cut", handleCut);
3469
+ container.addEventListener("paste", handlePaste);
3470
+ return () => {
3471
+ container.removeEventListener("copy", handleCopy);
3472
+ container.removeEventListener("cut", handleCut);
3473
+ container.removeEventListener("paste", handlePaste);
3474
+ };
3475
+ }, [enabled, containerRef, handleCopy, handleCut, handlePaste]);
3476
+ }
3477
+ function isEditableTarget2(el) {
3478
+ if (!el) return false;
3479
+ const tagName = el.tagName?.toLowerCase();
3480
+ if (tagName === "input" || tagName === "textarea" || tagName === "select") {
3481
+ return true;
3482
+ }
3483
+ if (el.isContentEditable) return true;
3484
+ return false;
3485
+ }
3486
+ function useFillHandle(table, options = {}) {
3487
+ const { enabled = true } = options;
3488
+ const [dragState, setDragState] = useState({
3489
+ isDragging: false,
3490
+ sourceCell: null,
3491
+ currentCell: null
3492
+ });
3493
+ const dragRef = useRef(dragState);
3494
+ dragRef.current = dragState;
3495
+ const onFillHandleMouseDown = useCallback(
3496
+ (rowIndex, columnIndex, e) => {
3497
+ if (!enabled) return;
3498
+ e.preventDefault();
3499
+ e.stopPropagation();
3500
+ setDragState({
3501
+ isDragging: true,
3502
+ sourceCell: { rowIndex, columnIndex },
3503
+ currentCell: { rowIndex, columnIndex }
3504
+ });
3505
+ },
3506
+ [enabled]
3507
+ );
3508
+ useEffect(() => {
3509
+ if (!dragState.isDragging) return;
3510
+ const handleMouseMove = (e) => {
3511
+ const target = document.elementFromPoint(e.clientX, e.clientY);
3512
+ if (!target) return;
3513
+ const td = target.closest("td[data-column-id]");
3514
+ const tr = target.closest("tr[data-row-id]");
3515
+ if (!td || !tr) return;
3516
+ const columnId = td.getAttribute("data-column-id");
3517
+ const rowId = tr.getAttribute("data-row-id");
3518
+ if (!columnId || !rowId) return;
3519
+ const rows = table.getRowModel().rows;
3520
+ const columns = table.getVisibleLeafColumns();
3521
+ const rowIndex = rows.findIndex((r) => r.id === rowId);
3522
+ const columnIndex = columns.findIndex((c) => c.id === columnId);
3523
+ if (rowIndex === -1 || columnIndex === -1) return;
3524
+ setDragState((prev) => ({
3525
+ ...prev,
3526
+ currentCell: { rowIndex, columnIndex }
3527
+ }));
3528
+ };
3529
+ const handleMouseUp = () => {
3530
+ const current = dragRef.current;
3531
+ if (current.sourceCell && current.currentCell) {
3532
+ const source = current.sourceCell;
3533
+ const target = current.currentCell;
3534
+ if (source.rowIndex !== target.rowIndex || source.columnIndex !== target.columnIndex) {
3535
+ const sourceRange = {
3536
+ startRow: source.rowIndex,
3537
+ startCol: source.columnIndex,
3538
+ endRow: source.rowIndex,
3539
+ endCol: source.columnIndex
3540
+ };
3541
+ const targetRange = {
3542
+ startRow: Math.min(source.rowIndex, target.rowIndex),
3543
+ startCol: Math.min(source.columnIndex, target.columnIndex),
3544
+ endRow: Math.max(source.rowIndex, target.rowIndex),
3545
+ endCol: Math.max(source.columnIndex, target.columnIndex)
3546
+ };
3547
+ table.fillRange(sourceRange, targetRange);
3548
+ }
3549
+ }
3550
+ setDragState({
3551
+ isDragging: false,
3552
+ sourceCell: null,
3553
+ currentCell: null
3554
+ });
3555
+ };
3556
+ document.addEventListener("mousemove", handleMouseMove);
3557
+ document.addEventListener("mouseup", handleMouseUp);
3558
+ return () => {
3559
+ document.removeEventListener("mousemove", handleMouseMove);
3560
+ document.removeEventListener("mouseup", handleMouseUp);
3561
+ };
3562
+ }, [dragState.isDragging, table]);
3563
+ return { dragState, onFillHandleMouseDown };
3564
+ }
3565
+ function FillHandle({
3566
+ rowIndex,
3567
+ columnIndex,
3568
+ onMouseDown
3569
+ }) {
3570
+ const handleMouseDown = useCallback(
3571
+ (e) => {
3572
+ e.preventDefault();
3573
+ e.stopPropagation();
3574
+ onMouseDown(rowIndex, columnIndex, e);
3575
+ },
3576
+ [rowIndex, columnIndex, onMouseDown]
3577
+ );
3578
+ return /* @__PURE__ */ jsx(
3579
+ "div",
3580
+ {
3581
+ className: "yable-fill-handle",
3582
+ onMouseDown: handleMouseDown,
3583
+ role: "presentation",
3584
+ "aria-hidden": "true",
3585
+ title: "Drag to fill",
3586
+ children: /* @__PURE__ */ jsx("div", { className: "yable-fill-handle-dot" })
3587
+ }
3588
+ );
3589
+ }
3590
+ function GripIcon() {
3591
+ return /* @__PURE__ */ jsxs(
3592
+ "svg",
3593
+ {
3594
+ width: "10",
3595
+ height: "16",
3596
+ viewBox: "0 0 10 16",
3597
+ fill: "none",
3598
+ xmlns: "http://www.w3.org/2000/svg",
3599
+ "aria-hidden": "true",
3600
+ children: [
3601
+ /* @__PURE__ */ jsx("circle", { cx: "2.5", cy: "3", r: "1.3", fill: "currentColor" }),
3602
+ /* @__PURE__ */ jsx("circle", { cx: "2.5", cy: "8", r: "1.3", fill: "currentColor" }),
3603
+ /* @__PURE__ */ jsx("circle", { cx: "2.5", cy: "13", r: "1.3", fill: "currentColor" }),
3604
+ /* @__PURE__ */ jsx("circle", { cx: "7.5", cy: "3", r: "1.3", fill: "currentColor" }),
3605
+ /* @__PURE__ */ jsx("circle", { cx: "7.5", cy: "8", r: "1.3", fill: "currentColor" }),
3606
+ /* @__PURE__ */ jsx("circle", { cx: "7.5", cy: "13", r: "1.3", fill: "currentColor" })
3607
+ ]
3608
+ }
3609
+ );
3610
+ }
3611
+ function DragHandle({
3612
+ onDragStart,
3613
+ isDragging,
3614
+ ariaLabel = "Drag to reorder row",
3615
+ className
3616
+ }) {
3617
+ const classes = [
3618
+ "yable-row-drag-handle",
3619
+ isDragging && "yable-row-drag-handle--dragging",
3620
+ className
3621
+ ].filter(Boolean).join(" ");
3622
+ const handleDragStart = useCallback(
3623
+ (e) => {
3624
+ if (e.dataTransfer && e.currentTarget instanceof HTMLElement) {
3625
+ e.dataTransfer.effectAllowed = "move";
3626
+ }
3627
+ onDragStart(e);
3628
+ },
3629
+ [onDragStart]
3630
+ );
3631
+ return /* @__PURE__ */ jsx(
3632
+ "button",
3633
+ {
3634
+ type: "button",
3635
+ className: classes,
3636
+ draggable: true,
3637
+ onDragStart: handleDragStart,
3638
+ "aria-label": ariaLabel,
3639
+ "aria-roledescription": "Drag handle",
3640
+ tabIndex: -1,
3641
+ children: /* @__PURE__ */ jsx(GripIcon, {})
3642
+ }
3643
+ );
3644
+ }
3645
+ function useRowDrag({
3646
+ table,
3647
+ data,
3648
+ onDataChange,
3649
+ onReorder
3650
+ }) {
3651
+ const [dragState, setDragState] = useState(getInitialRowDragState);
3652
+ const dragRowIdRef = useRef(null);
3653
+ const dragRowIndexRef = useRef(-1);
3654
+ const getRowDragProps = useCallback(
3655
+ (rowId, rowIndex) => {
3656
+ return {
3657
+ draggable: true,
3658
+ onDragStart: (e) => {
3659
+ dragRowIdRef.current = rowId;
3660
+ dragRowIndexRef.current = rowIndex;
3661
+ e.dataTransfer.effectAllowed = "move";
3662
+ e.dataTransfer.setData("text/plain", rowId);
3663
+ requestAnimationFrame(() => {
3664
+ setDragState({
3665
+ draggingRowId: rowId,
3666
+ overRowId: null,
3667
+ dropPosition: null
3668
+ });
3669
+ });
3670
+ table.events.emit("row:drag:start", {
3671
+ rowId,
3672
+ rowIndex,
3673
+ row: table.getRow(rowId, true)
3674
+ });
3675
+ },
3676
+ onDragOver: (e) => {
3677
+ e.preventDefault();
3678
+ e.dataTransfer.dropEffect = "move";
3679
+ if (!dragRowIdRef.current || dragRowIdRef.current === rowId) return;
3680
+ const rect = e.currentTarget.getBoundingClientRect();
3681
+ const midY = rect.top + rect.height / 2;
3682
+ const position = e.clientY < midY ? "before" : "after";
3683
+ setDragState((prev) => ({
3684
+ ...prev,
3685
+ overRowId: rowId,
3686
+ dropPosition: position
3687
+ }));
3688
+ },
3689
+ onDragLeave: (_e) => {
3690
+ setDragState((prev) => {
3691
+ if (prev.overRowId === rowId) {
3692
+ return { ...prev, overRowId: null, dropPosition: null };
3693
+ }
3694
+ return prev;
3695
+ });
3696
+ },
3697
+ onDrop: (e) => {
3698
+ e.preventDefault();
3699
+ const fromId = dragRowIdRef.current;
3700
+ if (!fromId || fromId === rowId) {
3701
+ setDragState(getInitialRowDragState());
3702
+ return;
3703
+ }
3704
+ const fromIndex = dragRowIndexRef.current;
3705
+ const rect = e.currentTarget.getBoundingClientRect();
3706
+ const midY = rect.top + rect.height / 2;
3707
+ const dropAfter = e.clientY >= midY;
3708
+ let toIndex = rowIndex;
3709
+ if (dropAfter && fromIndex > rowIndex) {
3710
+ toIndex = rowIndex + 1;
3711
+ } else if (!dropAfter && fromIndex < rowIndex) {
3712
+ toIndex = rowIndex - 1;
3713
+ }
3714
+ toIndex = Math.max(0, Math.min(toIndex, data.length - 1));
3715
+ const newData = moveRow(data, fromIndex, toIndex);
3716
+ onDataChange(newData);
3717
+ onReorder?.({ fromIndex, toIndex, rowId: fromId });
3718
+ table.events.emit("row:reorder", {
3719
+ fromIndex,
3720
+ toIndex,
3721
+ rowId: fromId
3722
+ });
3723
+ table.events.emit("row:drag:end", {
3724
+ rowId: fromId,
3725
+ row: table.getRow(fromId, true),
3726
+ cancelled: false
3727
+ });
3728
+ setDragState(getInitialRowDragState());
3729
+ dragRowIdRef.current = null;
3730
+ dragRowIndexRef.current = -1;
3731
+ },
3732
+ onDragEnd: (_e) => {
3733
+ if (dragRowIdRef.current) {
3734
+ try {
3735
+ table.events.emit("row:drag:end", {
3736
+ rowId: dragRowIdRef.current,
3737
+ row: table.getRow(dragRowIdRef.current, true),
3738
+ cancelled: true
3739
+ });
3740
+ } catch {
3741
+ }
3742
+ }
3743
+ setDragState(getInitialRowDragState());
3744
+ dragRowIdRef.current = null;
3745
+ dragRowIndexRef.current = -1;
3746
+ },
3747
+ "data-dragging": dragState.draggingRowId === rowId ? true : void 0,
3748
+ "data-drag-over": dragState.overRowId === rowId ? dragState.dropPosition ?? void 0 : void 0
3749
+ };
3750
+ },
3751
+ [data, dragState, onDataChange, onReorder, table]
3752
+ );
3753
+ const getDragHandleProps = useCallback(
3754
+ (rowId) => ({
3755
+ onDragStart: (e) => {
3756
+ e.dataTransfer.effectAllowed = "move";
3757
+ e.dataTransfer.setData("text/plain", rowId);
3758
+ }
3759
+ }),
3760
+ []
3761
+ );
3762
+ return {
3763
+ dragState,
3764
+ getRowDragProps,
3765
+ getDragHandleProps
3766
+ };
3767
+ }
3768
+ function TreeToggle({
3769
+ row,
3770
+ indentWidth = 20,
3771
+ showToggle,
3772
+ renderIcon
3773
+ }) {
3774
+ const depth = row._treeDepth ?? row.depth;
3775
+ const isLeaf = row._isLeaf ?? row.subRows.length === 0;
3776
+ const hasChildren = row._hasChildren ?? row.subRows.length > 0;
3777
+ const isExpanded = row.getIsExpanded();
3778
+ const canToggle = showToggle !== void 0 ? showToggle : hasChildren;
3779
+ const indent = depth * indentWidth;
3780
+ const handleClick = useCallback(
3781
+ (e) => {
3782
+ e.stopPropagation();
3783
+ if (canToggle) {
3784
+ row.toggleExpanded();
3785
+ }
3786
+ },
3787
+ [canToggle, row]
3788
+ );
3789
+ return /* @__PURE__ */ jsx(
3790
+ "span",
3791
+ {
3792
+ className: "yable-tree-toggle",
3793
+ style: { paddingLeft: `${indent}px` },
3794
+ "data-depth": depth,
3795
+ "data-leaf": isLeaf || void 0,
3796
+ children: canToggle ? /* @__PURE__ */ jsx(
3797
+ "button",
3798
+ {
3799
+ type: "button",
3800
+ className: `yable-tree-toggle-btn${isExpanded ? " yable-tree-toggle-btn--expanded" : ""}`,
3801
+ onClick: handleClick,
3802
+ "data-expanded": isExpanded || void 0,
3803
+ "aria-expanded": isExpanded,
3804
+ "aria-label": isExpanded ? "Collapse row" : "Expand row",
3805
+ tabIndex: -1,
3806
+ children: renderIcon ? renderIcon({ isExpanded, isLeaf }) : /* @__PURE__ */ jsx(
3807
+ "svg",
3808
+ {
3809
+ className: "yable-tree-chevron",
3810
+ width: "14",
3811
+ height: "14",
3812
+ viewBox: "0 0 14 14",
3813
+ fill: "none",
3814
+ xmlns: "http://www.w3.org/2000/svg",
3815
+ "aria-hidden": "true",
3816
+ style: {
3817
+ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
3818
+ transition: "transform var(--yable-transition, 150ms ease)"
3819
+ },
3820
+ children: /* @__PURE__ */ jsx(
3821
+ "path",
3822
+ {
3823
+ d: "M5 3L9 7L5 11",
3824
+ stroke: "currentColor",
3825
+ strokeWidth: "1.5",
3826
+ strokeLinecap: "round",
3827
+ strokeLinejoin: "round"
3828
+ }
3829
+ )
3830
+ }
3831
+ )
3832
+ }
3833
+ ) : /* @__PURE__ */ jsx("span", { className: "yable-tree-toggle-spacer", "aria-hidden": "true" })
3834
+ }
3835
+ );
3836
+ }
3837
+ function MasterDetail({
3838
+ row,
3839
+ table,
3840
+ colSpan,
3841
+ renderDetailPanel,
3842
+ animationClass
3843
+ }) {
3844
+ const renderer = renderDetailPanel ?? table.options.renderDetailPanel;
3845
+ if (!renderer) return null;
3846
+ const content = renderer(row);
3847
+ if (content == null) return null;
3848
+ const classes = [
3849
+ "yable-detail-row",
3850
+ "yable-detail-row--animated",
3851
+ animationClass
3852
+ ].filter(Boolean).join(" ");
3853
+ return /* @__PURE__ */ jsx(
3854
+ "tr",
3855
+ {
3856
+ className: classes,
3857
+ "data-detail-for": row.id,
3858
+ role: "row",
3859
+ "aria-label": `Details for row ${row.id}`,
3860
+ children: /* @__PURE__ */ jsx("td", { className: "yable-detail-cell", colSpan, role: "cell", children: /* @__PURE__ */ jsx("div", { className: "yable-detail-panel", children: /* @__PURE__ */ jsx("div", { className: "yable-detail-panel-inner", children: content }) }) })
3861
+ }
3862
+ );
3863
+ }
3864
+ function ExpandIcon({
3865
+ isExpanded,
3866
+ onClick,
3867
+ ariaLabel,
3868
+ size = 18,
3869
+ className
3870
+ }) {
3871
+ const classes = [
3872
+ "yable-detail-expand-icon",
3873
+ isExpanded && "yable-detail-expand-icon--expanded",
3874
+ className
3875
+ ].filter(Boolean).join(" ");
3876
+ const label = ariaLabel ?? (isExpanded ? "Collapse details" : "Expand details");
3877
+ const handleClick = useCallback(
3878
+ (e) => {
3879
+ e.stopPropagation();
3880
+ onClick(e);
3881
+ },
3882
+ [onClick]
3883
+ );
3884
+ return /* @__PURE__ */ jsx(
3885
+ "button",
3886
+ {
3887
+ type: "button",
3888
+ className: classes,
3889
+ onClick: handleClick,
3890
+ "aria-expanded": isExpanded,
3891
+ "aria-label": label,
3892
+ tabIndex: -1,
3893
+ children: /* @__PURE__ */ jsx(
3894
+ "svg",
3895
+ {
3896
+ className: "yable-detail-expand-chevron",
3897
+ width: size,
3898
+ height: size,
3899
+ viewBox: "0 0 18 18",
3900
+ fill: "none",
3901
+ xmlns: "http://www.w3.org/2000/svg",
3902
+ "aria-hidden": "true",
3903
+ style: {
3904
+ transform: isExpanded ? "rotate(0deg)" : "rotate(-90deg)",
3905
+ transition: "transform var(--yable-transition, 150ms ease)"
3906
+ },
3907
+ children: /* @__PURE__ */ jsx(
3908
+ "path",
3909
+ {
3910
+ d: "M6 7L9 10L12 7",
3911
+ stroke: "currentColor",
3912
+ strokeWidth: "1.5",
3913
+ strokeLinecap: "round",
3914
+ strokeLinejoin: "round"
3915
+ }
3916
+ )
3917
+ }
3918
+ )
3919
+ }
3920
+ );
3921
+ }
3922
+ var DEFAULT_AGGREGATION_OPTIONS = [
3923
+ { value: "sum", label: "Sum" },
3924
+ { value: "count", label: "Count" },
3925
+ { value: "mean", label: "Average" },
3926
+ { value: "min", label: "Min" },
3927
+ { value: "max", label: "Max" },
3928
+ { value: "median", label: "Median" },
3929
+ { value: "uniqueCount", label: "Distinct Count" }
3930
+ ];
3931
+ function PivotConfigPanel({
3932
+ availableFields,
3933
+ rowFields,
3934
+ columnFields,
3935
+ valueFields,
3936
+ onRowFieldsChange,
3937
+ onColumnFieldsChange,
3938
+ onValueFieldsChange,
3939
+ aggregationOptions = DEFAULT_AGGREGATION_OPTIONS,
3940
+ className
3941
+ }) {
3942
+ const [dragSource, setDragSource] = useState(null);
3943
+ const usedFields = /* @__PURE__ */ new Set([
3944
+ ...rowFields.map((f) => f.field),
3945
+ ...columnFields.map((f) => f.field),
3946
+ ...valueFields.map((f) => f.field)
3947
+ ]);
3948
+ const unplacedFields = availableFields.filter((f) => !usedFields.has(f.field));
3949
+ const handleDragStart = useCallback(
3950
+ (field, zone) => {
3951
+ setDragSource({ field, zone });
3952
+ },
3953
+ []
3954
+ );
3955
+ const handleDrop = useCallback(
3956
+ (targetZone) => {
3957
+ if (!dragSource) return;
3958
+ const { field, zone: sourceZone } = dragSource;
3959
+ if (sourceZone === targetZone) {
3960
+ setDragSource(null);
3961
+ return;
3962
+ }
3963
+ const fieldItem = availableFields.find((f) => f.field === field);
3964
+ if (!fieldItem) {
3965
+ setDragSource(null);
3966
+ return;
3967
+ }
3968
+ if (sourceZone === "rows") {
3969
+ onRowFieldsChange(rowFields.filter((f) => f.field !== field));
3970
+ } else if (sourceZone === "columns") {
3971
+ onColumnFieldsChange(columnFields.filter((f) => f.field !== field));
3972
+ } else if (sourceZone === "values") {
3973
+ onValueFieldsChange(valueFields.filter((f) => f.field !== field));
3974
+ }
3975
+ if (targetZone === "rows") {
3976
+ onRowFieldsChange([...rowFields, { field: fieldItem.field, label: fieldItem.label }]);
3977
+ } else if (targetZone === "columns") {
3978
+ onColumnFieldsChange([...columnFields, { field: fieldItem.field, label: fieldItem.label }]);
3979
+ } else if (targetZone === "values") {
3980
+ onValueFieldsChange([
3981
+ ...valueFields,
3982
+ { field: fieldItem.field, label: fieldItem.label, aggregation: "sum" }
3983
+ ]);
3984
+ }
3985
+ setDragSource(null);
3986
+ },
3987
+ [
3988
+ dragSource,
3989
+ availableFields,
3990
+ rowFields,
3991
+ columnFields,
3992
+ valueFields,
3993
+ onRowFieldsChange,
3994
+ onColumnFieldsChange,
3995
+ onValueFieldsChange
3996
+ ]
3997
+ );
3998
+ const handleAggregationChange = useCallback(
3999
+ (field, aggregation) => {
4000
+ onValueFieldsChange(
4001
+ valueFields.map(
4002
+ (vf) => vf.field === field ? { ...vf, aggregation } : vf
4003
+ )
4004
+ );
4005
+ },
4006
+ [valueFields, onValueFieldsChange]
4007
+ );
4008
+ const handleRemoveField = useCallback(
4009
+ (field, zone) => {
4010
+ if (zone === "rows") {
4011
+ onRowFieldsChange(rowFields.filter((f) => f.field !== field));
4012
+ } else if (zone === "columns") {
4013
+ onColumnFieldsChange(columnFields.filter((f) => f.field !== field));
4014
+ } else if (zone === "values") {
4015
+ onValueFieldsChange(valueFields.filter((f) => f.field !== field));
4016
+ }
4017
+ },
4018
+ [rowFields, columnFields, valueFields, onRowFieldsChange, onColumnFieldsChange, onValueFieldsChange]
4019
+ );
4020
+ const classes = ["yable-pivot-config", className].filter(Boolean).join(" ");
4021
+ return /* @__PURE__ */ jsxs("div", { className: classes, children: [
4022
+ /* @__PURE__ */ jsxs(
4023
+ "div",
4024
+ {
4025
+ className: "yable-pivot-zone yable-pivot-zone--available",
4026
+ onDragOver: (e) => {
4027
+ e.preventDefault();
4028
+ e.dataTransfer.dropEffect = "move";
4029
+ },
4030
+ onDrop: (e) => {
4031
+ e.preventDefault();
4032
+ handleDrop("available");
4033
+ },
4034
+ children: [
4035
+ /* @__PURE__ */ jsx("div", { className: "yable-pivot-zone-label", children: "Fields" }),
4036
+ /* @__PURE__ */ jsxs("div", { className: "yable-pivot-zone-items", children: [
4037
+ unplacedFields.map((f) => /* @__PURE__ */ jsx(
4038
+ "div",
4039
+ {
4040
+ className: "yable-pivot-field",
4041
+ draggable: true,
4042
+ onDragStart: () => handleDragStart(f.field, "available"),
4043
+ children: f.label
4044
+ },
4045
+ f.field
4046
+ )),
4047
+ unplacedFields.length === 0 && /* @__PURE__ */ jsx("div", { className: "yable-pivot-zone-empty", children: "All fields placed" })
4048
+ ] })
4049
+ ]
4050
+ }
4051
+ ),
4052
+ /* @__PURE__ */ jsx(
4053
+ DropZone,
4054
+ {
4055
+ label: "Row Groups",
4056
+ zone: "rows",
4057
+ items: rowFields,
4058
+ onDragStart: handleDragStart,
4059
+ onDrop: handleDrop,
4060
+ onRemove: (field) => handleRemoveField(field, "rows")
4061
+ }
4062
+ ),
4063
+ /* @__PURE__ */ jsx(
4064
+ DropZone,
4065
+ {
4066
+ label: "Column Groups",
4067
+ zone: "columns",
4068
+ items: columnFields,
4069
+ onDragStart: handleDragStart,
4070
+ onDrop: handleDrop,
4071
+ onRemove: (field) => handleRemoveField(field, "columns")
4072
+ }
4073
+ ),
4074
+ /* @__PURE__ */ jsxs(
4075
+ "div",
4076
+ {
4077
+ className: "yable-pivot-zone yable-pivot-zone--values",
4078
+ onDragOver: (e) => {
4079
+ e.preventDefault();
4080
+ e.dataTransfer.dropEffect = "move";
4081
+ },
4082
+ onDrop: (e) => {
4083
+ e.preventDefault();
4084
+ handleDrop("values");
4085
+ },
4086
+ children: [
4087
+ /* @__PURE__ */ jsx("div", { className: "yable-pivot-zone-label", children: "Values" }),
4088
+ /* @__PURE__ */ jsxs("div", { className: "yable-pivot-zone-items", children: [
4089
+ valueFields.map((vf) => /* @__PURE__ */ jsxs(
4090
+ "div",
4091
+ {
4092
+ className: "yable-pivot-field yable-pivot-field--value",
4093
+ draggable: true,
4094
+ onDragStart: () => handleDragStart(vf.field, "values"),
4095
+ children: [
4096
+ /* @__PURE__ */ jsx("span", { className: "yable-pivot-field-label", children: vf.label }),
4097
+ /* @__PURE__ */ jsx(
4098
+ "select",
4099
+ {
4100
+ className: "yable-pivot-agg-select",
4101
+ value: vf.aggregation,
4102
+ onChange: (e) => handleAggregationChange(vf.field, e.target.value),
4103
+ onClick: (e) => e.stopPropagation(),
4104
+ children: aggregationOptions.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
4105
+ }
4106
+ ),
4107
+ /* @__PURE__ */ jsx(
4108
+ "button",
4109
+ {
4110
+ type: "button",
4111
+ className: "yable-pivot-field-remove",
4112
+ onClick: () => handleRemoveField(vf.field, "values"),
4113
+ "aria-label": `Remove ${vf.label}`,
4114
+ children: "x"
4115
+ }
4116
+ )
4117
+ ]
4118
+ },
4119
+ vf.field
4120
+ )),
4121
+ valueFields.length === 0 && /* @__PURE__ */ jsx("div", { className: "yable-pivot-zone-empty", children: "Drop value fields here" })
4122
+ ] })
4123
+ ]
4124
+ }
4125
+ )
4126
+ ] });
4127
+ }
4128
+ function DropZone({
4129
+ label,
4130
+ zone,
4131
+ items,
4132
+ onDragStart,
4133
+ onDrop,
4134
+ onRemove
4135
+ }) {
4136
+ return /* @__PURE__ */ jsxs(
4137
+ "div",
4138
+ {
4139
+ className: `yable-pivot-zone yable-pivot-zone--${zone}`,
4140
+ onDragOver: (e) => {
4141
+ e.preventDefault();
4142
+ e.dataTransfer.dropEffect = "move";
4143
+ },
4144
+ onDrop: (e) => {
4145
+ e.preventDefault();
4146
+ onDrop(zone);
4147
+ },
4148
+ children: [
4149
+ /* @__PURE__ */ jsx("div", { className: "yable-pivot-zone-label", children: label }),
4150
+ /* @__PURE__ */ jsxs("div", { className: "yable-pivot-zone-items", children: [
4151
+ items.map((f) => /* @__PURE__ */ jsxs(
4152
+ "div",
4153
+ {
4154
+ className: "yable-pivot-field",
4155
+ draggable: true,
4156
+ onDragStart: () => onDragStart(f.field, zone),
4157
+ children: [
4158
+ /* @__PURE__ */ jsx("span", { className: "yable-pivot-field-label", children: f.label }),
4159
+ /* @__PURE__ */ jsx(
4160
+ "button",
4161
+ {
4162
+ type: "button",
4163
+ className: "yable-pivot-field-remove",
4164
+ onClick: () => onRemove(f.field),
4165
+ "aria-label": `Remove ${f.label}`,
4166
+ children: "x"
4167
+ }
4168
+ )
4169
+ ]
4170
+ },
4171
+ f.field
4172
+ )),
4173
+ items.length === 0 && /* @__PURE__ */ jsx("div", { className: "yable-pivot-zone-empty", children: "Drop fields here" })
4174
+ ] })
4175
+ ]
4176
+ }
4177
+ );
4178
+ }
4179
+ function TooltipArrow({ placement }) {
4180
+ return /* @__PURE__ */ jsx("div", { className: `yable-tooltip-arrow yable-tooltip-arrow--${placement}`, "aria-hidden": "true", children: /* @__PURE__ */ jsx(
4181
+ "svg",
4182
+ {
4183
+ className: "yable-tooltip-arrow-svg",
4184
+ width: "10",
4185
+ height: "5",
4186
+ viewBox: "0 0 10 5",
4187
+ fill: "none",
4188
+ children: /* @__PURE__ */ jsx("path", { d: "M0 0L5 5L10 0", fill: "currentColor" })
4189
+ }
4190
+ ) });
4191
+ }
4192
+ function Tooltip({
4193
+ visible,
4194
+ position,
4195
+ content,
4196
+ customComponent
4197
+ }) {
4198
+ const tooltipRef = useRef(null);
4199
+ useEffect(() => {
4200
+ if (!visible || !tooltipRef.current) return;
4201
+ const el = tooltipRef.current;
4202
+ const rect = el.getBoundingClientRect();
4203
+ const viewportWidth = window.innerWidth;
4204
+ const viewportHeight = window.innerHeight;
4205
+ if (rect.left < 4) {
4206
+ el.style.marginLeft = `${4 - rect.left}px`;
4207
+ } else if (rect.right > viewportWidth - 4) {
4208
+ el.style.marginLeft = `${viewportWidth - 4 - rect.right}px`;
4209
+ }
4210
+ if (rect.top < 4) {
4211
+ el.style.marginTop = `${4 - rect.top}px`;
4212
+ } else if (rect.bottom > viewportHeight - 4) {
4213
+ el.style.marginTop = `${viewportHeight - 4 - rect.bottom}px`;
4214
+ }
4215
+ }, [visible, position]);
4216
+ if (!visible || !content) return null;
4217
+ const style = {
4218
+ position: "fixed",
4219
+ zIndex: 9999,
4220
+ pointerEvents: "none"
4221
+ };
4222
+ const gap = 6;
4223
+ switch (position.placement) {
4224
+ case "top":
4225
+ style.left = position.x;
4226
+ style.top = position.y - gap;
4227
+ style.transform = "translate(-50%, -100%)";
4228
+ break;
4229
+ case "bottom":
4230
+ style.left = position.x;
4231
+ style.top = position.y + gap;
4232
+ style.transform = "translate(-50%, 0)";
4233
+ break;
4234
+ case "left":
4235
+ style.left = position.x - gap;
4236
+ style.top = position.y;
4237
+ style.transform = "translate(-100%, -50%)";
4238
+ break;
4239
+ case "right":
4240
+ style.left = position.x + gap;
4241
+ style.top = position.y;
4242
+ style.transform = "translate(0, -50%)";
4243
+ break;
4244
+ }
4245
+ return /* @__PURE__ */ jsxs(
4246
+ "div",
4247
+ {
4248
+ ref: tooltipRef,
4249
+ className: `yable-tooltip yable-tooltip--${position.placement}`,
4250
+ style,
4251
+ role: "tooltip",
4252
+ children: [
4253
+ position.placement === "bottom" && /* @__PURE__ */ jsx(TooltipArrow, { placement: position.placement }),
4254
+ position.placement === "right" && /* @__PURE__ */ jsx(TooltipArrow, { placement: position.placement }),
4255
+ customComponent ?? /* @__PURE__ */ jsx("div", { className: "yable-tooltip-content", children: content }),
4256
+ position.placement === "top" && /* @__PURE__ */ jsx(TooltipArrow, { placement: position.placement }),
4257
+ position.placement === "left" && /* @__PURE__ */ jsx(TooltipArrow, { placement: position.placement })
4258
+ ]
4259
+ }
4260
+ );
4261
+ }
4262
+ function useTooltip(options = {}) {
4263
+ const { delay = 500, placement = "top", enabled = true } = options;
4264
+ const [visible, setVisible] = useState(false);
4265
+ const [position, setPosition] = useState({
4266
+ x: 0,
4267
+ y: 0,
4268
+ placement
4269
+ });
4270
+ const [content, setContent] = useState("");
4271
+ const timerRef = useRef(void 0);
4272
+ const targetRef = useRef(null);
4273
+ const show = useCallback(
4274
+ (target, tooltipContent) => {
4275
+ if (!enabled || !tooltipContent) return;
4276
+ targetRef.current = target;
4277
+ setContent(tooltipContent);
4278
+ clearTimeout(timerRef.current);
4279
+ timerRef.current = setTimeout(() => {
4280
+ const rect = target.getBoundingClientRect();
4281
+ const viewportWidth = window.innerWidth;
4282
+ const viewportHeight = window.innerHeight;
4283
+ let finalPlacement = placement;
4284
+ let x = rect.left + rect.width / 2;
4285
+ let y;
4286
+ if (placement === "top" && rect.top < 60) {
4287
+ finalPlacement = "bottom";
4288
+ } else if (placement === "bottom" && viewportHeight - rect.bottom < 60) {
4289
+ finalPlacement = "top";
4290
+ }
4291
+ if (placement === "left" && rect.left < 120) {
4292
+ finalPlacement = "right";
4293
+ } else if (placement === "right" && viewportWidth - rect.right < 120) {
4294
+ finalPlacement = "left";
4295
+ }
4296
+ switch (finalPlacement) {
4297
+ case "top":
4298
+ y = rect.top - 8;
4299
+ break;
4300
+ case "bottom":
4301
+ y = rect.bottom + 8;
4302
+ break;
4303
+ case "left":
4304
+ x = rect.left - 8;
4305
+ y = rect.top + rect.height / 2;
4306
+ break;
4307
+ case "right":
4308
+ x = rect.right + 8;
4309
+ y = rect.top + rect.height / 2;
4310
+ break;
4311
+ default:
4312
+ y = rect.top - 8;
4313
+ }
4314
+ setPosition({ x, y, placement: finalPlacement });
4315
+ setVisible(true);
4316
+ }, delay);
4317
+ },
4318
+ [delay, placement, enabled]
4319
+ );
4320
+ const hide = useCallback(() => {
4321
+ clearTimeout(timerRef.current);
4322
+ setVisible(false);
4323
+ targetRef.current = null;
4324
+ }, []);
4325
+ useEffect(() => {
4326
+ return () => clearTimeout(timerRef.current);
4327
+ }, []);
4328
+ return {
4329
+ visible,
4330
+ position,
4331
+ content,
4332
+ show,
4333
+ hide
4334
+ };
4335
+ }
4336
+ var CSS_NAMED_COLORS = /* @__PURE__ */ new Set([
4337
+ "aliceblue",
4338
+ "antiquewhite",
4339
+ "aqua",
4340
+ "aquamarine",
4341
+ "azure",
4342
+ "beige",
4343
+ "bisque",
4344
+ "black",
4345
+ "blanchedalmond",
4346
+ "blue",
4347
+ "blueviolet",
4348
+ "brown",
4349
+ "burlywood",
4350
+ "cadetblue",
4351
+ "chartreuse",
4352
+ "chocolate",
4353
+ "coral",
4354
+ "cornflowerblue",
4355
+ "cornsilk",
4356
+ "crimson",
4357
+ "cyan",
4358
+ "darkblue",
4359
+ "darkcyan",
4360
+ "darkgoldenrod",
4361
+ "darkgray",
4362
+ "darkgreen",
4363
+ "darkgrey",
4364
+ "darkkhaki",
4365
+ "darkmagenta",
4366
+ "darkolivegreen",
4367
+ "darkorange",
4368
+ "darkorchid",
4369
+ "darkred",
4370
+ "darksalmon",
4371
+ "darkseagreen",
4372
+ "darkslateblue",
4373
+ "darkslategray",
4374
+ "darkslategrey",
4375
+ "darkturquoise",
4376
+ "darkviolet",
4377
+ "deeppink",
4378
+ "deepskyblue",
4379
+ "dimgray",
4380
+ "dimgrey",
4381
+ "dodgerblue",
4382
+ "firebrick",
4383
+ "floralwhite",
4384
+ "forestgreen",
4385
+ "fuchsia",
4386
+ "gainsboro",
4387
+ "ghostwhite",
4388
+ "gold",
4389
+ "goldenrod",
4390
+ "gray",
4391
+ "green",
4392
+ "greenyellow",
4393
+ "grey",
4394
+ "honeydew",
4395
+ "hotpink",
4396
+ "indianred",
4397
+ "indigo",
4398
+ "ivory",
4399
+ "khaki",
4400
+ "lavender",
4401
+ "lavenderblush",
4402
+ "lawngreen",
4403
+ "lemonchiffon",
4404
+ "lightblue",
4405
+ "lightcoral",
4406
+ "lightcyan",
4407
+ "lightgoldenrodyellow",
4408
+ "lightgray",
4409
+ "lightgreen",
4410
+ "lightgrey",
4411
+ "lightpink",
4412
+ "lightsalmon",
4413
+ "lightseagreen",
4414
+ "lightskyblue",
4415
+ "lightslategray",
4416
+ "lightslategrey",
4417
+ "lightsteelblue",
4418
+ "lightyellow",
4419
+ "lime",
4420
+ "limegreen",
4421
+ "linen",
4422
+ "magenta",
4423
+ "maroon",
4424
+ "mediumaquamarine",
4425
+ "mediumblue",
4426
+ "mediumorchid",
4427
+ "mediumpurple",
4428
+ "mediumseagreen",
4429
+ "mediumslateblue",
4430
+ "mediumspringgreen",
4431
+ "mediumturquoise",
4432
+ "mediumvioletred",
4433
+ "midnightblue",
4434
+ "mintcream",
4435
+ "mistyrose",
4436
+ "moccasin",
4437
+ "navajowhite",
4438
+ "navy",
4439
+ "oldlace",
4440
+ "olive",
4441
+ "olivedrab",
4442
+ "orange",
4443
+ "orangered",
4444
+ "orchid",
4445
+ "palegoldenrod",
4446
+ "palegreen",
4447
+ "paleturquoise",
4448
+ "palevioletred",
4449
+ "papayawhip",
4450
+ "peachpuff",
4451
+ "peru",
4452
+ "pink",
4453
+ "plum",
4454
+ "powderblue",
4455
+ "purple",
4456
+ "rebeccapurple",
4457
+ "red",
4458
+ "rosybrown",
4459
+ "royalblue",
4460
+ "saddlebrown",
4461
+ "salmon",
4462
+ "sandybrown",
4463
+ "seagreen",
4464
+ "seashell",
4465
+ "sienna",
4466
+ "silver",
4467
+ "skyblue",
4468
+ "slateblue",
4469
+ "slategray",
4470
+ "slategrey",
4471
+ "snow",
4472
+ "springgreen",
4473
+ "steelblue",
4474
+ "tan",
4475
+ "teal",
4476
+ "thistle",
4477
+ "tomato",
4478
+ "turquoise",
4479
+ "violet",
4480
+ "wheat",
4481
+ "white",
4482
+ "whitesmoke",
4483
+ "yellow",
4484
+ "yellowgreen"
4485
+ ]);
4486
+ var CSS_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
4487
+ "currentcolor",
4488
+ "inherit",
4489
+ "initial",
4490
+ "unset",
4491
+ "revert",
4492
+ "transparent"
4493
+ ]);
4494
+ function isValidCSSColor(value) {
4495
+ const trimmed = value.trim().toLowerCase();
4496
+ if (/url\s*\(/i.test(trimmed)) return false;
4497
+ if (/expression\s*\(/i.test(trimmed)) return false;
4498
+ if (/javascript\s*:/i.test(trimmed)) return false;
4499
+ if (/@import/i.test(trimmed)) return false;
4500
+ if (/[{};]/.test(trimmed)) return false;
4501
+ if (/^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;
4502
+ if (/^(rgba?|hsla?|oklch|oklab|lab|lch|color)\s*\(/.test(trimmed)) return true;
4503
+ if (CSS_NAMED_COLORS.has(trimmed)) return true;
4504
+ if (CSS_COLOR_KEYWORDS.has(trimmed)) return true;
4505
+ return false;
4506
+ }
4507
+ var DEFAULT_UP_COLOR = "#22c55e33";
4508
+ var DEFAULT_DOWN_COLOR = "#ef444433";
4509
+ var DEFAULT_CHANGE_COLOR = "#3b82f633";
4510
+ function safeColor(value, fallback, propName) {
4511
+ if (value === void 0) return void 0;
4512
+ if (isValidCSSColor(value)) return value;
4513
+ console.warn(
4514
+ `Yable FlashCell: Invalid CSS color for ${propName}: "${value}". Using default color instead. Colors must be valid CSS color values (hex, rgb, hsl, named colors, etc.) and must not contain url(), expression(), or javascript:.`
4515
+ );
4516
+ return fallback;
4517
+ }
4518
+ function FlashCell({
4519
+ flash,
4520
+ duration = 700,
4521
+ upColor,
4522
+ downColor,
4523
+ changeColor,
4524
+ children
4525
+ }) {
4526
+ if (!flash) {
4527
+ return /* @__PURE__ */ jsx(Fragment, { children });
4528
+ }
4529
+ const flashClass = `yable-flash-cell yable-flash-cell--${flash.direction}`;
4530
+ const style = {
4531
+ animationDuration: `${duration}ms`
4532
+ };
4533
+ if (flash.direction === "up" && upColor) {
4534
+ const validated = safeColor(upColor, DEFAULT_UP_COLOR, "upColor");
4535
+ if (validated) style["--yable-flash-up-color"] = validated;
4536
+ } else if (flash.direction === "down" && downColor) {
4537
+ const validated = safeColor(downColor, DEFAULT_DOWN_COLOR, "downColor");
4538
+ if (validated) style["--yable-flash-down-color"] = validated;
4539
+ } else if (flash.direction === "change" && changeColor) {
4540
+ const validated = safeColor(changeColor, DEFAULT_CHANGE_COLOR, "changeColor");
4541
+ if (validated) style["--yable-flash-change-color"] = validated;
4542
+ }
4543
+ return /* @__PURE__ */ jsx("div", { className: flashClass, style, children });
4544
+ }
4545
+ function useCellFlash(table, options = {}) {
4546
+ const { duration = 700 } = options;
4547
+ const [flashes, setFlashes] = useState(/* @__PURE__ */ new Map());
4548
+ const prevDataRef = useRef([]);
4549
+ const timersRef = useRef(/* @__PURE__ */ new Map());
4550
+ const clearFlash = useCallback((key) => {
4551
+ setFlashes((prev) => {
4552
+ const next = new Map(prev);
4553
+ next.delete(key);
4554
+ return next;
4555
+ });
4556
+ timersRef.current.delete(key);
4557
+ }, []);
4558
+ useEffect(() => {
4559
+ const currentData = table.options.data;
4560
+ const prevData = prevDataRef.current;
4561
+ if (prevData.length > 0 && currentData !== prevData) {
4562
+ const columns = table.getAllLeafColumns().map((col) => ({
4563
+ id: col.id,
4564
+ enableCellFlash: col.columnDef.enableCellFlash ?? false
4565
+ }));
4566
+ const getRowId = table.options.getRowId ?? ((_row, i) => String(i));
4567
+ const newFlashes = detectCellChanges(prevData, currentData, columns, getRowId);
4568
+ if (newFlashes.size > 0) {
4569
+ setFlashes((prev) => {
4570
+ const merged = new Map(prev);
4571
+ for (const [key, flash] of newFlashes) {
4572
+ merged.set(key, flash);
4573
+ const existingTimer = timersRef.current.get(key);
4574
+ if (existingTimer) clearTimeout(existingTimer);
4575
+ const timer = setTimeout(() => clearFlash(key), duration);
4576
+ timersRef.current.set(key, timer);
4577
+ }
4578
+ return merged;
4579
+ });
4580
+ for (const [, flash] of newFlashes) {
4581
+ table.events.emit("cell:flash", flash);
4582
+ }
4583
+ }
4584
+ }
4585
+ prevDataRef.current = currentData;
4586
+ }, [table.options.data, table, duration, clearFlash]);
4587
+ useEffect(() => {
4588
+ return () => {
4589
+ for (const timer of timersRef.current.values()) {
4590
+ clearTimeout(timer);
4591
+ }
4592
+ };
4593
+ }, []);
4594
+ const getFlash = useCallback(
4595
+ (rowId, columnId) => {
4596
+ return flashes.get(`${rowId}:${columnId}`);
4597
+ },
4598
+ [flashes]
4599
+ );
4600
+ return { flashes, getFlash };
4601
+ }
4602
+ function useRowAnimation(_table, options = {}) {
4603
+ const { enabled = false, duration = 250, easing = "ease" } = options;
4604
+ const prevPositionsRef = useRef(/* @__PURE__ */ new Map());
4605
+ const animatingRef = useRef(false);
4606
+ const capturePositions = useCallback(
4607
+ (containerEl) => {
4608
+ if (!enabled || !containerEl) return;
4609
+ const positions = /* @__PURE__ */ new Map();
4610
+ const rows = containerEl.querySelectorAll(".yable-tr[data-row-id]");
4611
+ rows.forEach((el) => {
4612
+ const rowId = el.getAttribute("data-row-id");
4613
+ if (rowId) {
4614
+ positions.set(rowId, {
4615
+ top: el.offsetTop,
4616
+ height: el.offsetHeight
4617
+ });
4618
+ }
4619
+ });
4620
+ prevPositionsRef.current = positions;
4621
+ },
4622
+ [enabled]
4623
+ );
4624
+ const animateRows = useCallback(
4625
+ (containerEl) => {
4626
+ if (!enabled || !containerEl || animatingRef.current) return;
4627
+ const prevPositions = prevPositionsRef.current;
4628
+ if (prevPositions.size === 0) return;
4629
+ const rows = containerEl.querySelectorAll(".yable-tr[data-row-id]");
4630
+ const currentPositions = /* @__PURE__ */ new Map();
4631
+ const currentIds = /* @__PURE__ */ new Set();
4632
+ rows.forEach((el) => {
4633
+ const rowId = el.getAttribute("data-row-id");
4634
+ if (rowId) {
4635
+ currentIds.add(rowId);
4636
+ currentPositions.set(rowId, {
4637
+ top: el.offsetTop,
4638
+ height: el.offsetHeight
4639
+ });
4640
+ }
4641
+ });
4642
+ animatingRef.current = true;
4643
+ rows.forEach((el) => {
4644
+ const rowId = el.getAttribute("data-row-id");
4645
+ if (!rowId) return;
4646
+ const prev = prevPositions.get(rowId);
4647
+ const curr = currentPositions.get(rowId);
4648
+ if (prev && curr) {
4649
+ const deltaY = prev.top - curr.top;
4650
+ if (Math.abs(deltaY) > 1) {
4651
+ el.style.transform = `translateY(${deltaY}px)`;
4652
+ el.style.transition = "none";
4653
+ requestAnimationFrame(() => {
4654
+ el.style.transition = `transform ${duration}ms ${easing}`;
4655
+ el.style.transform = "";
4656
+ });
4657
+ }
4658
+ } else if (!prev && curr) {
4659
+ el.classList.add("yable-row-enter");
4660
+ el.style.animation = `yable-row-fade-in ${duration}ms ${easing} forwards`;
4661
+ const cleanup = () => {
4662
+ el.classList.remove("yable-row-enter");
4663
+ el.style.animation = "";
4664
+ el.removeEventListener("animationend", cleanup);
4665
+ };
4666
+ el.addEventListener("animationend", cleanup);
4667
+ }
4668
+ });
4669
+ setTimeout(() => {
4670
+ animatingRef.current = false;
4671
+ rows.forEach((el) => {
4672
+ el.style.transform = "";
4673
+ el.style.transition = "";
4674
+ });
4675
+ }, duration + 50);
4676
+ },
4677
+ [enabled, duration, easing]
4678
+ );
4679
+ return {
4680
+ capturePositions,
4681
+ animateRows,
4682
+ enabled
4683
+ };
4684
+ }
4685
+ function PrintLayout({
4686
+ table,
4687
+ title,
4688
+ showTimestamp = true
4689
+ }) {
4690
+ const allRows = table.getPrePaginationRowModel().rows;
4691
+ const headerGroups = table.getHeaderGroups();
4692
+ const timestamp = (/* @__PURE__ */ new Date()).toLocaleString();
4693
+ return /* @__PURE__ */ jsxs("div", { className: "yable-print-layout", children: [
4694
+ (title || showTimestamp) && /* @__PURE__ */ jsxs("div", { className: "yable-print-header", children: [
4695
+ title && /* @__PURE__ */ jsx("h2", { className: "yable-print-title", children: title }),
4696
+ showTimestamp && /* @__PURE__ */ jsxs("span", { className: "yable-print-timestamp", children: [
4697
+ "Printed: ",
4698
+ timestamp
4699
+ ] })
4700
+ ] }),
4701
+ /* @__PURE__ */ jsx("div", { className: "yable-print-summary", children: /* @__PURE__ */ jsxs("span", { className: "yable-print-summary-text", children: [
4702
+ allRows.length.toLocaleString(),
4703
+ " row",
4704
+ allRows.length !== 1 ? "s" : ""
4705
+ ] }) }),
4706
+ /* @__PURE__ */ jsxs("table", { className: "yable-print-table", children: [
4707
+ /* @__PURE__ */ jsx("thead", { children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx("tr", { className: "yable-print-thead-row", children: headerGroup.headers.map((header) => {
4708
+ const headerContent = header.isPlaceholder ? null : typeof header.column.columnDef.header === "function" ? header.column.columnDef.header(header.getContext()) : header.column.columnDef.header ?? header.id;
4709
+ return /* @__PURE__ */ jsx("th", { colSpan: header.colSpan, className: "yable-print-th", children: headerContent }, header.id);
4710
+ }) }, headerGroup.id)) }),
4711
+ /* @__PURE__ */ jsx("tbody", { children: allRows.map((row, index) => /* @__PURE__ */ jsx(
4712
+ "tr",
4713
+ {
4714
+ className: `yable-print-tr${index % 2 === 1 ? " yable-print-tr--alt" : ""}`,
4715
+ children: row.getVisibleCells().map((cell) => {
4716
+ const cellDef = cell.column.columnDef.cell;
4717
+ const content = typeof cellDef === "function" ? cellDef(cell.getContext()) : cell.renderValue();
4718
+ return /* @__PURE__ */ jsx("td", { className: "yable-print-td", children: content }, cell.id);
4719
+ })
4720
+ },
4721
+ row.id
4722
+ )) })
4723
+ ] }),
4724
+ /* @__PURE__ */ jsxs("div", { className: "yable-print-footer", children: [
4725
+ /* @__PURE__ */ jsxs("span", { className: "yable-print-footer-count", children: [
4726
+ "Total: ",
4727
+ allRows.length.toLocaleString(),
4728
+ " row",
4729
+ allRows.length !== 1 ? "s" : ""
4730
+ ] }),
4731
+ showTimestamp && /* @__PURE__ */ jsx("span", { className: "yable-print-footer-timestamp", children: timestamp })
4732
+ ] })
4733
+ ] });
4734
+ }
4735
+ function sanitizeCSS(css) {
4736
+ let sanitized = css;
4737
+ sanitized = sanitized.replace(/@import\s+[^;]*;?/gi, "/* @import removed */");
4738
+ sanitized = sanitized.replace(/@charset\s+[^;]*;?/gi, "/* @charset removed */");
4739
+ sanitized = sanitized.replace(/url\s*\([^)]*\)/gi, "/* url() removed */");
4740
+ sanitized = sanitized.replace(/expression\s*\([^)]*\)/gi, "/* expression() removed */");
4741
+ sanitized = sanitized.replace(/javascript\s*:/gi, "/* javascript: removed */");
4742
+ return sanitized;
4743
+ }
4744
+ function usePrintLayout(table, options = {}) {
4745
+ const { title, additionalCSS } = options;
4746
+ const isPrintingRef = useRef(false);
4747
+ const preparePrint = useCallback(() => {
4748
+ const yableEl = document.querySelector(".yable");
4749
+ if (yableEl) {
4750
+ yableEl.classList.add("yable-print-mode");
4751
+ }
4752
+ const originalTitle = document.title;
4753
+ if (title) {
4754
+ document.title = title;
4755
+ }
4756
+ let styleEl = null;
4757
+ if (additionalCSS) {
4758
+ styleEl = document.createElement("style");
4759
+ styleEl.setAttribute("data-yable-print", "true");
4760
+ styleEl.textContent = sanitizeCSS(additionalCSS);
4761
+ document.head.appendChild(styleEl);
4762
+ }
4763
+ isPrintingRef.current = true;
4764
+ const cleanup = () => {
4765
+ isPrintingRef.current = false;
4766
+ if (yableEl) {
4767
+ yableEl.classList.remove("yable-print-mode");
4768
+ }
4769
+ if (title) {
4770
+ document.title = originalTitle;
4771
+ }
4772
+ if (styleEl) {
4773
+ styleEl.remove();
4774
+ }
4775
+ window.removeEventListener("afterprint", cleanup);
4776
+ };
4777
+ window.addEventListener("afterprint", cleanup);
4778
+ requestAnimationFrame(() => {
4779
+ window.print();
4780
+ });
4781
+ }, [table, title, additionalCSS]);
4782
+ return {
4783
+ preparePrint,
4784
+ isPrinting: isPrintingRef.current
4785
+ };
4786
+ }
4787
+ function useTheme(options = {}) {
4788
+ const { defaultTheme = "default", defaultColorScheme = "auto" } = options;
4789
+ const [theme, setThemeState] = useState(defaultTheme);
4790
+ const [colorScheme, setColorSchemeState] = useState(defaultColorScheme);
4791
+ const containerRef = useRef(null);
4792
+ const setTheme = useCallback((newTheme) => {
4793
+ setThemeState(newTheme);
4794
+ }, []);
4795
+ const setColorScheme = useCallback((scheme) => {
4796
+ setColorSchemeState(scheme);
4797
+ }, []);
4798
+ const toggleColorScheme = useCallback(() => {
4799
+ setColorSchemeState((prev) => {
4800
+ if (prev === "auto") return "dark";
4801
+ if (prev === "dark") return "light";
4802
+ return "auto";
4803
+ });
4804
+ }, []);
4805
+ useEffect(() => {
4806
+ const target = containerRef.current ?? document.documentElement;
4807
+ if (colorScheme === "auto") {
4808
+ target.removeAttribute("data-yable-theme");
4809
+ } else {
4810
+ target.setAttribute("data-yable-theme", colorScheme);
4811
+ }
4812
+ }, [colorScheme]);
4813
+ const [systemDark, setSystemDark] = useState(false);
4814
+ useEffect(() => {
4815
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
4816
+ setSystemDark(mq.matches);
4817
+ const handler = (e) => setSystemDark(e.matches);
4818
+ mq.addEventListener("change", handler);
4819
+ return () => mq.removeEventListener("change", handler);
4820
+ }, []);
4821
+ const resolvedColorScheme = colorScheme === "auto" ? systemDark ? "dark" : "light" : colorScheme;
4822
+ return {
4823
+ theme,
4824
+ setTheme,
4825
+ colorScheme,
4826
+ setColorScheme,
4827
+ toggleColorScheme,
4828
+ resolvedColorScheme,
4829
+ containerRef
4830
+ };
4831
+ }
4832
+
4833
+ export { CellBadge, CellBoolean, CellCheckbox, CellCurrency, CellDate, CellDatePicker, CellErrorBoundary, CellInput, CellLink, CellNumeric, CellProgress, CellRating, CellSelect, CellStatus, CellStatusBadge, CellToggle, ColumnsPanel, ContextMenu, ContextMenuItem, DEFAULT_TEXT_RECIPE, DragHandle, ErrorBoundary, ExpandIcon, FillHandle, FiltersPanel, FlashCell, GlobalFilter, LoadingOverlay, MasterDetail, NoRowsOverlay, Pagination, PivotConfigPanel, PrintLayout, Sidebar, SortIndicator, StatusBar, StatusBarPanelComponent, Table, TableBody, TableCell, TableFooter, TableHeader, TableProvider, Tooltip, TreeToggle, getMeasureRecipeForCellType, getRegisteredCellTypes, resolveMeasureRecipe, useAutoMeasurements, useCellFlash, useClipboard, useContextMenu, useFillHandle, useKeyboardNavigation, usePretextMeasurement, usePrintLayout, useRowAnimation, useRowDrag, useTable, useTableContext, useTableRowHeights, useTheme, useTooltip, useVirtualization };
4834
+ //# sourceMappingURL=index.js.map
4835
+ //# sourceMappingURL=index.js.map