@dxos/lit-grid 0.6.10-main.3cfcc89

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 ADDED
@@ -0,0 +1,8 @@
1
+ MIT License
2
+ Copyright (c) 2022 DXOS
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
+
6
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @dxos/lit-grid
2
+
3
+ A grid webcomponent using Lit
@@ -0,0 +1,429 @@
1
+ // packages/ui/lit-grid/src/dx-grid.ts
2
+ import { LitElement, html } from "lit";
3
+ import { customElement, state, property } from "lit/decorators.js";
4
+ import { ref, createRef } from "lit/directives/ref.js";
5
+ function _ts_decorate(decorators, target, key, desc) {
6
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
7
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
8
+ r = Reflect.decorate(decorators, target, key, desc);
9
+ else
10
+ for (var i = decorators.length - 1; i >= 0; i--)
11
+ if (d = decorators[i])
12
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
13
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
14
+ }
15
+ var gap = 1;
16
+ var resizeTolerance = 8;
17
+ var overscanCol = 1;
18
+ var overscanRow = 1;
19
+ var sizeColMin = 32;
20
+ var sizeColMax = 1024;
21
+ var sizeRowMin = 16;
22
+ var sizeRowMax = 1024;
23
+ var separator = ",";
24
+ var colToA1Notation = (col) => {
25
+ return (col >= 26 ? String.fromCharCode("A".charCodeAt(0) + Math.floor(col / 26) - 1) : "") + String.fromCharCode("A".charCodeAt(0) + col % 26);
26
+ };
27
+ var rowToA1Notation = (row) => {
28
+ return `${row + 1}`;
29
+ };
30
+ var localChId = (c0) => `ch--${c0}`;
31
+ var localRhId = (r0) => `rh--${r0}`;
32
+ var getPage = (axis, event) => axis === "col" ? event.pageX : event.pageY;
33
+ var DxGrid = class extends LitElement {
34
+ constructor() {
35
+ super(...arguments);
36
+ this.rowDefault = {
37
+ size: 32
38
+ };
39
+ this.columnDefault = {
40
+ size: 180
41
+ };
42
+ this.rows = {};
43
+ this.columns = {};
44
+ this.cells = {};
45
+ //
46
+ // `pos`, short for ‘position’, is the position in pixels of the viewport from the origin.
47
+ //
48
+ this.posInline = 0;
49
+ this.posBlock = 0;
50
+ //
51
+ // `size` (when not suffixed with ‘row’ or ‘col’, see above) is the size in pixels of the viewport.
52
+ //
53
+ this.sizeInline = 0;
54
+ this.sizeBlock = 0;
55
+ //
56
+ // `overscan` is the amount in pixels to offset the grid content due to the number of overscanned columns or rows.
57
+ //
58
+ this.overscanInline = 0;
59
+ this.overscanBlock = 0;
60
+ //
61
+ // `bin`, not short for anything, is the range in pixels within which virtualization does not need to reassess.
62
+ //
63
+ this.binInlineMin = 0;
64
+ this.binInlineMax = this.colSize(0);
65
+ this.binBlockMin = 0;
66
+ this.binBlockMax = this.rowSize(0);
67
+ //
68
+ // `vis`, short for ‘visible’, is the range in numeric index of the columns or rows which should be rendered within
69
+ // the viewport. These start with naïve values that are updated before first contentful render.
70
+ //
71
+ this.visColMin = 0;
72
+ this.visColMax = 1;
73
+ this.visRowMin = 0;
74
+ this.visRowMax = 1;
75
+ //
76
+ // `template` is the rendered value of `grid-{axis}-template`.
77
+ //
78
+ this.templateColumns = `${this.colSize(0)}px`;
79
+ this.templateRows = `${this.rowSize(0)}px`;
80
+ //
81
+ // Resize state and handlers
82
+ //
83
+ this.colSizes = {};
84
+ this.rowSizes = {};
85
+ this.resizing = null;
86
+ this.handlePointerDown = (event) => {
87
+ const actionEl = event.target?.closest("[data-dx-grid-action]");
88
+ const action = actionEl?.getAttribute("data-dx-grid-action");
89
+ if (action) {
90
+ if (action.startsWith("resize")) {
91
+ const [resize, index] = action.split(",");
92
+ const [_, axis] = resize.split("-");
93
+ this.resizing = {
94
+ axis,
95
+ size: axis === "col" ? this.colSize(index) : this.rowSize(index),
96
+ page: getPage(axis, event),
97
+ index
98
+ };
99
+ }
100
+ }
101
+ };
102
+ this.handlePointerUp = (_event) => {
103
+ this.resizing = null;
104
+ };
105
+ this.handlePointerMove = (event) => {
106
+ if (this.resizing) {
107
+ const delta = getPage(this.resizing.axis, event) - this.resizing.page;
108
+ if (this.resizing.axis === "col") {
109
+ const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, this.resizing.size + delta));
110
+ this.colSizes = {
111
+ ...this.colSizes,
112
+ [this.resizing.index]: nextSize
113
+ };
114
+ this.updateVisInline();
115
+ } else {
116
+ const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, this.resizing.size + delta));
117
+ this.rowSizes = {
118
+ ...this.rowSizes,
119
+ [this.resizing.index]: nextSize
120
+ };
121
+ this.updateVisBlock();
122
+ }
123
+ }
124
+ };
125
+ //
126
+ // Resize & reposition handlers, observer, ref
127
+ //
128
+ this.observer = new ResizeObserver((entries) => {
129
+ const { inlineSize, blockSize } = entries?.[0]?.contentBoxSize?.[0] ?? {
130
+ inlineSize: 0,
131
+ blockSize: 0
132
+ };
133
+ if (Math.abs(inlineSize - this.sizeInline) > resizeTolerance || Math.abs(blockSize - this.sizeBlock) > resizeTolerance) {
134
+ this.sizeInline = inlineSize;
135
+ this.sizeBlock = blockSize;
136
+ this.updateVis();
137
+ }
138
+ });
139
+ this.viewportRef = createRef();
140
+ this.handleWheel = ({ deltaX, deltaY }) => {
141
+ this.posInline = Math.max(0, this.posInline + deltaX);
142
+ this.posBlock = Math.max(0, this.posBlock + deltaY);
143
+ if (this.posInline >= this.binInlineMin && this.posInline < this.binInlineMax && this.posBlock >= this.binBlockMin && this.posBlock < this.binBlockMax) {
144
+ } else {
145
+ this.updateVis();
146
+ }
147
+ };
148
+ }
149
+ //
150
+ // Accessors
151
+ //
152
+ colSize(c) {
153
+ return this.colSizes?.[c] ?? this.columnDefault.size;
154
+ }
155
+ rowSize(r) {
156
+ return this.rowSizes?.[r] ?? this.rowDefault.size;
157
+ }
158
+ getCell(c, r) {
159
+ return this.cells[`${c}${separator}${r}`];
160
+ }
161
+ updateVisInline() {
162
+ let colIndex = 0;
163
+ let pxInline = this.colSize(colIndex);
164
+ while (pxInline < this.posInline) {
165
+ colIndex += 1;
166
+ pxInline += this.colSize(colIndex) + gap;
167
+ }
168
+ this.visColMin = colIndex - overscanCol;
169
+ this.binInlineMin = pxInline - this.colSize(colIndex) - gap;
170
+ this.binInlineMax = pxInline + gap;
171
+ this.overscanInline = [
172
+ ...Array(overscanCol)
173
+ ].reduce((acc, _, c0) => {
174
+ acc += this.colSize(this.visColMin + c0);
175
+ return acc;
176
+ }, 0) + gap * (overscanCol - 1);
177
+ while (pxInline < this.binInlineMax + this.sizeInline) {
178
+ colIndex += 1;
179
+ pxInline += this.colSize(colIndex) + gap;
180
+ }
181
+ this.visColMax = colIndex + overscanCol + 1;
182
+ this.templateColumns = [
183
+ ...Array(this.visColMax - this.visColMin)
184
+ ].map((_, c0) => `${this.colSize(this.visColMin + c0)}px`).join(" ");
185
+ }
186
+ updateVisBlock() {
187
+ let rowIndex = 0;
188
+ let pxBlock = this.rowSize(rowIndex);
189
+ while (pxBlock < this.posBlock) {
190
+ rowIndex += 1;
191
+ pxBlock += this.rowSize(rowIndex) + gap;
192
+ }
193
+ this.visRowMin = rowIndex - overscanRow;
194
+ this.binBlockMin = pxBlock - this.rowSize(rowIndex) - gap;
195
+ this.binBlockMax = pxBlock + gap;
196
+ this.overscanBlock = [
197
+ ...Array(overscanRow)
198
+ ].reduce((acc, _, r0) => {
199
+ acc += this.rowSize(this.visRowMin + r0);
200
+ return acc;
201
+ }, 0) + gap * (overscanRow - 1);
202
+ while (pxBlock < this.binBlockMax + this.sizeBlock) {
203
+ rowIndex += 1;
204
+ pxBlock += this.rowSize(rowIndex) + gap;
205
+ }
206
+ this.visRowMax = rowIndex + overscanRow + 1;
207
+ this.templateRows = [
208
+ ...Array(this.visRowMax - this.visRowMin)
209
+ ].map((_, r0) => `${this.rowSize(this.visRowMin + r0)}px`).join(" ");
210
+ }
211
+ updateVis() {
212
+ this.updateVisInline();
213
+ this.updateVisBlock();
214
+ }
215
+ //
216
+ // Render and other lifecycle methods
217
+ //
218
+ render() {
219
+ const visibleCols = this.visColMax - this.visColMin;
220
+ const visibleRows = this.visRowMax - this.visRowMin;
221
+ const offsetInline = this.binInlineMin - this.posInline - this.overscanInline;
222
+ const offsetBlock = this.binBlockMin - this.posBlock - this.overscanBlock;
223
+ return html`<div
224
+ role="none"
225
+ class="dx-grid"
226
+ @pointerdown=${this.handlePointerDown}
227
+ @pointerup=${this.handlePointerUp}
228
+ @pointermove=${this.handlePointerMove}
229
+ >
230
+ <div role="none" class="dx-grid__corner"></div>
231
+ <div role="none" class="dx-grid__columnheader">
232
+ <div
233
+ role="none"
234
+ class="dx-grid__columnheader__content"
235
+ style="transform:translate3d(${offsetInline}px,0,0);grid-template-columns:${this.templateColumns};"
236
+ >
237
+ ${[
238
+ ...Array(visibleCols)
239
+ ].map((_, c0) => {
240
+ const c = this.visColMin + c0;
241
+ return html`<div
242
+ role="columnheader"
243
+ ?inert=${c < 0}
244
+ style="inline-size:${this.colSize(c)}px;block-size:${this.rowDefault.size}px;grid-column:${c0 + 1}/${c0 + 2};"
245
+ >
246
+ <span id=${localChId(c0)}>${colToA1Notation(c)}</span>
247
+ ${(this.columns[c]?.resizeable ?? this.columnDefault.resizeable) && html`<button class="dx-grid__resize-handle" data-dx-grid-action=${`resize-col,${c}`}>
248
+ <span class="sr-only">Resize</span>
249
+ </button>`}
250
+ </div>`;
251
+ })}
252
+ </div>
253
+ </div>
254
+ <div role="none" class="dx-grid__corner"></div>
255
+ <div role="none" class="dx-grid__rowheader">
256
+ <div role="none" class="dx-grid__rowheader__content" style="transform:translate3d(0,${offsetBlock}px,0);">
257
+ ${[
258
+ ...Array(visibleRows)
259
+ ].map((_, r0) => {
260
+ const r = this.visRowMin + r0;
261
+ return html`<div
262
+ role="rowheader"
263
+ ?inert=${r < 0}
264
+ style="block-size:${this.rowSize(r)}px;grid-row:${r0 + 1}/${r0 + 2}"
265
+ >
266
+ <span id=${localRhId(r0)}>${rowToA1Notation(r)}</span>
267
+ ${(this.rows[r]?.resizeable ?? this.rowDefault.resizeable) && html`<button class="dx-grid__resize-handle" data-dx-grid-action=${`resize-row,${r}`}>
268
+ <span class="sr-only">Resize</span>
269
+ </button>`}
270
+ </div>`;
271
+ })}
272
+ </div>
273
+ </div>
274
+ <div role="none" class="dx-grid__viewport" @wheel="${this.handleWheel}" ${ref(this.viewportRef)}>
275
+ <div
276
+ role="grid"
277
+ class="dx-grid__content"
278
+ style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this.templateColumns};grid-template-rows:${this.templateRows};"
279
+ >
280
+ ${[
281
+ ...Array(visibleCols)
282
+ ].map((_, c0) => {
283
+ return [
284
+ ...Array(visibleRows)
285
+ ].map((_2, r0) => {
286
+ const c = c0 + this.visColMin;
287
+ const r = r0 + this.visRowMin;
288
+ const cell = this.getCell(c, r);
289
+ return html`<div
290
+ role="gridcell"
291
+ ?inert=${c < 0 || r < 0}
292
+ aria-rowindex=${r}
293
+ aria-colindex=${c}
294
+ data-dx-grid-action="cell"
295
+ style="grid-column:${c0 + 1};grid-row:${r0 + 1}"
296
+ >
297
+ ${cell?.value}
298
+ </div>`;
299
+ });
300
+ })}
301
+ </div>
302
+ </div>
303
+ <div role="none" class="dx-grid__scrollbar" aria-orientation="vertical">
304
+ <div role="none" class="dx-grid__scrollbar__thumb"></div>
305
+ </div>
306
+ <div role="none" class="dx-grid__corner"></div>
307
+ <div role="none" class="dx-grid__scrollbar" aria-orientation="horizontal">
308
+ <div role="none" class="dx-grid__scrollbar__thumb"></div>
309
+ </div>
310
+ <div role="none" class="dx-grid__corner"></div>
311
+ </div>`;
312
+ }
313
+ firstUpdated() {
314
+ this.observer.observe(this.viewportRef.value);
315
+ this.colSizes = Object.entries(this.columns).reduce((acc, [colId, colMeta]) => {
316
+ if (colMeta?.size) {
317
+ acc[colId] = colMeta.size;
318
+ }
319
+ return acc;
320
+ }, {});
321
+ this.rowSizes = Object.entries(this.rows).reduce((acc, [rowId, rowMeta]) => {
322
+ if (rowMeta?.size) {
323
+ acc[rowId] = rowMeta.size;
324
+ }
325
+ return acc;
326
+ }, {});
327
+ }
328
+ disconnectedCallback() {
329
+ super.disconnectedCallback();
330
+ if (this.viewportRef.value) {
331
+ this.observer.unobserve(this.viewportRef.value);
332
+ }
333
+ }
334
+ createRenderRoot() {
335
+ return this;
336
+ }
337
+ };
338
+ _ts_decorate([
339
+ property({
340
+ type: Object
341
+ })
342
+ ], DxGrid.prototype, "rowDefault", void 0);
343
+ _ts_decorate([
344
+ property({
345
+ type: Object
346
+ })
347
+ ], DxGrid.prototype, "columnDefault", void 0);
348
+ _ts_decorate([
349
+ property({
350
+ type: Object
351
+ })
352
+ ], DxGrid.prototype, "rows", void 0);
353
+ _ts_decorate([
354
+ property({
355
+ type: Object
356
+ })
357
+ ], DxGrid.prototype, "columns", void 0);
358
+ _ts_decorate([
359
+ property({
360
+ type: Object
361
+ })
362
+ ], DxGrid.prototype, "cells", void 0);
363
+ _ts_decorate([
364
+ state()
365
+ ], DxGrid.prototype, "posInline", void 0);
366
+ _ts_decorate([
367
+ state()
368
+ ], DxGrid.prototype, "posBlock", void 0);
369
+ _ts_decorate([
370
+ state()
371
+ ], DxGrid.prototype, "sizeInline", void 0);
372
+ _ts_decorate([
373
+ state()
374
+ ], DxGrid.prototype, "sizeBlock", void 0);
375
+ _ts_decorate([
376
+ state()
377
+ ], DxGrid.prototype, "overscanInline", void 0);
378
+ _ts_decorate([
379
+ state()
380
+ ], DxGrid.prototype, "overscanBlock", void 0);
381
+ _ts_decorate([
382
+ state()
383
+ ], DxGrid.prototype, "binInlineMin", void 0);
384
+ _ts_decorate([
385
+ state()
386
+ ], DxGrid.prototype, "binInlineMax", void 0);
387
+ _ts_decorate([
388
+ state()
389
+ ], DxGrid.prototype, "binBlockMin", void 0);
390
+ _ts_decorate([
391
+ state()
392
+ ], DxGrid.prototype, "binBlockMax", void 0);
393
+ _ts_decorate([
394
+ state()
395
+ ], DxGrid.prototype, "visColMin", void 0);
396
+ _ts_decorate([
397
+ state()
398
+ ], DxGrid.prototype, "visColMax", void 0);
399
+ _ts_decorate([
400
+ state()
401
+ ], DxGrid.prototype, "visRowMin", void 0);
402
+ _ts_decorate([
403
+ state()
404
+ ], DxGrid.prototype, "visRowMax", void 0);
405
+ _ts_decorate([
406
+ state()
407
+ ], DxGrid.prototype, "templateColumns", void 0);
408
+ _ts_decorate([
409
+ state()
410
+ ], DxGrid.prototype, "templateRows", void 0);
411
+ _ts_decorate([
412
+ state()
413
+ ], DxGrid.prototype, "colSizes", void 0);
414
+ _ts_decorate([
415
+ state()
416
+ ], DxGrid.prototype, "rowSizes", void 0);
417
+ _ts_decorate([
418
+ state()
419
+ ], DxGrid.prototype, "resizing", void 0);
420
+ _ts_decorate([
421
+ state()
422
+ ], DxGrid.prototype, "observer", void 0);
423
+ DxGrid = _ts_decorate([
424
+ customElement("dx-grid")
425
+ ], DxGrid);
426
+ export {
427
+ DxGrid
428
+ };
429
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/dx-grid.ts"],
4
+ "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { LitElement, html } from 'lit';\nimport { customElement, state, property } from 'lit/decorators.js';\nimport { ref, createRef, type Ref } from 'lit/directives/ref.js';\n\n/**\n * The size in pixels of the gap between cells\n */\nconst gap = 1;\n\n/**\n * This should be about the width of the `1` numeral so resize is triggered as the row header column’s intrinsic size\n * changes when scrolling vertically.\n */\nconst resizeTolerance = 8;\n\n//\n// `overscan` is the number of columns or rows to render outside of the viewport\n//\nconst overscanCol = 1;\nconst overscanRow = 1;\n\n//\n// `size`, when suffixed with ‘row’ or ‘col’, are limits on size applied when resizing\n//\nconst sizeColMin = 32;\nconst sizeColMax = 1024;\nconst sizeRowMin = 16;\nconst sizeRowMax = 1024;\n\n/**\n * Separator for serializing cell position vectors\n */\nconst separator = ',';\n\n//\n// A1 notation is the fallback for numbering columns and rows.\n//\n\nconst colToA1Notation = (col: number): string => {\n return (\n (col >= 26 ? String.fromCharCode('A'.charCodeAt(0) + Math.floor(col / 26) - 1) : '') +\n String.fromCharCode('A'.charCodeAt(0) + (col % 26))\n );\n};\n\nconst rowToA1Notation = (row: number): string => {\n return `${row + 1}`;\n};\n\nexport type CellValue = {\n /**\n * The content value\n */\n value: string;\n /**\n * If this is a merged cell, the bottomright-most of the range in numeric notation, otherwise undefined.\n */\n end?: string;\n /**\n * CSS inline styles to apply to the gridcell element\n */\n style?: string;\n};\n\ntype AxisMeta = {\n size: number;\n description?: string;\n resizeable?: boolean;\n};\n\nexport type DxGridProps = Pick<DxGrid, 'cells' | 'rows' | 'columns' | 'rowDefault' | 'columnDefault'>;\n\nconst localChId = (c0: number) => `ch--${c0}`;\nconst localRhId = (r0: number) => `rh--${r0}`;\n\nconst getPage = (axis: string, event: PointerEvent) => (axis === 'col' ? event.pageX : event.pageY);\n\n@customElement('dx-grid')\nexport class DxGrid extends LitElement {\n @property({ type: Object })\n rowDefault: AxisMeta = { size: 32 };\n\n @property({ type: Object })\n columnDefault: AxisMeta = { size: 180 };\n\n @property({ type: Object })\n rows: Record<string, AxisMeta> = {};\n\n @property({ type: Object })\n columns: Record<string, AxisMeta> = {};\n\n @property({ type: Object })\n cells: Record<string, CellValue> = {};\n\n //\n // `pos`, short for ‘position’, is the position in pixels of the viewport from the origin.\n //\n\n @state()\n posInline = 0;\n\n @state()\n posBlock = 0;\n\n //\n // `size` (when not suffixed with ‘row’ or ‘col’, see above) is the size in pixels of the viewport.\n //\n\n @state()\n sizeInline = 0;\n\n @state()\n sizeBlock = 0;\n\n //\n // `overscan` is the amount in pixels to offset the grid content due to the number of overscanned columns or rows.\n //\n\n @state()\n overscanInline = 0;\n\n @state()\n overscanBlock = 0;\n\n //\n // `bin`, not short for anything, is the range in pixels within which virtualization does not need to reassess.\n //\n\n @state()\n binInlineMin = 0;\n\n @state()\n binInlineMax = this.colSize(0);\n\n @state()\n binBlockMin = 0;\n\n @state()\n binBlockMax = this.rowSize(0);\n\n //\n // `vis`, short for ‘visible’, is the range in numeric index of the columns or rows which should be rendered within\n // the viewport. These start with naïve values that are updated before first contentful render.\n //\n\n @state()\n visColMin = 0;\n\n @state()\n visColMax = 1;\n\n @state()\n visRowMin = 0;\n\n @state()\n visRowMax = 1;\n\n //\n // `template` is the rendered value of `grid-{axis}-template`.\n //\n @state()\n templateColumns = `${this.colSize(0)}px`;\n\n @state()\n templateRows = `${this.rowSize(0)}px`;\n\n //\n // Resize state and handlers\n //\n\n @state()\n colSizes: Record<string, number> = {};\n\n @state()\n rowSizes: Record<string, number> = {};\n\n @state()\n resizing: null | { axis: 'col' | 'row'; page: number; size: number; index: string } = null;\n\n handlePointerDown = (event: PointerEvent) => {\n const actionEl = (event.target as HTMLElement)?.closest('[data-dx-grid-action]');\n const action = actionEl?.getAttribute('data-dx-grid-action');\n if (action) {\n if (action.startsWith('resize')) {\n const [resize, index] = action.split(',');\n const [_, axis] = resize.split('-');\n this.resizing = {\n axis: axis as 'col' | 'row',\n size: axis === 'col' ? this.colSize(index) : this.rowSize(index),\n page: getPage(axis, event),\n index,\n };\n }\n }\n };\n\n handlePointerUp = (_event: PointerEvent) => {\n this.resizing = null;\n };\n\n handlePointerMove = (event: PointerEvent) => {\n if (this.resizing) {\n const delta = getPage(this.resizing.axis, event) - this.resizing.page;\n if (this.resizing.axis === 'col') {\n const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, this.resizing.size + delta));\n this.colSizes = { ...this.colSizes, [this.resizing.index]: nextSize };\n this.updateVisInline();\n } else {\n const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, this.resizing.size + delta));\n this.rowSizes = { ...this.rowSizes, [this.resizing.index]: nextSize };\n this.updateVisBlock();\n }\n }\n };\n\n //\n // Accessors\n //\n\n private colSize(c: number | string) {\n return this.colSizes?.[c] ?? this.columnDefault.size;\n }\n\n private rowSize(r: number | string) {\n return this.rowSizes?.[r] ?? this.rowDefault.size;\n }\n\n private getCell(c: number | string, r: number | string) {\n return this.cells[`${c}${separator}${r}`];\n }\n\n //\n // Resize & reposition handlers, observer, ref\n //\n\n @state()\n observer = new ResizeObserver((entries) => {\n const { inlineSize, blockSize } = entries?.[0]?.contentBoxSize?.[0] ?? {\n inlineSize: 0,\n blockSize: 0,\n };\n if (\n Math.abs(inlineSize - this.sizeInline) > resizeTolerance ||\n Math.abs(blockSize - this.sizeBlock) > resizeTolerance\n ) {\n // console.info('[updating bounds]', 'resize', [inlineSize - this.sizeInline, blockSize - this.sizeBlock]);\n this.sizeInline = inlineSize;\n this.sizeBlock = blockSize;\n this.updateVis();\n }\n });\n\n viewportRef: Ref<HTMLDivElement> = createRef();\n\n handleWheel = ({ deltaX, deltaY }: WheelEvent) => {\n this.posInline = Math.max(0, this.posInline + deltaX);\n this.posBlock = Math.max(0, this.posBlock + deltaY);\n if (\n this.posInline >= this.binInlineMin &&\n this.posInline < this.binInlineMax &&\n this.posBlock >= this.binBlockMin &&\n this.posBlock < this.binBlockMax\n ) {\n // do nothing\n } else {\n // console.info(\n // '[updating bounds]',\n // 'wheel',\n // [this.binInlineMin, this.posInline, this.binInlineMax],\n // [this.binBlockMin, this.posBlock, this.binBlockMax],\n // );\n this.updateVis();\n }\n };\n\n private updateVisInline() {\n // todo: avoid starting from zero\n let colIndex = 0;\n let pxInline = this.colSize(colIndex);\n\n while (pxInline < this.posInline) {\n colIndex += 1;\n pxInline += this.colSize(colIndex) + gap;\n }\n\n this.visColMin = colIndex - overscanCol;\n\n this.binInlineMin = pxInline - this.colSize(colIndex) - gap;\n this.binInlineMax = pxInline + gap;\n\n this.overscanInline =\n [...Array(overscanCol)].reduce((acc, _, c0) => {\n acc += this.colSize(this.visColMin + c0);\n return acc;\n }, 0) +\n gap * (overscanCol - 1);\n\n while (pxInline < this.binInlineMax + this.sizeInline) {\n colIndex += 1;\n pxInline += this.colSize(colIndex) + gap;\n }\n\n this.visColMax = colIndex + overscanCol + 1;\n\n this.templateColumns = [...Array(this.visColMax - this.visColMin)]\n .map((_, c0) => `${this.colSize(this.visColMin + c0)}px`)\n .join(' ');\n }\n\n private updateVisBlock() {\n // todo: avoid starting from zero\n let rowIndex = 0;\n let pxBlock = this.rowSize(rowIndex);\n\n while (pxBlock < this.posBlock) {\n rowIndex += 1;\n pxBlock += this.rowSize(rowIndex) + gap;\n }\n\n this.visRowMin = rowIndex - overscanRow;\n\n this.binBlockMin = pxBlock - this.rowSize(rowIndex) - gap;\n this.binBlockMax = pxBlock + gap;\n\n this.overscanBlock =\n [...Array(overscanRow)].reduce((acc, _, r0) => {\n acc += this.rowSize(this.visRowMin + r0);\n return acc;\n }, 0) +\n gap * (overscanRow - 1);\n\n while (pxBlock < this.binBlockMax + this.sizeBlock) {\n rowIndex += 1;\n pxBlock += this.rowSize(rowIndex) + gap;\n }\n\n this.visRowMax = rowIndex + overscanRow + 1;\n\n this.templateRows = [...Array(this.visRowMax - this.visRowMin)]\n .map((_, r0) => `${this.rowSize(this.visRowMin + r0)}px`)\n .join(' ');\n }\n\n private updateVis() {\n this.updateVisInline();\n this.updateVisBlock();\n }\n\n //\n // Render and other lifecycle methods\n //\n\n override render() {\n const visibleCols = this.visColMax - this.visColMin;\n const visibleRows = this.visRowMax - this.visRowMin;\n // TODO NEXT -> ensure offset is using the right components\n const offsetInline = this.binInlineMin - this.posInline - this.overscanInline;\n const offsetBlock = this.binBlockMin - this.posBlock - this.overscanBlock;\n\n return html`<div\n role=\"none\"\n class=\"dx-grid\"\n @pointerdown=${this.handlePointerDown}\n @pointerup=${this.handlePointerUp}\n @pointermove=${this.handlePointerMove}\n >\n <div role=\"none\" class=\"dx-grid__corner\"></div>\n <div role=\"none\" class=\"dx-grid__columnheader\">\n <div\n role=\"none\"\n class=\"dx-grid__columnheader__content\"\n style=\"transform:translate3d(${offsetInline}px,0,0);grid-template-columns:${this.templateColumns};\"\n >\n ${[...Array(visibleCols)].map((_, c0) => {\n const c = this.visColMin + c0;\n return html`<div\n role=\"columnheader\"\n ?inert=${c < 0}\n style=\"inline-size:${this.colSize(c)}px;block-size:${this.rowDefault.size}px;grid-column:${c0 + 1}/${c0 +\n 2};\"\n >\n <span id=${localChId(c0)}>${colToA1Notation(c)}</span>\n ${(this.columns[c]?.resizeable ?? this.columnDefault.resizeable) &&\n html`<button class=\"dx-grid__resize-handle\" data-dx-grid-action=${`resize-col,${c}`}>\n <span class=\"sr-only\">Resize</span>\n </button>`}\n </div>`;\n })}\n </div>\n </div>\n <div role=\"none\" class=\"dx-grid__corner\"></div>\n <div role=\"none\" class=\"dx-grid__rowheader\">\n <div role=\"none\" class=\"dx-grid__rowheader__content\" style=\"transform:translate3d(0,${offsetBlock}px,0);\">\n ${[...Array(visibleRows)].map((_, r0) => {\n const r = this.visRowMin + r0;\n return html`<div\n role=\"rowheader\"\n ?inert=${r < 0}\n style=\"block-size:${this.rowSize(r)}px;grid-row:${r0 + 1}/${r0 + 2}\"\n >\n <span id=${localRhId(r0)}>${rowToA1Notation(r)}</span>\n ${(this.rows[r]?.resizeable ?? this.rowDefault.resizeable) &&\n html`<button class=\"dx-grid__resize-handle\" data-dx-grid-action=${`resize-row,${r}`}>\n <span class=\"sr-only\">Resize</span>\n </button>`}\n </div>`;\n })}\n </div>\n </div>\n <div role=\"none\" class=\"dx-grid__viewport\" @wheel=\"${this.handleWheel}\" ${ref(this.viewportRef)}>\n <div\n role=\"grid\"\n class=\"dx-grid__content\"\n style=\"transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this\n .templateColumns};grid-template-rows:${this.templateRows};\"\n >\n ${[...Array(visibleCols)].map((_, c0) => {\n return [...Array(visibleRows)].map((_, r0) => {\n const c = c0 + this.visColMin;\n const r = r0 + this.visRowMin;\n const cell = this.getCell(c, r);\n return html`<div\n role=\"gridcell\"\n ?inert=${c < 0 || r < 0}\n aria-rowindex=${r}\n aria-colindex=${c}\n data-dx-grid-action=\"cell\"\n style=\"grid-column:${c0 + 1};grid-row:${r0 + 1}\"\n >\n ${cell?.value}\n </div>`;\n });\n })}\n </div>\n </div>\n <div role=\"none\" class=\"dx-grid__scrollbar\" aria-orientation=\"vertical\">\n <div role=\"none\" class=\"dx-grid__scrollbar__thumb\"></div>\n </div>\n <div role=\"none\" class=\"dx-grid__corner\"></div>\n <div role=\"none\" class=\"dx-grid__scrollbar\" aria-orientation=\"horizontal\">\n <div role=\"none\" class=\"dx-grid__scrollbar__thumb\"></div>\n </div>\n <div role=\"none\" class=\"dx-grid__corner\"></div>\n </div>`;\n }\n\n override firstUpdated() {\n this.observer.observe(this.viewportRef.value!);\n this.colSizes = Object.entries(this.columns).reduce((acc: Record<string, number>, [colId, colMeta]) => {\n if (colMeta?.size) {\n acc[colId] = colMeta.size;\n }\n return acc;\n }, {});\n this.rowSizes = Object.entries(this.rows).reduce((acc: Record<string, number>, [rowId, rowMeta]) => {\n if (rowMeta?.size) {\n acc[rowId] = rowMeta.size;\n }\n return acc;\n }, {});\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n // console.log('[disconnected]', this.viewportRef.value);\n // TODO(thure): Will this even work?\n if (this.viewportRef.value) {\n this.observer.unobserve(this.viewportRef.value);\n }\n }\n\n override createRenderRoot() {\n return this;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["LitElement", "html", "customElement", "state", "property", "ref", "createRef", "gap", "resizeTolerance", "overscanCol", "overscanRow", "sizeColMin", "sizeColMax", "sizeRowMin", "sizeRowMax", "separator", "colToA1Notation", "col", "String", "fromCharCode", "charCodeAt", "Math", "floor", "rowToA1Notation", "row", "localChId", "c0", "localRhId", "r0", "getPage", "axis", "event", "pageX", "pageY", "DxGrid", "LitElement", "rowDefault", "size", "columnDefault", "rows", "columns", "cells", "posInline", "posBlock", "sizeInline", "sizeBlock", "overscanInline", "overscanBlock", "binInlineMin", "binInlineMax", "colSize", "binBlockMin", "binBlockMax", "rowSize", "visColMin", "visColMax", "visRowMin", "visRowMax", "templateColumns", "templateRows", "colSizes", "rowSizes", "resizing", "handlePointerDown", "actionEl", "target", "closest", "action", "getAttribute", "startsWith", "resize", "index", "split", "_", "page", "handlePointerUp", "_event", "handlePointerMove", "delta", "nextSize", "max", "min", "updateVisInline", "updateVisBlock", "observer", "ResizeObserver", "entries", "inlineSize", "blockSize", "contentBoxSize", "abs", "updateVis", "viewportRef", "createRef", "handleWheel", "deltaX", "deltaY", "c", "r", "getCell", "colIndex", "pxInline", "Array", "reduce", "acc", "map", "join", "rowIndex", "pxBlock", "render", "visibleCols", "visibleRows", "offsetInline", "offsetBlock", "html", "resizeable", "ref", "cell", "value", "firstUpdated", "observe", "Object", "colId", "colMeta", "rowId", "rowMeta", "disconnectedCallback", "unobserve", "createRenderRoot", "property", "type", "state", "customElement"]
7
+ }
@@ -0,0 +1 @@
1
+ {"inputs":{"packages/ui/lit-grid/src/dx-grid.ts":{"bytes":50156,"imports":[{"path":"lit","kind":"import-statement","external":true},{"path":"lit/decorators.js","kind":"import-statement","external":true},{"path":"lit/directives/ref.js","kind":"import-statement","external":true}],"format":"esm"},"packages/ui/lit-grid/src/index.ts":{"bytes":560,"imports":[{"path":"packages/ui/lit-grid/src/dx-grid.ts","kind":"import-statement","original":"./dx-grid"}],"format":"esm"}},"outputs":{"packages/ui/lit-grid/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":23985},"packages/ui/lit-grid/dist/lib/browser/index.mjs":{"imports":[{"path":"lit","kind":"import-statement","external":true},{"path":"lit/decorators.js","kind":"import-statement","external":true},{"path":"lit/directives/ref.js","kind":"import-statement","external":true}],"exports":["DxGrid"],"entryPoint":"packages/ui/lit-grid/src/index.ts","inputs":{"packages/ui/lit-grid/src/dx-grid.ts":{"bytesInOutput":13730},"packages/ui/lit-grid/src/index.ts":{"bytesInOutput":0}},"bytes":13825}}}
@@ -0,0 +1,71 @@
1
+ import { LitElement } from 'lit';
2
+ import { type Ref } from 'lit/directives/ref.js';
3
+ export type CellValue = {
4
+ /**
5
+ * The content value
6
+ */
7
+ value: string;
8
+ /**
9
+ * If this is a merged cell, the bottomright-most of the range in numeric notation, otherwise undefined.
10
+ */
11
+ end?: string;
12
+ /**
13
+ * CSS inline styles to apply to the gridcell element
14
+ */
15
+ style?: string;
16
+ };
17
+ type AxisMeta = {
18
+ size: number;
19
+ description?: string;
20
+ resizeable?: boolean;
21
+ };
22
+ export type DxGridProps = Pick<DxGrid, 'cells' | 'rows' | 'columns' | 'rowDefault' | 'columnDefault'>;
23
+ export declare class DxGrid extends LitElement {
24
+ rowDefault: AxisMeta;
25
+ columnDefault: AxisMeta;
26
+ rows: Record<string, AxisMeta>;
27
+ columns: Record<string, AxisMeta>;
28
+ cells: Record<string, CellValue>;
29
+ posInline: number;
30
+ posBlock: number;
31
+ sizeInline: number;
32
+ sizeBlock: number;
33
+ overscanInline: number;
34
+ overscanBlock: number;
35
+ binInlineMin: number;
36
+ binInlineMax: number;
37
+ binBlockMin: number;
38
+ binBlockMax: number;
39
+ visColMin: number;
40
+ visColMax: number;
41
+ visRowMin: number;
42
+ visRowMax: number;
43
+ templateColumns: string;
44
+ templateRows: string;
45
+ colSizes: Record<string, number>;
46
+ rowSizes: Record<string, number>;
47
+ resizing: null | {
48
+ axis: 'col' | 'row';
49
+ page: number;
50
+ size: number;
51
+ index: string;
52
+ };
53
+ handlePointerDown: (event: PointerEvent) => void;
54
+ handlePointerUp: (_event: PointerEvent) => void;
55
+ handlePointerMove: (event: PointerEvent) => void;
56
+ private colSize;
57
+ private rowSize;
58
+ private getCell;
59
+ observer: ResizeObserver;
60
+ viewportRef: Ref<HTMLDivElement>;
61
+ handleWheel: ({ deltaX, deltaY }: WheelEvent) => void;
62
+ private updateVisInline;
63
+ private updateVisBlock;
64
+ private updateVis;
65
+ render(): import("lit").TemplateResult<1>;
66
+ firstUpdated(): void;
67
+ disconnectedCallback(): void;
68
+ createRenderRoot(): this;
69
+ }
70
+ export {};
71
+ //# sourceMappingURL=dx-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dx-grid.d.ts","sourceRoot":"","sources":["../../../src/dx-grid.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAQ,MAAM,KAAK,CAAC;AAEvC,OAAO,EAAkB,KAAK,GAAG,EAAE,MAAM,uBAAuB,CAAC;AA+CjE,MAAM,MAAM,SAAS,GAAG;IACtB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,eAAe,CAAC,CAAC;AAOtG,qBACa,MAAO,SAAQ,UAAU;IAEpC,UAAU,EAAE,QAAQ,CAAgB;IAGpC,aAAa,EAAE,QAAQ,CAAiB;IAGxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAM;IAGpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAM;IAGvC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAM;IAOtC,SAAS,SAAK;IAGd,QAAQ,SAAK;IAOb,UAAU,SAAK;IAGf,SAAS,SAAK;IAOd,cAAc,SAAK;IAGnB,aAAa,SAAK;IAOlB,YAAY,SAAK;IAGjB,YAAY,SAAmB;IAG/B,WAAW,SAAK;IAGhB,WAAW,SAAmB;IAQ9B,SAAS,SAAK;IAGd,SAAS,SAAK;IAGd,SAAS,SAAK;IAGd,SAAS,SAAK;IAMd,eAAe,SAA0B;IAGzC,YAAY,SAA0B;IAOtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAGtC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAGtC,QAAQ,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAQ;IAE3F,iBAAiB,UAAW,YAAY,UAetC;IAEF,eAAe,WAAY,YAAY,UAErC;IAEF,iBAAiB,UAAW,YAAY,UAatC;IAMF,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,OAAO;IASf,QAAQ,iBAcL;IAEH,WAAW,EAAE,GAAG,CAAC,cAAc,CAAC,CAAe;IAE/C,WAAW,uBAAwB,UAAU,UAmB3C;IAEF,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,cAAc;IAkCtB,OAAO,CAAC,SAAS;IASR,MAAM;IA8FN,YAAY;IAgBZ,oBAAoB;IASpB,gBAAgB;CAG1B"}
@@ -0,0 +1,17 @@
1
+ import './dx-grid.ts';
2
+ import './dx-grid.pcss';
3
+ import { type DxGridProps } from './dx-grid';
4
+ declare const _default: {
5
+ title: string;
6
+ };
7
+ export default _default;
8
+ export declare const Basic: {
9
+ (props: DxGridProps): import("lit").TemplateResult<1>;
10
+ args: {
11
+ cells: string;
12
+ columnDefault: string;
13
+ rowDefault: string;
14
+ columns: string;
15
+ };
16
+ };
17
+ //# sourceMappingURL=dx-grid.lit-stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dx-grid.lit-stories.d.ts","sourceRoot":"","sources":["../../../src/dx-grid.lit-stories.ts"],"names":[],"mappings":"AAIA,OAAO,cAAc,CAAC;AACtB,OAAO,gBAAgB,CAAC;AAIxB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;;;;AAE7C,wBAEE;AAEF,eAAO,MAAM,KAAK;YAAW,WAAW;;;;;;;CAOvC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { DxGrid, type DxGridProps } from './dx-grid';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@dxos/lit-grid",
3
+ "version": "0.6.10-main.3cfcc89",
4
+ "description": "A grid webcomponent using Lit",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "types": "./dist/types/src/index.d.ts"
13
+ },
14
+ "./dx-grid.pcss": "./src/dx-grid.pcss"
15
+ },
16
+ "main": "src/index.ts",
17
+ "files": [
18
+ "src",
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "lit": "^3.2.0"
23
+ }
24
+ }
@@ -0,0 +1,47 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import './dx-grid.ts';
6
+ import './dx-grid.pcss';
7
+
8
+ import { html, nothing } from 'lit';
9
+
10
+ import { type DxGridProps } from './dx-grid';
11
+
12
+ export default {
13
+ title: 'dx-grid',
14
+ };
15
+
16
+ export const Basic = (props: DxGridProps) => {
17
+ return html`<dx-grid
18
+ cells=${props.cells ?? nothing}
19
+ columnDefault=${props.columnDefault ?? nothing}
20
+ rowDefault=${props.rowDefault ?? nothing}
21
+ columns=${props.columns ?? nothing}
22
+ ></dx-grid>`;
23
+ };
24
+
25
+ Basic.args = {
26
+ cells: JSON.stringify({
27
+ '1,1': {
28
+ // end: '8,1',
29
+ value: 'Weekly sales report',
30
+ },
31
+ } satisfies DxGridProps['cells']),
32
+ columnDefault: JSON.stringify({
33
+ size: 180,
34
+ resizeable: true,
35
+ } satisfies DxGridProps['columnDefault']),
36
+ rowDefault: JSON.stringify({
37
+ size: 32,
38
+ resizeable: true,
39
+ } satisfies DxGridProps['rowDefault']),
40
+ columns: JSON.stringify({
41
+ 0: { size: 200 },
42
+ 1: { size: 210 },
43
+ 2: { size: 230 },
44
+ 3: { size: 250 },
45
+ 4: { size: 270 },
46
+ } satisfies DxGridProps['columns']),
47
+ };
@@ -0,0 +1,101 @@
1
+ dx-grid {
2
+ display: contents;
3
+ }
4
+
5
+ .sr-only {
6
+ position: absolute;
7
+ width: 1px;
8
+ height: 1px;
9
+ padding: 0;
10
+ margin: -1px;
11
+ overflow: hidden;
12
+ clip: rect(0, 0, 0, 0);
13
+ white-space: nowrap;
14
+ border-width: 0;
15
+ }
16
+
17
+ .dx-grid {
18
+ position: fixed;
19
+ inset: 0;
20
+ display: grid;
21
+ grid-template-columns: min-content 1fr min-content;
22
+ grid-template-rows: min-content 1fr min-content;
23
+ font-variant-numeric: tabular-nums;
24
+ }
25
+
26
+ .dx-grid__scrollbar__thumb {
27
+ height: 1rem;
28
+ width: 1rem;
29
+ background: var(--dx-grid-thumb, var(--dx-separator));
30
+ }
31
+
32
+ .dx-grid__corner,
33
+ .dx-grid__scrollbar {
34
+ background: var(--dx-grid-corner, var(--dx-hoverSurface));
35
+ }
36
+
37
+ .dx-grid__columnheader__content,
38
+ .dx-grid__rowheader__content,
39
+ .dx-grid__content {
40
+ display: grid;
41
+ gap: 1px;
42
+ background: var(--dx-separator);
43
+ inline-size: min-content;
44
+ block-size: min-content;
45
+ }
46
+
47
+ .dx-grid__columnheader,
48
+ .dx-grid__rowheader,
49
+ .dx-grid__viewport {
50
+ overflow: hidden;
51
+ }
52
+
53
+ .dx-grid__columnheader__content {
54
+ border-block-end: 2px solid var(--dx-separator);
55
+ grid-template-columns: repeat(99, 64px);
56
+ }
57
+
58
+ .dx-grid__rowheader__content {
59
+ border-inline-end: 2px solid var(--dx-separator);
60
+ grid-template-columns: min-content;
61
+ text-align: end;
62
+ }
63
+
64
+ .dx-grid {
65
+ [role='gridcell'], [role='columnheader'], [role='rowheader'] {
66
+ background: var(--dx-base);
67
+ padding: 2px;
68
+ box-sizing: border-box;
69
+ &[inert] {
70
+ visibility: hidden;
71
+ }
72
+ }
73
+ }
74
+
75
+ .dx-grid__columnheader__content,
76
+ .dx-grid__rowheader__content {
77
+ & > [role='columnheader'], &> [role='rowheader'] {
78
+ position: relative;
79
+ & > button.dx-grid__resize-handle {
80
+ position: absolute;
81
+ background: transparent;
82
+ &:hover {
83
+ background: var(--dx-hoverSurface)
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ .dx-grid__columnheader__content > [role='columnheader'] > button.dx-grid__resize-handle {
90
+ inset-block: 0;
91
+ inset-inline-end: 0;
92
+ inline-size: .5rem;
93
+ cursor: col-resize;
94
+ }
95
+
96
+ .dx-grid__rowheader__content > [role='rowheader'] > button.dx-grid__resize-handle {
97
+ inset-inline: 0;
98
+ inset-block-end: 0;
99
+ block-size: .5rem;
100
+ cursor: row-resize;
101
+ }
package/src/dx-grid.ts ADDED
@@ -0,0 +1,479 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { LitElement, html } from 'lit';
6
+ import { customElement, state, property } from 'lit/decorators.js';
7
+ import { ref, createRef, type Ref } from 'lit/directives/ref.js';
8
+
9
+ /**
10
+ * The size in pixels of the gap between cells
11
+ */
12
+ const gap = 1;
13
+
14
+ /**
15
+ * This should be about the width of the `1` numeral so resize is triggered as the row header column’s intrinsic size
16
+ * changes when scrolling vertically.
17
+ */
18
+ const resizeTolerance = 8;
19
+
20
+ //
21
+ // `overscan` is the number of columns or rows to render outside of the viewport
22
+ //
23
+ const overscanCol = 1;
24
+ const overscanRow = 1;
25
+
26
+ //
27
+ // `size`, when suffixed with ‘row’ or ‘col’, are limits on size applied when resizing
28
+ //
29
+ const sizeColMin = 32;
30
+ const sizeColMax = 1024;
31
+ const sizeRowMin = 16;
32
+ const sizeRowMax = 1024;
33
+
34
+ /**
35
+ * Separator for serializing cell position vectors
36
+ */
37
+ const separator = ',';
38
+
39
+ //
40
+ // A1 notation is the fallback for numbering columns and rows.
41
+ //
42
+
43
+ const colToA1Notation = (col: number): string => {
44
+ return (
45
+ (col >= 26 ? String.fromCharCode('A'.charCodeAt(0) + Math.floor(col / 26) - 1) : '') +
46
+ String.fromCharCode('A'.charCodeAt(0) + (col % 26))
47
+ );
48
+ };
49
+
50
+ const rowToA1Notation = (row: number): string => {
51
+ return `${row + 1}`;
52
+ };
53
+
54
+ export type CellValue = {
55
+ /**
56
+ * The content value
57
+ */
58
+ value: string;
59
+ /**
60
+ * If this is a merged cell, the bottomright-most of the range in numeric notation, otherwise undefined.
61
+ */
62
+ end?: string;
63
+ /**
64
+ * CSS inline styles to apply to the gridcell element
65
+ */
66
+ style?: string;
67
+ };
68
+
69
+ type AxisMeta = {
70
+ size: number;
71
+ description?: string;
72
+ resizeable?: boolean;
73
+ };
74
+
75
+ export type DxGridProps = Pick<DxGrid, 'cells' | 'rows' | 'columns' | 'rowDefault' | 'columnDefault'>;
76
+
77
+ const localChId = (c0: number) => `ch--${c0}`;
78
+ const localRhId = (r0: number) => `rh--${r0}`;
79
+
80
+ const getPage = (axis: string, event: PointerEvent) => (axis === 'col' ? event.pageX : event.pageY);
81
+
82
+ @customElement('dx-grid')
83
+ export class DxGrid extends LitElement {
84
+ @property({ type: Object })
85
+ rowDefault: AxisMeta = { size: 32 };
86
+
87
+ @property({ type: Object })
88
+ columnDefault: AxisMeta = { size: 180 };
89
+
90
+ @property({ type: Object })
91
+ rows: Record<string, AxisMeta> = {};
92
+
93
+ @property({ type: Object })
94
+ columns: Record<string, AxisMeta> = {};
95
+
96
+ @property({ type: Object })
97
+ cells: Record<string, CellValue> = {};
98
+
99
+ //
100
+ // `pos`, short for ‘position’, is the position in pixels of the viewport from the origin.
101
+ //
102
+
103
+ @state()
104
+ posInline = 0;
105
+
106
+ @state()
107
+ posBlock = 0;
108
+
109
+ //
110
+ // `size` (when not suffixed with ‘row’ or ‘col’, see above) is the size in pixels of the viewport.
111
+ //
112
+
113
+ @state()
114
+ sizeInline = 0;
115
+
116
+ @state()
117
+ sizeBlock = 0;
118
+
119
+ //
120
+ // `overscan` is the amount in pixels to offset the grid content due to the number of overscanned columns or rows.
121
+ //
122
+
123
+ @state()
124
+ overscanInline = 0;
125
+
126
+ @state()
127
+ overscanBlock = 0;
128
+
129
+ //
130
+ // `bin`, not short for anything, is the range in pixels within which virtualization does not need to reassess.
131
+ //
132
+
133
+ @state()
134
+ binInlineMin = 0;
135
+
136
+ @state()
137
+ binInlineMax = this.colSize(0);
138
+
139
+ @state()
140
+ binBlockMin = 0;
141
+
142
+ @state()
143
+ binBlockMax = this.rowSize(0);
144
+
145
+ //
146
+ // `vis`, short for ‘visible’, is the range in numeric index of the columns or rows which should be rendered within
147
+ // the viewport. These start with naïve values that are updated before first contentful render.
148
+ //
149
+
150
+ @state()
151
+ visColMin = 0;
152
+
153
+ @state()
154
+ visColMax = 1;
155
+
156
+ @state()
157
+ visRowMin = 0;
158
+
159
+ @state()
160
+ visRowMax = 1;
161
+
162
+ //
163
+ // `template` is the rendered value of `grid-{axis}-template`.
164
+ //
165
+ @state()
166
+ templateColumns = `${this.colSize(0)}px`;
167
+
168
+ @state()
169
+ templateRows = `${this.rowSize(0)}px`;
170
+
171
+ //
172
+ // Resize state and handlers
173
+ //
174
+
175
+ @state()
176
+ colSizes: Record<string, number> = {};
177
+
178
+ @state()
179
+ rowSizes: Record<string, number> = {};
180
+
181
+ @state()
182
+ resizing: null | { axis: 'col' | 'row'; page: number; size: number; index: string } = null;
183
+
184
+ handlePointerDown = (event: PointerEvent) => {
185
+ const actionEl = (event.target as HTMLElement)?.closest('[data-dx-grid-action]');
186
+ const action = actionEl?.getAttribute('data-dx-grid-action');
187
+ if (action) {
188
+ if (action.startsWith('resize')) {
189
+ const [resize, index] = action.split(',');
190
+ const [_, axis] = resize.split('-');
191
+ this.resizing = {
192
+ axis: axis as 'col' | 'row',
193
+ size: axis === 'col' ? this.colSize(index) : this.rowSize(index),
194
+ page: getPage(axis, event),
195
+ index,
196
+ };
197
+ }
198
+ }
199
+ };
200
+
201
+ handlePointerUp = (_event: PointerEvent) => {
202
+ this.resizing = null;
203
+ };
204
+
205
+ handlePointerMove = (event: PointerEvent) => {
206
+ if (this.resizing) {
207
+ const delta = getPage(this.resizing.axis, event) - this.resizing.page;
208
+ if (this.resizing.axis === 'col') {
209
+ const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, this.resizing.size + delta));
210
+ this.colSizes = { ...this.colSizes, [this.resizing.index]: nextSize };
211
+ this.updateVisInline();
212
+ } else {
213
+ const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, this.resizing.size + delta));
214
+ this.rowSizes = { ...this.rowSizes, [this.resizing.index]: nextSize };
215
+ this.updateVisBlock();
216
+ }
217
+ }
218
+ };
219
+
220
+ //
221
+ // Accessors
222
+ //
223
+
224
+ private colSize(c: number | string) {
225
+ return this.colSizes?.[c] ?? this.columnDefault.size;
226
+ }
227
+
228
+ private rowSize(r: number | string) {
229
+ return this.rowSizes?.[r] ?? this.rowDefault.size;
230
+ }
231
+
232
+ private getCell(c: number | string, r: number | string) {
233
+ return this.cells[`${c}${separator}${r}`];
234
+ }
235
+
236
+ //
237
+ // Resize & reposition handlers, observer, ref
238
+ //
239
+
240
+ @state()
241
+ observer = new ResizeObserver((entries) => {
242
+ const { inlineSize, blockSize } = entries?.[0]?.contentBoxSize?.[0] ?? {
243
+ inlineSize: 0,
244
+ blockSize: 0,
245
+ };
246
+ if (
247
+ Math.abs(inlineSize - this.sizeInline) > resizeTolerance ||
248
+ Math.abs(blockSize - this.sizeBlock) > resizeTolerance
249
+ ) {
250
+ // console.info('[updating bounds]', 'resize', [inlineSize - this.sizeInline, blockSize - this.sizeBlock]);
251
+ this.sizeInline = inlineSize;
252
+ this.sizeBlock = blockSize;
253
+ this.updateVis();
254
+ }
255
+ });
256
+
257
+ viewportRef: Ref<HTMLDivElement> = createRef();
258
+
259
+ handleWheel = ({ deltaX, deltaY }: WheelEvent) => {
260
+ this.posInline = Math.max(0, this.posInline + deltaX);
261
+ this.posBlock = Math.max(0, this.posBlock + deltaY);
262
+ if (
263
+ this.posInline >= this.binInlineMin &&
264
+ this.posInline < this.binInlineMax &&
265
+ this.posBlock >= this.binBlockMin &&
266
+ this.posBlock < this.binBlockMax
267
+ ) {
268
+ // do nothing
269
+ } else {
270
+ // console.info(
271
+ // '[updating bounds]',
272
+ // 'wheel',
273
+ // [this.binInlineMin, this.posInline, this.binInlineMax],
274
+ // [this.binBlockMin, this.posBlock, this.binBlockMax],
275
+ // );
276
+ this.updateVis();
277
+ }
278
+ };
279
+
280
+ private updateVisInline() {
281
+ // todo: avoid starting from zero
282
+ let colIndex = 0;
283
+ let pxInline = this.colSize(colIndex);
284
+
285
+ while (pxInline < this.posInline) {
286
+ colIndex += 1;
287
+ pxInline += this.colSize(colIndex) + gap;
288
+ }
289
+
290
+ this.visColMin = colIndex - overscanCol;
291
+
292
+ this.binInlineMin = pxInline - this.colSize(colIndex) - gap;
293
+ this.binInlineMax = pxInline + gap;
294
+
295
+ this.overscanInline =
296
+ [...Array(overscanCol)].reduce((acc, _, c0) => {
297
+ acc += this.colSize(this.visColMin + c0);
298
+ return acc;
299
+ }, 0) +
300
+ gap * (overscanCol - 1);
301
+
302
+ while (pxInline < this.binInlineMax + this.sizeInline) {
303
+ colIndex += 1;
304
+ pxInline += this.colSize(colIndex) + gap;
305
+ }
306
+
307
+ this.visColMax = colIndex + overscanCol + 1;
308
+
309
+ this.templateColumns = [...Array(this.visColMax - this.visColMin)]
310
+ .map((_, c0) => `${this.colSize(this.visColMin + c0)}px`)
311
+ .join(' ');
312
+ }
313
+
314
+ private updateVisBlock() {
315
+ // todo: avoid starting from zero
316
+ let rowIndex = 0;
317
+ let pxBlock = this.rowSize(rowIndex);
318
+
319
+ while (pxBlock < this.posBlock) {
320
+ rowIndex += 1;
321
+ pxBlock += this.rowSize(rowIndex) + gap;
322
+ }
323
+
324
+ this.visRowMin = rowIndex - overscanRow;
325
+
326
+ this.binBlockMin = pxBlock - this.rowSize(rowIndex) - gap;
327
+ this.binBlockMax = pxBlock + gap;
328
+
329
+ this.overscanBlock =
330
+ [...Array(overscanRow)].reduce((acc, _, r0) => {
331
+ acc += this.rowSize(this.visRowMin + r0);
332
+ return acc;
333
+ }, 0) +
334
+ gap * (overscanRow - 1);
335
+
336
+ while (pxBlock < this.binBlockMax + this.sizeBlock) {
337
+ rowIndex += 1;
338
+ pxBlock += this.rowSize(rowIndex) + gap;
339
+ }
340
+
341
+ this.visRowMax = rowIndex + overscanRow + 1;
342
+
343
+ this.templateRows = [...Array(this.visRowMax - this.visRowMin)]
344
+ .map((_, r0) => `${this.rowSize(this.visRowMin + r0)}px`)
345
+ .join(' ');
346
+ }
347
+
348
+ private updateVis() {
349
+ this.updateVisInline();
350
+ this.updateVisBlock();
351
+ }
352
+
353
+ //
354
+ // Render and other lifecycle methods
355
+ //
356
+
357
+ override render() {
358
+ const visibleCols = this.visColMax - this.visColMin;
359
+ const visibleRows = this.visRowMax - this.visRowMin;
360
+ // TODO NEXT -> ensure offset is using the right components
361
+ const offsetInline = this.binInlineMin - this.posInline - this.overscanInline;
362
+ const offsetBlock = this.binBlockMin - this.posBlock - this.overscanBlock;
363
+
364
+ return html`<div
365
+ role="none"
366
+ class="dx-grid"
367
+ @pointerdown=${this.handlePointerDown}
368
+ @pointerup=${this.handlePointerUp}
369
+ @pointermove=${this.handlePointerMove}
370
+ >
371
+ <div role="none" class="dx-grid__corner"></div>
372
+ <div role="none" class="dx-grid__columnheader">
373
+ <div
374
+ role="none"
375
+ class="dx-grid__columnheader__content"
376
+ style="transform:translate3d(${offsetInline}px,0,0);grid-template-columns:${this.templateColumns};"
377
+ >
378
+ ${[...Array(visibleCols)].map((_, c0) => {
379
+ const c = this.visColMin + c0;
380
+ return html`<div
381
+ role="columnheader"
382
+ ?inert=${c < 0}
383
+ style="inline-size:${this.colSize(c)}px;block-size:${this.rowDefault.size}px;grid-column:${c0 + 1}/${c0 +
384
+ 2};"
385
+ >
386
+ <span id=${localChId(c0)}>${colToA1Notation(c)}</span>
387
+ ${(this.columns[c]?.resizeable ?? this.columnDefault.resizeable) &&
388
+ html`<button class="dx-grid__resize-handle" data-dx-grid-action=${`resize-col,${c}`}>
389
+ <span class="sr-only">Resize</span>
390
+ </button>`}
391
+ </div>`;
392
+ })}
393
+ </div>
394
+ </div>
395
+ <div role="none" class="dx-grid__corner"></div>
396
+ <div role="none" class="dx-grid__rowheader">
397
+ <div role="none" class="dx-grid__rowheader__content" style="transform:translate3d(0,${offsetBlock}px,0);">
398
+ ${[...Array(visibleRows)].map((_, r0) => {
399
+ const r = this.visRowMin + r0;
400
+ return html`<div
401
+ role="rowheader"
402
+ ?inert=${r < 0}
403
+ style="block-size:${this.rowSize(r)}px;grid-row:${r0 + 1}/${r0 + 2}"
404
+ >
405
+ <span id=${localRhId(r0)}>${rowToA1Notation(r)}</span>
406
+ ${(this.rows[r]?.resizeable ?? this.rowDefault.resizeable) &&
407
+ html`<button class="dx-grid__resize-handle" data-dx-grid-action=${`resize-row,${r}`}>
408
+ <span class="sr-only">Resize</span>
409
+ </button>`}
410
+ </div>`;
411
+ })}
412
+ </div>
413
+ </div>
414
+ <div role="none" class="dx-grid__viewport" @wheel="${this.handleWheel}" ${ref(this.viewportRef)}>
415
+ <div
416
+ role="grid"
417
+ class="dx-grid__content"
418
+ style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
419
+ .templateColumns};grid-template-rows:${this.templateRows};"
420
+ >
421
+ ${[...Array(visibleCols)].map((_, c0) => {
422
+ return [...Array(visibleRows)].map((_, r0) => {
423
+ const c = c0 + this.visColMin;
424
+ const r = r0 + this.visRowMin;
425
+ const cell = this.getCell(c, r);
426
+ return html`<div
427
+ role="gridcell"
428
+ ?inert=${c < 0 || r < 0}
429
+ aria-rowindex=${r}
430
+ aria-colindex=${c}
431
+ data-dx-grid-action="cell"
432
+ style="grid-column:${c0 + 1};grid-row:${r0 + 1}"
433
+ >
434
+ ${cell?.value}
435
+ </div>`;
436
+ });
437
+ })}
438
+ </div>
439
+ </div>
440
+ <div role="none" class="dx-grid__scrollbar" aria-orientation="vertical">
441
+ <div role="none" class="dx-grid__scrollbar__thumb"></div>
442
+ </div>
443
+ <div role="none" class="dx-grid__corner"></div>
444
+ <div role="none" class="dx-grid__scrollbar" aria-orientation="horizontal">
445
+ <div role="none" class="dx-grid__scrollbar__thumb"></div>
446
+ </div>
447
+ <div role="none" class="dx-grid__corner"></div>
448
+ </div>`;
449
+ }
450
+
451
+ override firstUpdated() {
452
+ this.observer.observe(this.viewportRef.value!);
453
+ this.colSizes = Object.entries(this.columns).reduce((acc: Record<string, number>, [colId, colMeta]) => {
454
+ if (colMeta?.size) {
455
+ acc[colId] = colMeta.size;
456
+ }
457
+ return acc;
458
+ }, {});
459
+ this.rowSizes = Object.entries(this.rows).reduce((acc: Record<string, number>, [rowId, rowMeta]) => {
460
+ if (rowMeta?.size) {
461
+ acc[rowId] = rowMeta.size;
462
+ }
463
+ return acc;
464
+ }, {});
465
+ }
466
+
467
+ override disconnectedCallback() {
468
+ super.disconnectedCallback();
469
+ // console.log('[disconnected]', this.viewportRef.value);
470
+ // TODO(thure): Will this even work?
471
+ if (this.viewportRef.value) {
472
+ this.observer.unobserve(this.viewportRef.value);
473
+ }
474
+ }
475
+
476
+ override createRenderRoot() {
477
+ return this;
478
+ }
479
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export { DxGrid, type DxGridProps } from './dx-grid';