@shiguri/solid-grid 0.0.1 → 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/LICENSE +21 -0
- package/README.md +35 -18
- package/dist/gridsheet-D9A0kuhj.d.ts +138 -0
- package/dist/index.d.ts +73 -69
- package/dist/index.js +673 -301
- package/dist/index.js.map +1 -1
- package/dist/preset-tailwind.css +1 -0
- package/dist/presets.d.ts +9 -0
- package/dist/presets.js +56 -0
- package/dist/presets.js.map +1 -0
- package/package.json +26 -9
- package/dist/index.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { className, createComponent, delegateEvents, effect, insert, setAttribute, style, template, use } from "solid-js/web";
|
|
1
|
+
import { className, createComponent, delegateEvents, effect, insert, memo, setAttribute, style, template, use } from "solid-js/web";
|
|
2
2
|
import { For, Index, batch, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/gridsheet.tsx
|
|
5
5
|
var _tmpl$ = /* @__PURE__ */ template(`<table data-slot=gridsheet tabindex=-1><thead data-slot=gridsheet-header><tr data-slot=gridsheet-row><th data-slot=gridsheet-corner></th></tr></thead><tbody data-slot=gridsheet-body>`), _tmpl$2 = /* @__PURE__ */ template(`<tr data-slot=gridsheet-row>`), _tmpl$3 = /* @__PURE__ */ template(`<th data-slot=gridsheet-rowheader>`), _tmpl$4 = /* @__PURE__ */ template(`<th data-slot=gridsheet-colheader>`), _tmpl$5 = /* @__PURE__ */ template(`<td data-slot=gridsheet-cell tabindex=-1>`);
|
|
6
|
+
/** Zero-based cell position. */
|
|
7
|
+
/** Inclusive range of cells. */
|
|
6
8
|
function isPositionInRange(pos, range) {
|
|
7
9
|
return pos.row >= range.min.row && pos.row <= range.max.row && pos.col >= range.min.col && pos.col <= range.max.col;
|
|
8
10
|
}
|
|
11
|
+
/** Normalize two positions into an inclusive range. */
|
|
9
12
|
function normalizeRange(pos1, pos2) {
|
|
10
13
|
return {
|
|
11
14
|
min: {
|
|
@@ -18,296 +21,144 @@ function normalizeRange(pos1, pos2) {
|
|
|
18
21
|
}
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const isCellActive = (pos) => {
|
|
32
|
-
const ac = activeCell();
|
|
33
|
-
return ac !== null && ac.row === pos.row && ac.col === pos.col;
|
|
24
|
+
/** Render-time context for a single cell. */
|
|
25
|
+
/** Events emitted from Gridsheet to plugins/handlers. */
|
|
26
|
+
/** Return true to stop further handling. */
|
|
27
|
+
/** Props for the Gridsheet component. */
|
|
28
|
+
function createControllable(propsValue, onChange, initial) {
|
|
29
|
+
const [inner, setInner] = createSignal(initial);
|
|
30
|
+
const value = createMemo(() => propsValue() === void 0 ? inner() : propsValue());
|
|
31
|
+
const set = (v) => {
|
|
32
|
+
setInner(() => v);
|
|
33
|
+
onChange?.(v);
|
|
34
34
|
};
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
return [value, set];
|
|
36
|
+
}
|
|
37
|
+
/** Patch for a single cell update. */
|
|
38
|
+
/** API exposed to plugins and external handlers. */
|
|
39
|
+
function createGridApi(props) {
|
|
40
|
+
const numRows = createMemo(() => props.data.length);
|
|
41
|
+
const numCols = createMemo(() => props.data[0]?.length ?? 0);
|
|
42
|
+
const [activeCell, setActiveCell] = createControllable(() => props.activeCell, props.onActiveCellChange, null);
|
|
43
|
+
const [selection, setSelectionRaw] = createControllable(() => props.selection, props.onSelectionChange, null);
|
|
37
44
|
const setSelection = (range) => {
|
|
38
|
-
|
|
39
|
-
setInnerSelection(normalizedRange);
|
|
40
|
-
if (props.onSelectionChange) props.onSelectionChange(normalizedRange);
|
|
41
|
-
};
|
|
42
|
-
const isCellSelected = (pos) => {
|
|
43
|
-
const sel = selection();
|
|
44
|
-
return sel !== null && isPositionInRange(pos, sel);
|
|
45
|
-
};
|
|
46
|
-
const isRowHeaderSelected = (rowIndex) => {
|
|
47
|
-
const sel = selection();
|
|
48
|
-
return sel !== null && rowIndex >= sel.min.row && rowIndex <= sel.max.row;
|
|
49
|
-
};
|
|
50
|
-
const isColHeaderSelected = (colIndex) => {
|
|
51
|
-
const sel = selection();
|
|
52
|
-
return sel !== null && colIndex >= sel.min.col && colIndex <= sel.max.col;
|
|
53
|
-
};
|
|
54
|
-
const [innerIsEditing, setInnerIsEditing] = createSignal(false);
|
|
55
|
-
const isEditing = createMemo(() => props.isEditing === void 0 ? innerIsEditing() : props.isEditing);
|
|
56
|
-
const setIsEditing = (editing) => {
|
|
57
|
-
setInnerIsEditing(editing);
|
|
58
|
-
if (props.onIsEditingChange) props.onIsEditingChange(editing);
|
|
45
|
+
setSelectionRaw(range ? normalizeRange(range.min, range.max) : null);
|
|
59
46
|
};
|
|
60
|
-
const
|
|
61
|
-
|
|
47
|
+
const [isEditing, setIsEditing] = createControllable(() => props.isEditing, props.onIsEditingChange, false);
|
|
48
|
+
const updateCells = (patches) => {
|
|
49
|
+
props.onCellsChange?.(patches);
|
|
62
50
|
};
|
|
63
|
-
const
|
|
51
|
+
const beginEdit = (pos) => {
|
|
64
52
|
batch(() => {
|
|
65
53
|
setIsEditing(true);
|
|
66
54
|
setActiveCell(pos);
|
|
67
55
|
setSelection(normalizeRange(pos, pos));
|
|
68
56
|
});
|
|
69
57
|
};
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
setIsEditing(false);
|
|
78
|
-
};
|
|
79
|
-
const cancelCellEdit = () => {
|
|
58
|
+
const cancelEdit = () => setIsEditing(false);
|
|
59
|
+
const commitEdit = (pos, value) => {
|
|
60
|
+
updateCells([{
|
|
61
|
+
pos,
|
|
62
|
+
value
|
|
63
|
+
}]);
|
|
80
64
|
setIsEditing(false);
|
|
81
65
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (typeof document === "undefined" || previousBodyUserSelect === null) return;
|
|
95
|
-
const body = document.body;
|
|
96
|
-
if (body) body.style.userSelect = previousBodyUserSelect;
|
|
97
|
-
previousBodyUserSelect = null;
|
|
98
|
-
};
|
|
99
|
-
const handleMouseUp = () => {
|
|
100
|
-
setIsMouseDown(false);
|
|
101
|
-
setSelectionMode(DEFAULT_SELECTION_MODE);
|
|
102
|
-
setSelectionAnchor(null);
|
|
103
|
-
restoreTextSelectionAfterDrag();
|
|
104
|
-
};
|
|
105
|
-
onMount(() => {
|
|
106
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
107
|
-
});
|
|
108
|
-
onCleanup(() => {
|
|
109
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
110
|
-
restoreTextSelectionAfterDrag();
|
|
111
|
-
});
|
|
112
|
-
const handleMouseDownOnCell = (pos, e) => {
|
|
113
|
-
if (e.button !== 0) return;
|
|
114
|
-
if (isCellEditing(pos)) return;
|
|
115
|
-
if (e.detail === 2) {
|
|
116
|
-
e.preventDefault();
|
|
117
|
-
beginCellEdit(pos);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
e.preventDefault();
|
|
121
|
-
batch(() => {
|
|
122
|
-
disableTextSelectionDuringDrag();
|
|
123
|
-
setIsMouseDown(true);
|
|
124
|
-
setSelectionMode("cell");
|
|
125
|
-
setSelectionAnchor(pos);
|
|
126
|
-
setIsEditing(false);
|
|
127
|
-
setSelection(normalizeRange(pos, pos));
|
|
128
|
-
setActiveCell(pos);
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
|
-
const handleMouseOverOnCell = (pos, e) => {
|
|
132
|
-
if ((e.buttons & 1) === 0) return;
|
|
133
|
-
batch(() => {
|
|
134
|
-
if (!isMouseDown() || selectionMode() !== "cell") return;
|
|
135
|
-
const start = selectionAnchor();
|
|
136
|
-
if (start) setSelection(normalizeRange(start, pos));
|
|
137
|
-
else console.warn("selectionAnchor is null during mouse drag selection");
|
|
138
|
-
});
|
|
66
|
+
return {
|
|
67
|
+
numRows,
|
|
68
|
+
numCols,
|
|
69
|
+
activeCell,
|
|
70
|
+
selection,
|
|
71
|
+
isEditing,
|
|
72
|
+
setActiveCell,
|
|
73
|
+
setSelection,
|
|
74
|
+
beginEdit,
|
|
75
|
+
cancelEdit,
|
|
76
|
+
commitEdit,
|
|
77
|
+
updateCells
|
|
139
78
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
setSelectionMode("row");
|
|
146
|
-
const start = {
|
|
147
|
-
row: rowIndex,
|
|
148
|
-
col: 0
|
|
149
|
-
};
|
|
150
|
-
const end = {
|
|
151
|
-
row: rowIndex,
|
|
152
|
-
col: numCols() - 1
|
|
153
|
-
};
|
|
154
|
-
setSelectionAnchor(start);
|
|
155
|
-
setIsEditing(false);
|
|
156
|
-
setSelection(normalizeRange(start, end));
|
|
157
|
-
setActiveCell(start);
|
|
158
|
-
});
|
|
79
|
+
}
|
|
80
|
+
function Gridsheet(props) {
|
|
81
|
+
const api = createGridApi(props);
|
|
82
|
+
const emit = (ev) => {
|
|
83
|
+
props.onEvent?.(ev, api);
|
|
159
84
|
};
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const start = selectionAnchor();
|
|
164
|
-
if (start) setSelection(normalizeRange(start, {
|
|
165
|
-
row: rowIndex,
|
|
166
|
-
col: numCols() - 1
|
|
167
|
-
}));
|
|
168
|
-
else console.warn("selectionAnchor is null during mouse drag selection");
|
|
169
|
-
});
|
|
85
|
+
const isCellActive = (pos) => {
|
|
86
|
+
const ac = api.activeCell();
|
|
87
|
+
return ac !== null && ac.row === pos.row && ac.col === pos.col;
|
|
170
88
|
};
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
disableTextSelectionDuringDrag();
|
|
175
|
-
setIsMouseDown(true);
|
|
176
|
-
setSelectionMode("col");
|
|
177
|
-
const start = {
|
|
178
|
-
row: 0,
|
|
179
|
-
col: colIndex
|
|
180
|
-
};
|
|
181
|
-
const end = {
|
|
182
|
-
row: numRows() - 1,
|
|
183
|
-
col: colIndex
|
|
184
|
-
};
|
|
185
|
-
setSelectionAnchor(start);
|
|
186
|
-
setIsEditing(false);
|
|
187
|
-
setSelection(normalizeRange(start, end));
|
|
188
|
-
setActiveCell(start);
|
|
189
|
-
});
|
|
89
|
+
const isCellSelected = (pos) => {
|
|
90
|
+
const sel = api.selection();
|
|
91
|
+
return sel !== null && isPositionInRange(pos, sel);
|
|
190
92
|
};
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const start = selectionAnchor();
|
|
195
|
-
if (start) setSelection(normalizeRange(start, {
|
|
196
|
-
row: numRows() - 1,
|
|
197
|
-
col: colIndex
|
|
198
|
-
}));
|
|
199
|
-
else console.warn("selectionAnchor is null during mouse drag selection");
|
|
200
|
-
});
|
|
93
|
+
const isRowHeaderSelected = (rowIndex) => {
|
|
94
|
+
const sel = api.selection();
|
|
95
|
+
return sel !== null && rowIndex >= sel.min.row && rowIndex <= sel.max.row;
|
|
201
96
|
};
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
setIsEditing(false);
|
|
206
|
-
setSelection(normalizeRange({
|
|
207
|
-
row: 0,
|
|
208
|
-
col: 0
|
|
209
|
-
}, {
|
|
210
|
-
row: numRows() - 1,
|
|
211
|
-
col: numCols() - 1
|
|
212
|
-
}));
|
|
213
|
-
setActiveCell({
|
|
214
|
-
row: 0,
|
|
215
|
-
col: 0
|
|
216
|
-
});
|
|
217
|
-
});
|
|
97
|
+
const isColHeaderSelected = (colIndex) => {
|
|
98
|
+
const sel = api.selection();
|
|
99
|
+
return sel !== null && colIndex >= sel.min.col && colIndex <= sel.max.col;
|
|
218
100
|
};
|
|
219
|
-
const
|
|
220
|
-
|
|
101
|
+
const renderRowHeader = props.renderRowHeader ?? defaultRenderRowHeader;
|
|
102
|
+
const renderColHeader = props.renderColHeader ?? defaultRenderColHeader;
|
|
103
|
+
const isCellEditing = (pos) => api.isEditing() && isCellActive(pos);
|
|
104
|
+
const cellRefs = /* @__PURE__ */ new Map();
|
|
105
|
+
const getCellKey = (pos) => `${pos.row}:${pos.col}`;
|
|
106
|
+
createEffect(() => {
|
|
107
|
+
if (api.isEditing()) return;
|
|
108
|
+
const ac = api.activeCell();
|
|
221
109
|
if (!ac) return;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (!anchor) {
|
|
225
|
-
anchor = ac;
|
|
226
|
-
setSelectionAnchor(anchor);
|
|
227
|
-
}
|
|
228
|
-
const sel = selection();
|
|
229
|
-
let headRow = anchor.row;
|
|
230
|
-
let headCol = anchor.col;
|
|
231
|
-
if (sel) {
|
|
232
|
-
headRow = sel.min.row === anchor.row ? sel.max.row : sel.min.row;
|
|
233
|
-
headCol = sel.min.col === anchor.col ? sel.max.col : sel.min.col;
|
|
234
|
-
}
|
|
235
|
-
const nextHeadRow = Math.max(0, Math.min(numRows() - 1, headRow + deltaRow));
|
|
236
|
-
const nextHeadCol = Math.max(0, Math.min(numCols() - 1, headCol + deltaCol));
|
|
237
|
-
setSelection(normalizeRange(anchor, {
|
|
238
|
-
row: nextHeadRow,
|
|
239
|
-
col: nextHeadCol
|
|
240
|
-
}));
|
|
241
|
-
} else {
|
|
242
|
-
const nextPos = {
|
|
243
|
-
row: Math.max(0, Math.min(numRows() - 1, ac.row + deltaRow)),
|
|
244
|
-
col: Math.max(0, Math.min(numCols() - 1, ac.col + deltaCol))
|
|
245
|
-
};
|
|
246
|
-
setActiveCell(nextPos);
|
|
247
|
-
setSelection(normalizeRange(nextPos, nextPos));
|
|
248
|
-
setSelectionAnchor(null);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
110
|
+
cellRefs.get(getCellKey(ac))?.focus();
|
|
111
|
+
});
|
|
251
112
|
const handleKeyDown = (e) => {
|
|
252
113
|
if (e.isComposing) return;
|
|
253
|
-
if (isEditing()) return;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
e.preventDefault();
|
|
259
|
-
navigate(-1, 0, e.shiftKey);
|
|
260
|
-
break;
|
|
261
|
-
case "ArrowDown":
|
|
262
|
-
e.preventDefault();
|
|
263
|
-
navigate(1, 0, e.shiftKey);
|
|
264
|
-
break;
|
|
265
|
-
case "ArrowLeft":
|
|
266
|
-
e.preventDefault();
|
|
267
|
-
navigate(0, -1, e.shiftKey);
|
|
268
|
-
break;
|
|
269
|
-
case "ArrowRight":
|
|
270
|
-
e.preventDefault();
|
|
271
|
-
navigate(0, 1, e.shiftKey);
|
|
272
|
-
break;
|
|
273
|
-
case "Tab":
|
|
274
|
-
e.preventDefault();
|
|
275
|
-
navigate(0, e.shiftKey ? -1 : 1, false);
|
|
276
|
-
break;
|
|
277
|
-
case "Enter":
|
|
278
|
-
e.preventDefault();
|
|
279
|
-
beginCellEdit(ac);
|
|
280
|
-
break;
|
|
281
|
-
default: break;
|
|
282
|
-
}
|
|
114
|
+
if (api.isEditing()) return;
|
|
115
|
+
emit({
|
|
116
|
+
type: "key:down",
|
|
117
|
+
e
|
|
118
|
+
});
|
|
283
119
|
};
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
120
|
+
const onUp = (e) => emit({
|
|
121
|
+
type: "pointer:up",
|
|
122
|
+
e
|
|
123
|
+
});
|
|
124
|
+
onMount(() => {
|
|
125
|
+
window.addEventListener("pointerup", onUp);
|
|
126
|
+
});
|
|
127
|
+
onCleanup(() => {
|
|
128
|
+
window.removeEventListener("pointerup", onUp);
|
|
290
129
|
});
|
|
291
130
|
return (() => {
|
|
292
131
|
var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$2.nextSibling;
|
|
293
132
|
var _ref$ = props.ref;
|
|
294
133
|
typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$;
|
|
295
134
|
_el$.$$keydown = handleKeyDown;
|
|
296
|
-
_el$4.$$click =
|
|
135
|
+
_el$4.$$click = (e) => emit({
|
|
136
|
+
type: "corner:click",
|
|
137
|
+
e
|
|
138
|
+
});
|
|
297
139
|
insert(_el$3, createComponent(Index, {
|
|
298
140
|
get each() {
|
|
299
|
-
return Array.from({ length: numCols() });
|
|
141
|
+
return Array.from({ length: api.numCols() });
|
|
300
142
|
},
|
|
301
143
|
children: (_, colIndex) => createComponent(ColHeader, {
|
|
302
144
|
index: colIndex,
|
|
303
145
|
get isSelected() {
|
|
304
146
|
return isColHeaderSelected(colIndex);
|
|
305
147
|
},
|
|
306
|
-
onMouseDown:
|
|
307
|
-
|
|
148
|
+
onMouseDown: (col, e) => emit({
|
|
149
|
+
type: "colheader:pointerdown",
|
|
150
|
+
col,
|
|
151
|
+
e
|
|
152
|
+
}),
|
|
153
|
+
onMouseOver: (col, e) => emit({
|
|
154
|
+
type: "colheader:pointerover",
|
|
155
|
+
col,
|
|
156
|
+
e
|
|
157
|
+
}),
|
|
308
158
|
get ["class"]() {
|
|
309
159
|
return props.classes?.colHeader;
|
|
310
|
-
}
|
|
160
|
+
},
|
|
161
|
+
renderHeader: renderColHeader
|
|
311
162
|
})
|
|
312
163
|
}), null);
|
|
313
164
|
insert(_el$5, createComponent(For, {
|
|
@@ -323,11 +174,20 @@ function Gridsheet(props) {
|
|
|
323
174
|
get isSelected() {
|
|
324
175
|
return isRowHeaderSelected(rowIndex());
|
|
325
176
|
},
|
|
326
|
-
onMouseDown:
|
|
327
|
-
|
|
177
|
+
onMouseDown: (r, e) => emit({
|
|
178
|
+
type: "rowheader:pointerdown",
|
|
179
|
+
row: r,
|
|
180
|
+
e
|
|
181
|
+
}),
|
|
182
|
+
onMouseOver: (r, e) => emit({
|
|
183
|
+
type: "rowheader:pointerover",
|
|
184
|
+
row: r,
|
|
185
|
+
e
|
|
186
|
+
}),
|
|
328
187
|
get ["class"]() {
|
|
329
188
|
return props.classes?.rowHeader;
|
|
330
|
-
}
|
|
189
|
+
},
|
|
190
|
+
renderHeader: renderRowHeader
|
|
331
191
|
}), null);
|
|
332
192
|
insert(_el$6, createComponent(For, {
|
|
333
193
|
each: row,
|
|
@@ -357,20 +217,31 @@ function Gridsheet(props) {
|
|
|
357
217
|
col: colIndex()
|
|
358
218
|
});
|
|
359
219
|
},
|
|
360
|
-
beginEdit:
|
|
361
|
-
commitEdit:
|
|
362
|
-
cancelEditing:
|
|
363
|
-
setActiveCell,
|
|
364
|
-
setSelection,
|
|
220
|
+
beginEdit: (pos) => api.beginEdit(pos),
|
|
221
|
+
commitEdit: (pos, value) => api.commitEdit(pos, value),
|
|
222
|
+
cancelEditing: () => api.cancelEdit(),
|
|
365
223
|
get renderCell() {
|
|
366
224
|
return props.renderCell;
|
|
367
225
|
},
|
|
368
|
-
onMouseDown:
|
|
369
|
-
|
|
370
|
-
|
|
226
|
+
onMouseDown: (pos, e) => emit({
|
|
227
|
+
type: "cell:pointerdown",
|
|
228
|
+
pos,
|
|
229
|
+
e
|
|
230
|
+
}),
|
|
231
|
+
onMouseOver: (pos, e) => emit({
|
|
232
|
+
type: "cell:pointerover",
|
|
233
|
+
pos,
|
|
234
|
+
e
|
|
235
|
+
}),
|
|
236
|
+
onDoubleClick: (pos, e) => emit({
|
|
237
|
+
type: "cell:dblclick",
|
|
238
|
+
pos,
|
|
239
|
+
e
|
|
240
|
+
}),
|
|
241
|
+
registerCellRef: (r, c, el) => {
|
|
371
242
|
cellRefs.set(getCellKey({
|
|
372
|
-
row:
|
|
373
|
-
col:
|
|
243
|
+
row: r,
|
|
244
|
+
col: c
|
|
374
245
|
}), el);
|
|
375
246
|
},
|
|
376
247
|
get ["class"]() {
|
|
@@ -403,12 +274,18 @@ function Gridsheet(props) {
|
|
|
403
274
|
function getRowLabel(rowIndex) {
|
|
404
275
|
return `${rowIndex + 1}`;
|
|
405
276
|
}
|
|
277
|
+
function defaultRenderRowHeader(ctx) {
|
|
278
|
+
return memo(() => getRowLabel(ctx.index));
|
|
279
|
+
}
|
|
406
280
|
function RowHeader(props) {
|
|
407
281
|
return (() => {
|
|
408
282
|
var _el$7 = _tmpl$3();
|
|
409
|
-
_el$7.$$mouseover = () => props.onMouseOver(props.index);
|
|
410
|
-
_el$7.$$mousedown = () => props.onMouseDown(props.index);
|
|
411
|
-
insert(_el$7, () =>
|
|
283
|
+
_el$7.$$mouseover = (e) => props.onMouseOver(props.index, e);
|
|
284
|
+
_el$7.$$mousedown = (e) => props.onMouseDown(props.index, e);
|
|
285
|
+
insert(_el$7, () => props.renderHeader({
|
|
286
|
+
index: props.index,
|
|
287
|
+
isSelected: props.isSelected
|
|
288
|
+
}));
|
|
412
289
|
effect((_p$) => {
|
|
413
290
|
var _v$6 = typeof props.class === "function" ? props.class({
|
|
414
291
|
rowIndex: props.index,
|
|
@@ -434,12 +311,18 @@ function getColLabel(colIndex) {
|
|
|
434
311
|
}
|
|
435
312
|
return label;
|
|
436
313
|
}
|
|
314
|
+
function defaultRenderColHeader(ctx) {
|
|
315
|
+
return memo(() => getColLabel(ctx.index));
|
|
316
|
+
}
|
|
437
317
|
function ColHeader(props) {
|
|
438
318
|
return (() => {
|
|
439
319
|
var _el$8 = _tmpl$4();
|
|
440
|
-
_el$8.$$mouseover = () => props.onMouseOver(props.index);
|
|
441
|
-
_el$8.$$mousedown = () => props.onMouseDown(props.index);
|
|
442
|
-
insert(_el$8, () =>
|
|
320
|
+
_el$8.$$mouseover = (e) => props.onMouseOver(props.index, e);
|
|
321
|
+
_el$8.$$mousedown = (e) => props.onMouseDown(props.index, e);
|
|
322
|
+
insert(_el$8, () => props.renderHeader({
|
|
323
|
+
index: props.index,
|
|
324
|
+
isSelected: props.isSelected
|
|
325
|
+
}));
|
|
443
326
|
effect((_p$) => {
|
|
444
327
|
var _v$8 = typeof props.class === "function" ? props.class({
|
|
445
328
|
colIndex: props.index,
|
|
@@ -461,30 +344,11 @@ function Cell(props) {
|
|
|
461
344
|
row: props.row,
|
|
462
345
|
col: props.col
|
|
463
346
|
});
|
|
464
|
-
const commitEdit = (value) => {
|
|
465
|
-
props.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}, value);
|
|
469
|
-
};
|
|
347
|
+
const commitEdit = (value) => props.commitEdit({
|
|
348
|
+
row: props.row,
|
|
349
|
+
col: props.col
|
|
350
|
+
}, value);
|
|
470
351
|
const cancelEditing = () => props.cancelEditing();
|
|
471
|
-
const handleMouseDown = (e) => {
|
|
472
|
-
props.onMouseDown({
|
|
473
|
-
row: props.row,
|
|
474
|
-
col: props.col
|
|
475
|
-
}, e);
|
|
476
|
-
};
|
|
477
|
-
const handleMouseOver = (e) => {
|
|
478
|
-
props.onMouseOver({
|
|
479
|
-
row: props.row,
|
|
480
|
-
col: props.col
|
|
481
|
-
}, e);
|
|
482
|
-
};
|
|
483
|
-
const handleDoubleClick = (e) => {
|
|
484
|
-
if (e.button !== 0) return;
|
|
485
|
-
e.preventDefault();
|
|
486
|
-
if (!props.isEditing) beginEdit();
|
|
487
|
-
};
|
|
488
352
|
const className$1 = createMemo(() => {
|
|
489
353
|
if (typeof props.class === "function") return props.class({
|
|
490
354
|
row: props.row,
|
|
@@ -498,13 +362,22 @@ function Cell(props) {
|
|
|
498
362
|
commitEdit,
|
|
499
363
|
cancelEditing
|
|
500
364
|
});
|
|
501
|
-
|
|
365
|
+
return props.class;
|
|
502
366
|
});
|
|
503
367
|
return (() => {
|
|
504
368
|
var _el$9 = _tmpl$5();
|
|
505
|
-
_el$9.$$dblclick =
|
|
506
|
-
|
|
507
|
-
|
|
369
|
+
_el$9.$$dblclick = (e) => props.onDoubleClick({
|
|
370
|
+
row: props.row,
|
|
371
|
+
col: props.col
|
|
372
|
+
}, e);
|
|
373
|
+
_el$9.$$mouseover = (e) => props.onMouseOver({
|
|
374
|
+
row: props.row,
|
|
375
|
+
col: props.col
|
|
376
|
+
}, e);
|
|
377
|
+
_el$9.$$mousedown = (e) => props.onMouseDown({
|
|
378
|
+
row: props.row,
|
|
379
|
+
col: props.col
|
|
380
|
+
}, e);
|
|
508
381
|
use((el) => {
|
|
509
382
|
cellRef = el;
|
|
510
383
|
props.registerCellRef(props.row, props.col, el);
|
|
@@ -550,5 +423,504 @@ delegateEvents([
|
|
|
550
423
|
]);
|
|
551
424
|
|
|
552
425
|
//#endregion
|
|
553
|
-
|
|
426
|
+
//#region src/plugin.ts
|
|
427
|
+
/** Compose multiple plugins into a single event handler. */
|
|
428
|
+
function createPluginHost(plugins) {
|
|
429
|
+
const onEvent = (ev, api) => {
|
|
430
|
+
for (const p of plugins) if (p.onEvent?.(ev, api) === true) return true;
|
|
431
|
+
return false;
|
|
432
|
+
};
|
|
433
|
+
return { onEvent };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/plugins/utils.ts
|
|
438
|
+
function buildClearPatches(range, getEmptyValue) {
|
|
439
|
+
const patches = [];
|
|
440
|
+
for (let r = range.min.row; r <= range.max.row; r++) for (let c = range.min.col; c <= range.max.col; c++) {
|
|
441
|
+
const pos = {
|
|
442
|
+
row: r,
|
|
443
|
+
col: c
|
|
444
|
+
};
|
|
445
|
+
patches.push({
|
|
446
|
+
pos,
|
|
447
|
+
value: getEmptyValue(pos)
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return patches;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
//#endregion
|
|
454
|
+
//#region src/plugins/clipboard-memory.ts
|
|
455
|
+
function extractSelection(data, range) {
|
|
456
|
+
const result = [];
|
|
457
|
+
for (let r = range.min.row; r <= range.max.row; r++) {
|
|
458
|
+
const row = [];
|
|
459
|
+
for (let c = range.min.col; c <= range.max.col; c++) {
|
|
460
|
+
const rowData = data[r];
|
|
461
|
+
if (rowData && c < rowData.length) {
|
|
462
|
+
const value = rowData[c];
|
|
463
|
+
if (value !== void 0) row.push(value);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
result.push(row);
|
|
467
|
+
}
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
function buildPastePatches(data, start, rows, cols) {
|
|
471
|
+
const patches = [];
|
|
472
|
+
for (let r = 0; r < data.length; r++) {
|
|
473
|
+
const row = data[r];
|
|
474
|
+
if (!row) continue;
|
|
475
|
+
for (let c = 0; c < row.length; c++) {
|
|
476
|
+
const targetRow = start.row + r;
|
|
477
|
+
const targetCol = start.col + c;
|
|
478
|
+
if (targetRow < 0 || targetCol < 0) continue;
|
|
479
|
+
if (targetRow >= rows || targetCol >= cols) continue;
|
|
480
|
+
const value = row[c];
|
|
481
|
+
if (value === void 0) continue;
|
|
482
|
+
patches.push({
|
|
483
|
+
pos: {
|
|
484
|
+
row: targetRow,
|
|
485
|
+
col: targetCol
|
|
486
|
+
},
|
|
487
|
+
value
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return patches;
|
|
492
|
+
}
|
|
493
|
+
/** In-memory clipboard with optional cut clearing. */
|
|
494
|
+
function clipboardMemoryPlugin(options) {
|
|
495
|
+
let clipboard = null;
|
|
496
|
+
const copyKeys = options.copyKeys ?? ["c"];
|
|
497
|
+
const cutKeys = options.cutKeys ?? ["x"];
|
|
498
|
+
const pasteKeys = options.pasteKeys ?? ["v"];
|
|
499
|
+
const getEmptyValue = options.getEmptyValue ?? (options.emptyValue !== void 0 ? () => options.emptyValue : null);
|
|
500
|
+
const setClipboard = (next) => {
|
|
501
|
+
clipboard = next;
|
|
502
|
+
options.onClipboardChange?.(next);
|
|
503
|
+
};
|
|
504
|
+
return {
|
|
505
|
+
name: "clipboard-memory",
|
|
506
|
+
onEvent(ev, api) {
|
|
507
|
+
if (ev.type !== "key:down") return;
|
|
508
|
+
const e = ev.e;
|
|
509
|
+
if (e.isComposing || api.isEditing()) return;
|
|
510
|
+
const key = e.key.toLowerCase();
|
|
511
|
+
const isCopy = (e.ctrlKey || e.metaKey) && copyKeys.includes(key);
|
|
512
|
+
const isCut = (e.ctrlKey || e.metaKey) && cutKeys.includes(key);
|
|
513
|
+
const isPaste = (e.ctrlKey || e.metaKey) && pasteKeys.includes(key);
|
|
514
|
+
if (!isCopy && !isCut && !isPaste) return;
|
|
515
|
+
const sel = api.selection();
|
|
516
|
+
const ac = api.activeCell();
|
|
517
|
+
if (isCopy || isCut) {
|
|
518
|
+
if (!sel) return true;
|
|
519
|
+
e.preventDefault();
|
|
520
|
+
(async () => {
|
|
521
|
+
const copiedData = extractSelection(options.getData(), sel);
|
|
522
|
+
if ((isCopy ? await options.onCopy?.(copiedData, sel) : await options.onCut?.(copiedData, sel)) === false) return;
|
|
523
|
+
setClipboard({
|
|
524
|
+
data: copiedData,
|
|
525
|
+
range: sel
|
|
526
|
+
});
|
|
527
|
+
if (isCut && getEmptyValue) {
|
|
528
|
+
const patches = buildClearPatches(sel, getEmptyValue);
|
|
529
|
+
if (patches.length > 0) api.updateCells(patches);
|
|
530
|
+
}
|
|
531
|
+
})();
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
if (isPaste) {
|
|
535
|
+
if (!ac || !clipboard) return true;
|
|
536
|
+
e.preventDefault();
|
|
537
|
+
(async () => {
|
|
538
|
+
const result = await options.onPaste?.(clipboard.data, ac);
|
|
539
|
+
if (result === false) return;
|
|
540
|
+
const patches = result ?? buildPastePatches(clipboard.data, ac, api.numRows(), api.numCols());
|
|
541
|
+
if (patches.length > 0) api.updateCells(patches);
|
|
542
|
+
})();
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/plugins/clipboard-text.ts
|
|
551
|
+
const DEFAULT_COPY_KEYS = ["c"];
|
|
552
|
+
const DEFAULT_CUT_KEYS = ["x"];
|
|
553
|
+
const DEFAULT_PASTE_KEYS = ["v"];
|
|
554
|
+
function defaultReadText() {
|
|
555
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) return Promise.resolve("");
|
|
556
|
+
return navigator.clipboard.readText();
|
|
557
|
+
}
|
|
558
|
+
function defaultWriteText(text) {
|
|
559
|
+
if (typeof navigator === "undefined" || !navigator.clipboard) return Promise.resolve();
|
|
560
|
+
return navigator.clipboard.writeText(text);
|
|
561
|
+
}
|
|
562
|
+
function serializeTsv(data, formatCell) {
|
|
563
|
+
return data.map((row) => row.map(formatCell).join(" ")).join("\n");
|
|
564
|
+
}
|
|
565
|
+
function parseTsv(text, target, parseCell) {
|
|
566
|
+
const rows = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
567
|
+
const patches = [];
|
|
568
|
+
for (let r = 0; r < rows.length; r++) {
|
|
569
|
+
const cols = rows[r]?.split(" ") ?? [];
|
|
570
|
+
for (let c = 0; c < cols.length; c++) patches.push({
|
|
571
|
+
pos: {
|
|
572
|
+
row: target.row + r,
|
|
573
|
+
col: target.col + c
|
|
574
|
+
},
|
|
575
|
+
value: parseCell(cols[c] ?? "")
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return patches;
|
|
579
|
+
}
|
|
580
|
+
/** System clipboard integration with TSV defaults. */
|
|
581
|
+
function clipboardTextPlugin(options) {
|
|
582
|
+
const copyKeys = options.copyKeys ?? DEFAULT_COPY_KEYS;
|
|
583
|
+
const cutKeys = options.cutKeys ?? DEFAULT_CUT_KEYS;
|
|
584
|
+
const pasteKeys = options.pasteKeys ?? DEFAULT_PASTE_KEYS;
|
|
585
|
+
const parseCell = options.parseCell ?? ((raw) => raw);
|
|
586
|
+
const formatCell = options.formatCell ?? ((value) => String(value));
|
|
587
|
+
const toText = options.toText ?? ((data) => serializeTsv(data, formatCell));
|
|
588
|
+
const fromText = options.fromText ?? ((text, target) => parseTsv(text, target, parseCell));
|
|
589
|
+
const readText = options.readText ?? defaultReadText;
|
|
590
|
+
const writeText = options.writeText ?? defaultWriteText;
|
|
591
|
+
const getEmptyValue = options.getEmptyValue ?? (options.emptyValue !== void 0 ? () => options.emptyValue : null);
|
|
592
|
+
return {
|
|
593
|
+
name: "clipboard-text",
|
|
594
|
+
onEvent(ev, api) {
|
|
595
|
+
if (ev.type !== "key:down") return;
|
|
596
|
+
const e = ev.e;
|
|
597
|
+
if (e.isComposing || api.isEditing()) return;
|
|
598
|
+
const key = e.key.toLowerCase();
|
|
599
|
+
const isCopy = (e.ctrlKey || e.metaKey) && copyKeys.includes(key);
|
|
600
|
+
const isCut = (e.ctrlKey || e.metaKey) && cutKeys.includes(key);
|
|
601
|
+
const isPaste = (e.ctrlKey || e.metaKey) && pasteKeys.includes(key);
|
|
602
|
+
if (!isCopy && !isCut && !isPaste) return;
|
|
603
|
+
if (isCopy || isCut) {
|
|
604
|
+
const sel = api.selection();
|
|
605
|
+
if (!sel) return true;
|
|
606
|
+
e.preventDefault();
|
|
607
|
+
(async () => {
|
|
608
|
+
await writeText(toText(options.getData().slice(sel.min.row, sel.max.row + 1).map((row) => row.slice(sel.min.col, sel.max.col + 1))));
|
|
609
|
+
if (isCut && getEmptyValue) {
|
|
610
|
+
const patches = buildClearPatches(sel, getEmptyValue);
|
|
611
|
+
if (patches.length > 0) api.updateCells(patches);
|
|
612
|
+
}
|
|
613
|
+
})();
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
if (isPaste) {
|
|
617
|
+
const ac = api.activeCell();
|
|
618
|
+
if (!ac) return true;
|
|
619
|
+
e.preventDefault();
|
|
620
|
+
(async () => {
|
|
621
|
+
const text = await readText();
|
|
622
|
+
if (!text) return;
|
|
623
|
+
const patches = fromText(text, ac);
|
|
624
|
+
if (patches === false) return;
|
|
625
|
+
if (patches.length > 0) api.updateCells(patches);
|
|
626
|
+
})();
|
|
627
|
+
return true;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/plugins/delete.ts
|
|
635
|
+
/** Clears selected range with provided empty values. */
|
|
636
|
+
function deletePlugin(options) {
|
|
637
|
+
const keys = options.keys ?? ["Delete", "Backspace"];
|
|
638
|
+
const getEmptyValue = options.getEmptyValue ?? (options.emptyValue !== void 0 ? () => options.emptyValue : null);
|
|
639
|
+
return {
|
|
640
|
+
name: "delete",
|
|
641
|
+
onEvent(ev, api) {
|
|
642
|
+
if (ev.type !== "key:down") return;
|
|
643
|
+
const e = ev.e;
|
|
644
|
+
if (e.isComposing || api.isEditing()) return;
|
|
645
|
+
if (!keys.includes(e.key)) return;
|
|
646
|
+
const range = api.selection();
|
|
647
|
+
if (!range) {
|
|
648
|
+
e.preventDefault();
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
const result = options.onDelete?.(range);
|
|
652
|
+
if (result === false) {
|
|
653
|
+
e.preventDefault();
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
if (Array.isArray(result)) {
|
|
657
|
+
api.updateCells(result);
|
|
658
|
+
e.preventDefault();
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
if (getEmptyValue) {
|
|
662
|
+
const patches = buildClearPatches(range, getEmptyValue);
|
|
663
|
+
if (patches.length > 0) api.updateCells(patches);
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region src/plugins/editing.ts
|
|
674
|
+
/** Starts editing via double click or configured keys. */
|
|
675
|
+
function editingPlugin(options = {}) {
|
|
676
|
+
const triggerKeys = options.triggerKeys ?? ["Enter"];
|
|
677
|
+
return {
|
|
678
|
+
name: "editing",
|
|
679
|
+
onEvent(ev, api) {
|
|
680
|
+
if (ev.type === "cell:dblclick") {
|
|
681
|
+
if (api.isEditing()) return true;
|
|
682
|
+
api.beginEdit(ev.pos);
|
|
683
|
+
return true;
|
|
684
|
+
}
|
|
685
|
+
if (ev.type === "key:down") {
|
|
686
|
+
const e = ev.e;
|
|
687
|
+
if (e.isComposing || api.isEditing()) return;
|
|
688
|
+
if (!triggerKeys.includes(e.key)) return;
|
|
689
|
+
const ac = api.activeCell();
|
|
690
|
+
if (!ac) return true;
|
|
691
|
+
e.preventDefault();
|
|
692
|
+
api.beginEdit(ac);
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
//#endregion
|
|
700
|
+
//#region src/utils.ts
|
|
701
|
+
function clamp(n, min, max) {
|
|
702
|
+
return Math.max(min, Math.min(max, n));
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/plugins/selection.ts
|
|
707
|
+
function moveBy(pos, dr, dc, rows, cols) {
|
|
708
|
+
return {
|
|
709
|
+
row: clamp(pos.row + dr, 0, rows - 1),
|
|
710
|
+
col: clamp(pos.col + dc, 0, cols - 1)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
/** Selection, drag, and arrow-key navigation. */
|
|
714
|
+
function selectionPlugin() {
|
|
715
|
+
let anchor = null;
|
|
716
|
+
let head = null;
|
|
717
|
+
let dragging = false;
|
|
718
|
+
let headerMode = null;
|
|
719
|
+
let previousBodyUserSelect = null;
|
|
720
|
+
const disableTextSelectionDuringDrag = () => {
|
|
721
|
+
if (typeof document === "undefined" || previousBodyUserSelect !== null) return;
|
|
722
|
+
const body = document.body;
|
|
723
|
+
if (!body) return;
|
|
724
|
+
previousBodyUserSelect = body.style.userSelect;
|
|
725
|
+
body.style.userSelect = "none";
|
|
726
|
+
};
|
|
727
|
+
const restoreTextSelectionAfterDrag = () => {
|
|
728
|
+
if (typeof document === "undefined" || previousBodyUserSelect === null) return;
|
|
729
|
+
const body = document.body;
|
|
730
|
+
if (body) body.style.userSelect = previousBodyUserSelect;
|
|
731
|
+
previousBodyUserSelect = null;
|
|
732
|
+
};
|
|
733
|
+
const setSingle = (pos, api) => {
|
|
734
|
+
anchor = pos;
|
|
735
|
+
head = pos;
|
|
736
|
+
batch(() => {
|
|
737
|
+
api.setActiveCell(pos);
|
|
738
|
+
api.setSelection(normalizeRange(pos, pos));
|
|
739
|
+
});
|
|
740
|
+
};
|
|
741
|
+
const setRange = (rangeAnchor, rangeHead, api) => {
|
|
742
|
+
anchor = rangeAnchor;
|
|
743
|
+
head = rangeHead;
|
|
744
|
+
api.setSelection(normalizeRange(rangeAnchor, rangeHead));
|
|
745
|
+
};
|
|
746
|
+
return {
|
|
747
|
+
name: "selection",
|
|
748
|
+
onEvent(ev, api) {
|
|
749
|
+
switch (ev.type) {
|
|
750
|
+
case "cell:pointerdown":
|
|
751
|
+
if (api.isEditing()) return true;
|
|
752
|
+
if (ev.e.button !== 0) return;
|
|
753
|
+
ev.e.preventDefault();
|
|
754
|
+
dragging = true;
|
|
755
|
+
headerMode = null;
|
|
756
|
+
disableTextSelectionDuringDrag();
|
|
757
|
+
setSingle(ev.pos, api);
|
|
758
|
+
return true;
|
|
759
|
+
case "cell:pointerover":
|
|
760
|
+
if (!dragging) return;
|
|
761
|
+
if ((ev.e.buttons & 1) === 0) return;
|
|
762
|
+
if (!anchor) anchor = ev.pos;
|
|
763
|
+
setRange(anchor, ev.pos, api);
|
|
764
|
+
return true;
|
|
765
|
+
case "pointer:up":
|
|
766
|
+
if (dragging) dragging = false;
|
|
767
|
+
headerMode = null;
|
|
768
|
+
restoreTextSelectionAfterDrag();
|
|
769
|
+
return;
|
|
770
|
+
case "corner:click": {
|
|
771
|
+
const rows = api.numRows();
|
|
772
|
+
const cols = api.numCols();
|
|
773
|
+
if (rows <= 0 || cols <= 0) return true;
|
|
774
|
+
const min = {
|
|
775
|
+
row: 0,
|
|
776
|
+
col: 0
|
|
777
|
+
};
|
|
778
|
+
const max = {
|
|
779
|
+
row: rows - 1,
|
|
780
|
+
col: cols - 1
|
|
781
|
+
};
|
|
782
|
+
dragging = false;
|
|
783
|
+
anchor = min;
|
|
784
|
+
head = max;
|
|
785
|
+
batch(() => {
|
|
786
|
+
api.setActiveCell(min);
|
|
787
|
+
api.setSelection(normalizeRange(min, max));
|
|
788
|
+
});
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
case "rowheader:pointerdown": {
|
|
792
|
+
const cols = api.numCols();
|
|
793
|
+
if (cols <= 0) return true;
|
|
794
|
+
const min = {
|
|
795
|
+
row: ev.row,
|
|
796
|
+
col: 0
|
|
797
|
+
};
|
|
798
|
+
const max = {
|
|
799
|
+
row: ev.row,
|
|
800
|
+
col: cols - 1
|
|
801
|
+
};
|
|
802
|
+
dragging = false;
|
|
803
|
+
headerMode = "row";
|
|
804
|
+
disableTextSelectionDuringDrag();
|
|
805
|
+
anchor = min;
|
|
806
|
+
head = max;
|
|
807
|
+
batch(() => {
|
|
808
|
+
api.setActiveCell(min);
|
|
809
|
+
api.setSelection(normalizeRange(min, max));
|
|
810
|
+
});
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
case "rowheader:pointerover": {
|
|
814
|
+
if (headerMode !== "row") return;
|
|
815
|
+
if ((ev.e.buttons & 1) === 0) return;
|
|
816
|
+
const cols = api.numCols();
|
|
817
|
+
if (cols <= 0) return true;
|
|
818
|
+
const startRow = anchor?.row ?? ev.row;
|
|
819
|
+
const min = {
|
|
820
|
+
row: Math.min(startRow, ev.row),
|
|
821
|
+
col: 0
|
|
822
|
+
};
|
|
823
|
+
const max = {
|
|
824
|
+
row: Math.max(startRow, ev.row),
|
|
825
|
+
col: cols - 1
|
|
826
|
+
};
|
|
827
|
+
head = max;
|
|
828
|
+
api.setSelection(normalizeRange(min, max));
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
case "colheader:pointerdown": {
|
|
832
|
+
const rows = api.numRows();
|
|
833
|
+
if (rows <= 0) return true;
|
|
834
|
+
const min = {
|
|
835
|
+
row: 0,
|
|
836
|
+
col: ev.col
|
|
837
|
+
};
|
|
838
|
+
const max = {
|
|
839
|
+
row: rows - 1,
|
|
840
|
+
col: ev.col
|
|
841
|
+
};
|
|
842
|
+
dragging = false;
|
|
843
|
+
headerMode = "col";
|
|
844
|
+
disableTextSelectionDuringDrag();
|
|
845
|
+
anchor = min;
|
|
846
|
+
head = max;
|
|
847
|
+
batch(() => {
|
|
848
|
+
api.setActiveCell(min);
|
|
849
|
+
api.setSelection(normalizeRange(min, max));
|
|
850
|
+
});
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
case "colheader:pointerover": {
|
|
854
|
+
if (headerMode !== "col") return;
|
|
855
|
+
if ((ev.e.buttons & 1) === 0) return;
|
|
856
|
+
const rows = api.numRows();
|
|
857
|
+
if (rows <= 0) return true;
|
|
858
|
+
const startCol = anchor?.col ?? ev.col;
|
|
859
|
+
const min = {
|
|
860
|
+
row: 0,
|
|
861
|
+
col: Math.min(startCol, ev.col)
|
|
862
|
+
};
|
|
863
|
+
const max = {
|
|
864
|
+
row: rows - 1,
|
|
865
|
+
col: Math.max(startCol, ev.col)
|
|
866
|
+
};
|
|
867
|
+
head = max;
|
|
868
|
+
api.setSelection(normalizeRange(min, max));
|
|
869
|
+
return true;
|
|
870
|
+
}
|
|
871
|
+
case "key:down": {
|
|
872
|
+
const e = ev.e;
|
|
873
|
+
if (e.isComposing) return;
|
|
874
|
+
if (api.isEditing()) return;
|
|
875
|
+
const ac = api.activeCell();
|
|
876
|
+
if (!ac) return;
|
|
877
|
+
const key = e.key;
|
|
878
|
+
const isArrow = key === "ArrowUp" || key === "ArrowDown" || key === "ArrowLeft" || key === "ArrowRight";
|
|
879
|
+
const isTab = key === "Tab";
|
|
880
|
+
if (!isArrow && !isTab) return;
|
|
881
|
+
e.preventDefault();
|
|
882
|
+
const rows = api.numRows();
|
|
883
|
+
const cols = api.numCols();
|
|
884
|
+
if (rows <= 0 || cols <= 0) return true;
|
|
885
|
+
if (isTab) {
|
|
886
|
+
let next = ac;
|
|
887
|
+
if (e.shiftKey) {
|
|
888
|
+
if (ac.col > 0) next = {
|
|
889
|
+
row: ac.row,
|
|
890
|
+
col: ac.col - 1
|
|
891
|
+
};
|
|
892
|
+
else if (ac.row > 0) next = {
|
|
893
|
+
row: ac.row - 1,
|
|
894
|
+
col: cols - 1
|
|
895
|
+
};
|
|
896
|
+
} else if (ac.col < cols - 1) next = {
|
|
897
|
+
row: ac.row,
|
|
898
|
+
col: ac.col + 1
|
|
899
|
+
};
|
|
900
|
+
else if (ac.row < rows - 1) next = {
|
|
901
|
+
row: ac.row + 1,
|
|
902
|
+
col: 0
|
|
903
|
+
};
|
|
904
|
+
dragging = false;
|
|
905
|
+
setSingle(next, api);
|
|
906
|
+
return true;
|
|
907
|
+
}
|
|
908
|
+
const dr = key === "ArrowUp" ? -1 : key === "ArrowDown" ? 1 : 0;
|
|
909
|
+
const dc = key === "ArrowLeft" ? -1 : key === "ArrowRight" ? 1 : 0;
|
|
910
|
+
if (e.shiftKey) setRange(anchor ?? ac, moveBy(head ?? ac, dr, dc, rows, cols), api);
|
|
911
|
+
else {
|
|
912
|
+
const next = moveBy(ac, dr, dc, rows, cols);
|
|
913
|
+
dragging = false;
|
|
914
|
+
setSingle(next, api);
|
|
915
|
+
}
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
default: return;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
//#endregion
|
|
925
|
+
export { Gridsheet, clipboardMemoryPlugin, clipboardTextPlugin, createPluginHost, deletePlugin, editingPlugin, normalizeRange, selectionPlugin };
|
|
554
926
|
//# sourceMappingURL=index.js.map
|