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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useClipboardCatcher.d.ts","sourceRoot":"","sources":["../../src/hooks/useClipboardCatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAG5C,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,MAAM,EAAE,EAAE,CAAC;IAC/B,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,uBAAuB;;;gBAU5D,cAAc;iBAWd,cAAc;EAYrB"}
@@ -0,0 +1,29 @@
1
+ import { useCallback, useRef } from "react";
2
+ import { parseTSV, toTSV } from "../utils";
3
+ export function useClipboardCatcher(options) {
4
+ const { isEditing, getCopyBlock, onPasteBlock, onCopy: onCopyCb } = options;
5
+ const clipRef = useRef(null);
6
+ const focusClipboard = useCallback(() => {
7
+ clipRef.current?.focus();
8
+ clipRef.current?.select();
9
+ }, []);
10
+ const onCopy = useCallback((e) => {
11
+ if (isEditing)
12
+ return;
13
+ e.preventDefault();
14
+ const block = getCopyBlock();
15
+ e.clipboardData.setData("text/plain", toTSV(block));
16
+ onCopyCb?.();
17
+ }, [getCopyBlock, isEditing, onCopyCb]);
18
+ const onPaste = useCallback((e) => {
19
+ if (isEditing)
20
+ return;
21
+ e.preventDefault();
22
+ const text = e.clipboardData.getData("text/plain");
23
+ const block = parseTSV(text);
24
+ if (block.length === 0)
25
+ return;
26
+ onPasteBlock(block);
27
+ }, [isEditing, onPasteBlock]);
28
+ return { clipRef, focusClipboard, onCopy, onPaste };
29
+ }
@@ -0,0 +1,17 @@
1
+ import type { ColumnDef } from "../types";
2
+ type FiltersResult<Row extends Record<string, any>> = {
3
+ filterOpenKey: string | null;
4
+ filterSearch: string;
5
+ rowFilter: (row: Row) => boolean;
6
+ getFilterOptions: (key: string) => string[];
7
+ isFilterChecked: (key: string, value: string) => boolean;
8
+ isAllChecked: (key: string) => boolean;
9
+ openFilter: (key: string) => void;
10
+ closeFilter: () => void;
11
+ setFilterSearch: (value: string) => void;
12
+ toggleFilterValue: (key: string, value: string) => void;
13
+ toggleAll: (key: string) => void;
14
+ };
15
+ export declare function useColumnFilters<Row extends Record<string, any>>(columns: ColumnDef<Row>[], rows: Row[]): FiltersResult<Row>;
16
+ export {};
17
+ //# sourceMappingURL=useColumnFilters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useColumnFilters.d.ts","sourceRoot":"","sources":["../../src/hooks/useColumnFilters.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,KAAK,aAAa,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;IACpD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IACjC,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,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACvC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,GAAG,CAAC,CAmF5H"}
@@ -0,0 +1,95 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ export function useColumnFilters(columns, rows) {
3
+ const [filters, setFilters] = useState({});
4
+ const [filterOpenKey, setFilterOpenKey] = useState(null);
5
+ const [filterSearch, setFilterSearch] = useState("");
6
+ const rowFilter = useCallback((row) => {
7
+ for (const key of Object.keys(filters)) {
8
+ const set = filters[key];
9
+ if (!set)
10
+ continue;
11
+ if (set.size === 0)
12
+ return false;
13
+ const v = row[key];
14
+ const s = v == null ? "" : String(v);
15
+ if (!set.has(s))
16
+ return false;
17
+ }
18
+ if (filterOpenKey && filterSearch.trim() !== "") {
19
+ const v = row[filterOpenKey];
20
+ const s = v == null ? "" : String(v);
21
+ if (!s.toLowerCase().includes(filterSearch.toLowerCase()))
22
+ return false;
23
+ }
24
+ return true;
25
+ }, [filterOpenKey, filterSearch, filters]);
26
+ const filterOptions = useMemo(() => {
27
+ const map = new Map();
28
+ columns.forEach((col) => {
29
+ const set = new Set();
30
+ rows.forEach((r) => set.add(String(r[col.key] ?? "")));
31
+ map.set(col.key, Array.from(set).sort());
32
+ });
33
+ return map;
34
+ }, [columns, rows]);
35
+ const getFilterOptions = useCallback((key) => {
36
+ const list = filterOptions.get(key) ?? [];
37
+ if (!filterSearch || filterOpenKey !== key)
38
+ return list;
39
+ const q = filterSearch.toLowerCase();
40
+ return list.filter((v) => v.toLowerCase().includes(q));
41
+ }, [filterOpenKey, filterOptions, filterSearch]);
42
+ const isFilterChecked = useCallback((key, value) => {
43
+ const set = filters[key];
44
+ return !set || set.has(value);
45
+ }, [filters]);
46
+ const isAllChecked = useCallback((key) => !filters[key], [filters]);
47
+ const toggleFilterValue = useCallback((key, value) => {
48
+ const all = filterOptions.get(key) ?? [];
49
+ setFilters((prev) => {
50
+ const cur = prev[key];
51
+ const next = cur ? new Set(cur) : new Set(all);
52
+ if (next.has(value))
53
+ next.delete(value);
54
+ else
55
+ next.add(value);
56
+ const out = { ...prev };
57
+ if (next.size === all.length)
58
+ delete out[key];
59
+ else
60
+ out[key] = next;
61
+ return out;
62
+ });
63
+ }, [filterOptions]);
64
+ const toggleAll = useCallback((key) => {
65
+ setFilters((prev) => {
66
+ const next = { ...prev };
67
+ if (next[key])
68
+ delete next[key];
69
+ else
70
+ next[key] = new Set();
71
+ return next;
72
+ });
73
+ }, []);
74
+ const openFilter = useCallback((key) => {
75
+ setFilterOpenKey((k) => (k === key ? null : key));
76
+ setFilterSearch("");
77
+ }, []);
78
+ const closeFilter = useCallback(() => {
79
+ setFilterOpenKey(null);
80
+ setFilterSearch("");
81
+ }, []);
82
+ return {
83
+ filterOpenKey,
84
+ filterSearch,
85
+ rowFilter,
86
+ getFilterOptions,
87
+ isFilterChecked,
88
+ isAllChecked,
89
+ openFilter,
90
+ closeFilter,
91
+ setFilterSearch,
92
+ toggleFilterValue,
93
+ toggleAll,
94
+ };
95
+ }
@@ -0,0 +1,27 @@
1
+ import type { ColumnDef } from "../types";
2
+ type GroupHeader = {
3
+ key: string;
4
+ label: string;
5
+ width: number;
6
+ collapsible: boolean;
7
+ collapsed: boolean;
8
+ };
9
+ type VisibleCol<Row extends Record<string, any>> = {
10
+ col: ColumnDef<Row>;
11
+ idx: number | null;
12
+ };
13
+ type ColumnGroupsOptions<Row extends Record<string, any>> = {
14
+ columns: ColumnDef<Row>[];
15
+ collapsedWidth?: number;
16
+ };
17
+ export declare function useColumnGroups<Row extends Record<string, any>>(options: ColumnGroupsOptions<Row>): {
18
+ visibleColumns: VisibleCol<Row>[];
19
+ groupHeaders: GroupHeader[];
20
+ getColWidth: (visibleIndex: number) => number;
21
+ setColWidth: (visibleIndex: number, w: number) => void;
22
+ toggleGroup: (key: string) => void;
23
+ resetWidths: () => void;
24
+ getOrigIndex: (visibleIndex: number) => number | null;
25
+ };
26
+ export {};
27
+ //# sourceMappingURL=useColumnGroups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useColumnGroups.d.ts","sourceRoot":"","sources":["../../src/hooks/useColumnGroups.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AASF,KAAK,UAAU,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;IAAE,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE/F,KAAK,mBAAmB,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI;IAC1D,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,eAAe,CAAC,GAAG,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7D,OAAO,EAAE,mBAAmB,CAAC,GAAG,CAAC;;;gCAwChB,MAAM;gCAUN,MAAM,KAAK,MAAM;uBA6B1B,MAAM;;iCAYG,MAAM;EAaxB"}
@@ -0,0 +1,89 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ export function useColumnGroups(options) {
3
+ const { columns, collapsedWidth = 56 } = options;
4
+ const [collapsed, setCollapsed] = useState({});
5
+ const [colWidths, setColWidths] = useState(columns.map((c) => c.width ?? 140));
6
+ const groups = useMemo(() => {
7
+ const list = [];
8
+ let lastKey = "";
9
+ columns.forEach((col, idx) => {
10
+ const gKey = col.group ?? `__col_${idx}`;
11
+ const label = col.group ?? "";
12
+ const collapsible = col.group != null && col.groupCollapsible !== false;
13
+ if (list.length === 0 || lastKey !== gKey) {
14
+ list.push({ key: gKey, label, cols: [{ col, idx }], collapsible });
15
+ lastKey = gKey;
16
+ }
17
+ else {
18
+ list[list.length - 1].cols.push({ col, idx });
19
+ if (!collapsible)
20
+ list[list.length - 1].collapsible = false;
21
+ }
22
+ });
23
+ return list;
24
+ }, [columns]);
25
+ const visibleColumns = useMemo(() => {
26
+ const out = [];
27
+ for (const g of groups) {
28
+ const isCollapsed = g.collapsible && collapsed[g.key];
29
+ if (!g.collapsible || !isCollapsed) {
30
+ g.cols.forEach(({ col, idx }) => out.push({ col, idx }));
31
+ }
32
+ else {
33
+ out.push({ col: { key: `__group_${g.key}`, label: "", editable: false }, idx: null });
34
+ }
35
+ }
36
+ return out;
37
+ }, [groups, collapsed]);
38
+ const getColWidth = useCallback((visibleIndex) => {
39
+ const v = visibleColumns[visibleIndex];
40
+ if (!v)
41
+ return 140;
42
+ if (v.idx == null)
43
+ return collapsedWidth;
44
+ return colWidths[v.idx] ?? 140;
45
+ }, [collapsedWidth, colWidths, visibleColumns]);
46
+ const setColWidth = useCallback((visibleIndex, w) => {
47
+ const v = visibleColumns[visibleIndex];
48
+ if (!v || v.idx == null)
49
+ return;
50
+ setColWidths((prev) => {
51
+ const next = [...prev];
52
+ next[v.idx] = w;
53
+ return next;
54
+ });
55
+ }, [visibleColumns]);
56
+ const groupHeaders = useMemo(() => {
57
+ return groups.map((g) => {
58
+ const isCollapsed = g.collapsible && collapsed[g.key];
59
+ const width = isCollapsed
60
+ ? collapsedWidth
61
+ : g.cols.reduce((sum, { idx }) => sum + (colWidths[idx] ?? 140), 0);
62
+ return {
63
+ key: g.key,
64
+ label: g.label,
65
+ width,
66
+ collapsible: g.collapsible,
67
+ collapsed: Boolean(isCollapsed),
68
+ };
69
+ });
70
+ }, [collapsed, collapsedWidth, colWidths, groups]);
71
+ const toggleGroup = useCallback((key) => setCollapsed((p) => ({ ...p, [key]: !p[key] })), []);
72
+ const resetWidths = useCallback(() => {
73
+ setColWidths((prev) => {
74
+ if (!prev.length)
75
+ return columns.map((c) => c.width ?? 140);
76
+ return columns.map((c, i) => prev[i] ?? c.width ?? 140);
77
+ });
78
+ }, [columns]);
79
+ const getOrigIndex = useCallback((visibleIndex) => visibleColumns[visibleIndex]?.idx ?? null, [visibleColumns]);
80
+ return {
81
+ visibleColumns,
82
+ groupHeaders,
83
+ getColWidth,
84
+ setColWidth,
85
+ toggleGroup,
86
+ resetWidths,
87
+ getOrigIndex,
88
+ };
89
+ }
@@ -0,0 +1,12 @@
1
+ type ResizeOptions = {
2
+ colCount: number;
3
+ getWidth: (c: number) => number;
4
+ setWidth: (c: number, w: number) => void;
5
+ minWidth?: number;
6
+ };
7
+ export declare function useColumnResize(options: ResizeOptions): {
8
+ startResize: (col: number, startX: number) => void;
9
+ isResizing: boolean;
10
+ };
11
+ export {};
12
+ //# sourceMappingURL=useColumnResize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useColumnResize.d.ts","sourceRoot":"","sources":["../../src/hooks/useColumnResize.ts"],"names":[],"mappings":"AAIA,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa;uBAK5C,MAAM,UAAU,MAAM;;EA2B/B"}
@@ -0,0 +1,27 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ export function useColumnResize(options) {
3
+ const { colCount, getWidth, setWidth, minWidth = 60 } = options;
4
+ const [drag, setDrag] = useState(null);
5
+ const startResize = useCallback((col, startX) => {
6
+ if (col < 0 || col >= colCount)
7
+ return;
8
+ setDrag({ col, startX, startW: getWidth(col) });
9
+ }, [colCount, getWidth]);
10
+ useEffect(() => {
11
+ if (!drag)
12
+ return;
13
+ const onMove = (e) => {
14
+ const dx = e.clientX - drag.startX;
15
+ const next = Math.max(minWidth, drag.startW + dx);
16
+ setWidth(drag.col, next);
17
+ };
18
+ const onUp = () => setDrag(null);
19
+ window.addEventListener("mousemove", onMove);
20
+ window.addEventListener("mouseup", onUp);
21
+ return () => {
22
+ window.removeEventListener("mousemove", onMove);
23
+ window.removeEventListener("mouseup", onUp);
24
+ };
25
+ }, [drag, minWidth, setWidth]);
26
+ return { startResize, isResizing: Boolean(drag) };
27
+ }
@@ -0,0 +1,24 @@
1
+ import type { KeyboardEvent, RefObject } from "react";
2
+ import type { CellPos } from "../types";
3
+ type EditorOptions = {
4
+ active: CellPos;
5
+ activeCellRef: RefObject<HTMLDivElement | null>;
6
+ getValue: (r: number, c: number) => string;
7
+ onCommit: (value: string) => void;
8
+ onCancel?: () => void;
9
+ onTab?: (dir: number) => void;
10
+ onEnter?: (dir: number) => void;
11
+ };
12
+ export declare function useEditorOverlay(options: EditorOptions): {
13
+ isEditing: boolean;
14
+ draft: string;
15
+ setDraft: import("react").Dispatch<import("react").SetStateAction<string>>;
16
+ editorRect: DOMRect | null;
17
+ editorRef: RefObject<HTMLInputElement | null>;
18
+ startEdit: (initialValue?: string) => void;
19
+ commitEdit: (value?: string) => void;
20
+ cancelEdit: () => void;
21
+ onEditorKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
22
+ };
23
+ export {};
24
+ //# sourceMappingURL=useEditorOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEditorOverlay.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorOverlay.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,KAAK,aAAa,GAAG;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAChD,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa;;;;;;+BA2BnC,MAAM;yBAegB,MAAM;;yBAWxC,aAAa,CAAC,gBAAgB,CAAC;EA4BtC"}
@@ -0,0 +1,74 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ export function useEditorOverlay(options) {
3
+ const { active, activeCellRef, getValue, onCommit, onCancel, onTab, onEnter } = options;
4
+ const [isEditing, setIsEditing] = useState(false);
5
+ const [draft, setDraft] = useState("");
6
+ const [editorRect, setEditorRect] = useState(null);
7
+ const [selectAll, setSelectAll] = useState(true);
8
+ const editorRef = useRef(null);
9
+ const measureRect = useCallback(() => {
10
+ const el = activeCellRef.current;
11
+ if (!el)
12
+ return;
13
+ setEditorRect(el.getBoundingClientRect());
14
+ requestAnimationFrame(() => {
15
+ const input = editorRef.current;
16
+ if (!input)
17
+ return;
18
+ input.focus();
19
+ if (selectAll) {
20
+ input.select();
21
+ }
22
+ else {
23
+ const len = input.value.length;
24
+ input.setSelectionRange(len, len);
25
+ }
26
+ });
27
+ }, [activeCellRef, selectAll]);
28
+ const startEdit = useCallback((initialValue) => {
29
+ const current = getValue(active.r, active.c);
30
+ setDraft(initialValue ?? current);
31
+ setSelectAll(initialValue == null);
32
+ setIsEditing(true);
33
+ requestAnimationFrame(measureRect);
34
+ }, [active, getValue, measureRect]);
35
+ const finish = useCallback(() => {
36
+ setIsEditing(false);
37
+ setEditorRect(null);
38
+ }, []);
39
+ const commitEdit = useCallback((value) => {
40
+ onCommit(value ?? draft);
41
+ finish();
42
+ }, [draft, finish, onCommit]);
43
+ const cancelEdit = useCallback(() => {
44
+ onCancel?.();
45
+ finish();
46
+ }, [finish, onCancel]);
47
+ const onEditorKeyDown = useCallback((e) => {
48
+ if (e.key === "Enter") {
49
+ e.preventDefault();
50
+ commitEdit();
51
+ onEnter?.(e.shiftKey ? -1 : 1);
52
+ }
53
+ else if (e.key === "Escape") {
54
+ e.preventDefault();
55
+ cancelEdit();
56
+ }
57
+ else if (e.key === "Tab") {
58
+ e.preventDefault();
59
+ commitEdit();
60
+ onTab?.(e.shiftKey ? -1 : 1);
61
+ }
62
+ }, [cancelEdit, commitEdit, onEnter, onTab]);
63
+ return {
64
+ isEditing,
65
+ draft,
66
+ setDraft,
67
+ editorRect,
68
+ editorRef,
69
+ startEdit,
70
+ commitEdit,
71
+ cancelEdit,
72
+ onEditorKeyDown,
73
+ };
74
+ }
@@ -0,0 +1,16 @@
1
+ type DragState = {
2
+ startR: number;
3
+ startC: number;
4
+ endR: number;
5
+ endC: number;
6
+ };
7
+ type FillHandleOptions = {
8
+ onApply: (startR: number, startC: number, endR: number, endC: number) => void;
9
+ };
10
+ export declare function useFillHandle(options: FillHandleOptions): {
11
+ drag: DragState | null;
12
+ startDrag: (r: number, c: number) => void;
13
+ isPreview: (r: number, c: number) => boolean;
14
+ };
15
+ export {};
16
+ //# sourceMappingURL=useFillHandle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFillHandle.d.ts","sourceRoot":"","sources":["../../src/hooks/useFillHandle.ts"],"names":[],"mappings":"AAEA,KAAK,SAAS,GAAG;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/E,CAAC;AAEF,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB;;mBAIpB,MAAM,KAAK,MAAM;mBAkC7C,MAAM,KAAK,MAAM;EAmBxB"}
@@ -0,0 +1,52 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ export function useFillHandle(options) {
3
+ const { onApply } = options;
4
+ const [drag, setDrag] = useState(null);
5
+ const startDrag = useCallback((r, c) => {
6
+ setDrag({ startR: r, startC: c, endR: r, endC: c });
7
+ }, []);
8
+ useEffect(() => {
9
+ if (!drag)
10
+ return;
11
+ const onMove = (e) => {
12
+ const el = document.elementFromPoint(e.clientX, e.clientY);
13
+ const cell = el?.closest("[data-row][data-col]");
14
+ if (!cell)
15
+ return;
16
+ const r = Number(cell.dataset.row);
17
+ const c = Number(cell.dataset.col);
18
+ if (Number.isNaN(r) || Number.isNaN(c))
19
+ return;
20
+ setDrag((prev) => (prev ? { ...prev, endR: r, endC: c } : prev));
21
+ };
22
+ const onUp = () => {
23
+ onApply(drag.startR, drag.startC, drag.endR, drag.endC);
24
+ setDrag(null);
25
+ };
26
+ window.addEventListener("mousemove", onMove);
27
+ window.addEventListener("mouseup", onUp);
28
+ return () => {
29
+ window.removeEventListener("mousemove", onMove);
30
+ window.removeEventListener("mouseup", onUp);
31
+ };
32
+ }, [drag, onApply]);
33
+ const isPreview = useCallback((r, c) => {
34
+ if (!drag)
35
+ return false;
36
+ const dr = Math.abs(drag.endR - drag.startR);
37
+ const dc = Math.abs(drag.endC - drag.startC);
38
+ if (dc >= dr) {
39
+ if (r !== drag.startR)
40
+ return false;
41
+ const from = Math.min(drag.startC, drag.endC);
42
+ const to = Math.max(drag.startC, drag.endC);
43
+ return c >= from && c <= to;
44
+ }
45
+ if (c !== drag.startC)
46
+ return false;
47
+ const from = Math.min(drag.startR, drag.endR);
48
+ const to = Math.max(drag.startR, drag.endR);
49
+ return r >= from && r <= to;
50
+ }, [drag]);
51
+ return { drag, startDrag, isPreview };
52
+ }
@@ -0,0 +1,18 @@
1
+ import type { KeyboardEvent } from "react";
2
+ import type { CellPos } from "../types";
3
+ type GridKeydownOptions = {
4
+ active: CellPos;
5
+ rowCount: number;
6
+ colCount: number;
7
+ isEditing: boolean;
8
+ moveActive: (dr: number, dc: number) => void;
9
+ moveTo: (r: number, c: number) => void;
10
+ startEdit: (initial?: string) => void;
11
+ clearCell: () => void;
12
+ undo: () => void;
13
+ redo: () => void;
14
+ onShiftMove?: (dr: number, dc: number) => void;
15
+ };
16
+ export declare function useGridKeydown(options: GridKeydownOptions): (e: KeyboardEvent<HTMLElement>) => void;
17
+ export {};
18
+ //# sourceMappingURL=useGridKeydown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGridKeydown.d.ts","sourceRoot":"","sources":["../../src/hooks/useGridKeydown.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,SAAS,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,OAgBlD,aAAa,CAAC,WAAW,CAAC,UA0FjC"}
@@ -0,0 +1,109 @@
1
+ import { useCallback } from "react";
2
+ export function useGridKeydown(options) {
3
+ const { active, rowCount, colCount, isEditing, moveActive, moveTo, startEdit, clearCell, undo, redo, onShiftMove, } = options;
4
+ return useCallback((e) => {
5
+ if (isEditing)
6
+ return;
7
+ const isCtrl = e.ctrlKey || e.metaKey;
8
+ const lastRow = Math.max(0, rowCount - 1);
9
+ const lastCol = Math.max(0, colCount - 1);
10
+ if (isCtrl && (e.key === "z" || e.key === "Z")) {
11
+ e.preventDefault();
12
+ if (e.shiftKey)
13
+ redo();
14
+ else
15
+ undo();
16
+ return;
17
+ }
18
+ if (isCtrl && (e.key === "y" || e.key === "Y")) {
19
+ e.preventDefault();
20
+ redo();
21
+ return;
22
+ }
23
+ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
24
+ e.preventDefault();
25
+ }
26
+ switch (e.key) {
27
+ case "ArrowUp":
28
+ if (e.shiftKey && onShiftMove)
29
+ onShiftMove(-1, 0);
30
+ else if (isCtrl)
31
+ moveTo(0, active.c);
32
+ else
33
+ moveActive(-1, 0);
34
+ break;
35
+ case "ArrowDown":
36
+ if (e.shiftKey && onShiftMove)
37
+ onShiftMove(1, 0);
38
+ else if (isCtrl)
39
+ moveTo(lastRow, active.c);
40
+ else
41
+ moveActive(1, 0);
42
+ break;
43
+ case "ArrowLeft":
44
+ if (e.shiftKey && onShiftMove)
45
+ onShiftMove(0, -1);
46
+ else if (isCtrl)
47
+ moveTo(active.r, 0);
48
+ else
49
+ moveActive(0, -1);
50
+ break;
51
+ case "ArrowRight":
52
+ if (e.shiftKey && onShiftMove)
53
+ onShiftMove(0, 1);
54
+ else if (isCtrl)
55
+ moveTo(active.r, lastCol);
56
+ else
57
+ moveActive(0, 1);
58
+ break;
59
+ case "Tab":
60
+ e.preventDefault();
61
+ moveActive(0, e.shiftKey ? -1 : 1);
62
+ break;
63
+ case "Enter":
64
+ e.preventDefault();
65
+ startEdit();
66
+ break;
67
+ case "F2":
68
+ e.preventDefault();
69
+ startEdit();
70
+ break;
71
+ case "Home":
72
+ e.preventDefault();
73
+ if (isCtrl)
74
+ moveTo(0, 0);
75
+ else
76
+ moveTo(active.r, 0);
77
+ break;
78
+ case "End":
79
+ e.preventDefault();
80
+ if (isCtrl)
81
+ moveTo(lastRow, lastCol);
82
+ else
83
+ moveTo(active.r, lastCol);
84
+ break;
85
+ case "Backspace":
86
+ case "Delete":
87
+ e.preventDefault();
88
+ clearCell();
89
+ break;
90
+ default:
91
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
92
+ e.preventDefault();
93
+ startEdit(e.key);
94
+ }
95
+ break;
96
+ }
97
+ }, [
98
+ active,
99
+ clearCell,
100
+ colCount,
101
+ isEditing,
102
+ moveActive,
103
+ moveTo,
104
+ redo,
105
+ rowCount,
106
+ startEdit,
107
+ undo,
108
+ ]);
109
+ }
@@ -0,0 +1,7 @@
1
+ type OutsideClickOptions = {
2
+ isOpen: boolean;
3
+ onClose: () => void;
4
+ };
5
+ export declare function useOutsideClick(options: OutsideClickOptions): void;
6
+ export {};
7
+ //# sourceMappingURL=useOutsideClick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOutsideClick.d.ts","sourceRoot":"","sources":["../../src/hooks/useOutsideClick.ts"],"names":[],"mappings":"AAEA,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,QAqB3D"}
@@ -0,0 +1,26 @@
1
+ import { useEffect } from "react";
2
+ export function useOutsideClick(options) {
3
+ const { isOpen, onClose } = options;
4
+ useEffect(() => {
5
+ if (!isOpen)
6
+ return;
7
+ const onDoc = (e) => {
8
+ const target = e.target;
9
+ if (!target)
10
+ return;
11
+ if (target.closest(".xontable-filter-menu") || target.closest(".xontable-filter-btn"))
12
+ return;
13
+ onClose();
14
+ };
15
+ const onKey = (e) => {
16
+ if (e.key === "Escape")
17
+ onClose();
18
+ };
19
+ document.addEventListener("mousedown", onDoc);
20
+ document.addEventListener("keydown", onKey);
21
+ return () => {
22
+ document.removeEventListener("mousedown", onDoc);
23
+ document.removeEventListener("keydown", onKey);
24
+ };
25
+ }, [isOpen, onClose]);
26
+ }