@dxos/lit-grid 0.8.4-main.67995b8 → 0.8.4-main.72ec0f3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/src/defs.d.ts +1 -0
  2. package/dist/src/defs.d.ts.map +1 -1
  3. package/dist/src/defs.js +1 -0
  4. package/dist/src/defs.js.map +1 -1
  5. package/dist/src/dx-grid-axis-resize-handle.d.ts.map +1 -1
  6. package/dist/src/dx-grid-axis-resize-handle.js +3 -5
  7. package/dist/src/dx-grid-axis-resize-handle.js.map +1 -1
  8. package/dist/src/dx-grid-multiselect-cell.d.ts.map +1 -1
  9. package/dist/src/dx-grid-multiselect-cell.js +2 -1
  10. package/dist/src/dx-grid-multiselect-cell.js.map +1 -1
  11. package/dist/src/dx-grid.d.ts +12 -7
  12. package/dist/src/dx-grid.d.ts.map +1 -1
  13. package/dist/src/dx-grid.js +196 -162
  14. package/dist/src/dx-grid.js.map +1 -1
  15. package/dist/src/dx-grid.lit-stories.js +15 -18
  16. package/dist/src/dx-grid.lit-stories.js.map +1 -1
  17. package/dist/src/playwright/dx-grid.spec.js.map +1 -1
  18. package/dist/src/testing/dx-grid-manager.d.ts.map +1 -1
  19. package/dist/src/testing/dx-grid-manager.js.map +1 -1
  20. package/dist/src/types.d.ts +9 -0
  21. package/dist/src/types.d.ts.map +1 -1
  22. package/dist/src/types.js.map +1 -1
  23. package/dist/src/util.d.ts +4 -1
  24. package/dist/src/util.d.ts.map +1 -1
  25. package/dist/src/util.js +11 -11
  26. package/dist/src/util.js.map +1 -1
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +4 -4
  29. package/src/defs.ts +1 -0
  30. package/src/dx-grid-axis-resize-handle.pcss +6 -0
  31. package/src/dx-grid-axis-resize-handle.ts +1 -1
  32. package/src/dx-grid-multiselect-cell.ts +2 -1
  33. package/src/dx-grid.pcss +22 -9
  34. package/src/dx-grid.ts +190 -109
  35. package/src/playwright/dx-grid.spec.ts +1 -1
  36. package/src/testing/dx-grid-manager.ts +1 -1
  37. package/src/types.ts +11 -0
  38. package/src/util.ts +13 -9
package/src/dx-grid.ts CHANGED
@@ -3,73 +3,75 @@
3
3
  //
4
4
 
5
5
  import { LitElement, html, nothing } from 'lit';
6
- import { customElement, state, property } from 'lit/decorators.js';
7
- import { ref, createRef, type Ref } from 'lit/directives/ref.js';
6
+ import { customElement, property, state } from 'lit/decorators.js';
7
+ import { type Ref, createRef, ref } from 'lit/directives/ref.js';
8
8
  import { styleMap } from 'lit/directives/style-map.js';
9
- import { unsafeStatic, html as staticHtml } from 'lit/static-html.js';
9
+ import { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
10
10
 
11
- import { defaultColSize, defaultRowSize } from './defs';
11
+ import { defaultColSize, defaultRowSize, focusUnfurlDefault } from './defs';
12
12
  import './dx-grid-axis-resize-handle';
13
13
  import {
14
- type DxGridAxisMetaProps,
15
- type DxGridAxisSizes,
16
- type DxGridPlaneCellIndex,
17
- type DxGridCellValue,
18
14
  DxAxisResize,
19
15
  type DxAxisResizeInternal,
20
16
  DxEditRequest,
17
+ type DxGridAnnotatedPanEvent,
18
+ type DxGridAxis,
21
19
  type DxGridAxisMeta,
20
+ type DxGridAxisMetaProps,
21
+ type DxGridAxisSizes,
22
+ type DxGridCellValue,
22
23
  type DxGridCells,
23
24
  DxGridCellsSelect,
24
25
  type DxGridFixedPlane,
26
+ type DxGridFocusIndicatorVariant,
25
27
  type DxGridFrozenAxes,
26
28
  type DxGridFrozenColsPlane,
27
29
  type DxGridFrozenPlane,
28
30
  type DxGridFrozenRowsPlane,
29
31
  type DxGridMode,
32
+ type DxGridOverscroll,
30
33
  type DxGridPlane,
34
+ type DxGridPlaneCellIndex,
31
35
  type DxGridPlaneCells,
32
36
  type DxGridPlaneRange,
33
37
  type DxGridPlaneRecord,
34
38
  type DxGridPointer,
35
39
  type DxGridPosition,
36
- type DxGridAxis,
37
- type DxGridSelectionProps,
38
- type DxGridAnnotatedPanEvent,
39
40
  type DxGridRange,
41
+ type DxGridSelectionProps,
40
42
  separator,
41
43
  } from './types';
42
44
  import {
43
- toCellIndex,
44
- gap,
45
- resizeTolerance,
46
- sizeColMin,
47
- sizeColMax,
48
- sizeRowMin,
49
- sizeRowMax,
50
- shouldSelect,
51
- selectionProps,
52
45
  cellSelected,
53
46
  closestAction,
54
47
  closestCell,
55
- targetIsPlane,
56
- resolveRowPlane,
48
+ gap,
49
+ isReadonly,
50
+ isSameCell,
51
+ resizeTolerance,
57
52
  resolveColPlane,
58
53
  resolveFrozenPlane,
59
- isSameCell,
60
- isReadonly,
54
+ resolveRowPlane,
55
+ selectionProps,
56
+ shouldSelect,
57
+ sizeColMax,
58
+ sizeColMin,
59
+ sizeRowMax,
60
+ sizeRowMin,
61
+ targetIsPlane,
62
+ toCellIndex,
61
63
  } from './util';
62
64
 
63
65
  @customElement('dx-grid')
64
66
  export class DxGrid extends LitElement {
65
67
  constructor() {
66
68
  super();
67
- // Wheel, top-level and element-level
69
+ // Wheel, top-level and element-level.
68
70
  document.defaultView?.addEventListener('wheel', this.handleTopLevelWheel, { passive: false });
69
71
  this.addEventListener('wheel', this.handleWheel);
70
- // Custom event(s)
72
+ // Custom event(s).
71
73
  this.addEventListener('dx-axis-resize-internal', this.handleAxisResizeInternal as EventListener);
72
- // Standard events
74
+ // Standard events.
73
75
  this.addEventListener('pointerdown', this.handlePointerDown);
74
76
  this.addEventListener('pointermove', this.handlePointerMove);
75
77
  this.addEventListener('pointerup', this.handlePointerUp);
@@ -83,12 +85,12 @@ export class DxGrid extends LitElement {
83
85
  gridId: string = 'default-grid-id';
84
86
 
85
87
  @property({ type: Object })
86
- rowDefault: DxGridPlaneRecord<DxGridFrozenRowsPlane, DxGridAxisMetaProps> = {
88
+ rowDefault: Partial<DxGridPlaneRecord<DxGridFrozenRowsPlane, Partial<DxGridAxisMetaProps>>> = {
87
89
  grid: { size: defaultRowSize },
88
90
  };
89
91
 
90
92
  @property({ type: Object })
91
- columnDefault: DxGridPlaneRecord<DxGridFrozenColsPlane, DxGridAxisMetaProps> = {
93
+ columnDefault: Partial<DxGridPlaneRecord<DxGridFrozenColsPlane, Partial<DxGridAxisMetaProps>>> = {
92
94
  grid: { size: defaultColSize },
93
95
  };
94
96
 
@@ -114,11 +116,14 @@ export class DxGrid extends LitElement {
114
116
  frozen: DxGridFrozenAxes = {};
115
117
 
116
118
  @property({ type: String })
117
- overscroll: 'inline' | 'block' | 'trap' | undefined = undefined;
119
+ overscroll: DxGridOverscroll = undefined;
118
120
 
119
121
  @property({ type: String })
120
122
  activeRefs = '';
121
123
 
124
+ @property({ type: String })
125
+ focusIndicatorVariant: DxGridFocusIndicatorVariant = 'sheet';
126
+
122
127
  /**
123
128
  * When this function is defined, it is used first to try to get a value for a cell,
124
129
  * and otherwise will fall back to `cells`.
@@ -194,6 +199,7 @@ export class DxGrid extends LitElement {
194
199
  //
195
200
  // `template` is the rendered value of `grid-{axis}-template`.
196
201
  //
202
+
197
203
  @state()
198
204
  private templateGridColumns = '0';
199
205
 
@@ -212,6 +218,16 @@ export class DxGrid extends LitElement {
212
218
  @state()
213
219
  private templatefrozenRowsEnd = '';
214
220
 
221
+ //
222
+ // `frozen…Size` is used to measure space available for the non-fixed planes
223
+ //
224
+
225
+ @state()
226
+ private frozenColsSize = 0;
227
+
228
+ @state()
229
+ private frozenRowsSize = 0;
230
+
215
231
  //
216
232
  // Focus, selection, and resize states
217
233
  //
@@ -307,7 +323,7 @@ export class DxGrid extends LitElement {
307
323
  this.dispatchSelectionChange();
308
324
  }
309
325
  if (this.mode === 'edit-select') {
310
- // Prevent focus moving when editing while selection is possible
326
+ // Prevent focus moving when editing while selection is possible.
311
327
  event.preventDefault();
312
328
  } else if (this.focusActive && isSameCell(this.focusedCell, cellCoords)) {
313
329
  this.dispatchEditRequest();
@@ -415,15 +431,13 @@ export class DxGrid extends LitElement {
415
431
  }
416
432
 
417
433
  private moveFocusIntoPlane(plane: DxGridPlane): void {
418
- if (this.focusedCell.plane !== plane) {
419
- const colPlane = resolveColPlane(plane);
420
- const rowPlane = resolveRowPlane(plane);
421
- this.focusedCell = {
422
- plane,
423
- col: colPlane === 'grid' ? this.visColMin : 0,
424
- row: rowPlane === 'grid' ? this.visRowMin : 0,
425
- };
426
- }
434
+ const colPlane = resolveColPlane(plane);
435
+ const rowPlane = resolveRowPlane(plane);
436
+ this.focusedCell = {
437
+ plane,
438
+ col: colPlane === 'grid' ? this.visColMin : 0,
439
+ row: rowPlane === 'grid' ? this.visRowMin : 0,
440
+ };
427
441
  this.focusedCellElement()?.focus({ preventScroll: true });
428
442
  }
429
443
 
@@ -580,7 +594,7 @@ export class DxGrid extends LitElement {
580
594
  }
581
595
 
582
596
  //
583
- // Resize & reposition handlers, observer, ref
597
+ // Resize & reposition handlers, observer, ref.
584
598
  //
585
599
 
586
600
  @state()
@@ -590,19 +604,18 @@ export class DxGrid extends LitElement {
590
604
  blockSize: 0,
591
605
  };
592
606
  if (
593
- Math.abs(inlineSize - this.sizeInline) > resizeTolerance ||
594
- Math.abs(blockSize - this.sizeBlock) > resizeTolerance
607
+ Math.abs(inlineSize - this.frozenColsSize - this.sizeInline) > resizeTolerance ||
608
+ Math.abs(blockSize - this.frozenRowsSize - this.sizeBlock) > resizeTolerance
595
609
  ) {
596
610
  // console.info('[updating bounds]', 'resize', [inlineSize - this.sizeInline, blockSize - this.sizeBlock]);
597
- this.sizeInline = inlineSize;
598
- this.sizeBlock = blockSize;
611
+ this.sizeInline = inlineSize - this.frozenColsSize;
612
+ this.sizeBlock = blockSize - this.frozenRowsSize;
599
613
  this.updateVis();
600
614
  queueMicrotask(() => this.updatePos());
601
615
  }
602
616
  });
603
617
 
604
618
  private gridRef: Ref<HTMLDivElement> = createRef();
605
- private viewportRef: Ref<HTMLDivElement> = createRef();
606
619
 
607
620
  private maybeUpdateVisInline = () => {
608
621
  if (this.posInline < this.binInlineMin || this.posInline >= this.binInlineMax) {
@@ -681,7 +694,7 @@ export class DxGrid extends LitElement {
681
694
  };
682
695
 
683
696
  private updateVisInline(): void {
684
- // todo: avoid starting from zero
697
+ // todo: avoid starting from zero.
685
698
  let axisCursor = 0;
686
699
  let pxCursor = this.colSize(axisCursor, 'grid');
687
700
 
@@ -727,10 +740,19 @@ export class DxGrid extends LitElement {
727
740
  this.templatefrozenColsEnd = [...Array(this.frozen.frozenColsEnd ?? 0)]
728
741
  .map((_, c0) => `${this.colSize(c0, 'frozenColsEnd')}px`)
729
742
  .join(' ');
743
+
744
+ this.frozenColsSize =
745
+ [...Array(this.frozen.frozenColsStart ?? 0)].reduce(
746
+ (sum, _, c0) => sum + this.colSize(c0, 'frozenColsStart'),
747
+ 0,
748
+ ) +
749
+ gap * Math.max(0, this.frozen.frozenColsStart ?? 0 - 1) +
750
+ [...Array(this.frozen.frozenColsEnd ?? 0)].reduce((sum, _, c0) => sum + this.colSize(c0, 'frozenColsEnd'), 0) +
751
+ gap * Math.max(0, this.frozen.frozenColsEnd ?? 0 - 1);
730
752
  }
731
753
 
732
754
  private updateVisBlock(): void {
733
- // todo: avoid starting from zero
755
+ // todo: avoid starting from zero.
734
756
  let axisCursor = 0;
735
757
  let pxCursor = this.rowSize(axisCursor, 'grid');
736
758
 
@@ -776,6 +798,15 @@ export class DxGrid extends LitElement {
776
798
  this.templatefrozenRowsEnd = [...Array(this.frozen.frozenRowsEnd ?? 0)]
777
799
  .map((_, r0) => `${this.rowSize(r0, 'frozenRowsEnd')}px`)
778
800
  .join(' ');
801
+
802
+ this.frozenRowsSize =
803
+ [...Array(this.frozen.frozenRowsStart ?? 0)].reduce(
804
+ (sum, _, r0) => sum + this.rowSize(r0, 'frozenRowsStart'),
805
+ 0,
806
+ ) +
807
+ gap * Math.max(0, this.frozen.frozenRowsStart ?? 0 - 1) +
808
+ [...Array(this.frozen.frozenRowsEnd ?? 0)].reduce((sum, _, r0) => sum + this.rowSize(r0, 'frozenRowsEnd'), 0) +
809
+ gap * Math.max(0, this.frozen.frozenRowsEnd ?? 0 - 1);
779
810
  }
780
811
 
781
812
  private updateVis(): void {
@@ -997,7 +1028,7 @@ export class DxGrid extends LitElement {
997
1028
  }
998
1029
 
999
1030
  /**
1000
- * Updates `pos` so that a cell in focus is fully within the viewport
1031
+ * Updates `pos` so that a cell in focus is fully within the viewport.
1001
1032
  */
1002
1033
  snapPosToFocusedCell(): void {
1003
1034
  const outOfVis = this.focusedCellOutOfVis();
@@ -1074,16 +1105,40 @@ export class DxGrid extends LitElement {
1074
1105
  : !!(this.rows[plane]?.[index]?.resizeable ?? this.rowDefault[plane as DxGridFrozenRowsPlane]?.resizeable);
1075
1106
  }
1076
1107
 
1108
+ private clampAxisSize(
1109
+ plane: 'grid' | DxGridFrozenPlane,
1110
+ axis: DxGridAxis,
1111
+ index: number | string,
1112
+ requestedSize: number,
1113
+ ): number {
1114
+ const minSize =
1115
+ axis === 'col'
1116
+ ? (this.columns[plane]?.[index]?.minSize ??
1117
+ this.columnDefault[plane as DxGridFrozenColsPlane]?.minSize ??
1118
+ sizeColMin)
1119
+ : (this.rows[plane]?.[index]?.minSize ??
1120
+ this.rowDefault[plane as DxGridFrozenRowsPlane]?.minSize ??
1121
+ sizeRowMin);
1122
+ const maxSize =
1123
+ axis === 'col'
1124
+ ? (this.columns[plane]?.[index]?.maxSize ??
1125
+ this.columnDefault[plane as DxGridFrozenColsPlane]?.maxSize ??
1126
+ sizeColMax)
1127
+ : (this.rows[plane]?.[index]?.maxSize ??
1128
+ this.rowDefault[plane as DxGridFrozenRowsPlane]?.maxSize ??
1129
+ sizeRowMax);
1130
+ return Math.max(minSize, Math.min(maxSize, requestedSize));
1131
+ }
1132
+
1077
1133
  private handleAxisResizeInternal(event: DxAxisResizeInternal): void {
1078
1134
  event.stopPropagation();
1079
1135
  const { plane, axis, delta, size, index, state } = event;
1136
+ const nextSize = this.clampAxisSize(plane, axis, index, size + delta);
1080
1137
  if (axis === 'col') {
1081
- const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, size + delta));
1082
1138
  this.colSizes = { ...this.colSizes, [plane]: { ...this.colSizes[plane], [index]: nextSize } };
1083
1139
  this.updateVisInline();
1084
1140
  this.updateIntrinsicInlineSize();
1085
1141
  } else {
1086
- const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, size + delta));
1087
1142
  this.rowSizes = { ...this.colSizes, [plane]: { ...this.rowSizes[plane], [index]: nextSize } };
1088
1143
  this.updateVisBlock();
1089
1144
  this.updateIntrinsicBlockSize();
@@ -1101,7 +1156,7 @@ export class DxGrid extends LitElement {
1101
1156
  }
1102
1157
 
1103
1158
  //
1104
- // Render and other lifecycle methods
1159
+ // Render and other lifecycle methods.
1105
1160
  //
1106
1161
 
1107
1162
  // TODO(thure): This is for rendering presentational objects superimposed onto the canonical grid (e.g. DnD drop line for #8108).
@@ -1184,7 +1239,7 @@ export class DxGrid extends LitElement {
1184
1239
  ) {
1185
1240
  const rowPlane = resolveRowPlane(plane) as DxGridFrozenPlane;
1186
1241
  const rows = this.frozen[rowPlane];
1187
- return (rows ?? 0) > 0
1242
+ return (rows ?? 0) > 0 && this.limitColumns > 0
1188
1243
  ? html`<div
1189
1244
  role="none"
1190
1245
  class="dx-grid__plane--frozen-row"
@@ -1218,7 +1273,7 @@ export class DxGrid extends LitElement {
1218
1273
  ) {
1219
1274
  const colPlane = resolveColPlane(plane) as DxGridFrozenPlane;
1220
1275
  const cols = this.frozen[colPlane];
1221
- return (cols ?? 0) > 0
1276
+ return (cols ?? 0) > 0 && this.limitRows > 0
1222
1277
  ? html`<div
1223
1278
  role="none"
1224
1279
  class="dx-grid__plane--frozen-col"
@@ -1244,6 +1299,40 @@ export class DxGrid extends LitElement {
1244
1299
  : null;
1245
1300
  }
1246
1301
 
1302
+ private renderMainGrid(
1303
+ visibleCols: number,
1304
+ visibleRows: number,
1305
+ offsetInline: number,
1306
+ offsetBlock: number,
1307
+ selection: DxGridSelectionProps,
1308
+ ) {
1309
+ return this.limitRows > 0 && this.limitColumns > 0
1310
+ ? html`<div
1311
+ role="grid"
1312
+ class="dx-grid__plane--grid"
1313
+ tabindex="0"
1314
+ data-dx-grid-plane="grid"
1315
+ data-dx-grid-plane-row="1"
1316
+ data-dx-grid-plane-col="1"
1317
+ >
1318
+ <div
1319
+ role="none"
1320
+ class="dx-grid__plane--grid__content"
1321
+ style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
1322
+ .templateGridColumns};grid-template-rows:${this.templateGridRows};"
1323
+ >
1324
+ ${[...Array(visibleRows)].map((_, r0) => {
1325
+ return [...Array(visibleCols)].map((_, c0) => {
1326
+ const c = c0 + this.visColMin;
1327
+ const r = r0 + this.visRowMin;
1328
+ return this.renderCell(c, r, 'grid', cellSelected(c, r, 'grid', selection), c0, r0);
1329
+ });
1330
+ })}
1331
+ </div>
1332
+ </div>`
1333
+ : null;
1334
+ }
1335
+
1247
1336
  private cellReadonly(col: number, row: number, plane: DxGridPlane): boolean {
1248
1337
  const colPlane = resolveColPlane(plane);
1249
1338
  const rowPlane = resolveRowPlane(plane);
@@ -1261,6 +1350,23 @@ export class DxGrid extends LitElement {
1261
1350
  return isReadonly(colReadOnly) || isReadonly(rowReadOnly);
1262
1351
  }
1263
1352
 
1353
+ private cellFocusUnfurl(col: number, row: number, plane: DxGridPlane): boolean {
1354
+ const colPlane = resolveColPlane(plane);
1355
+ const rowPlane = resolveRowPlane(plane);
1356
+
1357
+ // Check cell-specific setting first.
1358
+ const cellUnfurl = this.cell(col, row, plane)?.focusUnfurl;
1359
+ if (cellUnfurl !== undefined) {
1360
+ return cellUnfurl;
1361
+ }
1362
+
1363
+ // Check column/row defaults.
1364
+ const colUnfurl = this.columns?.[colPlane]?.[col]?.focusUnfurl ?? this.columnDefault?.[colPlane]?.focusUnfurl;
1365
+ const rowUnfurl = this.rows?.[rowPlane]?.[row]?.focusUnfurl ?? this.rowDefault?.[rowPlane]?.focusUnfurl;
1366
+
1367
+ return colUnfurl ?? rowUnfurl ?? focusUnfurlDefault;
1368
+ }
1369
+
1264
1370
  /**
1265
1371
  * Determines if the cell's text content should be selectable based on its readonly value.
1266
1372
  * @returns true if the cells text content is selectable, false otherwise.
@@ -1323,6 +1429,7 @@ export class DxGrid extends LitElement {
1323
1429
  const cell = this.cell(col, row, plane);
1324
1430
  const active = this.cellActive(col, row, plane);
1325
1431
  const readonly = this.cellReadonly(col, row, plane);
1432
+ const focusUnfurl = this.cellFocusUnfurl(col, row, plane);
1326
1433
  const textSelectable = this.cellTextSelectable(col, row, plane);
1327
1434
  const resizeIndex = cell?.resizeHandle ? (cell.resizeHandle === 'col' ? col : row) : undefined;
1328
1435
  const resizePlane = cell?.resizeHandle ? resolveFrozenPlane(cell.resizeHandle, plane) : undefined;
@@ -1334,11 +1441,13 @@ export class DxGrid extends LitElement {
1334
1441
  aria-readonly=${readonly ? 'true' : nothing}
1335
1442
  class=${cell?.className ?? nothing}
1336
1443
  data-refs=${cell?.dataRefs ?? nothing}
1444
+ data-focus-unfurl=${focusUnfurl ? nothing : 'false'}
1337
1445
  ?data-dx-active=${active}
1338
1446
  data-text-selectable=${textSelectable ? 'true' : 'false'}
1339
1447
  data-dx-grid-action="cell"
1340
1448
  aria-colindex=${col}
1341
1449
  aria-rowindex=${row}
1450
+ data-testid=${`${plane}.${col}.${row}`}
1342
1451
  style="grid-column:${visCol + 1};grid-row:${visRow + 1}"
1343
1452
  >
1344
1453
  <div role="none" class="dx-grid__cell__content">${cell?.value}${accessory}</div>
@@ -1376,13 +1485,24 @@ export class DxGrid extends LitElement {
1376
1485
  <div
1377
1486
  role="none"
1378
1487
  class="dx-grid"
1488
+ data-arrow-keys="all"
1379
1489
  style=${styleMap({
1380
- 'grid-template-columns': `${this.templatefrozenColsStart ? 'min-content ' : ''}minmax(0, ${
1381
- Number.isFinite(this.limitColumns) ? `${Math.max(0, this.intrinsicInlineSize)}px` : '1fr'
1382
- })${this.templatefrozenColsEnd ? ' min-content' : ''}`,
1383
- 'grid-template-rows': `${this.templatefrozenRowsStart ? 'min-content ' : ''}minmax(0, ${
1384
- Number.isFinite(this.limitRows) ? `${Math.max(0, this.intrinsicBlockSize)}px` : '1fr'
1385
- })${this.templatefrozenRowsEnd ? ' min-content' : ''}`,
1490
+ 'grid-template-columns': [
1491
+ this.templatefrozenColsStart ? 'min-content' : false,
1492
+ this.limitColumns > 0 &&
1493
+ `minmax(0, ${Number.isFinite(this.limitColumns) ? `${Math.max(0, this.intrinsicInlineSize)}px` : '1fr'})`,
1494
+ this.templatefrozenColsEnd ? 'min-content' : false,
1495
+ ]
1496
+ .filter(Boolean)
1497
+ .join(' '),
1498
+ 'grid-template-rows': [
1499
+ this.templatefrozenRowsStart ? 'min-content' : false,
1500
+ this.limitRows > 0 &&
1501
+ `minmax(0, ${Number.isFinite(this.limitRows) ? `${Math.max(0, this.intrinsicBlockSize)}px` : '1fr'})`,
1502
+ this.templatefrozenRowsEnd ? ' min-content' : false,
1503
+ ]
1504
+ .filter(Boolean)
1505
+ .join(' '),
1386
1506
  '--dx-grid-content-inline-size': Number.isFinite(this.limitColumns)
1387
1507
  ? `${Math.max(0, this.totalIntrinsicInlineSize)}px`
1388
1508
  : 'max-content',
@@ -1392,6 +1512,7 @@ export class DxGrid extends LitElement {
1392
1512
  })}
1393
1513
  data-grid=${this.gridId}
1394
1514
  data-grid-mode=${this.mode}
1515
+ data-grid-focus-indicator-variant=${this.focusIndicatorVariant}
1395
1516
  ?data-grid-select=${selection.visible}
1396
1517
  ${ref(this.gridRef)}
1397
1518
  >
@@ -1406,30 +1527,7 @@ export class DxGrid extends LitElement {
1406
1527
  offsetBlock,
1407
1528
  selection,
1408
1529
  )}
1409
- <div
1410
- role="grid"
1411
- class="dx-grid__plane--grid"
1412
- tabindex="0"
1413
- data-dx-grid-plane="grid"
1414
- data-dx-grid-plane-row="1"
1415
- data-dx-grid-plane-col="1"
1416
- ${ref(this.viewportRef)}
1417
- >
1418
- <div
1419
- role="none"
1420
- class="dx-grid__plane--grid__content"
1421
- style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
1422
- .templateGridColumns};grid-template-rows:${this.templateGridRows};"
1423
- >
1424
- ${[...Array(visibleRows)].map((_, r0) => {
1425
- return [...Array(visibleCols)].map((_, c0) => {
1426
- const c = c0 + this.visColMin;
1427
- const r = r0 + this.visRowMin;
1428
- return this.renderCell(c, r, 'grid', cellSelected(c, r, 'grid', selection), c0, r0);
1429
- });
1430
- })}
1431
- </div>
1432
- </div>
1530
+ ${this.renderMainGrid(visibleCols, visibleRows, offsetInline, offsetBlock, selection)}
1433
1531
  ${this.renderFrozenColumns('frozenColsEnd', visibleRows, offsetBlock, selection)}${this.renderFixed(
1434
1532
  'fixedEndStart',
1435
1533
  selection,
@@ -1443,37 +1541,19 @@ export class DxGrid extends LitElement {
1443
1541
  private updateIntrinsicInlineSize(): void {
1444
1542
  this.intrinsicInlineSize = Number.isFinite(this.limitColumns)
1445
1543
  ? [...Array(this.limitColumns)].reduce((acc, _, c0) => acc + this.colSize(c0, 'grid'), 0) +
1446
- gap * (this.limitColumns - 1)
1544
+ gap * Math.max(0, this.limitColumns - 1)
1447
1545
  : Infinity;
1448
1546
  this.totalIntrinsicInlineSize =
1449
- this.intrinsicInlineSize +
1450
- (Number.isFinite(this.frozen.frozenColsStart)
1451
- ? [...Array(this.frozen.frozenColsStart)].reduce(
1452
- (acc, _, c0) => acc + gap + this.colSize(c0, 'frozenColsStart'),
1453
- 0,
1454
- )
1455
- : 0) +
1456
- (Number.isFinite(this.frozen.frozenColsEnd)
1457
- ? [...Array(this.frozen.frozenColsEnd)].reduce((acc, _, c0) => acc + gap + this.colSize(c0, 'frozenColsEnd'), 0)
1458
- : 0);
1547
+ this.limitColumns > 0 ? this.intrinsicInlineSize + this.frozenColsSize : this.frozenColsSize - gap;
1459
1548
  }
1460
1549
 
1461
1550
  private updateIntrinsicBlockSize(): void {
1462
1551
  this.intrinsicBlockSize = Number.isFinite(this.limitRows)
1463
1552
  ? [...Array(this.limitRows)].reduce((acc, _, r0) => acc + this.rowSize(r0, 'grid'), 0) +
1464
- gap * (this.limitRows - 1)
1553
+ gap * Math.max(0, this.limitRows - 1)
1465
1554
  : Infinity;
1466
1555
  this.totalIntrinsicBlockSize =
1467
- this.intrinsicBlockSize +
1468
- (Number.isFinite(this.frozen.frozenRowsStart)
1469
- ? [...Array(this.frozen.frozenRowsStart)].reduce(
1470
- (acc, _, r0) => acc + gap + this.rowSize(r0, 'frozenRowsStart'),
1471
- 0,
1472
- )
1473
- : 0) +
1474
- (Number.isFinite(this.frozen.frozenRowsEnd)
1475
- ? [...Array(this.frozen.frozenRowsEnd)].reduce((acc, _, r0) => acc + gap + this.rowSize(r0, 'frozenRowsEnd'), 0)
1476
- : 0);
1556
+ this.limitRows > 0 ? this.intrinsicBlockSize + this.frozenRowsSize : this.frozenRowsSize - gap;
1477
1557
  }
1478
1558
 
1479
1559
  private updateIntrinsicSizes(): void {
@@ -1521,7 +1601,7 @@ export class DxGrid extends LitElement {
1521
1601
  if (this.getCells) {
1522
1602
  this.updateCells(true);
1523
1603
  }
1524
- this.observer.observe(this.viewportRef.value!);
1604
+ this.observer.observe(this.gridRef.value!);
1525
1605
  this.computeColSizes();
1526
1606
  this.computeRowSizes();
1527
1607
  this.updateIntrinsicSizes();
@@ -1595,8 +1675,8 @@ export class DxGrid extends LitElement {
1595
1675
 
1596
1676
  override disconnectedCallback(): void {
1597
1677
  super.disconnectedCallback();
1598
- if (this.viewportRef.value) {
1599
- this.observer.unobserve(this.viewportRef.value);
1678
+ if (this.gridRef.value) {
1679
+ this.observer.unobserve(this.gridRef.value);
1600
1680
  }
1601
1681
  document.defaultView?.removeEventListener('wheel', this.handleTopLevelWheel);
1602
1682
  }
@@ -1614,6 +1694,7 @@ export {
1614
1694
  parseCellIndex,
1615
1695
  toPlaneCellIndex,
1616
1696
  cellQuery,
1697
+ accessoryHandlesPointerdownAttrs,
1617
1698
  } from './util';
1618
1699
 
1619
1700
  export const commentedClassName = 'dx-grid__cell--commented';
@@ -2,7 +2,7 @@
2
2
  // Copyright 2021 DXOS.org
3
3
  //
4
4
 
5
- import { expect, test, type Page } from '@playwright/test';
5
+ import { type Page, expect, test } from '@playwright/test';
6
6
 
7
7
  import { setupPage, storybookUrl } from '@dxos/test-utils/playwright';
8
8
 
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { expect, type Locator, type Page } from '@playwright/test';
5
+ import { type Locator, type Page, expect } from '@playwright/test';
6
6
 
7
7
  import type { DxGridPlanePosition } from '../types';
8
8
 
package/src/types.ts CHANGED
@@ -83,6 +83,10 @@ export type DxGridCellValue = {
83
83
  * Controls the read-only state of the cell.
84
84
  */
85
85
  readonly?: DxGridReadonlyValue;
86
+ /**
87
+ * Controls whether the cell content should unfurl when the cell has focus.
88
+ */
89
+ focusUnfurl?: boolean;
86
90
  };
87
91
 
88
92
  export type DxGridAxisMetaProps = {
@@ -90,6 +94,9 @@ export type DxGridAxisMetaProps = {
90
94
  description?: string;
91
95
  resizeable?: boolean;
92
96
  readonly?: DxGridReadonlyValue;
97
+ focusUnfurl?: boolean;
98
+ minSize?: number;
99
+ maxSize?: number;
93
100
  };
94
101
 
95
102
  export type DxGridAxisSizes = DxGridPlaneRecord<DxGridFrozenPlane, Record<string, number>>;
@@ -121,6 +128,10 @@ export type DxGridSelectionProps = {
121
128
  visible?: boolean;
122
129
  };
123
130
 
131
+ export type DxGridFocusIndicatorVariant = 'sheet' | 'stack';
132
+
133
+ export type DxGridOverscroll = 'inline' | 'block' | 'trap' | undefined;
134
+
124
135
  export class DxAxisResize extends Event {
125
136
  public readonly axis: DxGridAxis;
126
137
  public readonly plane: 'grid' | DxGridFrozenPlane;
package/src/util.ts CHANGED
@@ -4,19 +4,19 @@
4
4
 
5
5
  import { defaultRowSize } from './defs';
6
6
  import {
7
- type DxGridPlaneCellIndex,
7
+ type DxGridAxis,
8
8
  type DxGridCellIndex,
9
- type DxGridPosition,
10
- type DxGridPointer,
11
- type DxGridSelectionProps,
12
- type DxGridPositionNullable,
13
- type DxGridPlane,
14
- type DxGridFrozenRowsPlane,
15
9
  type DxGridFrozenColsPlane,
16
10
  type DxGridFrozenPlane,
17
- type DxGridAxis,
11
+ type DxGridFrozenRowsPlane,
12
+ type DxGridPlane,
13
+ type DxGridPlaneCellIndex,
18
14
  type DxGridPlanePosition,
15
+ type DxGridPointer,
16
+ type DxGridPosition,
17
+ type DxGridPositionNullable,
19
18
  type DxGridReadonlyValue,
19
+ type DxGridSelectionProps,
20
20
  separator,
21
21
  } from './types';
22
22
 
@@ -25,7 +25,7 @@ export const toPlaneCellIndex = (cellCoords: Partial<DxGridPosition> & DxGridPla
25
25
 
26
26
  export function parseCellIndex(index: DxGridCellIndex): DxGridPosition;
27
27
  export function parseCellIndex(index: DxGridPlaneCellIndex): DxGridPlanePosition;
28
- // eslint-disable-next-line prefer-arrow-functions/prefer-arrow-functions
28
+
29
29
  export function parseCellIndex(index: DxGridPlaneCellIndex | DxGridCellIndex): DxGridPlanePosition | DxGridPosition {
30
30
  const coords = index.split(separator);
31
31
  if (coords.length === 3) {
@@ -123,6 +123,10 @@ export const closestAction = (target: EventTarget | null): { action: string | nu
123
123
  return { actionEl, action: actionEl?.getAttribute('data-dx-grid-action') ?? null };
124
124
  };
125
125
 
126
+ export const accessoryHandlesPointerdownAttrs = {
127
+ 'data-dx-grid-action': 'accessory',
128
+ };
129
+
126
130
  export const closestCell = (target: EventTarget | null, actionEl?: HTMLElement | null): DxGridPositionNullable => {
127
131
  let cellElement = actionEl;
128
132
  if (!cellElement) {