xontable 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/XOnTable.d.ts +4 -0
  4. package/dist/XOnTable.d.ts.map +1 -0
  5. package/dist/XOnTable.js +182 -0
  6. package/dist/components/ColumnFilterMenu.d.ts +13 -0
  7. package/dist/components/ColumnFilterMenu.d.ts.map +1 -0
  8. package/dist/components/ColumnFilterMenu.js +7 -0
  9. package/dist/components/SelectMenu.d.ts +15 -0
  10. package/dist/components/SelectMenu.d.ts.map +1 -0
  11. package/dist/components/SelectMenu.js +38 -0
  12. package/dist/components/XOnTableGrid.d.ts +62 -0
  13. package/dist/components/XOnTableGrid.d.ts.map +1 -0
  14. package/dist/components/XOnTableGrid.js +42 -0
  15. package/dist/components/XOnTableHeader.d.ts +34 -0
  16. package/dist/components/XOnTableHeader.d.ts.map +1 -0
  17. package/dist/components/XOnTableHeader.js +7 -0
  18. package/dist/components/index.d.ts +5 -0
  19. package/dist/components/index.d.ts.map +1 -0
  20. package/dist/components/index.js +4 -0
  21. package/dist/hooks/index.d.ts +14 -0
  22. package/dist/hooks/index.d.ts.map +1 -0
  23. package/dist/hooks/index.js +13 -0
  24. package/dist/hooks/useAutoRows.d.ts +9 -0
  25. package/dist/hooks/useAutoRows.d.ts.map +1 -0
  26. package/dist/hooks/useAutoRows.js +41 -0
  27. package/dist/hooks/useClipboardCatcher.d.ts +15 -0
  28. package/dist/hooks/useClipboardCatcher.d.ts.map +1 -0
  29. package/dist/hooks/useClipboardCatcher.js +29 -0
  30. package/dist/hooks/useColumnFilters.d.ts +17 -0
  31. package/dist/hooks/useColumnFilters.d.ts.map +1 -0
  32. package/dist/hooks/useColumnFilters.js +95 -0
  33. package/dist/hooks/useColumnGroups.d.ts +27 -0
  34. package/dist/hooks/useColumnGroups.d.ts.map +1 -0
  35. package/dist/hooks/useColumnGroups.js +89 -0
  36. package/dist/hooks/useColumnResize.d.ts +12 -0
  37. package/dist/hooks/useColumnResize.d.ts.map +1 -0
  38. package/dist/hooks/useColumnResize.js +27 -0
  39. package/dist/hooks/useEditorOverlay.d.ts +24 -0
  40. package/dist/hooks/useEditorOverlay.d.ts.map +1 -0
  41. package/dist/hooks/useEditorOverlay.js +74 -0
  42. package/dist/hooks/useFillHandle.d.ts +16 -0
  43. package/dist/hooks/useFillHandle.d.ts.map +1 -0
  44. package/dist/hooks/useFillHandle.js +52 -0
  45. package/dist/hooks/useGridKeydown.d.ts +18 -0
  46. package/dist/hooks/useGridKeydown.d.ts.map +1 -0
  47. package/dist/hooks/useGridKeydown.js +109 -0
  48. package/dist/hooks/useOutsideClick.d.ts +7 -0
  49. package/dist/hooks/useOutsideClick.d.ts.map +1 -0
  50. package/dist/hooks/useOutsideClick.js +26 -0
  51. package/dist/hooks/useRangeSelection.d.ts +23 -0
  52. package/dist/hooks/useRangeSelection.d.ts.map +1 -0
  53. package/dist/hooks/useRangeSelection.js +48 -0
  54. package/dist/hooks/useSelectOptions.d.ts +13 -0
  55. package/dist/hooks/useSelectOptions.d.ts.map +1 -0
  56. package/dist/hooks/useSelectOptions.js +35 -0
  57. package/dist/hooks/useTableModel.d.ts +31 -0
  58. package/dist/hooks/useTableModel.d.ts.map +1 -0
  59. package/dist/hooks/useTableModel.js +135 -0
  60. package/dist/hooks/useValidation.d.ts +9 -0
  61. package/dist/hooks/useValidation.d.ts.map +1 -0
  62. package/dist/hooks/useValidation.js +42 -0
  63. package/dist/index.d.ts +3 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +1 -0
  66. package/dist/types.d.ts +37 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +1 -0
  69. package/dist/utils/cellKey.d.ts +2 -0
  70. package/dist/utils/cellKey.d.ts.map +1 -0
  71. package/dist/utils/cellKey.js +3 -0
  72. package/dist/utils/index.d.ts +3 -0
  73. package/dist/utils/index.d.ts.map +1 -0
  74. package/dist/utils/index.js +2 -0
  75. package/dist/utils/tsv.d.ts +3 -0
  76. package/dist/utils/tsv.d.ts.map +1 -0
  77. package/dist/utils/tsv.js +9 -0
  78. package/package.json +36 -0
  79. package/src/styles/xontable.base.css +71 -0
  80. package/src/styles/xontable.css +3 -0
  81. package/src/styles/xontable.filter.css +65 -0
  82. package/src/styles/xontable.theme.css +42 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xontable
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # xontable
2
+
3
+ A spreadsheet-like React table component with selection, clipboard, fill handle, validation, filters, select dropdowns, and checkbox cells.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install xontable
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```tsx
14
+ import { XOnTable, type ColumnDef } from "xontable";
15
+ import "xontable/styles";
16
+
17
+ const columns: ColumnDef<Row>[] = [
18
+ { key: "name", label: "Name", type: "text", editable: true },
19
+ { key: "qty", label: "Qty", type: "number", editable: true },
20
+ ];
21
+
22
+ <XOnTable columns={columns} rows={rows} onChange={setRows} />;
23
+ ```
24
+
25
+ ## Notes
26
+ - Built for React 19+
27
+ - Styles are included via `xontable/styles`
28
+
29
+ See the root `BOOK.md` for full developer documentation.
@@ -0,0 +1,4 @@
1
+ import "./styles/xontable.css";
2
+ import type { XOnTableProps } from "./types";
3
+ export declare function XOnTable<Row extends Record<string, any>>(props: XOnTableProps<Row>): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=XOnTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"XOnTable.d.ts","sourceRoot":"","sources":["../src/XOnTable.tsx"],"names":[],"mappings":"AACA,OAAO,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C,wBAAgB,QAAQ,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,2CAkIlF"}
@@ -0,0 +1,182 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import "./styles/xontable.css";
4
+ import { SelectMenu, XOnTableGrid } from "./components";
5
+ import { useAutoRows, useClipboardCatcher, useColumnFilters, useColumnGroups, useColumnResize, useEditorOverlay, useFillHandle, useGridKeydown, useOutsideClick, useRangeSelection, useSelectOptions, useTableModel } from "./hooks";
6
+ const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
7
+ export function XOnTable(props) {
8
+ const { columns, rows, rowIdKey = "id", onChange, readOnly = false, theme = "light" } = props;
9
+ const activeCellRef = React.useRef(null);
10
+ const [editingRowId, setEditingRowId] = React.useState(null);
11
+ const { normalizedRows, handleChange, createRow } = useAutoRows(columns, rows, rowIdKey, onChange, editingRowId);
12
+ const filters = useColumnFilters(columns, normalizedRows);
13
+ const { visibleColumns, groupHeaders, getColWidth, setColWidth, toggleGroup, resetWidths, getOrigIndex } = useColumnGroups({ columns });
14
+ const { getOptions, ensureOptions, isLoading } = useSelectOptions(columns);
15
+ const selection = useRangeSelection();
16
+ const [copiedBounds, setCopiedBounds] = React.useState(null);
17
+ React.useEffect(() => { resetWidths(); }, [columns, resetWidths]);
18
+ const { data, active, setActive, getValue, updateCells, moveActive, rowCount, colCount, hasError, getError, setCellErrorView, undo, redo } = useTableModel({
19
+ columns: visibleColumns.map((v) => v.col),
20
+ rows: normalizedRows,
21
+ rowFilter: filters.rowFilter,
22
+ onChange: handleChange,
23
+ createRow,
24
+ });
25
+ React.useEffect(() => { if (active.c >= colCount)
26
+ setActive({ r: active.r, c: Math.max(0, colCount - 1) }); }, [active, colCount, setActive]);
27
+ const activeCol = visibleColumns[active.c]?.col;
28
+ const activeRow = data[active.r];
29
+ const validateSelect = React.useCallback((r, c, value, row) => {
30
+ const col = visibleColumns[c]?.col;
31
+ if (!col || col.type !== "select" || !row)
32
+ return null;
33
+ const opts = getOptions(row, col);
34
+ const match = opts.find((o) => o.value === value || o.label === value) ?? null;
35
+ if (!match && value.trim() !== "") {
36
+ setCellErrorView(r, c, "Invalid option");
37
+ return null;
38
+ }
39
+ setCellErrorView(r, c, null);
40
+ return match;
41
+ }, [getOptions, setCellErrorView, visibleColumns]);
42
+ const normalizeSelectValue = React.useCallback((r, c, value, row) => { const match = validateSelect(r, c, value, row); return match ? match.value : value; }, [validateSelect]);
43
+ const ensureSelect = React.useCallback((r, c, row) => { const col = visibleColumns[c]?.col; if (!col || col.type !== "select" || !row)
44
+ return; ensureOptions(row, col); }, [ensureOptions, visibleColumns]);
45
+ const { startResize } = useColumnResize({ colCount, getWidth: getColWidth, setWidth: setColWidth, minWidth: 60 });
46
+ const measureRef = React.useRef(null);
47
+ React.useEffect(() => () => { if (measureRef.current)
48
+ measureRef.current.remove(); }, []);
49
+ const measureText = React.useCallback((text) => { let el = measureRef.current; if (!el) {
50
+ el = document.createElement("div");
51
+ el.className = "xontable-cell xontable-measure";
52
+ document.body.appendChild(el);
53
+ measureRef.current = el;
54
+ } el.textContent = text || ""; return Math.ceil(el.getBoundingClientRect().width); }, []);
55
+ const autoFitCol = React.useCallback((visibleIndex) => { const orig = getOrigIndex(visibleIndex); if (orig == null)
56
+ return; let max = measureText(columns[orig]?.label ?? ""); for (let r = 0; r < data.length; r++) {
57
+ const w = measureText(getValue(r, visibleIndex));
58
+ if (w > max)
59
+ max = w;
60
+ } setColWidth(visibleIndex, Math.min(600, Math.max(60, max + 15))); }, [columns, data.length, getOrigIndex, getValue, measureText, setColWidth]);
61
+ const { isEditing, draft, setDraft, editorRect, editorRef, startEdit, commitEdit, onEditorKeyDown } = useEditorOverlay({
62
+ active, activeCellRef, getValue,
63
+ onCommit: (value) => {
64
+ const row = data[active.r];
65
+ const col = visibleColumns[active.c]?.col;
66
+ const v = col?.type === "select" ? normalizeSelectValue(active.r, active.c, value, row) : value;
67
+ updateCells([{ r: active.r, c: active.c, value: v }], { type: "edit", cell: active });
68
+ },
69
+ onEnter: (dir) => moveActive(dir, 0), onTab: (dir) => moveActive(0, dir),
70
+ });
71
+ React.useEffect(() => { if (!isEditing)
72
+ setEditingRowId(null); }, [isEditing]);
73
+ const startEditCell = React.useCallback((initial) => { if (readOnly)
74
+ return; const id = activeRow ? String(activeRow[rowIdKey] ?? "") : ""; setEditingRowId(id || null); ensureSelect(active.r, active.c, activeRow); startEdit(initial); }, [active.c, active.r, activeRow, ensureSelect, readOnly, rowIdKey, startEdit]);
75
+ const toggleCheckbox = React.useCallback((r, c) => {
76
+ if (readOnly)
77
+ return;
78
+ const col = visibleColumns[c]?.col;
79
+ const row = data[r];
80
+ if (!col || col.type !== "checkbox" || !row)
81
+ return;
82
+ const checked = row[col.key] === true || row[col.key] === "true";
83
+ updateCells([{ r, c, value: !checked }], { type: "edit", cell: { r, c } });
84
+ }, [data, readOnly, updateCells, visibleColumns]);
85
+ const clearRange = React.useCallback(() => { const b = selection.getBounds(); if (!b) {
86
+ updateCells([{ r: active.r, c: active.c, value: "" }], { type: "edit", cell: active });
87
+ return;
88
+ } const updates = []; for (let r = b.r1; r <= b.r2; r++)
89
+ for (let c = b.c1; c <= b.c2; c++)
90
+ updates.push({ r, c, value: "" }); updateCells(updates, { type: "edit", cell: active }); }, [active, selection, updateCells]);
91
+ const { clipRef, focusClipboard, onCopy, onPaste } = useClipboardCatcher({
92
+ isEditing,
93
+ getCopyBlock: () => { const b = selection.getBounds(); if (!b)
94
+ return [[getValue(active.r, active.c)]]; const block = []; for (let r = b.r1; r <= b.r2; r++) {
95
+ const row = [];
96
+ for (let c = b.c1; c <= b.c2; c++)
97
+ row.push(getValue(r, c));
98
+ block.push(row);
99
+ } return block; },
100
+ onCopy: () => setCopiedBounds(selection.getBounds() ?? { r1: active.r, r2: active.r, c1: active.c, c2: active.c }),
101
+ onPasteBlock: (block) => { if (readOnly)
102
+ return; const updates = []; for (let rOff = 0; rOff < block.length; rOff++)
103
+ for (let cOff = 0; cOff < block[rOff].length; cOff++) {
104
+ const r = active.r + rOff;
105
+ const c = active.c + cOff;
106
+ if (c < colCount)
107
+ updates.push({ r, c, value: block[rOff][cOff] });
108
+ } updateCells(updates, { type: "paste", cell: active }); updates.forEach((u) => validateSelect(u.r, u.c, u.value, data[u.r])); },
109
+ });
110
+ React.useEffect(() => { if (!isEditing)
111
+ focusClipboard(); }, [focusClipboard, isEditing]);
112
+ const openSelectAt = React.useCallback((r, c) => {
113
+ if (readOnly)
114
+ return;
115
+ setActive({ r, c });
116
+ selection.startSelection({ r, c });
117
+ focusClipboard();
118
+ const row = data[r];
119
+ const id = row ? String(row[rowIdKey] ?? "") : "";
120
+ setEditingRowId(id || null);
121
+ ensureSelect(r, c, row);
122
+ requestAnimationFrame(() => startEdit());
123
+ }, [data, ensureSelect, focusClipboard, readOnly, rowIdKey, selection, setActive, startEdit]);
124
+ const { startDrag, isPreview } = useFillHandle({
125
+ onApply: (startR, startC, endR, endC) => {
126
+ if (readOnly)
127
+ return;
128
+ const value = getValue(startR, startC);
129
+ const updates = [];
130
+ const dr = Math.abs(endR - startR);
131
+ const dc = Math.abs(endC - startC);
132
+ if (dc >= dr) {
133
+ const from = Math.min(startC, endC);
134
+ const to = Math.max(startC, endC);
135
+ for (let c = from; c <= to; c++)
136
+ updates.push({ r: startR, c, value });
137
+ }
138
+ else {
139
+ const from = Math.min(startR, endR);
140
+ const to = Math.max(startR, endR);
141
+ for (let r = from; r <= to; r++)
142
+ updates.push({ r, c: startC, value });
143
+ }
144
+ updateCells(updates, { type: "fill", cell: { r: startR, c: startC } });
145
+ updates.forEach((u) => validateSelect(u.r, u.c, u.value, data[u.r]));
146
+ focusClipboard();
147
+ },
148
+ });
149
+ const onShiftMove = React.useCallback((dr, dc) => { const next = { r: clamp(active.r + dr, 0, rowCount - 1), c: clamp(active.c + dc, 0, colCount - 1) }; if (!selection.selection)
150
+ selection.startSelection(active); selection.updateSelection(next); setActive(next); }, [active, colCount, rowCount, selection, setActive]);
151
+ const onGridKeyDown = useGridKeydown({
152
+ active, rowCount, colCount, isEditing, moveActive, moveTo: (r, c) => setActive({ r, c }),
153
+ startEdit: readOnly ? () => { } : startEditCell,
154
+ clearCell: () => { if (!readOnly)
155
+ clearRange(); },
156
+ undo: () => { if (!readOnly)
157
+ undo(); }, redo: () => { if (!readOnly)
158
+ redo(); },
159
+ onShiftMove,
160
+ });
161
+ const onGridKeyDownWithCopy = React.useCallback((e) => {
162
+ const target = e.target;
163
+ const isClip = Boolean(target && target.classList.contains("xontable-clip"));
164
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA") && !isClip)
165
+ return;
166
+ const isCopy = (e.ctrlKey || e.metaKey) && (e.key === "c" || e.key === "C");
167
+ if (isCopy)
168
+ setCopiedBounds(selection.getBounds() ?? { r1: active.r, r2: active.r, c1: active.c, c2: active.c });
169
+ else
170
+ setCopiedBounds(null);
171
+ onGridKeyDown(e);
172
+ }, [active.c, active.r, onGridKeyDown, selection]);
173
+ useOutsideClick({ isOpen: filters.filterOpenKey != null, onClose: filters.closeFilter });
174
+ return (_jsxs("div", { className: `xontable-wrap theme-${theme}${readOnly ? " is-readonly" : ""}`, children: [_jsx("textarea", { ref: clipRef, className: "xontable-clip", name: "xontable-clip", "aria-hidden": "true", tabIndex: -1, onCopy: onCopy, onPaste: onPaste, onKeyDown: onGridKeyDownWithCopy, readOnly: true }), _jsx("div", { className: "xontable-surface", tabIndex: 0, onFocus: (e) => {
175
+ const target = e.target;
176
+ if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA"))
177
+ return;
178
+ focusClipboard();
179
+ }, onKeyDown: onGridKeyDownWithCopy, children: _jsx(XOnTableGrid, { columns: visibleColumns, groups: groupHeaders.some((g) => g.label) ? groupHeaders : undefined, rowNumberWidth: 44, data: data, rowIdKey: rowIdKey, active: active, activeCol: active.c, isEditing: isEditing, readOnly: readOnly, selectionBounds: selection.getBounds(), copiedBounds: copiedBounds, getColWidth: getColWidth, getValue: getValue, hasError: hasError, getError: getError, isPreview: isPreview, activeCellRef: activeCellRef, onCellMouseDown: (r, c, ev) => { ev.preventDefault(); setActive({ r, c }); selection.startSelection({ r, c }); setCopiedBounds(null); focusClipboard(); if (filters.filterOpenKey)
180
+ filters.closeFilter(); }, onCellMouseEnter: (r, c) => { if (selection.isSelecting)
181
+ selection.updateSelection({ r, c }); }, onCellDoubleClick: (r, c) => { setActive({ r, c }); startEditCell(); }, onCheckboxToggle: toggleCheckbox, onSelectOpen: openSelectAt, onFillStart: (r, c, ev) => { ev.preventDefault(); ev.stopPropagation(); startDrag(r, c); }, onResizeStart: (c, ev) => { ev.preventDefault(); ev.stopPropagation(); startResize(c, ev.clientX); }, onResizeDoubleClick: (c, ev) => { ev.preventDefault(); ev.stopPropagation(); autoFitCol(c); }, onGroupToggle: toggleGroup, filterOpenKey: filters.filterOpenKey, filterSearch: filters.filterSearch, getFilterOptions: filters.getFilterOptions, isFilterChecked: filters.isFilterChecked, isFilterAllChecked: filters.isAllChecked, onFilterOpen: filters.openFilter, onFilterSearch: filters.setFilterSearch, onFilterToggle: filters.toggleFilterValue, onFilterToggleAll: filters.toggleAll }) }), isEditing && editorRect && (_jsx("input", { ref: editorRef, className: "xontable-editor", name: "xontable-editor", value: draft, onChange: (e) => setDraft(e.target.value), onKeyDown: onEditorKeyDown, onBlur: () => { commitEdit(); validateSelect(active.r, active.c, draft, activeRow); }, style: { position: "fixed", left: editorRect.left, top: editorRect.top, width: editorRect.width, height: editorRect.height } })), _jsx(SelectMenu, { isOpen: Boolean(isEditing && activeCol?.type === "select"), rect: editorRect, options: activeRow && activeCol ? getOptions(activeRow, activeCol) : [], loading: Boolean(activeRow && activeCol && isLoading(activeRow, activeCol)), filter: draft, onSelect: (v) => { setDraft(v); commitEdit(v); validateSelect(active.r, active.c, v, activeRow); } })] }));
182
+ }
@@ -0,0 +1,13 @@
1
+ type ColumnFilterMenuProps = {
2
+ isOpen: boolean;
3
+ search: string;
4
+ options: string[];
5
+ allChecked: boolean;
6
+ isChecked: (value: string) => boolean;
7
+ onSearchChange: (value: string) => void;
8
+ onToggle: (value: string) => void;
9
+ onToggleAll: () => void;
10
+ };
11
+ export declare function ColumnFilterMenu(props: ColumnFilterMenuProps): import("react/jsx-runtime").JSX.Element | null;
12
+ export {};
13
+ //# sourceMappingURL=ColumnFilterMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColumnFilterMenu.d.ts","sourceRoot":"","sources":["../../src/components/ColumnFilterMenu.tsx"],"names":[],"mappings":"AAEA,KAAK,qBAAqB,GAAG;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,kDAiD5D"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function ColumnFilterMenu(props) {
3
+ const { isOpen, search, options, allChecked, isChecked, onSearchChange, onToggle, onToggleAll, } = props;
4
+ if (!isOpen)
5
+ return null;
6
+ return (_jsxs("div", { className: "xontable-filter-menu", children: [_jsx("input", { className: "xontable-filter-search", name: "xontable-filter-search", value: search, onChange: (e) => onSearchChange(e.target.value), onKeyDown: (e) => e.stopPropagation(), onMouseDown: (e) => e.stopPropagation(), placeholder: "Search" }), _jsx("div", { className: "xontable-filter-actions", children: _jsxs("label", { className: "xontable-filter-toggle", children: [_jsx("input", { type: "checkbox", checked: allChecked, onChange: onToggleAll }), _jsx("span", { children: "All" })] }) }), _jsx("div", { className: "xontable-filter-list", children: options.map((opt) => (_jsxs("label", { className: "xontable-filter-item", children: [_jsx("input", { type: "checkbox", checked: isChecked(opt), onChange: () => onToggle(opt) }), _jsx("span", { title: opt, children: opt || "(blank)" })] }, opt))) })] }));
7
+ }
@@ -0,0 +1,15 @@
1
+ type Option = {
2
+ value: string;
3
+ label: string;
4
+ };
5
+ type SelectMenuProps = {
6
+ isOpen: boolean;
7
+ rect: DOMRect | null;
8
+ options: Option[];
9
+ loading: boolean;
10
+ filter: string;
11
+ onSelect: (value: string) => void;
12
+ };
13
+ export declare function SelectMenu(props: SelectMenuProps): import("react/jsx-runtime").JSX.Element | null;
14
+ export {};
15
+ //# sourceMappingURL=SelectMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectMenu.d.ts","sourceRoot":"","sources":["../../src/components/SelectMenu.tsx"],"names":[],"mappings":"AAEA,KAAK,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/C,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,kDA+DhD"}
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function SelectMenu(props) {
3
+ const { isOpen, rect, options, loading, filter, onSelect } = props;
4
+ if (!isOpen || !rect)
5
+ return null;
6
+ const q = filter.toLowerCase();
7
+ const list = q ? options.filter((o) => o.label.toLowerCase().includes(q)) : options;
8
+ const current = filter.trim();
9
+ const vw = window.innerWidth;
10
+ const vh = window.innerHeight;
11
+ const width = Math.min(rect.width, vw - 16);
12
+ const left = Math.min(Math.max(8, rect.left), vw - width - 8);
13
+ const maxH = Math.min(220, vh - rect.bottom - 12);
14
+ const placeAbove = maxH < 120;
15
+ const top = placeAbove ? Math.max(8, rect.top - 6) : rect.bottom + 2;
16
+ const renderLabel = (label) => {
17
+ if (!q)
18
+ return label;
19
+ const idx = label.toLowerCase().indexOf(q);
20
+ if (idx < 0)
21
+ return label;
22
+ const before = label.slice(0, idx);
23
+ const match = label.slice(idx, idx + q.length);
24
+ const after = label.slice(idx + q.length);
25
+ return (_jsxs(_Fragment, { children: [before, _jsx("strong", { children: match }), after] }));
26
+ };
27
+ return (_jsxs("div", { className: "xontable-select-menu", style: {
28
+ position: "fixed",
29
+ left,
30
+ top,
31
+ width,
32
+ maxHeight: placeAbove ? Math.min(220, rect.top - 12) : maxH,
33
+ transform: placeAbove ? "translateY(-100%)" : "none",
34
+ }, children: [loading && _jsx("div", { className: "xontable-select-item", children: "Loading..." }), !loading && list.length === 0 && (_jsx("div", { className: "xontable-select-item", children: "No results" })), !loading && list.map((opt) => (_jsx("button", { type: "button", className: [
35
+ "xontable-select-item",
36
+ current && (opt.value === current || opt.label === current) ? "is-active" : "",
37
+ ].join(" "), onMouseDown: (e) => e.preventDefault(), onClick: () => onSelect(opt.value), title: opt.label, children: renderLabel(opt.label) }, opt.value)))] }));
38
+ }
@@ -0,0 +1,62 @@
1
+ import React from "react";
2
+ import type { CellPos, ColumnDef } from "../types";
3
+ type GroupHeader = {
4
+ key: string;
5
+ label: string;
6
+ width: number;
7
+ collapsible: boolean;
8
+ collapsed: boolean;
9
+ };
10
+ type GridProps<Row extends Record<string, any>> = {
11
+ columns: Array<{
12
+ col: ColumnDef<Row>;
13
+ idx: number | null;
14
+ }>;
15
+ groups?: GroupHeader[];
16
+ rowNumberWidth: number;
17
+ data: Row[];
18
+ rowIdKey: keyof Row;
19
+ active: CellPos;
20
+ activeCol: number;
21
+ isEditing: boolean;
22
+ readOnly: boolean;
23
+ selectionBounds: {
24
+ r1: number;
25
+ r2: number;
26
+ c1: number;
27
+ c2: number;
28
+ } | null;
29
+ copiedBounds: {
30
+ r1: number;
31
+ r2: number;
32
+ c1: number;
33
+ c2: number;
34
+ } | null;
35
+ getColWidth: (c: number) => number;
36
+ getValue: (r: number, c: number) => string;
37
+ hasError: (r: number, c: number) => boolean;
38
+ getError: (r: number, c: number) => string | null;
39
+ isPreview: (r: number, c: number) => boolean;
40
+ activeCellRef: React.RefObject<HTMLDivElement | null>;
41
+ onCellMouseDown: (r: number, c: number, ev: React.MouseEvent) => void;
42
+ onCellMouseEnter: (r: number, c: number, ev: React.MouseEvent) => void;
43
+ onCellDoubleClick: (r: number, c: number) => void;
44
+ onCheckboxToggle: (r: number, c: number) => void;
45
+ onSelectOpen: (r: number, c: number) => void;
46
+ onFillStart: (r: number, c: number, ev: React.MouseEvent) => void;
47
+ onResizeStart: (c: number, ev: React.MouseEvent) => void;
48
+ onResizeDoubleClick: (c: number, ev: React.MouseEvent) => void;
49
+ onGroupToggle: (key: string) => void;
50
+ filterOpenKey: string | null;
51
+ filterSearch: string;
52
+ getFilterOptions: (key: string) => string[];
53
+ isFilterChecked: (key: string, value: string) => boolean;
54
+ isFilterAllChecked: (key: string) => boolean;
55
+ onFilterOpen: (key: string) => void;
56
+ onFilterSearch: (value: string) => void;
57
+ onFilterToggle: (key: string, value: string) => void;
58
+ onFilterToggleAll: (key: string) => void;
59
+ };
60
+ export declare function XOnTableGrid<Row extends Record<string, any>>(props: GridProps<Row>): import("react/jsx-runtime").JSX.Element;
61
+ export {};
62
+ //# sourceMappingURL=XOnTableGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"XOnTableGrid.d.ts","sourceRoot":"","sources":["../../src/components/XOnTableGrid.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { XOnTableHeader } from "./XOnTableHeader";
3
+ export function XOnTableGrid(props) {
4
+ const { columns, data, rowIdKey, active, isEditing, readOnly, selectionBounds, copiedBounds, getColWidth, getValue, hasError, getError, isPreview, activeCellRef, onCellMouseDown, onCellMouseEnter, onCellDoubleClick, onCheckboxToggle, onSelectOpen, onFillStart } = props;
5
+ return (_jsxs("div", { className: "xontable", children: [_jsx(XOnTableHeader, { ...props }), data.map((row, r) => (_jsxs("div", { className: ["xontable-row", r % 2 === 1 ? "is-zebra" : ""].join(" "), "data-row": r, children: [_jsx("div", { className: ["xontable-cell", "xontable-rownum-cell", r === active.r ? "is-active-rownum" : ""].join(" "), style: { width: props.rowNumberWidth }, children: r + 1 }), columns.map(({ col, idx }, c) => {
6
+ const isActive = active.r === r && active.c === c;
7
+ const invalid = hasError(r, c);
8
+ const preview = isPreview(r, c);
9
+ const inSel = !!selectionBounds && r >= selectionBounds.r1 && r <= selectionBounds.r2 && c >= selectionBounds.c1 && c <= selectionBounds.c2;
10
+ const inCopy = !!copiedBounds && r >= copiedBounds.r1 && r <= copiedBounds.r2 && c >= copiedBounds.c1 && c <= copiedBounds.c2;
11
+ const selTop = inSel && selectionBounds && r === selectionBounds.r1;
12
+ const selBottom = inSel && selectionBounds && r === selectionBounds.r2;
13
+ const selLeft = inSel && selectionBounds && c === selectionBounds.c1;
14
+ const selRight = inSel && selectionBounds && c === selectionBounds.c2;
15
+ const copyTop = inCopy && copiedBounds && r === copiedBounds.r1;
16
+ const copyBottom = inCopy && copiedBounds && r === copiedBounds.r2;
17
+ const copyLeft = inCopy && copiedBounds && c === copiedBounds.c1;
18
+ const copyRight = inCopy && copiedBounds && c === copiedBounds.c2;
19
+ const isCheckbox = col.type === "checkbox";
20
+ const isSelect = col.type === "select";
21
+ const checked = row[col.key] === true || row[col.key] === "true";
22
+ const isPlaceholder = idx == null;
23
+ return (_jsxs("div", { ref: isActive ? activeCellRef : null, "data-row": r, "data-col": c, className: [
24
+ "xontable-cell",
25
+ isActive ? "is-active" : "",
26
+ inSel ? "is-range" : "",
27
+ selTop ? "is-range-top" : "",
28
+ selRight ? "is-range-right" : "",
29
+ selBottom ? "is-range-bottom" : "",
30
+ selLeft ? "is-range-left" : "",
31
+ inCopy ? "is-copied-range" : "",
32
+ copyTop ? "is-copied-top" : "",
33
+ copyRight ? "is-copied-right" : "",
34
+ copyBottom ? "is-copied-bottom" : "",
35
+ copyLeft ? "is-copied-left" : "",
36
+ isCheckbox ? "is-checkbox" : "",
37
+ isSelect ? "is-select" : "",
38
+ invalid ? "is-invalid" : "",
39
+ preview ? "is-fill-preview" : "",
40
+ ].join(" "), style: { width: getColWidth(c) }, title: invalid ? (getError(r, c) ?? "") : "", onMouseDown: (ev) => onCellMouseDown(r, c, ev), onMouseEnter: (ev) => onCellMouseEnter(r, c, ev), onDoubleClick: () => onCellDoubleClick(r, c), children: [isCheckbox ? (_jsx("input", { type: "checkbox", className: "xontable-checkbox", name: "xontable-checkbox", checked: checked, onChange: () => onCheckboxToggle(r, c), onClick: (ev) => ev.stopPropagation(), onMouseDown: (ev) => ev.stopPropagation() })) : (isPlaceholder ? "" : getValue(r, c)), isSelect && !readOnly && !isEditing && (_jsx("button", { type: "button", className: "xontable-select-trigger", title: "Open", onClick: (ev) => { ev.preventDefault(); ev.stopPropagation(); onSelectOpen(r, c); } })), isActive && !isEditing && !readOnly && (_jsx("div", { className: "xontable-fill-handle", onMouseDown: (ev) => onFillStart(r, c, ev), title: "Drag to fill" }))] }, col.key + String(idx ?? c)));
41
+ })] }, String(row[rowIdKey] ?? r))))] }));
42
+ }
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import type { ColumnDef } from "../types";
3
+ type GroupHeader = {
4
+ key: string;
5
+ label: string;
6
+ width: number;
7
+ collapsible: boolean;
8
+ collapsed: boolean;
9
+ };
10
+ type HeaderProps<Row extends Record<string, any>> = {
11
+ columns: Array<{
12
+ col: ColumnDef<Row>;
13
+ idx: number | null;
14
+ }>;
15
+ groups?: GroupHeader[];
16
+ rowNumberWidth: number;
17
+ activeCol: number;
18
+ getColWidth: (c: number) => number;
19
+ onResizeStart: (c: number, ev: React.MouseEvent) => void;
20
+ onResizeDoubleClick: (c: number, ev: React.MouseEvent) => void;
21
+ onGroupToggle: (key: string) => void;
22
+ filterOpenKey: string | null;
23
+ filterSearch: string;
24
+ getFilterOptions: (key: string) => string[];
25
+ isFilterChecked: (key: string, value: string) => boolean;
26
+ isFilterAllChecked: (key: string) => boolean;
27
+ onFilterOpen: (key: string) => void;
28
+ onFilterSearch: (value: string) => void;
29
+ onFilterToggle: (key: string, value: string) => void;
30
+ onFilterToggleAll: (key: string) => void;
31
+ };
32
+ export declare function XOnTableHeader<Row extends Record<string, any>>(props: HeaderProps<Row>): import("react/jsx-runtime").JSX.Element;
33
+ export {};
34
+ //# sourceMappingURL=XOnTableHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"XOnTableHeader.d.ts","sourceRoot":"","sources":["../../src/components/XOnTableHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,KAAK,WAAW,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3G,KAAK,WAAW,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;IAClD,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC5D,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IACzD,mBAAmB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC/D,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;IAC5C,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACzD,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C,CAAC;AAEF,wBAAgB,cAAc,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,2CAiDtF"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Filter, Minus, Plus } from "lucide-react";
3
+ import { ColumnFilterMenu } from "./ColumnFilterMenu";
4
+ export function XOnTableHeader(props) {
5
+ const { columns, groups, rowNumberWidth, activeCol, getColWidth, onResizeStart, onResizeDoubleClick, onGroupToggle, filterOpenKey, filterSearch, getFilterOptions, isFilterChecked, isFilterAllChecked, onFilterOpen, onFilterSearch, onFilterToggle, onFilterToggleAll } = props;
6
+ return (_jsxs(_Fragment, { children: [groups && groups.length > 0 && (_jsxs("div", { className: "xontable-row xontable-group-row", children: [_jsx("div", { className: "xontable-cell xontable-rownum-cell xontable-group-cell", style: { width: rowNumberWidth } }), groups.map((g) => (_jsxs("div", { className: "xontable-cell xontable-group-cell", style: { width: g.width }, children: [_jsx("span", { className: "xontable-group-label", children: g.label }), g.collapsible && (_jsx("button", { type: "button", className: "xontable-group-toggle", onClick: () => onGroupToggle(g.key), title: g.collapsed ? "Expand" : "Collapse", children: g.collapsed ? _jsx(Plus, { size: 12 }) : _jsx(Minus, { size: 12 }) }))] }, g.key)))] })), _jsxs("div", { className: "xontable-row xontable-head", children: [_jsx("div", { className: "xontable-cell xontable-rownum-cell xontable-head-cell", style: { width: rowNumberWidth } }), columns.map(({ col, idx }, c) => (_jsxs("div", { className: ["xontable-cell", "xontable-head-cell", c === activeCol ? "is-active-col-head" : ""].join(" "), style: { width: getColWidth(c) }, children: [_jsx("span", { className: "xontable-head-label", children: col.label }), idx != null && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "xontable-filter-btn", onClick: () => onFilterOpen(col.key), title: "Filter", children: _jsx(Filter, { size: 14 }) }), _jsx("div", { className: "xontable-col-resizer", onMouseDown: (ev) => onResizeStart(c, ev), onDoubleClick: (ev) => onResizeDoubleClick(c, ev), title: "Drag to resize" }), _jsx(ColumnFilterMenu, { isOpen: filterOpenKey === col.key, search: filterSearch, options: getFilterOptions(col.key), allChecked: isFilterAllChecked(col.key), isChecked: (v) => isFilterChecked(col.key, v), onSearchChange: onFilterSearch, onToggle: (v) => onFilterToggle(col.key, v), onToggleAll: () => onFilterToggleAll(col.key) })] }))] }, col.key + String(idx ?? c))))] })] }));
7
+ }
@@ -0,0 +1,5 @@
1
+ export { ColumnFilterMenu } from "./ColumnFilterMenu";
2
+ export { SelectMenu } from "./SelectMenu";
3
+ export { XOnTableGrid } from "./XOnTableGrid";
4
+ export { XOnTableHeader } from "./XOnTableHeader";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { ColumnFilterMenu } from "./ColumnFilterMenu";
2
+ export { SelectMenu } from "./SelectMenu";
3
+ export { XOnTableGrid } from "./XOnTableGrid";
4
+ export { XOnTableHeader } from "./XOnTableHeader";
@@ -0,0 +1,14 @@
1
+ export { useAutoRows } from "./useAutoRows";
2
+ export { useClipboardCatcher } from "./useClipboardCatcher";
3
+ export { useColumnFilters } from "./useColumnFilters";
4
+ export { useColumnGroups } from "./useColumnGroups";
5
+ export { useColumnResize } from "./useColumnResize";
6
+ export { useEditorOverlay } from "./useEditorOverlay";
7
+ export { useFillHandle } from "./useFillHandle";
8
+ export { useGridKeydown } from "./useGridKeydown";
9
+ export { useOutsideClick } from "./useOutsideClick";
10
+ export { useRangeSelection } from "./useRangeSelection";
11
+ export { useSelectOptions } from "./useSelectOptions";
12
+ export { useTableModel } from "./useTableModel";
13
+ export { useValidation } from "./useValidation";
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,13 @@
1
+ export { useAutoRows } from "./useAutoRows";
2
+ export { useClipboardCatcher } from "./useClipboardCatcher";
3
+ export { useColumnFilters } from "./useColumnFilters";
4
+ export { useColumnGroups } from "./useColumnGroups";
5
+ export { useColumnResize } from "./useColumnResize";
6
+ export { useEditorOverlay } from "./useEditorOverlay";
7
+ export { useFillHandle } from "./useFillHandle";
8
+ export { useGridKeydown } from "./useGridKeydown";
9
+ export { useOutsideClick } from "./useOutsideClick";
10
+ export { useRangeSelection } from "./useRangeSelection";
11
+ export { useSelectOptions } from "./useSelectOptions";
12
+ export { useTableModel } from "./useTableModel";
13
+ export { useValidation } from "./useValidation";
@@ -0,0 +1,9 @@
1
+ import type { ColumnDef, XOnTableMeta } from "../types";
2
+ type AutoRowsResult<Row> = {
3
+ normalizedRows: Row[];
4
+ handleChange: (rows: Row[], meta: XOnTableMeta) => void;
5
+ createRow: () => Row;
6
+ };
7
+ export declare function useAutoRows<Row extends Record<string, any>>(columns: ColumnDef<Row>[], rows: Row[], rowIdKey: keyof Row, onChange?: (rows: Row[], meta: XOnTableMeta) => void, pinnedRowId?: string | null): AutoRowsResult<Row>;
8
+ export {};
9
+ //# sourceMappingURL=useAutoRows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAutoRows.d.ts","sourceRoot":"","sources":["../../src/hooks/useAutoRows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExD,KAAK,cAAc,CAAC,GAAG,IAAI;IACzB,cAAc,EAAE,GAAG,EAAE,CAAC;IACtB,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACxD,SAAS,EAAE,MAAM,GAAG,CAAC;CACtB,CAAC;AAEF,wBAAgB,WAAW,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzD,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,EACzB,IAAI,EAAE,GAAG,EAAE,EACX,QAAQ,EAAE,MAAM,GAAG,EACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,EACpD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,cAAc,CAAC,GAAG,CAAC,CAiCrB"}
@@ -0,0 +1,41 @@
1
+ import { useCallback, useMemo, useRef } from "react";
2
+ export function useAutoRows(columns, rows, rowIdKey, onChange, pinnedRowId) {
3
+ const idRef = useRef(0);
4
+ const makeRowId = useCallback(() => `row_${Date.now()}_${idRef.current++}`, []);
5
+ const createRow = useCallback(() => {
6
+ const row = {};
7
+ columns.forEach((col) => { if (col.key !== String(rowIdKey))
8
+ row[col.key] = col.type === "checkbox" ? false : ""; });
9
+ row[String(rowIdKey)] = makeRowId();
10
+ return row;
11
+ }, [columns, makeRowId, rowIdKey]);
12
+ const isRowEmpty = useCallback((row) => {
13
+ const id = String(row[rowIdKey] ?? "");
14
+ if (pinnedRowId && id === pinnedRowId)
15
+ return false;
16
+ return columns.every((col) => {
17
+ if (col.key === String(rowIdKey))
18
+ return true;
19
+ const v = row[col.key];
20
+ if (col.type === "checkbox")
21
+ return v !== true;
22
+ return v == null || v === "";
23
+ });
24
+ }, [columns, pinnedRowId, rowIdKey]);
25
+ const ensureTrailingBlank = useCallback((list) => {
26
+ if (list.length === 0)
27
+ return [createRow()];
28
+ let lastNonEmpty = -1;
29
+ for (let i = 0; i < list.length; i++)
30
+ if (!isRowEmpty(list[i]))
31
+ lastNonEmpty = i;
32
+ const keep = lastNonEmpty === -1 ? [] : list.slice(0, lastNonEmpty + 1);
33
+ const tail = list[lastNonEmpty + 1];
34
+ return [...keep, tail && isRowEmpty(tail) ? tail : createRow()];
35
+ }, [createRow, isRowEmpty]);
36
+ const normalizedRows = useMemo(() => ensureTrailingBlank(rows), [ensureTrailingBlank, rows]);
37
+ const handleChange = useCallback((next, meta) => {
38
+ onChange?.(ensureTrailingBlank(next), meta);
39
+ }, [ensureTrailingBlank, onChange]);
40
+ return { normalizedRows, handleChange, createRow };
41
+ }
@@ -0,0 +1,15 @@
1
+ import type { ClipboardEvent } from "react";
2
+ type ClipboardCatcherOptions = {
3
+ isEditing: boolean;
4
+ getCopyBlock: () => string[][];
5
+ onPasteBlock: (block: string[][]) => void;
6
+ onCopy?: () => void;
7
+ };
8
+ export declare function useClipboardCatcher(options: ClipboardCatcherOptions): {
9
+ clipRef: import("react").RefObject<HTMLTextAreaElement | null>;
10
+ focusClipboard: () => void;
11
+ onCopy: (e: ClipboardEvent) => void;
12
+ onPaste: (e: ClipboardEvent) => void;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=useClipboardCatcher.d.ts.map