@dxos/lit-grid 0.6.11 → 0.6.12-main.15a606f

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.
@@ -1,8 +1,12 @@
1
1
  // packages/ui/lit-grid/src/dx-grid.ts
2
- import { LitElement, html } from "lit";
2
+ import { LitElement, html, nothing } from "lit";
3
3
  import { customElement, state, property, eventOptions } from "lit/decorators.js";
4
4
  import { ref, createRef } from "lit/directives/ref.js";
5
5
 
6
+ // packages/ui/lit-grid/src/util.ts
7
+ var separator = ",";
8
+ var toCellIndex = (cellCoords) => `${cellCoords.col}${separator}${cellCoords.row}`;
9
+
6
10
  // packages/ui/lit-grid/src/types.ts
7
11
  var DxAxisResize = class extends Event {
8
12
  constructor(props) {
@@ -12,6 +16,25 @@ var DxAxisResize = class extends Event {
12
16
  this.size = props.size;
13
17
  }
14
18
  };
19
+ var DxEditRequest = class extends Event {
20
+ constructor(props) {
21
+ super("dx-edit-request");
22
+ this.cellIndex = props.cellIndex;
23
+ this.cellBox = props.cellBox;
24
+ this.initialContent = props.initialContent;
25
+ }
26
+ };
27
+ var DxGridCellsSelect = class extends Event {
28
+ constructor({ start, end }) {
29
+ super("dx-grid-cells-select");
30
+ this.start = toCellIndex(start);
31
+ this.end = toCellIndex(end);
32
+ this.minCol = Math.min(start.col, end.col);
33
+ this.maxCol = Math.max(start.col, end.col);
34
+ this.minRow = Math.min(start.row, end.row);
35
+ this.maxRow = Math.max(start.row, end.row);
36
+ }
37
+ };
15
38
 
16
39
  // packages/ui/lit-grid/src/dx-grid.ts
17
40
  function _ts_decorate(decorators, target, key, desc) {
@@ -28,19 +51,46 @@ var sizeColMin = 32;
28
51
  var sizeColMax = 1024;
29
52
  var sizeRowMin = 16;
30
53
  var sizeRowMax = 1024;
31
- var separator = ",";
32
54
  var colToA1Notation = (col) => {
33
55
  return (col >= 26 ? String.fromCharCode("A".charCodeAt(0) + Math.floor(col / 26) - 1) : "") + String.fromCharCode("A".charCodeAt(0) + col % 26);
34
56
  };
35
57
  var rowToA1Notation = (row) => {
36
58
  return `${row + 1}`;
37
59
  };
60
+ var closestAction = (target) => {
61
+ const actionEl = target?.closest("[data-dx-grid-action]") ?? null;
62
+ return {
63
+ actionEl,
64
+ action: actionEl?.getAttribute("data-dx-grid-action") ?? null
65
+ };
66
+ };
67
+ var closestCell = (target, actionEl) => {
68
+ let cellElement = actionEl;
69
+ if (!cellElement) {
70
+ const { action, actionEl: actionEl2 } = closestAction(target);
71
+ if (action === "cell") {
72
+ cellElement = actionEl2;
73
+ }
74
+ }
75
+ if (cellElement) {
76
+ const col = parseInt(cellElement.getAttribute("aria-colindex") ?? "never");
77
+ const row = parseInt(cellElement.getAttribute("aria-rowindex") ?? "never");
78
+ return {
79
+ col,
80
+ row
81
+ };
82
+ } else {
83
+ return null;
84
+ }
85
+ };
86
+ var isSameCell = (a, b) => a && b && Number.isFinite(a.col) && Number.isFinite(a.row) && a.col === b.col && a.row === b.row;
38
87
  var localChId = (c0) => `ch--${c0}`;
39
88
  var localRhId = (r0) => `rh--${r0}`;
40
89
  var getPage = (axis, event) => axis === "col" ? event.pageX : event.pageY;
41
90
  var DxGrid = class extends LitElement {
42
91
  constructor() {
43
92
  super(...arguments);
93
+ this.gridId = "default-grid-id";
44
94
  this.rowDefault = {
45
95
  size: 32
46
96
  };
@@ -50,6 +100,7 @@ var DxGrid = class extends LitElement {
50
100
  this.rows = {};
51
101
  this.columns = {};
52
102
  this.cells = {};
103
+ this.mode = "browse";
53
104
  //
54
105
  // `pos`, short for ‘position’, is the position in pixels of the viewport from the origin.
55
106
  //
@@ -86,56 +137,100 @@ var DxGrid = class extends LitElement {
86
137
  this.templateColumns = `${this.colSize(0)}px`;
87
138
  this.templateRows = `${this.rowSize(0)}px`;
88
139
  //
89
- // Resize state and handlers
140
+ // Focus, selection, and resize states
90
141
  //
142
+ this.pointer = null;
91
143
  this.colSizes = {};
92
144
  this.rowSizes = {};
93
- this.resizing = null;
145
+ this.focusActive = false;
146
+ this.focusedCell = {
147
+ col: 0,
148
+ row: 0
149
+ };
150
+ this.selectionStart = {
151
+ col: 0,
152
+ row: 0
153
+ };
154
+ this.selectionEnd = {
155
+ col: 0,
156
+ row: 0
157
+ };
94
158
  this.handlePointerDown = (event) => {
95
- const actionEl = event.target?.closest("[data-dx-grid-action]");
96
- const action = actionEl?.getAttribute("data-dx-grid-action");
97
- if (action) {
98
- if (action.startsWith("resize")) {
99
- const [resize, index] = action.split(",");
100
- const [_, axis] = resize.split("-");
101
- this.resizing = {
102
- axis,
103
- size: axis === "col" ? this.colSize(index) : this.rowSize(index),
104
- page: getPage(axis, event),
105
- index
106
- };
159
+ if (event.isPrimary) {
160
+ const { action, actionEl } = closestAction(event.target);
161
+ if (action) {
162
+ if (action.startsWith("resize") && this.mode === "browse") {
163
+ const [resize, index] = action.split(",");
164
+ const [_, axis] = resize.split("-");
165
+ this.pointer = {
166
+ state: "resizing",
167
+ axis,
168
+ size: axis === "col" ? this.colSize(index) : this.rowSize(index),
169
+ page: getPage(axis, event),
170
+ index
171
+ };
172
+ } else if (action === "cell") {
173
+ const cellCoords = closestCell(event.target, actionEl);
174
+ if (cellCoords) {
175
+ this.pointer = {
176
+ state: "selecting"
177
+ };
178
+ this.selectionStart = cellCoords;
179
+ }
180
+ if (this.mode === "edit") {
181
+ event.preventDefault();
182
+ } else {
183
+ if (this.focusActive && isSameCell(this.focusedCell, cellCoords)) {
184
+ this.dispatchEditRequest();
185
+ }
186
+ }
187
+ }
107
188
  }
108
189
  }
109
190
  };
110
- this.handlePointerUp = (_event) => {
111
- if (this.resizing) {
191
+ this.handlePointerUp = (event) => {
192
+ if (this.pointer?.state === "resizing") {
112
193
  const resizeEvent = new DxAxisResize({
113
- axis: this.resizing.axis,
114
- index: this.resizing.index,
115
- size: this[this.resizing.axis === "col" ? "colSize" : "rowSize"](this.resizing.index)
194
+ axis: this.pointer.axis,
195
+ index: this.pointer.index,
196
+ size: this[this.pointer.axis === "col" ? "colSize" : "rowSize"](this.pointer.index)
116
197
  });
117
198
  this.dispatchEvent(resizeEvent);
118
- this.resizing = null;
199
+ } else {
200
+ const cell = closestCell(event.target);
201
+ if (cell) {
202
+ this.selectionEnd = cell;
203
+ this.dispatchEvent(new DxGridCellsSelect({
204
+ start: this.selectionStart,
205
+ end: this.selectionEnd
206
+ }));
207
+ }
119
208
  }
209
+ this.pointer = null;
120
210
  };
121
211
  this.handlePointerMove = (event) => {
122
- if (this.resizing) {
123
- const delta = getPage(this.resizing.axis, event) - this.resizing.page;
124
- if (this.resizing.axis === "col") {
125
- const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, this.resizing.size + delta));
212
+ if (this.pointer?.state === "resizing") {
213
+ const delta = getPage(this.pointer.axis, event) - this.pointer.page;
214
+ if (this.pointer.axis === "col") {
215
+ const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, this.pointer.size + delta));
126
216
  this.colSizes = {
127
217
  ...this.colSizes,
128
- [this.resizing.index]: nextSize
218
+ [this.pointer.index]: nextSize
129
219
  };
130
220
  this.updateVisInline();
131
221
  } else {
132
- const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, this.resizing.size + delta));
222
+ const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, this.pointer.size + delta));
133
223
  this.rowSizes = {
134
224
  ...this.rowSizes,
135
- [this.resizing.index]: nextSize
225
+ [this.pointer.index]: nextSize
136
226
  };
137
227
  this.updateVisBlock();
138
228
  }
229
+ } else if (this.pointer?.state === "selecting") {
230
+ const cell = closestCell(event.target);
231
+ if (cell && (cell.col !== this.selectionEnd.col || cell.row !== this.selectionEnd.row)) {
232
+ this.selectionEnd = cell;
233
+ }
139
234
  }
140
235
  };
141
236
  //
@@ -154,19 +249,75 @@ var DxGrid = class extends LitElement {
154
249
  });
155
250
  this.viewportRef = createRef();
156
251
  this.handleWheel = ({ deltaX, deltaY }) => {
157
- this.posInline = Math.max(0, this.posInline + deltaX);
158
- this.posBlock = Math.max(0, this.posBlock + deltaY);
159
- if (this.posInline >= this.binInlineMin && this.posInline < this.binInlineMax && this.posBlock >= this.binBlockMin && this.posBlock < this.binBlockMax) {
160
- } else {
161
- this.updateVis();
252
+ if (this.mode === "browse") {
253
+ this.posInline = Math.max(0, this.posInline + deltaX);
254
+ this.posBlock = Math.max(0, this.posBlock + deltaY);
255
+ if (this.posInline >= this.binInlineMin && this.posInline < this.binInlineMax && this.posBlock >= this.binBlockMin && this.posBlock < this.binBlockMax) {
256
+ } else {
257
+ this.updateVis();
258
+ }
162
259
  }
163
260
  };
164
- // Focus handlers
165
- this.focusedCell = {
166
- col: 0,
167
- row: 0
168
- };
169
- this.focusActive = false;
261
+ }
262
+ //
263
+ // Primary pointer and keyboard handlers
264
+ //
265
+ dispatchEditRequest(initialContent) {
266
+ this.snapPosToFocusedCell();
267
+ queueMicrotask(() => this.dispatchEvent(new DxEditRequest({
268
+ cellIndex: toCellIndex(this.focusedCell),
269
+ cellBox: this.focusedCellBox(),
270
+ initialContent
271
+ })));
272
+ }
273
+ handleKeydown(event) {
274
+ if (this.focusActive && this.mode === "browse") {
275
+ switch (event.key) {
276
+ case "ArrowDown":
277
+ this.focusedCell = {
278
+ ...this.focusedCell,
279
+ row: this.focusedCell.row + 1
280
+ };
281
+ break;
282
+ case "ArrowUp":
283
+ this.focusedCell = {
284
+ ...this.focusedCell,
285
+ row: Math.max(0, this.focusedCell.row - 1)
286
+ };
287
+ break;
288
+ case "ArrowRight":
289
+ this.focusedCell = {
290
+ ...this.focusedCell,
291
+ col: this.focusedCell.col + 1
292
+ };
293
+ break;
294
+ case "ArrowLeft":
295
+ this.focusedCell = {
296
+ ...this.focusedCell,
297
+ col: Math.max(0, this.focusedCell.col - 1)
298
+ };
299
+ break;
300
+ }
301
+ switch (event.key) {
302
+ case "Enter":
303
+ this.dispatchEditRequest();
304
+ break;
305
+ default:
306
+ if (event.key.length === 1 && event.key.match(/\P{Cc}/u)) {
307
+ this.dispatchEditRequest(event.key);
308
+ }
309
+ break;
310
+ }
311
+ switch (event.key) {
312
+ case "ArrowDown":
313
+ case "ArrowUp":
314
+ case "ArrowRight":
315
+ case "ArrowLeft":
316
+ event.preventDefault();
317
+ this.snapPosToFocusedCell();
318
+ break;
319
+ }
320
+ }
170
321
  }
171
322
  //
172
323
  // Accessors
@@ -177,9 +328,33 @@ var DxGrid = class extends LitElement {
177
328
  rowSize(r) {
178
329
  return this.rowSizes?.[r] ?? this.rowDefault.size;
179
330
  }
180
- getCell(c, r) {
331
+ cell(c, r) {
181
332
  return this.cells[`${c}${separator}${r}`];
182
333
  }
334
+ focusedCellBox() {
335
+ const cellElement = this.focusedCellElement();
336
+ const cellSize = {
337
+ inlineSize: this.colSize(this.focusedCell.col),
338
+ blockSize: this.rowSize(this.focusedCell.row)
339
+ };
340
+ if (!cellElement) {
341
+ return {
342
+ insetInlineStart: NaN,
343
+ insetBlockStart: NaN,
344
+ ...cellSize
345
+ };
346
+ }
347
+ const contentElement = cellElement.offsetParent;
348
+ const [_translate3d, inlineStr, blockStr] = contentElement.style.transform.split(/[()]|px,?\s?/);
349
+ const contentOffsetInline = parseFloat(inlineStr);
350
+ const contentOffsetBlock = parseFloat(blockStr);
351
+ const offsetParent = contentElement.offsetParent;
352
+ return {
353
+ insetInlineStart: cellElement.offsetLeft + contentOffsetInline + offsetParent.offsetLeft,
354
+ insetBlockStart: cellElement.offsetTop + contentOffsetBlock + offsetParent.offsetTop,
355
+ ...cellSize
356
+ };
357
+ }
183
358
  updateVisInline() {
184
359
  let colIndex = 0;
185
360
  let pxInline = this.colSize(colIndex);
@@ -234,31 +409,45 @@ var DxGrid = class extends LitElement {
234
409
  this.updateVisInline();
235
410
  this.updateVisBlock();
236
411
  }
412
+ // Focus handlers
237
413
  handleFocus(event) {
238
- const target = event.target;
239
- const action = target.getAttribute("data-dx-grid-action");
240
- if (action === "cell") {
241
- const c = parseInt(target.getAttribute("aria-colindex") ?? "never");
242
- const r = parseInt(target.getAttribute("aria-rowindex") ?? "never");
243
- this.focusedCell = {
244
- col: c,
245
- row: r
246
- };
414
+ const cellCoords = closestCell(event.target);
415
+ if (cellCoords) {
416
+ this.focusedCell = cellCoords;
247
417
  this.focusActive = true;
248
418
  }
249
419
  }
250
420
  handleBlur(event) {
251
- if (!event.relatedTarget || event.relatedTarget.closest(".dx-grid__viewport") !== this.viewportRef.value) {
421
+ if (!event.relatedTarget || !event.relatedTarget.closest(`[data-grid="${this.gridId}"]`)) {
252
422
  this.focusActive = false;
253
423
  }
254
424
  }
425
+ focusedCellElement() {
426
+ return this.viewportRef.value?.querySelector(`[aria-colindex="${this.focusedCell.col}"][aria-rowindex="${this.focusedCell.row}"]`);
427
+ }
255
428
  /**
256
429
  * Moves focus to the cell with actual focus, otherwise moves focus to the viewport.
257
430
  */
258
- refocus() {
259
- (this.focusedCell.row < this.visRowMin || this.focusedCell.row > this.visRowMax || this.focusedCell.col < this.visColMin || this.focusedCell.col > this.visColMax ? this.viewportRef.value : this.viewportRef.value?.querySelector(`[aria-colindex="${this.focusedCell.col}"][aria-rowindex="${this.focusedCell.row}"]`))?.focus({
431
+ refocus(increment, delta = 1) {
432
+ switch (increment) {
433
+ case "row":
434
+ this.focusedCell = {
435
+ ...this.focusedCell,
436
+ row: this.focusedCell.row + delta
437
+ };
438
+ break;
439
+ case "col":
440
+ this.focusedCell = {
441
+ ...this.focusedCell,
442
+ col: this.focusedCell.col + delta
443
+ };
444
+ }
445
+ (this.focusedCell.row < this.visRowMin || this.focusedCell.row > this.visRowMax || this.focusedCell.col < this.visColMin || this.focusedCell.col > this.visColMax ? this.viewportRef.value : this.focusedCellElement())?.focus({
260
446
  preventScroll: true
261
447
  });
448
+ if (increment) {
449
+ this.snapPosToFocusedCell();
450
+ }
262
451
  }
263
452
  /**
264
453
  * Updates `pos` so that a cell in focus is fully within the viewport
@@ -277,7 +466,7 @@ var DxGrid = class extends LitElement {
277
466
  acc += this.colSize(this.visColMin + overscanCol + c0) + gap;
278
467
  return acc;
279
468
  }, 0);
280
- this.posInline = this.binInlineMin + sizeSumCol + gap * 2 - this.sizeInline;
469
+ this.posInline = Math.max(0, this.binInlineMin + sizeSumCol + gap * 2 - this.sizeInline);
281
470
  this.updateVisInline();
282
471
  }
283
472
  if (this.focusedCell.row <= this.visRowMin + overscanRow) {
@@ -290,65 +479,34 @@ var DxGrid = class extends LitElement {
290
479
  acc += this.rowSize(this.visRowMin + overscanRow + r0) + gap;
291
480
  return acc;
292
481
  }, 0);
293
- this.posBlock = this.binBlockMin + sizeSumRow + gap * 2 - this.sizeBlock;
482
+ this.posBlock = Math.max(0, this.binBlockMin + sizeSumRow + gap * 2 - this.sizeBlock);
294
483
  this.updateVisBlock();
295
484
  }
296
485
  }
297
486
  }
298
- // Keyboard interactions
299
- handleKeydown(event) {
300
- if (this.focusActive) {
301
- switch (event.key) {
302
- case "ArrowDown":
303
- this.focusedCell = {
304
- ...this.focusedCell,
305
- row: this.focusedCell.row + 1
306
- };
307
- break;
308
- case "ArrowUp":
309
- this.focusedCell = {
310
- ...this.focusedCell,
311
- row: Math.max(0, this.focusedCell.row - 1)
312
- };
313
- break;
314
- case "ArrowRight":
315
- this.focusedCell = {
316
- ...this.focusedCell,
317
- col: this.focusedCell.col + 1
318
- };
319
- break;
320
- case "ArrowLeft":
321
- this.focusedCell = {
322
- ...this.focusedCell,
323
- col: Math.max(0, this.focusedCell.col - 1)
324
- };
325
- break;
326
- }
327
- switch (event.key) {
328
- case "ArrowDown":
329
- case "ArrowUp":
330
- case "ArrowRight":
331
- case "ArrowLeft":
332
- event.preventDefault();
333
- this.snapPosToFocusedCell();
334
- break;
335
- }
336
- }
337
- }
338
487
  //
339
488
  // Render and other lifecycle methods
340
489
  //
341
490
  render() {
342
491
  const visibleCols = this.visColMax - this.visColMin;
343
492
  const visibleRows = this.visRowMax - this.visRowMin;
344
- const offsetInline = gap + this.binInlineMin - this.posInline - this.overscanInline;
345
- const offsetBlock = gap + this.binBlockMin - this.posBlock - this.overscanBlock;
493
+ const offsetInline = this.binInlineMin - this.posInline - this.overscanInline;
494
+ const offsetBlock = this.binBlockMin - this.posBlock - this.overscanBlock;
495
+ const selectColMin = Math.min(this.selectionStart.col, this.selectionEnd.col);
496
+ const selectColMax = Math.max(this.selectionStart.col, this.selectionEnd.col);
497
+ const selectRowMin = Math.min(this.selectionStart.row, this.selectionEnd.row);
498
+ const selectRowMax = Math.max(this.selectionStart.row, this.selectionEnd.row);
499
+ const selectVisible = selectColMin !== selectColMax || selectRowMin !== selectRowMax;
346
500
  return html`<div
347
501
  role="none"
348
502
  class="dx-grid"
503
+ data-grid=${this.gridId}
504
+ data-grid-mode=${this.mode}
505
+ ?data-grid-select=${selectVisible}
349
506
  @pointerdown=${this.handlePointerDown}
350
507
  @pointerup=${this.handlePointerUp}
351
508
  @pointermove=${this.handlePointerMove}
509
+ @pointerleave=${this.handlePointerUp}
352
510
  @focus=${this.handleFocus}
353
511
  @blur=${this.handleBlur}
354
512
  @keydown=${this.handleKeydown}
@@ -411,11 +569,15 @@ var DxGrid = class extends LitElement {
411
569
  ].map((_2, r0) => {
412
570
  const c = c0 + this.visColMin;
413
571
  const r = r0 + this.visRowMin;
414
- const cell = this.getCell(c, r);
572
+ const cell = this.cell(c, r);
573
+ const active = this.focusActive && this.focusedCell.col === c && this.focusedCell.row === r;
574
+ const selected = c >= selectColMin && c <= selectColMax && r >= selectRowMin && r <= selectRowMax;
415
575
  return html`<div
416
576
  role="gridcell"
417
577
  tabindex="0"
418
578
  ?inert=${c < 0 || r < 0}
579
+ ?aria-selected=${selected}
580
+ class=${cell || active ? (cell?.className ? cell.className + " " : "") + (active ? "dx-grid__cell--active" : "") : nothing}
419
581
  aria-rowindex=${r}
420
582
  aria-colindex=${c}
421
583
  data-dx-grid-action="cell"
@@ -467,6 +629,11 @@ var DxGrid = class extends LitElement {
467
629
  return this;
468
630
  }
469
631
  };
632
+ _ts_decorate([
633
+ property({
634
+ type: String
635
+ })
636
+ ], DxGrid.prototype, "gridId", void 0);
470
637
  _ts_decorate([
471
638
  property({
472
639
  type: Object
@@ -492,6 +659,11 @@ _ts_decorate([
492
659
  type: Object
493
660
  })
494
661
  ], DxGrid.prototype, "cells", void 0);
662
+ _ts_decorate([
663
+ property({
664
+ type: String
665
+ })
666
+ ], DxGrid.prototype, "mode", void 0);
495
667
  _ts_decorate([
496
668
  state()
497
669
  ], DxGrid.prototype, "posInline", void 0);
@@ -540,6 +712,9 @@ _ts_decorate([
540
712
  _ts_decorate([
541
713
  state()
542
714
  ], DxGrid.prototype, "templateRows", void 0);
715
+ _ts_decorate([
716
+ state()
717
+ ], DxGrid.prototype, "pointer", void 0);
543
718
  _ts_decorate([
544
719
  state()
545
720
  ], DxGrid.prototype, "colSizes", void 0);
@@ -548,16 +723,19 @@ _ts_decorate([
548
723
  ], DxGrid.prototype, "rowSizes", void 0);
549
724
  _ts_decorate([
550
725
  state()
551
- ], DxGrid.prototype, "resizing", void 0);
726
+ ], DxGrid.prototype, "focusActive", void 0);
552
727
  _ts_decorate([
553
728
  state()
554
- ], DxGrid.prototype, "observer", void 0);
729
+ ], DxGrid.prototype, "focusedCell", void 0);
555
730
  _ts_decorate([
556
731
  state()
557
- ], DxGrid.prototype, "focusedCell", void 0);
732
+ ], DxGrid.prototype, "selectionStart", void 0);
558
733
  _ts_decorate([
559
734
  state()
560
- ], DxGrid.prototype, "focusActive", void 0);
735
+ ], DxGrid.prototype, "selectionEnd", void 0);
736
+ _ts_decorate([
737
+ state()
738
+ ], DxGrid.prototype, "observer", void 0);
561
739
  _ts_decorate([
562
740
  eventOptions({
563
741
  capture: true
@@ -573,6 +751,8 @@ DxGrid = _ts_decorate([
573
751
  ], DxGrid);
574
752
  export {
575
753
  DxAxisResize,
576
- DxGrid
754
+ DxEditRequest,
755
+ DxGrid,
756
+ DxGridCellsSelect
577
757
  };
578
758
  //# sourceMappingURL=index.mjs.map