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