@shiguri/solid-grid 0.0.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.
package/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # solid-grid
2
+
3
+ Headless spreadsheet-like grid for SolidJS. Works with a 2D array (`T[][]`) and gives you full control over rendering and styling.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @shiguri/solid-grid
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import { Gridsheet } from "@shiguri/solid-grid";
15
+ import { createSignal } from "solid-js";
16
+
17
+ const [data, setData] = createSignal([
18
+ ["A1", "B1"],
19
+ ["A2", "B2"],
20
+ ]);
21
+
22
+ <Gridsheet
23
+ data={data()}
24
+ onDataChange={setData}
25
+ renderCell={(ctx) =>
26
+ ctx.isEditing ? (
27
+ <input
28
+ value={ctx.value}
29
+ onKeyDown={(e) =>
30
+ e.key === "Enter" && ctx.commitEdit(e.currentTarget.value)
31
+ }
32
+ />
33
+ ) : (
34
+ <span>{ctx.value}</span>
35
+ )
36
+ }
37
+ />;
38
+ ```
39
+
40
+ ## More Info
41
+
42
+ See full docs in the root repository `docs/`.
@@ -0,0 +1,73 @@
1
+ import { JSX } from "solid-js/jsx-runtime";
2
+
3
+ //#region src/index.d.ts
4
+ interface CellPosition {
5
+ row: number;
6
+ col: number;
7
+ }
8
+ interface CellRange {
9
+ min: CellPosition;
10
+ max: CellPosition;
11
+ }
12
+ interface CellRenderContext<T> {
13
+ /** Row index */
14
+ row: number;
15
+ /** Column index */
16
+ col: number;
17
+ /** Cell value */
18
+ value: T;
19
+ /** Is this the active cell (focused) */
20
+ isActive: boolean;
21
+ /** Is this cell currently selected */
22
+ isSelected: boolean;
23
+ /** Is this cell currently being edited */
24
+ isEditing: boolean;
25
+ /** Solid ref for scrolling, focusing, measuring, etc. */
26
+ cellRef: HTMLTableCellElement | undefined;
27
+ /** Enter editing mode for this cell */
28
+ beginEdit: () => void;
29
+ /** Commit edit with new value */
30
+ commitEdit: (value: T) => void;
31
+ /** Exit editing mode for this cell */
32
+ cancelEditing: () => void;
33
+ }
34
+ interface GridsheetProps<T> {
35
+ /** 2D matrix of values */
36
+ data: T[][];
37
+ onDataChange?: (next: T[][]) => void;
38
+ /** Renderer for each cell */
39
+ renderCell: (ctx: CellRenderContext<T>) => JSX.Element;
40
+ /** Currently focused cell */
41
+ activeCell?: CellPosition | null;
42
+ onActiveCellChange?: (pos: CellPosition | null) => void;
43
+ /** Current selection range */
44
+ selection?: CellRange | null;
45
+ onSelectionChange?: (range: CellRange | null) => void;
46
+ /** Is the active cell in editing mode */
47
+ isEditing?: boolean;
48
+ onIsEditingChange?: (isEditing: boolean) => void;
49
+ ref?: HTMLTableElement | ((el: HTMLTableElement) => void) | undefined;
50
+ class?: string;
51
+ style?: JSX.CSSProperties | string;
52
+ classes?: {
53
+ cell?: string | ((ctx: CellRenderContext<T>) => string);
54
+ row?: string | ((ctx: {
55
+ rowIndex: number;
56
+ }) => string);
57
+ rowHeader?: string | ((ctx: {
58
+ rowIndex: number;
59
+ isSelected: boolean;
60
+ }) => string);
61
+ colHeader?: string | ((ctx: {
62
+ colIndex: number;
63
+ isSelected: boolean;
64
+ }) => string);
65
+ corner?: string;
66
+ header?: string;
67
+ body?: string;
68
+ };
69
+ }
70
+ declare function Gridsheet<T>(props: GridsheetProps<T>): JSX.Element;
71
+ //#endregion
72
+ export { CellPosition, CellRange, CellRenderContext, Gridsheet, GridsheetProps };
73
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.tsx"],"sourcesContent":[],"mappings":";;;UAYiB,YAAA;;EAAA,GAAA,EAAA,MAAA;AAKjB;AA2BiB,UA3BA,SAAA,CA2BiB;EAQzB,GAAA,EAlCF,YAkCE;EAYE,GAAA,EA7CJ,YA6CI;;AAMY,UA1BN,iBA0BM,CAAA,CAAA,CAAA,CAAA;EAMN;EAET,GAAA,EAAA,MAAA;EACgB;EAGc,GAAA,EAAA,MAAA;EAAlB;EAAyB,KAAI,EA9BxC,CA8BwC;EAGlC;EACc,QAAA,EAAA,OAAA;EAGf;EACgB,UAAA,EAAA,OAAA;EAMtB;EAAyB,SAAA,EAAA,OAAA;EAEvB;EAEmC,OAAA,EApClC,oBAoCkC,GAAA,SAAA;EAAlB;EAAiB,SAAA,EAAA,GAAA,GAAA,IAAA;EAiB5B;EAAmC,UAAA,EAAA,CAAA,KAAA,EA/C7B,CA+C6B,EAAA,GAAA,IAAA;EAAf;EAAoB,aAAI,EAAA,GAAA,GAAA,IAAA;;UAzC3C;;QAET;wBACgB;;oBAGJ,kBAAkB,OAAO,GAAA,CAAI;;eAGlC;6BACc;;cAGf;8BACgB;;;;QAMtB,yBAAyB;;UAEvB,GAAA,CAAI;;2BAEa,kBAAkB;;;;;;;;;;;;;;;;;iBAiB7B,oBAAoB,eAAe,KAAK,GAAA,CAAI"}
package/dist/index.js ADDED
@@ -0,0 +1,554 @@
1
+ import { className, createComponent, delegateEvents, effect, insert, setAttribute, style, template, use } from "solid-js/web";
2
+ import { For, Index, batch, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js";
3
+
4
+ //#region src/index.tsx
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
+ function isPositionInRange(pos, range) {
7
+ return pos.row >= range.min.row && pos.row <= range.max.row && pos.col >= range.min.col && pos.col <= range.max.col;
8
+ }
9
+ function normalizeRange(pos1, pos2) {
10
+ return {
11
+ min: {
12
+ row: Math.min(pos1.row, pos2.row),
13
+ col: Math.min(pos1.col, pos2.col)
14
+ },
15
+ max: {
16
+ row: Math.max(pos1.row, pos2.row),
17
+ col: Math.max(pos1.col, pos2.col)
18
+ }
19
+ };
20
+ }
21
+ const DEFAULT_SELECTION_MODE = "cell";
22
+ function Gridsheet(props) {
23
+ const numRows = createMemo(() => props.data.length);
24
+ const numCols = createMemo(() => props.data[0] ? props.data[0].length : 0);
25
+ const [innerActiveCell, setInnerActiveCell] = createSignal(null);
26
+ const activeCell = createMemo(() => props.activeCell === void 0 ? innerActiveCell() : props.activeCell);
27
+ const setActiveCell = (pos) => {
28
+ setInnerActiveCell(pos);
29
+ if (props.onActiveCellChange) props.onActiveCellChange(pos);
30
+ };
31
+ const isCellActive = (pos) => {
32
+ const ac = activeCell();
33
+ return ac !== null && ac.row === pos.row && ac.col === pos.col;
34
+ };
35
+ const [innerSelection, setInnerSelection] = createSignal(null);
36
+ const selection = createMemo(() => props.selection === void 0 ? innerSelection() : props.selection);
37
+ const setSelection = (range) => {
38
+ const normalizedRange = range ? normalizeRange(range.min, range.max) : null;
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);
59
+ };
60
+ const isCellEditing = (pos) => {
61
+ return isEditing() && isCellActive(pos);
62
+ };
63
+ const beginCellEdit = (pos) => {
64
+ batch(() => {
65
+ setIsEditing(true);
66
+ setActiveCell(pos);
67
+ setSelection(normalizeRange(pos, pos));
68
+ });
69
+ };
70
+ const commitCellEdit = (pos, value) => {
71
+ const nextData = props.data.map((row) => row.slice());
72
+ const targetRow = nextData[pos.row];
73
+ if (targetRow && pos.col >= 0 && pos.col < targetRow.length) {
74
+ targetRow[pos.col] = value;
75
+ if (props.onDataChange) props.onDataChange(nextData);
76
+ }
77
+ setIsEditing(false);
78
+ };
79
+ const cancelCellEdit = () => {
80
+ setIsEditing(false);
81
+ };
82
+ const [isMouseDown, setIsMouseDown] = createSignal(false);
83
+ const [selectionMode, setSelectionMode] = createSignal(DEFAULT_SELECTION_MODE);
84
+ const [selectionAnchor, setSelectionAnchor] = createSignal(null);
85
+ let previousBodyUserSelect = null;
86
+ const disableTextSelectionDuringDrag = () => {
87
+ if (typeof document === "undefined" || previousBodyUserSelect !== null) return;
88
+ const body = document.body;
89
+ if (!body) return;
90
+ previousBodyUserSelect = body.style.userSelect;
91
+ body.style.userSelect = "none";
92
+ };
93
+ const restoreTextSelectionAfterDrag = () => {
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
+ });
139
+ };
140
+ const handleMouseDownOnRowHeader = (rowIndex) => {
141
+ if (numCols() === 0) return;
142
+ batch(() => {
143
+ disableTextSelectionDuringDrag();
144
+ setIsMouseDown(true);
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
+ });
159
+ };
160
+ const handleMouseOverOnRowHeader = (rowIndex) => {
161
+ batch(() => {
162
+ if (!isMouseDown() || selectionMode() !== "row") return;
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
+ });
170
+ };
171
+ const handleMouseDownOnColHeader = (colIndex) => {
172
+ if (numRows() === 0) return;
173
+ batch(() => {
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
+ });
190
+ };
191
+ const handleMouseOverOnColHeader = (colIndex) => {
192
+ batch(() => {
193
+ if (!isMouseDown() || selectionMode() !== "col") return;
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
+ });
201
+ };
202
+ const handleClickOnCorner = () => {
203
+ if (numCols() === 0 || numRows() === 0) return;
204
+ batch(() => {
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
+ });
218
+ };
219
+ const navigate = (deltaRow, deltaCol, isSelection) => {
220
+ const ac = activeCell();
221
+ if (!ac) return;
222
+ if (isSelection) {
223
+ let anchor = selectionAnchor();
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
+ };
251
+ const handleKeyDown = (e) => {
252
+ if (e.isComposing) return;
253
+ if (isEditing()) return;
254
+ const ac = activeCell();
255
+ if (!ac) return;
256
+ switch (e.key) {
257
+ case "ArrowUp":
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
+ }
283
+ };
284
+ const cellRefs = /* @__PURE__ */ new Map();
285
+ const getCellKey = (pos) => `${pos.row}:${pos.col}`;
286
+ createEffect(() => {
287
+ if (isEditing()) return;
288
+ const ac = activeCell();
289
+ if (ac) cellRefs.get(getCellKey(ac))?.focus();
290
+ });
291
+ return (() => {
292
+ var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _el$4 = _el$3.firstChild, _el$5 = _el$2.nextSibling;
293
+ var _ref$ = props.ref;
294
+ typeof _ref$ === "function" ? use(_ref$, _el$) : props.ref = _el$;
295
+ _el$.$$keydown = handleKeyDown;
296
+ _el$4.$$click = handleClickOnCorner;
297
+ insert(_el$3, createComponent(Index, {
298
+ get each() {
299
+ return Array.from({ length: numCols() });
300
+ },
301
+ children: (_, colIndex) => createComponent(ColHeader, {
302
+ index: colIndex,
303
+ get isSelected() {
304
+ return isColHeaderSelected(colIndex);
305
+ },
306
+ onMouseDown: handleMouseDownOnColHeader,
307
+ onMouseOver: handleMouseOverOnColHeader,
308
+ get ["class"]() {
309
+ return props.classes?.colHeader;
310
+ }
311
+ })
312
+ }), null);
313
+ insert(_el$5, createComponent(For, {
314
+ get each() {
315
+ return props.data;
316
+ },
317
+ children: (row, rowIndex) => (() => {
318
+ var _el$6 = _tmpl$2();
319
+ insert(_el$6, createComponent(RowHeader, {
320
+ get index() {
321
+ return rowIndex();
322
+ },
323
+ get isSelected() {
324
+ return isRowHeaderSelected(rowIndex());
325
+ },
326
+ onMouseDown: handleMouseDownOnRowHeader,
327
+ onMouseOver: handleMouseOverOnRowHeader,
328
+ get ["class"]() {
329
+ return props.classes?.rowHeader;
330
+ }
331
+ }), null);
332
+ insert(_el$6, createComponent(For, {
333
+ each: row,
334
+ children: (cell, colIndex) => createComponent(Cell, {
335
+ get row() {
336
+ return rowIndex();
337
+ },
338
+ get col() {
339
+ return colIndex();
340
+ },
341
+ value: cell,
342
+ get isActive() {
343
+ return isCellActive({
344
+ row: rowIndex(),
345
+ col: colIndex()
346
+ });
347
+ },
348
+ get isEditing() {
349
+ return isCellEditing({
350
+ row: rowIndex(),
351
+ col: colIndex()
352
+ });
353
+ },
354
+ get isSelected() {
355
+ return isCellSelected({
356
+ row: rowIndex(),
357
+ col: colIndex()
358
+ });
359
+ },
360
+ beginEdit: beginCellEdit,
361
+ commitEdit: commitCellEdit,
362
+ cancelEditing: cancelCellEdit,
363
+ setActiveCell,
364
+ setSelection,
365
+ get renderCell() {
366
+ return props.renderCell;
367
+ },
368
+ onMouseDown: handleMouseDownOnCell,
369
+ onMouseOver: handleMouseOverOnCell,
370
+ registerCellRef: (rowPos, colPos, el) => {
371
+ cellRefs.set(getCellKey({
372
+ row: rowPos,
373
+ col: colPos
374
+ }), el);
375
+ },
376
+ get ["class"]() {
377
+ return props.classes?.cell;
378
+ }
379
+ })
380
+ }), null);
381
+ effect(() => className(_el$6, typeof props.classes?.row === "function" ? props.classes?.row({ rowIndex: rowIndex() }) : props.classes?.row));
382
+ return _el$6;
383
+ })()
384
+ }));
385
+ effect((_p$) => {
386
+ var _v$ = props.class, _v$2 = props.style, _v$3 = props.classes?.header, _v$4 = props.classes?.corner, _v$5 = props.classes?.body;
387
+ _v$ !== _p$.e && className(_el$, _p$.e = _v$);
388
+ _p$.t = style(_el$, _v$2, _p$.t);
389
+ _v$3 !== _p$.a && className(_el$2, _p$.a = _v$3);
390
+ _v$4 !== _p$.o && className(_el$4, _p$.o = _v$4);
391
+ _v$5 !== _p$.i && className(_el$5, _p$.i = _v$5);
392
+ return _p$;
393
+ }, {
394
+ e: void 0,
395
+ t: void 0,
396
+ a: void 0,
397
+ o: void 0,
398
+ i: void 0
399
+ });
400
+ return _el$;
401
+ })();
402
+ }
403
+ function getRowLabel(rowIndex) {
404
+ return `${rowIndex + 1}`;
405
+ }
406
+ function RowHeader(props) {
407
+ return (() => {
408
+ 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, () => getRowLabel(props.index));
412
+ effect((_p$) => {
413
+ var _v$6 = typeof props.class === "function" ? props.class({
414
+ rowIndex: props.index,
415
+ isSelected: props.isSelected
416
+ }) : props.class, _v$7 = props.isSelected || void 0;
417
+ _v$6 !== _p$.e && className(_el$7, _p$.e = _v$6);
418
+ _v$7 !== _p$.t && setAttribute(_el$7, "data-selected", _p$.t = _v$7);
419
+ return _p$;
420
+ }, {
421
+ e: void 0,
422
+ t: void 0
423
+ });
424
+ return _el$7;
425
+ })();
426
+ }
427
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
428
+ function getColLabel(colIndex) {
429
+ let label = "";
430
+ let n = colIndex + 1;
431
+ while (n > 0) {
432
+ label = ALPHABET[(n - 1) % 26] + label;
433
+ n = Math.floor((n - 1) / 26);
434
+ }
435
+ return label;
436
+ }
437
+ function ColHeader(props) {
438
+ return (() => {
439
+ 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, () => getColLabel(props.index));
443
+ effect((_p$) => {
444
+ var _v$8 = typeof props.class === "function" ? props.class({
445
+ colIndex: props.index,
446
+ isSelected: props.isSelected
447
+ }) : props.class, _v$9 = props.isSelected || void 0;
448
+ _v$8 !== _p$.e && className(_el$8, _p$.e = _v$8);
449
+ _v$9 !== _p$.t && setAttribute(_el$8, "data-selected", _p$.t = _v$9);
450
+ return _p$;
451
+ }, {
452
+ e: void 0,
453
+ t: void 0
454
+ });
455
+ return _el$8;
456
+ })();
457
+ }
458
+ function Cell(props) {
459
+ let cellRef;
460
+ const beginEdit = () => props.beginEdit({
461
+ row: props.row,
462
+ col: props.col
463
+ });
464
+ const commitEdit = (value) => {
465
+ props.commitEdit({
466
+ row: props.row,
467
+ col: props.col
468
+ }, value);
469
+ };
470
+ 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
+ const className$1 = createMemo(() => {
489
+ if (typeof props.class === "function") return props.class({
490
+ row: props.row,
491
+ col: props.col,
492
+ value: props.value,
493
+ isSelected: props.isSelected,
494
+ isActive: props.isActive,
495
+ isEditing: props.isEditing,
496
+ cellRef,
497
+ beginEdit,
498
+ commitEdit,
499
+ cancelEditing
500
+ });
501
+ else return props.class;
502
+ });
503
+ return (() => {
504
+ var _el$9 = _tmpl$5();
505
+ _el$9.$$dblclick = handleDoubleClick;
506
+ _el$9.$$mouseover = handleMouseOver;
507
+ _el$9.$$mousedown = handleMouseDown;
508
+ use((el) => {
509
+ cellRef = el;
510
+ props.registerCellRef(props.row, props.col, el);
511
+ }, _el$9);
512
+ insert(_el$9, () => props.renderCell({
513
+ row: props.row,
514
+ col: props.col,
515
+ value: props.value,
516
+ cellRef,
517
+ isSelected: props.isSelected,
518
+ isActive: props.isActive,
519
+ isEditing: props.isEditing,
520
+ beginEdit,
521
+ commitEdit,
522
+ cancelEditing
523
+ }));
524
+ effect((_p$) => {
525
+ var _v$0 = className$1(), _v$1 = props.row, _v$10 = props.col, _v$11 = props.isSelected || void 0, _v$12 = props.isActive || void 0, _v$13 = props.isEditing || void 0;
526
+ _v$0 !== _p$.e && className(_el$9, _p$.e = _v$0);
527
+ _v$1 !== _p$.t && setAttribute(_el$9, "data-row", _p$.t = _v$1);
528
+ _v$10 !== _p$.a && setAttribute(_el$9, "data-col", _p$.a = _v$10);
529
+ _v$11 !== _p$.o && setAttribute(_el$9, "data-selected", _p$.o = _v$11);
530
+ _v$12 !== _p$.i && setAttribute(_el$9, "data-active", _p$.i = _v$12);
531
+ _v$13 !== _p$.n && setAttribute(_el$9, "data-editing", _p$.n = _v$13);
532
+ return _p$;
533
+ }, {
534
+ e: void 0,
535
+ t: void 0,
536
+ a: void 0,
537
+ o: void 0,
538
+ i: void 0,
539
+ n: void 0
540
+ });
541
+ return _el$9;
542
+ })();
543
+ }
544
+ delegateEvents([
545
+ "keydown",
546
+ "click",
547
+ "mousedown",
548
+ "mouseover",
549
+ "dblclick"
550
+ ]);
551
+
552
+ //#endregion
553
+ export { Gridsheet };
554
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import {\n batch,\n createEffect,\n createMemo,\n createSignal,\n For,\n Index,\n onCleanup,\n onMount,\n} from \"solid-js\";\nimport type { JSX } from \"solid-js/jsx-runtime\";\n\nexport interface CellPosition {\n row: number;\n col: number;\n}\n\nexport interface CellRange {\n min: CellPosition;\n max: CellPosition;\n}\n\nfunction isPositionInRange(pos: CellPosition, range: CellRange): boolean {\n return (\n pos.row >= range.min.row &&\n pos.row <= range.max.row &&\n pos.col >= range.min.col &&\n pos.col <= range.max.col\n );\n}\n\nfunction normalizeRange(pos1: CellPosition, pos2: CellPosition): CellRange {\n return {\n min: {\n row: Math.min(pos1.row, pos2.row),\n col: Math.min(pos1.col, pos2.col),\n },\n max: {\n row: Math.max(pos1.row, pos2.row),\n col: Math.max(pos1.col, pos2.col),\n },\n };\n}\n\nexport interface CellRenderContext<T> {\n /** Row index */\n row: number;\n\n /** Column index */\n col: number;\n\n /** Cell value */\n value: T;\n\n /** Is this the active cell (focused) */\n isActive: boolean;\n\n /** Is this cell currently selected */\n isSelected: boolean;\n\n /** Is this cell currently being edited */\n isEditing: boolean;\n\n /** Solid ref for scrolling, focusing, measuring, etc. */\n cellRef: HTMLTableCellElement | undefined;\n\n /** Enter editing mode for this cell */\n beginEdit: () => void;\n\n /** Commit edit with new value */\n commitEdit: (value: T) => void;\n\n /** Exit editing mode for this cell */\n cancelEditing: () => void;\n}\n\nexport interface GridsheetProps<T> {\n /** 2D matrix of values */\n data: T[][];\n onDataChange?: (next: T[][]) => void;\n\n /** Renderer for each cell */\n renderCell: (ctx: CellRenderContext<T>) => JSX.Element;\n\n /** Currently focused cell */\n activeCell?: CellPosition | null;\n onActiveCellChange?: (pos: CellPosition | null) => void;\n\n /** Current selection range */\n selection?: CellRange | null;\n onSelectionChange?: (range: CellRange | null) => void;\n\n /** Is the active cell in editing mode */\n isEditing?: boolean;\n onIsEditingChange?: (isEditing: boolean) => void;\n\n ref?: HTMLTableElement | ((el: HTMLTableElement) => void) | undefined;\n class?: string;\n style?: JSX.CSSProperties | string;\n classes?: {\n cell?: string | ((ctx: CellRenderContext<T>) => string);\n row?: string | ((ctx: { rowIndex: number }) => string);\n rowHeader?:\n | string\n | ((ctx: { rowIndex: number; isSelected: boolean }) => string);\n colHeader?:\n | string\n | ((ctx: { colIndex: number; isSelected: boolean }) => string);\n corner?: string;\n header?: string;\n body?: string;\n };\n}\n\ntype SelectionMode = \"cell\" | \"row\" | \"col\";\nconst DEFAULT_SELECTION_MODE: SelectionMode = \"cell\";\n\nexport function Gridsheet<T>(props: GridsheetProps<T>): JSX.Element {\n const numRows = createMemo(() => props.data.length);\n const numCols = createMemo(() => (props.data[0] ? props.data[0].length : 0));\n\n const [innerActiveCell, setInnerActiveCell] =\n createSignal<CellPosition | null>(null);\n const activeCell = createMemo(() =>\n props.activeCell === undefined ? innerActiveCell() : props.activeCell,\n );\n const setActiveCell = (pos: CellPosition | null) => {\n // propsでactiveCellを管理している場合でも、内部stateを更新する\n // props.activeCellがundefinedに変更される可能性があるため\n setInnerActiveCell(pos);\n\n if (props.onActiveCellChange) {\n props.onActiveCellChange(pos);\n }\n };\n\n const isCellActive = (pos: CellPosition) => {\n const ac = activeCell();\n return ac !== null && ac.row === pos.row && ac.col === pos.col;\n };\n\n const [innerSelection, setInnerSelection] = createSignal<CellRange | null>(\n null,\n );\n const selection = createMemo(() =>\n props.selection === undefined ? innerSelection() : props.selection,\n );\n const setSelection = (range: CellRange | null) => {\n const normalizedRange = range ? normalizeRange(range.min, range.max) : null;\n // propsでselectionを管理している場合でも、内部stateを更新する\n // props.selectionがundefinedに変更される可能性があるため\n setInnerSelection(normalizedRange);\n\n if (props.onSelectionChange) {\n props.onSelectionChange(normalizedRange);\n }\n };\n\n const isCellSelected = (pos: CellPosition) => {\n const sel = selection();\n return sel !== null && isPositionInRange(pos, sel);\n };\n\n const isRowHeaderSelected = (rowIndex: number) => {\n const sel = selection();\n return sel !== null && rowIndex >= sel.min.row && rowIndex <= sel.max.row;\n };\n\n const isColHeaderSelected = (colIndex: number) => {\n const sel = selection();\n return sel !== null && colIndex >= sel.min.col && colIndex <= sel.max.col;\n };\n\n const [innerIsEditing, setInnerIsEditing] = createSignal<boolean>(false);\n const isEditing = createMemo(() =>\n props.isEditing === undefined ? innerIsEditing() : props.isEditing,\n );\n const setIsEditing = (editing: boolean) => {\n setInnerIsEditing(editing);\n\n if (props.onIsEditingChange) {\n props.onIsEditingChange(editing);\n }\n };\n\n const isCellEditing = (pos: CellPosition) => {\n return isEditing() && isCellActive(pos);\n };\n\n const beginCellEdit = (pos: CellPosition) => {\n batch(() => {\n setIsEditing(true);\n setActiveCell(pos);\n setSelection(normalizeRange(pos, pos));\n });\n };\n\n const commitCellEdit = (pos: CellPosition, value: T) => {\n const nextData = props.data.map((row) => row.slice());\n\n const targetRow = nextData[pos.row];\n if (targetRow && pos.col >= 0 && pos.col < targetRow.length) {\n targetRow[pos.col] = value;\n if (props.onDataChange) {\n props.onDataChange(nextData);\n }\n }\n\n setIsEditing(false);\n };\n\n const cancelCellEdit = () => {\n setIsEditing(false);\n };\n\n const [isMouseDown, setIsMouseDown] = createSignal(false);\n const [selectionMode, setSelectionMode] = createSignal<SelectionMode>(\n DEFAULT_SELECTION_MODE,\n );\n const [selectionAnchor, setSelectionAnchor] =\n createSignal<CellPosition | null>(null);\n\n let previousBodyUserSelect: string | null = null;\n const disableTextSelectionDuringDrag = () => {\n if (typeof document === \"undefined\" || previousBodyUserSelect !== null)\n return;\n const body = document.body;\n if (!body) return;\n previousBodyUserSelect = body.style.userSelect;\n body.style.userSelect = \"none\";\n };\n const restoreTextSelectionAfterDrag = () => {\n if (typeof document === \"undefined\" || previousBodyUserSelect === null)\n return;\n const body = document.body;\n if (body) {\n body.style.userSelect = previousBodyUserSelect;\n }\n previousBodyUserSelect = null;\n };\n\n const handleMouseUp = () => {\n setIsMouseDown(false);\n setSelectionMode(DEFAULT_SELECTION_MODE);\n setSelectionAnchor(null);\n restoreTextSelectionAfterDrag();\n };\n onMount(() => {\n window.addEventListener(\"mouseup\", handleMouseUp);\n });\n onCleanup(() => {\n window.removeEventListener(\"mouseup\", handleMouseUp);\n restoreTextSelectionAfterDrag();\n });\n\n const handleMouseDownOnCell = (pos: CellPosition, e: MouseEvent) => {\n if (e.button !== 0) return;\n\n // 編集中のセルは選択操作を無効化し、編集状態を維持する\n if (isCellEditing(pos)) return;\n\n // ダブルクリック(2回目のmousedown)で即座に編集に入る\n if (e.detail === 2) {\n e.preventDefault();\n beginCellEdit(pos);\n return;\n }\n\n e.preventDefault();\n\n batch(() => {\n disableTextSelectionDuringDrag();\n setIsMouseDown(true);\n setSelectionMode(\"cell\");\n setSelectionAnchor(pos);\n\n setIsEditing(false);\n setSelection(normalizeRange(pos, pos));\n setActiveCell(pos);\n });\n };\n const handleMouseOverOnCell = (pos: CellPosition, e: MouseEvent) => {\n // ドラッグされていない場合は何もしない\n if ((e.buttons & 1) === 0) return;\n\n batch(() => {\n if (!isMouseDown() || selectionMode() !== \"cell\") return;\n\n const start = selectionAnchor();\n if (start) {\n setSelection(normalizeRange(start, pos));\n } else {\n console.warn(\"selectionAnchor is null during mouse drag selection\");\n }\n });\n };\n\n const handleMouseDownOnRowHeader = (rowIndex: number) => {\n if (numCols() === 0) {\n return;\n }\n\n batch(() => {\n disableTextSelectionDuringDrag();\n setIsMouseDown(true);\n setSelectionMode(\"row\");\n const start: CellPosition = { row: rowIndex, col: 0 };\n const end: CellPosition = { row: rowIndex, col: numCols() - 1 };\n setSelectionAnchor(start);\n\n setIsEditing(false);\n setSelection(normalizeRange(start, end));\n setActiveCell(start);\n });\n };\n const handleMouseOverOnRowHeader = (rowIndex: number) => {\n batch(() => {\n if (!isMouseDown() || selectionMode() !== \"row\") return;\n\n const start = selectionAnchor();\n if (start) {\n const end: CellPosition = { row: rowIndex, col: numCols() - 1 };\n setSelection(normalizeRange(start, end));\n } else {\n console.warn(\"selectionAnchor is null during mouse drag selection\");\n }\n });\n };\n\n const handleMouseDownOnColHeader = (colIndex: number) => {\n if (numRows() === 0) {\n return;\n }\n\n batch(() => {\n disableTextSelectionDuringDrag();\n setIsMouseDown(true);\n setSelectionMode(\"col\");\n const start: CellPosition = { row: 0, col: colIndex };\n const end: CellPosition = { row: numRows() - 1, col: colIndex };\n setSelectionAnchor(start);\n\n setIsEditing(false);\n setSelection(normalizeRange(start, end));\n setActiveCell(start);\n });\n };\n const handleMouseOverOnColHeader = (colIndex: number) => {\n batch(() => {\n if (!isMouseDown() || selectionMode() !== \"col\") return;\n\n const start = selectionAnchor();\n if (start) {\n const end: CellPosition = { row: numRows() - 1, col: colIndex };\n setSelection(normalizeRange(start, end));\n } else {\n console.warn(\"selectionAnchor is null during mouse drag selection\");\n }\n });\n };\n\n const handleClickOnCorner = () => {\n if (numCols() === 0 || numRows() === 0) {\n return;\n }\n\n batch(() => {\n setIsEditing(false);\n setSelection(\n normalizeRange(\n { row: 0, col: 0 },\n { row: numRows() - 1, col: numCols() - 1 },\n ),\n );\n setActiveCell({ row: 0, col: 0 });\n });\n };\n\n const navigate = (\n deltaRow: number,\n deltaCol: number,\n isSelection: boolean,\n ) => {\n const ac = activeCell();\n if (!ac) return;\n\n if (isSelection) {\n let anchor = selectionAnchor();\n if (!anchor) {\n anchor = ac;\n setSelectionAnchor(anchor);\n }\n\n const sel = selection();\n let headRow = anchor.row;\n let headCol = anchor.col;\n\n if (sel) {\n headRow = sel.min.row === anchor.row ? sel.max.row : sel.min.row;\n headCol = sel.min.col === anchor.col ? sel.max.col : sel.min.col;\n }\n\n const nextHeadRow = Math.max(\n 0,\n Math.min(numRows() - 1, headRow + deltaRow),\n );\n const nextHeadCol = Math.max(\n 0,\n Math.min(numCols() - 1, headCol + deltaCol),\n );\n\n setSelection(\n normalizeRange(anchor, { row: nextHeadRow, col: nextHeadCol }),\n );\n } else {\n const nextRow = Math.max(0, Math.min(numRows() - 1, ac.row + deltaRow));\n const nextCol = Math.max(0, Math.min(numCols() - 1, ac.col + deltaCol));\n const nextPos = { row: nextRow, col: nextCol };\n\n setActiveCell(nextPos);\n setSelection(normalizeRange(nextPos, nextPos));\n setSelectionAnchor(null);\n }\n };\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.isComposing) return;\n\n // 編集中のキー操作はrenderCell側で処理する\n if (isEditing()) return;\n\n const ac = activeCell();\n if (!ac) return;\n\n switch (e.key) {\n case \"ArrowUp\":\n e.preventDefault();\n navigate(-1, 0, e.shiftKey);\n break;\n case \"ArrowDown\":\n e.preventDefault();\n navigate(1, 0, e.shiftKey);\n break;\n case \"ArrowLeft\":\n e.preventDefault();\n navigate(0, -1, e.shiftKey);\n break;\n case \"ArrowRight\":\n e.preventDefault();\n navigate(0, 1, e.shiftKey);\n break;\n case \"Tab\":\n e.preventDefault();\n navigate(0, e.shiftKey ? -1 : 1, false);\n break;\n case \"Enter\":\n e.preventDefault();\n beginCellEdit(ac);\n break;\n\n default:\n break;\n }\n };\n\n const cellRefs = new Map<string, HTMLElement>();\n const getCellKey = (pos: CellPosition) => `${pos.row}:${pos.col}`;\n createEffect(() => {\n if (isEditing()) return;\n const ac = activeCell();\n if (ac) {\n const el = cellRefs.get(getCellKey(ac));\n el?.focus();\n }\n });\n\n return (\n <table\n data-slot=\"gridsheet\"\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n ref={props.ref}\n class={props.class}\n style={props.style}\n >\n <thead data-slot=\"gridsheet-header\" class={props.classes?.header}>\n <tr data-slot=\"gridsheet-row\">\n <th\n data-slot=\"gridsheet-corner\"\n onClick={handleClickOnCorner}\n class={props.classes?.corner}\n ></th>\n <Index each={Array.from({ length: numCols() })}>\n {(_, colIndex) => (\n <ColHeader\n index={colIndex}\n isSelected={isColHeaderSelected(colIndex)}\n onMouseDown={handleMouseDownOnColHeader}\n onMouseOver={handleMouseOverOnColHeader}\n class={props.classes?.colHeader}\n />\n )}\n </Index>\n </tr>\n </thead>\n <tbody data-slot=\"gridsheet-body\" class={props.classes?.body}>\n <For each={props.data}>\n {(row, rowIndex) => (\n <tr\n data-slot=\"gridsheet-row\"\n class={\n typeof props.classes?.row === \"function\"\n ? props.classes?.row({ rowIndex: rowIndex() })\n : props.classes?.row\n }\n >\n <RowHeader\n index={rowIndex()}\n isSelected={isRowHeaderSelected(rowIndex())}\n onMouseDown={handleMouseDownOnRowHeader}\n onMouseOver={handleMouseOverOnRowHeader}\n class={props.classes?.rowHeader}\n />\n <For each={row}>\n {(cell, colIndex) => (\n <Cell\n row={rowIndex()}\n col={colIndex()}\n value={cell}\n isActive={isCellActive({\n row: rowIndex(),\n col: colIndex(),\n })}\n isEditing={isCellEditing({\n row: rowIndex(),\n col: colIndex(),\n })}\n isSelected={isCellSelected({\n row: rowIndex(),\n col: colIndex(),\n })}\n beginEdit={beginCellEdit}\n commitEdit={commitCellEdit}\n cancelEditing={cancelCellEdit}\n setActiveCell={setActiveCell}\n setSelection={setSelection}\n renderCell={props.renderCell}\n onMouseDown={handleMouseDownOnCell}\n onMouseOver={handleMouseOverOnCell}\n registerCellRef={(rowPos, colPos, el) => {\n cellRefs.set(\n getCellKey({ row: rowPos, col: colPos }),\n el,\n );\n }}\n class={props.classes?.cell}\n />\n )}\n </For>\n </tr>\n )}\n </For>\n </tbody>\n </table>\n );\n}\n\ninterface RowHeaderProps {\n index: number;\n isSelected: boolean;\n\n onMouseDown: (rowIndex: number) => void;\n onMouseOver: (rowIndex: number) => void;\n\n class?: string | ((ctx: { rowIndex: number; isSelected: boolean }) => string);\n}\n\nfunction getRowLabel(rowIndex: number): string {\n return `${rowIndex + 1}`;\n}\n\nfunction RowHeader(props: RowHeaderProps) {\n return (\n // biome-ignore lint/a11y/useKeyWithMouseEvents: 親のtableでキー操作を処理している\n <th\n data-slot=\"gridsheet-rowheader\"\n onMouseDown={() => props.onMouseDown(props.index)}\n onMouseOver={() => props.onMouseOver(props.index)}\n class={\n typeof props.class === \"function\"\n ? props.class({ rowIndex: props.index, isSelected: props.isSelected })\n : props.class\n }\n data-selected={props.isSelected || undefined}\n >\n {getRowLabel(props.index)}\n </th>\n );\n}\n\ninterface ColHeaderProps {\n index: number;\n isSelected: boolean;\n\n onMouseDown: (colIndex: number) => void;\n onMouseOver: (colIndex: number) => void;\n\n class?: string | ((ctx: { colIndex: number; isSelected: boolean }) => string);\n}\n\nconst ALPHABET = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\nfunction getColLabel(colIndex: number): string {\n let label = \"\";\n let n = colIndex + 1;\n while (n > 0) {\n const rem = (n - 1) % 26;\n label = ALPHABET[rem] + label;\n n = Math.floor((n - 1) / 26);\n }\n return label;\n}\n\nfunction ColHeader(props: ColHeaderProps) {\n return (\n // biome-ignore lint/a11y/useKeyWithMouseEvents: 親のtableでキー操作を処理している\n <th\n data-slot=\"gridsheet-colheader\"\n onMouseDown={() => props.onMouseDown(props.index)}\n onMouseOver={() => props.onMouseOver(props.index)}\n class={\n typeof props.class === \"function\"\n ? props.class({ colIndex: props.index, isSelected: props.isSelected })\n : props.class\n }\n data-selected={props.isSelected || undefined}\n >\n {getColLabel(props.index)}\n </th>\n );\n}\n\ninterface CellProps<T> {\n row: number;\n col: number;\n value: T;\n\n isSelected: boolean;\n isActive: boolean;\n isEditing: boolean;\n\n beginEdit: (pos: CellPosition) => void;\n commitEdit: (pos: CellPosition, value: T) => void;\n cancelEditing: () => void;\n\n setActiveCell?: (pos: CellPosition) => void;\n setSelection?: (range: CellRange | null) => void;\n\n onMouseDown: (pos: CellPosition, e: MouseEvent) => void;\n onMouseOver: (pos: CellPosition, e: MouseEvent) => void;\n\n registerCellRef: (row: number, col: number, el: HTMLTableCellElement) => void;\n\n renderCell: (ctx: CellRenderContext<T>) => JSX.Element;\n\n class?: string | ((ctx: CellRenderContext<T>) => string);\n}\n\nfunction Cell<T>(props: CellProps<T>) {\n let cellRef!: HTMLTableCellElement;\n\n const beginEdit = () => props.beginEdit({ row: props.row, col: props.col });\n const commitEdit = (value: T) => {\n props.commitEdit({ row: props.row, col: props.col }, value);\n };\n const cancelEditing = () => props.cancelEditing();\n\n const handleMouseDown = (e: MouseEvent) => {\n props.onMouseDown({ row: props.row, col: props.col }, e);\n };\n const handleMouseOver = (e: MouseEvent) => {\n props.onMouseOver({ row: props.row, col: props.col }, e);\n };\n const handleDoubleClick = (e: MouseEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n if (!props.isEditing) {\n beginEdit();\n }\n };\n\n const className = createMemo(() => {\n if (typeof props.class === \"function\") {\n return props.class({\n row: props.row,\n col: props.col,\n value: props.value,\n isSelected: props.isSelected,\n isActive: props.isActive,\n isEditing: props.isEditing,\n cellRef,\n beginEdit,\n commitEdit,\n cancelEditing,\n });\n } else {\n return props.class;\n }\n });\n\n return (\n // biome-ignore lint/a11y/useKeyWithMouseEvents: 親のtableでキー操作を処理している\n <td\n data-slot=\"gridsheet-cell\"\n ref={(el) => {\n cellRef = el;\n props.registerCellRef(props.row, props.col, el);\n }}\n onMouseDown={handleMouseDown}\n onMouseOver={handleMouseOver}\n onDblClick={handleDoubleClick}\n tabIndex={-1}\n class={className()}\n data-row={props.row}\n data-col={props.col}\n data-selected={props.isSelected || undefined}\n data-active={props.isActive || undefined}\n data-editing={props.isEditing || undefined}\n >\n {props.renderCell({\n row: props.row,\n col: props.col,\n value: props.value,\n\n cellRef: cellRef,\n\n isSelected: props.isSelected,\n isActive: props.isActive,\n isEditing: props.isEditing,\n\n beginEdit,\n commitEdit,\n cancelEditing,\n })}\n </td>\n );\n}\n"],"mappings":";;;;AASA,IAAI,SAAa,yBAAA,yLAAA,EACf,UAAuB,yBAAW,+BAAW,4EAE7C,UAAuB,yBAAK,qCAAA,EAC5B,UAAW,yBAAA,4CAAA;AAEb,SAAA,kBAAA,KAAA,OAAA;;;AAGA,SAAS,eAAU,MAAA,MAAA;AACjB,QAAO;EACT,KAAA;;GAEM,KAAK,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI;GAClC;EACD,KAAK;GACH,KAAK,KAAK,IAAI,KAAK,KAAK,KAAA,IAAA;GACxB,KAAK,KAAK,IAAI,KAAK,KAAK,KAAA,IAAA;GACzB;EACF;;;AAGH,SAAgB,UAAU,OAAO;CAC/B,MAAM,UAAC,iBAAA,MAAA,KAAA,OAAA;CACP,MAAM,UAAC,iBAAA,MAAA,KAAA,KAAA,MAAA,KAAA,GAAA,SAAA,EAAA;CACP,MAAM,CAAC,iBAAiB,sBAAa,aAAA,KAAA;CACrC,MAAM,aAAa,iBAAiB,MAAC,eAAA,SAAA,iBAAA,GAAA,MAAA,WAAA;CACrC,MAAG,iBAAA,QAAA;AAGD,qBAAmB,IAAI;AACvB,MAAC,MAAA,mBACF,OAAA,mBAAA,IAAA;;CAGD,MAAM,gBAAe,QAAO;EAC1B,MAAM,KAAK,YAAE;AACb,SAAO,OAAE,QAAA,GAAA,QAAA,IAAA,OAAA,GAAA,QAAA,IAAA;;CAEX,MAAM,CAAC,gBAAW,qBAAA,aAAA,KAAA;CAClB,MAAM,YAAK,iBAAA,MAAA,cAAA,SAAA,gBAAA,GAAA,MAAA,UAAA;;EAET,MAAM,kBAAQ,QAAA,eAAA,MAAA,KAAA,MAAA,IAAA,GAAA;AAGd,oBAAkB,gBAAgB;AAClC,MAAI,MAAM;;;EAKV,MAAM,MAAM,WAAW;AACvB,SAAO,QAAQ,QAAC,kBAAA,KAAA,IAAA;;CAElB,MAAM,uBAAsB,aAAY;EACtC,MAAM,MAAM,WAAW;;;CAGzB,MAAM,uBAAe,aAAA;;AAEnB,SAAO,QAAQ,QAAQ,YAAO,IAAA,IAAA,OAAA,YAAA,IAAA,IAAA;;;CAGhC,MAAM,YAAY,iBAAiB,MAAE,cAAA,SAAA,gBAAA,GAAA,MAAA,UAAA;CACrC,MAAM,gBAAe,YAAI;AAC3B,oBAAA,QAAA;8BAEM,OAAM,kBAAkB,QAAK;;CAGjC,MAAM,iBAAgB,QAAO;;;CAG7B,MAAM,iBAAgB,QAAO;;AAEzB,gBAAa,KAAK;AAClB,iBAAc,IAAI;AAClB,gBAAa,eAAe,KAAK,IAAI,CAAC;;;CAG1C,MAAM,kBAAkB,KAAI,UAAA;EAC1B,MAAM,WAAW,MAAM,KAAK,KAAI,QAAO,IAAI,OAAO,CAAC;;AAEnD,MAAI,aAAa,IAAI,OAAO,KAAK,IAAI,MAAC,UAAA,QAAA;AACpC,aAAU,IAAI,OAAC;AACf,OAAI,MAAM;;AAIZ,eAAa,MAAM;;CAErB,MAAM,uBAAuB;AAC3B,eAAa,MAAM;;CAErB,MAAM,CAAC,aAAI,kBAAA,aAAA,MAAA;CACX,MAAM,CAAC,eAAe,oBAAoB,aAAa,uBAAW;CAClE,MAAM,CAAC,iBAAK,sBAAA,aAAA,KAAA;CACZ,IAAI,yBAAO;CACX,MAAM,uCAAuC;AAC3C,MAAI,OAAO,aAAI,eAAA,2BAAA,KAAA;EACf,MAAM,OAAO,SAAE;AACf,MAAI,CAAC,KAAM;AACZ,2BAAA,KAAA,MAAA;AACH,OAAA,MAAA,aAAA;;CAEE,MAAM,sCAAmC;AACvC,MAAI,OAAO,aAAa,eAAe,2BAAS,KAAA;;AAEhD,MAAI,KACF,MAAK,MAAM,aAAa;;;CAI5B,MAAM,sBAAsB;AAC1B,iBAAe,MAAM;AACrB,mBAAiB,uBAAuB;AACzC,qBAAA,KAAA;AACC,iCAA+B;;AAEjC,eAAc;AACZ,SAAO,iBAAgB,WAAA,cAAA;;AAEzB,iBAAgB;AACd,SAAO,oBAAoB,WAAI,cAAA;AAC/B,iCAAA;GACD;;AAEC,MAAI,EAAE,WAAW,EAAG;AAGrB,MAAA,cAAA,IAAA,CAAA;AAGC,MAAI,EAAA,WAAA,GAAA;AACL,KAAA,gBAAA;AACG,iBAAc,IAAI;AAClB;;AAEF,IAAE,gBAAgB;AAClB,cAAY;AACV,mCAAgC;AAChC,kBAAe,KAAK;AACpB,oBAAiB,OAAO;;AAExB,gBAAa,MAAM;AACnB,gBAAa,eAAe,KAAK,IAAI,CAAC;AACxC,iBAAA,IAAA;IACD;;CAED,MAAM,yBAAyB,KAAK,MAAM;AAExC,OAAK,EAAE,UAAU,OAAO,EAAG;AAC5B,cAAA;;GAEG,MAAM,QAAQ,iBAAiB;AAC/B,OAAI,MACF,cAAa,eAAe,OAAO,IAAI,CAAC;;IAI1C;;CAEJ,MAAC,8BAAA,aAAA;sBAEG;AAEF,cAAY;AACb,mCAAA;AACG,kBAAe,KAAK;AACpB,oBAAiB,MAAM;;IAErB,KAAK;IACL,KAAK;IACT;GACD,MAAA,MAAA;;IAEK,KAAK,SAAS,GAAG;IAClB;AACJ,sBAAA,MAAA;;AAEG,gBAAa,eAAe,OAAO,IAAI,CAAC;AACxC,iBAAU,MAAA;IACV;;CAEJ,MAAM,8BAA6B,aAAO;AACxC,cAAE;AACH,OAAA,CAAA,aAAA,IAAA,eAAA,KAAA,MAAA;;AAEG,OAAI,MAKF,cAAa,eAAW,OAJZ;;IAEV,KAAK,SAAS,GAAG;IAClB,CACuB,CAAA;OAExB,SAAQ,KAAK,sDAAe;IAEhC;;CAEF,MAAM,8BAAe,aAAA;AACpB,MAAA,SAAA,KAAA;AAGC,cAAY;AACb,mCAAA;;AAEG,oBAAiB,MAAM;GACvB,MAAM,QAAQ;IACZ,KAAK;IACV,KAAA;IACI;GACD,MAAM,MAAM;;IAEV,KAAK;IACN;AACD,sBAAmB,MAAM;AACzB,gBAAM,MAAA;AACN,gBAAa,eAAW,OAAA,IAAA,CAAA;AACxB,iBAAc,MAAC;IACf;;CAEJ,MAAC,8BAAA,aAAA;AACC,cAAY;AACV,OAAI,CAAC,aAAa,IAAI,eAAe,KAAK,MAAO;GACjD,MAAM,QAAA,iBAAA;AACN,OAAI,MAKP,cAAA,eAAA,OAJW;IACJ,KAAK,SAAS,GAAG;IACvB,KAAA;IACK,CACN,CAAA;OAEK,SAAQ,KAAK,sDAAS;IAExB;;CAEJ,MAAM,4BAA2B;AAChC,MAAA,SAAA,KAAA,KAAA,SAAA,KAAA,EACG;AAEF,cAAA;AACE,gBAAY,MAAA;AACZ,gBAAa,eAAe;IAC1B,KAAK;IACT,KAAA;;IAEI,KAAK,SAAS,GAAG;IACjB,KAAK,SAAS,GAAG;;AAEnB,iBAAc;IACZ,KAAK;;IAEN,CAAC;IACF;;CAEJ,MAAM,YAAY,UAAI,UAAA,gBAAA;EACpB,MAAM,KAAE,YAAA;AACR,MAAA,CAAA,GAAA;;GAEE,IAAI,SAAS,iBAAG;;AAEd,aAAQ;AACR,uBAAmB,OAAO;;GAE5B,MAAM,MAAM,WAAW;GACvB,IAAI,UAAU,OAAO;;AAErB,OAAI,KAAK;AACP,cAAU,IAAI,IAAI,QAAQ,OAAO,MAAG,IAAA,IAAA,MAAA,IAAA,IAAA;AACpC,cAAU,IAAI,IAAE,QAAA,OAAA,MAAA,IAAA,IAAA,MAAA,IAAA,IAAA;;GAErB,MAAA,cAAA,KAAA,IAAA,GAAA,KAAA,IAAA,SAAA,GAAA,GAAA,UAAA,SAAA,CAAA;GACG,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,GAAG,GAAG,UAAQ,SAAA,CAAA;AAChE,gBAAa,eAAK,QAAA;IAChB,KAAK;;IAEN,CAAC,CAAC;SACE;GAGL,MAAM,UAAK;IACT;IACA,KAHc,KAAK,IAAI,GAAG,KAAG,IAAA,SAAA,GAAA,GAAA,GAAA,MAAA,SAAA,CAAA;IAI9B;AACD,iBAAA,QAAA;AACA,gBAAA,eAAA,SAAA,QAAA,CAAA;AACH,sBAAA,KAAA;;;CAGD,MAAM,iBAAgB,MAAC;AACrB,MAAI,EAAE,YAAE;AAGR,MAAI,WAAQ,CAAA;EACZ,MAAM,KAAK,YAAY;AACvB,MAAI,CAAC,GAAI;AACT,UAAQ,EAAE,KAAV;GACE,KAAK;AACH,MAAE,gBAAgB;AAClB,aAAS,IAAI,GAAG,EAAE,SAAK;;GAEzB,KAAK;AACH,MAAE,gBAAgB;AAClB,aAAS,GAAG,GAAG,EAAE,SAAC;AACpB;GACH,KAAA;AACK,MAAE,gBAAgB;AAClB,aAAQ,GAAA,IAAA,EAAA,SAAA;AACR;;AAEA,MAAE,gBAAgB;AAClB,aAAS,GAAA,GAAA,EAAA,SAAA;AACT;GACF,KAAK;AACH,MAAE,gBAAG;AACL,aAAS,GAAG,EAAE,WAAW,KAAK,GAAG,MAAM;AACzC;GACA,KAAA;AACH,MAAA,gBAAA;;AAEK;GACF,QACE;;;CAGN,MAAM,2BAAQ,IAAA,KAAA;CACd,MAAM,cAAa,QAAO,GAAG,IAAI,IAAG,GAAA,IAAA;AACpC,oBAAmB;AACjB,MAAI,WAAW,CAAE;EACjB,MAAM,KAAK,YAAY;AACvB,MAAI,IACS,SAAS,IAAI,WAAC,GAAA,CAAA;GAG3B;AACF,eAAc;EACZ,IAAE,OAAA,QAAA,EACH,QAAA,KAAA,YACG,QAAQ,MAAM,YACd,QAAQ,MAAE,YACV,QAAQ,MAAM;;AAEhB,SAAO,UAAU,aAAa,IAAG,OAAA,KAAA,GAAA,MAAA,MAAA;AACjC,OAAK,YAAQ;AACb,QAAM,UAAU;AAChB,SAAS,OAAO,gBAAkB,OAAO;GACvC,IAAI,OAAG;AACL,WAAO,MAAM,KAAK,EACpB,QAAA,SAAA,EACA,CAAA;;;IAGE,OAAO;IACP,IAAI,aAAa;AACf,YAAE,oBAAA,SAAA;;;IAGJ,aAAQ;IACR,KAAK,WAAW;AACd,YAAO,MAAC,SAAA;;IAEX,CAAC;GACH,CAAC,EAAE,KAAK;AACT,SAAK,OAAA,gBAAA,KAAA;GACH,IAAC,OAAA;AACC,WAAO,MAAM;;GAElB,WAAA,KAAA,oBAAA;;AAEK,WAAS,OAAE,gBAAA,WAAA;KACT,IAAI,QAAM;AACR,aAAO,UAAC;;KAEb,IAAA,aAAA;AACK,aAAO,oBAAQ,UAAA,CAAA;;;KAGjB,aAAW;KACX,KAAK,WAAW;AACd,aAAO,MAAA,SAAA;;KAEV,CAAC,EAAE,KAAK;AACX,WAAA,OAAA,gBAAA,KAAA;;KAEI,WAAW,MAAM,aAAE,gBAAA,MAAA;MACjB,IAAI,MAAM;AACR,cAAO,UAAS;;MAElB,IAAG,MAAA;AACD,cAAO,UAAU;;MAEzB,OAAA;;AAEQ,cAAO,aAAa;QACzB,KAAA,UAAA;QACO,KAAK,UAAU;QACxB,CAAA;;MAEE,IAAA,YAAA;AACK,cAAO,cAAc;QAC5B,KAAA,UAAA;;QAEQ,CAAC;;MAET,IAAA,aAAA;AACI,cAAA,eAAA;QACK,KAAK,UAAU;QACf,KAAK,UAAU;QAChB,CAAC;;MAEJ,WAAW;MACX,YAAY;MACZ,eAAe;MACvB;MACD;;AAEW,cAAO,MAAM;;;MAGf,aAAa;MACb,kBAAe,QAAA,QAAA,OAAA;;QAEX,KAAK;QACL,KAAG;;;MAGP,KAAK,WAAG;AACN,cAAO,MAAK,SAAA;;MAEf,CAAC;KACH,CAAC,EAAE,KAAK;AACT,iBAAe,UAAG,OAAA,OAAA,MAAA,SAAA,QAAA,aAAA,MAAA,SAAA,IAAA,EAChB,UAAU,UAAU,EACrB,CAAC,GAAG,MAAA,SAAA,IAAA,CAAA;AACL,WAAO;OACL;GACL,CAAC,CAAC;AACH,UAAS,QAAA;GACP,IAAI,MAAM,MAAM,OACd,OAAO,MAAM,OACb,OAAO,MAAM,SAAS,QACtB,OAAK,MAAA,SAAA,QACL,OAAO,MAAC,SAAA;AACV,WAAQ,IAAI,KAAK,UAAG,MAAA,IAAA,IAAA,IAAA;AACpB,OAAI,IAAI,MAAQ,MAAM,MAAM,IAAI,EAAE;AAClC,YAAO,IAAA,KAAA,UAAA,OAAA,IAAA,IAAA,KAAA;AACP,YAAS,IAAG,KAAA,UAAA,OAAA,IAAA,IAAA,KAAA;AACZ,YAAS,IAAI,KAAK,UAAE,OAAA,IAAA,IAAA,KAAA;AACpB,UAAO;KACN;;GAED,GAAG;GACH,GAAG;GACL,GAAA;GACD,GAAA;;AAEC,SAAO;KACL;;AAEN,SAAS,YAAY,UAAM;AACzB,QAAO,GAAG,WAAW;;AAEvB,SAAS,UAAU,OAAO;AACxB,eACE;EACA,IAAA,QAAA,SAAA;;AAEE,QAAG,oBAAA,MAAA,YAAA,MAAA,MAAA;AACH,SAAG,aAAA,YAAA,MAAA,MAAA,CAAA;AACH,UAAS,QAAO;GACd,IAAI,OAAM,OAAA,MAAA,UAAA,aAAA,MAAA,MAAA;IACN,UAAU,MAAM;IAChB,YAAQ,MAAA;IACT,CAAC,GAAG,MAAM,OACX,OAAO,MAAM,cAAC;AACpB,YAAA,IAAA,KAAA,UAAA,OAAA,IAAA,IAAA,KAAA;AACI,YAAS,IAAI,KAAK,aAAe,OAAO,iBAAiB,IAAI,IAAE,KAAA;AAC/D,UAAO;KACN;GACD,GAAG;GACH,GAAG;GACJ,CAAC;AACF,SAAO;KACL;;AAGR,MAAM,WAAW;AACjB,SAAS,YAAY,UAAU;CAC7B,IAAI,QAAQ;CACZ,IAAI,IAAI,WAAW;AACnB,QAAO,IAAI,GAAG;AAEZ,UAAQ,UADG,IAAA,KAAA,MACF;AACT,MAAI,KAAK,OAAI,IAAA,KAAA,GAAA;;AAEf,QAAO;;AAET,SAAS,UAAU,OAAO;AACxB,eACS;EACL,IAAI,QAAQ,SAAS;AACrB,QAAM,oBAAQ,MAAA,YAAA,MAAA,MAAA;AACd,QAAM,oBAAoB,MAAM,YAAY,MAAK,MAAA;AACjD,SAAS,aAAa,YAAY,MAAM,MAAM,CAAC;AAC/C,UAAS,QAAO;GACd,IAAI,OAAE,OAAA,MAAA,UAAA,aAAA,MAAA,MAAA;IACF,UAAA,MAAA;IACA,YAAW,MAAA;IACZ,CAAC,GAAG,MAAM,OACX,OAAO,MAAM,cAAc;AAC7B,YAAS,IAAI,KAAK,UAAY,OAAO,IAAI,IAAI,KAAE;AAC/C,YAAS,IAAI,KAAK,aAAe,OAAO,iBAAO,IAAA,IAAA,KAAA;AAC/C,UAAO;KACN;GACD,GAAG;GACH,GAAG;GACJ,CAAC;AACF,SAAO;KACL;;AAGR,SAAS,KAAK,OAAO;CACnB,IAAI;CACJ,MAAM,kBAAc,MAAA,UAAA;EAClB,KAAK,MAAM;EACX,KAAK,MAAM;EACZ,CAAC;CACF,MAAM,cAAa,UAAC;AAClB,QAAM,WAAW;GACf,KAAK,MAAM;GACX,KAAK,MAAM;GACZ,EAAE,MAAM;;CAEX,MAAM,sBAAsB,MAAM,eAAU;CAC5C,MAAM,mBAAkB,MAAK;AAC3B,QAAM,YAAY;GAChB,KAAK,MAAM;GACX,KAAK,MAAM;GACZ,EAAE,EAAE;;CAEP,MAAM,mBAAkB,MAAK;AAC3B,QAAM,YAAY;GAChB,KAAK,MAAM;GACX,KAAK,MAAM;GACZ,EAAE,EAAE;;CAEP,MAAM,qBAAoB,MAAK;AAC7B,MAAI,EAAE,WAAS,EAAA;AACf,IAAE,gBAAW;AACb,MAAI,CAAC,MAAM,UACT,YAAU;;CAGd,MAAM,cAAK,iBAAA;AACT,MAAI,OAAG,MAAA,UAAA,WACR,QAAA,MAAA,MAAA;GACH,KAAA,MAAA;;GAEQ,OAAO,MAAM;GACb,YAAO,MAAA;GACP,UAAU,MAAG;;GAEb;GACA;;GAEA;GACR,CAAA;MAEM,QAAO,MAAM;GAEnB;eAEW;EACL,IAAG,QAAA,SAAA;AACH,QAAM,aAAa;AACnB,QAAA,cAAA;AACA,QAAM,cAAc;AACpB,OAAM,OAAM;AACV,aAAU;AACV,SAAI,gBAAA,MAAA,KAAA,MAAA,KAAA,GAAA;KACH,MAAM;AACT,SAAS,aAAa,MAAM,WAAW;GACrC,KAAK,MAAM;GACb,KAAA,MAAA;GACE,OAAO,MAAM;GACjB;GACI,YAAY,MAAM;GAClB,UAAA,MAAA;GACL,WAAA,MAAA;GACH;;GAEQ;GACD,CAAC,CAAC;AACH,UAAS,QAAM;6BAEX,OAAO,MAAM,KACb,QAAQ,MAAM,yCAEd,QAAQ,MAAM,YAAY,QACpC,QAAA,MAAA,aAAA;;AAEQ,YAAS,IAAI,KAAK,aAAe,OAAI,YAAA,IAAA,IAAA,KAAA;AACrC,aAAU,IAAI,KAAK,aAAe,OAAK,YAAA,IAAA,IAAA,MAAA;AACvC,aAAQ,IAAA,KAAA,aAAA,OAAA,iBAAA,IAAA,IAAA,MAAA;AACR,aAAU,IAAI,KAAA,aAAA,OAAA,eAAA,IAAA,IAAA,MAAA;AACd,aAAQ,IAAA,KAAA,aAAA,OAAA,gBAAA,IAAA,IAAA,MAAA;AACR,UAAO;KACN;GACD,GAAG;GACT,GAAA;GACM,GAAG;GACX,GAAA;;GAEQ,GAAG;GACJ,CAAC;AACF,SAAO;KACP;;AAGN,eAAiB;CAAC;CAAW;CAAS;CAAa;CAAI;CAAA,CAAA"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@shiguri/solid-grid",
3
+ "author": {
4
+ "name": "shiguri",
5
+ "url": "https://github.com/shiguri-01"
6
+ },
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/shiguri-01/solid-grid"
10
+ },
11
+ "license": "MIT",
12
+ "version": "0.0.1",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsdown"
28
+ },
29
+ "peerDependencies": {
30
+ "solid-js": "^1.9.0"
31
+ },
32
+ "devDependencies": {
33
+ "rolldown-plugin-solid": "^0.2.1",
34
+ "tsdown": "^0.16.1"
35
+ }
36
+ }