@dxos/lit-grid 0.8.3 → 0.8.4-main.1f223c7

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 (107) 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 +11 -6
  12. package/dist/src/dx-grid.d.ts.map +1 -1
  13. package/dist/src/dx-grid.js +229 -150
  14. package/dist/src/dx-grid.js.map +1 -1
  15. package/dist/src/dx-grid.lit-stories.d.ts +1 -0
  16. package/dist/src/dx-grid.lit-stories.d.ts.map +1 -1
  17. package/dist/src/dx-grid.lit-stories.js +19 -20
  18. package/dist/src/dx-grid.lit-stories.js.map +1 -1
  19. package/dist/src/playwright/dx-grid.spec.d.ts.map +1 -0
  20. package/dist/src/{testing/playwright → playwright}/dx-grid.spec.js +3 -3
  21. package/dist/src/playwright/dx-grid.spec.js.map +1 -0
  22. package/dist/src/playwright/playwright.config.d.ts +3 -0
  23. package/dist/src/playwright/playwright.config.d.ts.map +1 -0
  24. package/dist/src/playwright/playwright.config.js +15 -0
  25. package/dist/src/playwright/playwright.config.js.map +1 -0
  26. package/dist/src/testing/{playwright/dx-grid-manager.d.ts → dx-grid-manager.d.ts} +1 -1
  27. package/dist/src/testing/dx-grid-manager.d.ts.map +1 -0
  28. package/dist/src/testing/dx-grid-manager.js.map +1 -0
  29. package/dist/src/testing/index.d.ts +1 -1
  30. package/dist/src/testing/index.d.ts.map +1 -1
  31. package/dist/src/testing/index.js +1 -1
  32. package/dist/src/testing/index.js.map +1 -1
  33. package/dist/src/types.d.ts +7 -0
  34. package/dist/src/types.d.ts.map +1 -1
  35. package/dist/src/types.js.map +1 -1
  36. package/dist/src/util.d.ts +4 -1
  37. package/dist/src/util.d.ts.map +1 -1
  38. package/dist/src/util.js +11 -11
  39. package/dist/src/util.js.map +1 -1
  40. package/dist/tsconfig.tsbuildinfo +1 -1
  41. package/package.json +6 -5
  42. package/src/defs.ts +1 -0
  43. package/src/dx-grid-axis-resize-handle.pcss +7 -0
  44. package/src/dx-grid-axis-resize-handle.ts +1 -1
  45. package/src/dx-grid-multiselect-cell.pcss +2 -2
  46. package/src/dx-grid-multiselect-cell.ts +2 -1
  47. package/src/dx-grid.lit-stories.ts +6 -2
  48. package/src/dx-grid.pcss +133 -15
  49. package/src/dx-grid.ts +240 -100
  50. package/src/{testing/playwright → playwright}/dx-grid.spec.ts +5 -5
  51. package/src/playwright/playwright.config.ts +17 -0
  52. package/src/testing/{playwright/dx-grid-manager.ts → dx-grid-manager.ts} +2 -2
  53. package/src/testing/index.ts +1 -1
  54. package/src/types.ts +7 -0
  55. package/src/util.ts +13 -9
  56. package/dist/src/testing/playwright/dx-grid-manager.d.ts.map +0 -1
  57. package/dist/src/testing/playwright/dx-grid-manager.js.map +0 -1
  58. package/dist/src/testing/playwright/dx-grid.spec.d.ts.map +0 -1
  59. package/dist/src/testing/playwright/dx-grid.spec.js.map +0 -1
  60. package/dist/types/src/defs.d.ts +0 -3
  61. package/dist/types/src/defs.d.ts.map +0 -1
  62. package/dist/types/src/defs.js +0 -6
  63. package/dist/types/src/defs.js.map +0 -1
  64. package/dist/types/src/dx-grid-axis-resize-handle.d.ts +0 -16
  65. package/dist/types/src/dx-grid-axis-resize-handle.d.ts.map +0 -1
  66. package/dist/types/src/dx-grid-axis-resize-handle.js +0 -100
  67. package/dist/types/src/dx-grid-axis-resize-handle.js.map +0 -1
  68. package/dist/types/src/dx-grid-multiselect-cell.d.ts +0 -13
  69. package/dist/types/src/dx-grid-multiselect-cell.d.ts.map +0 -1
  70. package/dist/types/src/dx-grid-multiselect-cell.js +0 -56
  71. package/dist/types/src/dx-grid-multiselect-cell.js.map +0 -1
  72. package/dist/types/src/dx-grid.d.ts +0 -170
  73. package/dist/types/src/dx-grid.d.ts.map +0 -1
  74. package/dist/types/src/dx-grid.js +0 -1347
  75. package/dist/types/src/dx-grid.js.map +0 -1
  76. package/dist/types/src/dx-grid.lit-stories.d.ts +0 -46
  77. package/dist/types/src/dx-grid.lit-stories.d.ts.map +0 -1
  78. package/dist/types/src/dx-grid.lit-stories.js +0 -179
  79. package/dist/types/src/dx-grid.lit-stories.js.map +0 -1
  80. package/dist/types/src/index.d.ts +0 -5
  81. package/dist/types/src/index.d.ts.map +0 -1
  82. package/dist/types/src/index.js +0 -8
  83. package/dist/types/src/index.js.map +0 -1
  84. package/dist/types/src/testing/index.d.ts +0 -2
  85. package/dist/types/src/testing/index.d.ts.map +0 -1
  86. package/dist/types/src/testing/index.js +0 -5
  87. package/dist/types/src/testing/index.js.map +0 -1
  88. package/dist/types/src/testing/playwright/dx-grid-manager.d.ts +0 -24
  89. package/dist/types/src/testing/playwright/dx-grid-manager.d.ts.map +0 -1
  90. package/dist/types/src/testing/playwright/dx-grid-manager.js +0 -79
  91. package/dist/types/src/testing/playwright/dx-grid-manager.js.map +0 -1
  92. package/dist/types/src/testing/playwright/dx-grid.spec.d.ts +0 -2
  93. package/dist/types/src/testing/playwright/dx-grid.spec.d.ts.map +0 -1
  94. package/dist/types/src/testing/playwright/dx-grid.spec.js +0 -92
  95. package/dist/types/src/testing/playwright/dx-grid.spec.js.map +0 -1
  96. package/dist/types/src/types.d.ts +0 -137
  97. package/dist/types/src/types.d.ts.map +0 -1
  98. package/dist/types/src/types.js +0 -46
  99. package/dist/types/src/types.js.map +0 -1
  100. package/dist/types/src/util.d.ts +0 -39
  101. package/dist/types/src/util.d.ts.map +0 -1
  102. package/dist/types/src/util.js +0 -165
  103. package/dist/types/src/util.js.map +0 -1
  104. package/dist/types/tsconfig.tsbuildinfo +0 -1
  105. package/src/testing/playwright/playwright.config.cts +0 -18
  106. /package/dist/src/{testing/playwright → playwright}/dx-grid.spec.d.ts +0 -0
  107. /package/dist/src/testing/{playwright/dx-grid-manager.js → dx-grid-manager.js} +0 -0
package/src/dx-grid.ts CHANGED
@@ -3,23 +3,23 @@
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';
12
- // eslint-disable-next-line unused-imports/no-unused-imports
11
+ import { defaultColSize, defaultRowSize, focusUnfurlDefault } from './defs';
13
12
  import './dx-grid-axis-resize-handle';
14
13
  import {
15
- type DxGridAxisMetaProps,
16
- type DxGridAxisSizes,
17
- type DxGridPlaneCellIndex,
18
- type DxGridCellValue,
19
14
  DxAxisResize,
20
15
  type DxAxisResizeInternal,
21
16
  DxEditRequest,
17
+ type DxGridAnnotatedPanEvent,
18
+ type DxGridAxis,
22
19
  type DxGridAxisMeta,
20
+ type DxGridAxisMetaProps,
21
+ type DxGridAxisSizes,
22
+ type DxGridCellValue,
23
23
  type DxGridCells,
24
24
  DxGridCellsSelect,
25
25
  type DxGridFixedPlane,
@@ -29,36 +29,35 @@ import {
29
29
  type DxGridFrozenRowsPlane,
30
30
  type DxGridMode,
31
31
  type DxGridPlane,
32
+ type DxGridPlaneCellIndex,
32
33
  type DxGridPlaneCells,
33
34
  type DxGridPlaneRange,
34
35
  type DxGridPlaneRecord,
35
36
  type DxGridPointer,
36
37
  type DxGridPosition,
37
- type DxGridAxis,
38
- type DxGridSelectionProps,
39
- type DxGridAnnotatedPanEvent,
40
38
  type DxGridRange,
39
+ type DxGridSelectionProps,
41
40
  separator,
42
41
  } from './types';
43
42
  import {
44
- toCellIndex,
45
- gap,
46
- resizeTolerance,
47
- sizeColMin,
48
- sizeColMax,
49
- sizeRowMin,
50
- sizeRowMax,
51
- shouldSelect,
52
- selectionProps,
53
43
  cellSelected,
54
44
  closestAction,
55
45
  closestCell,
56
- targetIsPlane,
57
- resolveRowPlane,
46
+ gap,
47
+ isReadonly,
48
+ isSameCell,
49
+ resizeTolerance,
58
50
  resolveColPlane,
59
51
  resolveFrozenPlane,
60
- isSameCell,
61
- isReadonly,
52
+ resolveRowPlane,
53
+ selectionProps,
54
+ shouldSelect,
55
+ sizeColMax,
56
+ sizeColMin,
57
+ sizeRowMax,
58
+ sizeRowMin,
59
+ targetIsPlane,
60
+ toCellIndex,
62
61
  } from './util';
63
62
 
64
63
  @customElement('dx-grid')
@@ -84,12 +83,12 @@ export class DxGrid extends LitElement {
84
83
  gridId: string = 'default-grid-id';
85
84
 
86
85
  @property({ type: Object })
87
- rowDefault: DxGridPlaneRecord<DxGridFrozenRowsPlane, DxGridAxisMetaProps> = {
86
+ rowDefault: Partial<DxGridPlaneRecord<DxGridFrozenRowsPlane, Partial<DxGridAxisMetaProps>>> = {
88
87
  grid: { size: defaultRowSize },
89
88
  };
90
89
 
91
90
  @property({ type: Object })
92
- columnDefault: DxGridPlaneRecord<DxGridFrozenColsPlane, DxGridAxisMetaProps> = {
91
+ columnDefault: Partial<DxGridPlaneRecord<DxGridFrozenColsPlane, Partial<DxGridAxisMetaProps>>> = {
93
92
  grid: { size: defaultColSize },
94
93
  };
95
94
 
@@ -213,6 +212,16 @@ export class DxGrid extends LitElement {
213
212
  @state()
214
213
  private templatefrozenRowsEnd = '';
215
214
 
215
+ //
216
+ // `frozen…Size` is used to measure space available for the non-fixed planes
217
+ //
218
+
219
+ @state()
220
+ private frozenColsSize = 0;
221
+
222
+ @state()
223
+ private frozenRowsSize = 0;
224
+
216
225
  //
217
226
  // Focus, selection, and resize states
218
227
  //
@@ -481,6 +490,13 @@ export class DxGrid extends LitElement {
481
490
  event.preventDefault();
482
491
  this.dispatchEditRequest();
483
492
  break;
493
+ case 'Backspace':
494
+ case 'Delete':
495
+ if (!event.defaultPrevented) {
496
+ event.preventDefault();
497
+ this.dispatchEditRequest('');
498
+ }
499
+ break;
484
500
  default:
485
501
  if (event.key.length === 1 && event.key.match(/\P{Cc}/u) && !(event.metaKey || event.ctrlKey)) {
486
502
  this.dispatchEditRequest(event.key);
@@ -584,19 +600,18 @@ export class DxGrid extends LitElement {
584
600
  blockSize: 0,
585
601
  };
586
602
  if (
587
- Math.abs(inlineSize - this.sizeInline) > resizeTolerance ||
588
- Math.abs(blockSize - this.sizeBlock) > resizeTolerance
603
+ Math.abs(inlineSize - this.frozenColsSize - this.sizeInline) > resizeTolerance ||
604
+ Math.abs(blockSize - this.frozenRowsSize - this.sizeBlock) > resizeTolerance
589
605
  ) {
590
606
  // console.info('[updating bounds]', 'resize', [inlineSize - this.sizeInline, blockSize - this.sizeBlock]);
591
- this.sizeInline = inlineSize;
592
- this.sizeBlock = blockSize;
607
+ this.sizeInline = inlineSize - this.frozenColsSize;
608
+ this.sizeBlock = blockSize - this.frozenRowsSize;
593
609
  this.updateVis();
594
610
  queueMicrotask(() => this.updatePos());
595
611
  }
596
612
  });
597
613
 
598
614
  private gridRef: Ref<HTMLDivElement> = createRef();
599
- private viewportRef: Ref<HTMLDivElement> = createRef();
600
615
 
601
616
  private maybeUpdateVisInline = () => {
602
617
  if (this.posInline < this.binInlineMin || this.posInline >= this.binInlineMax) {
@@ -640,16 +655,29 @@ export class DxGrid extends LitElement {
640
655
  (this.overscroll === 'inline' && event.overscrollInline === 0) ||
641
656
  (this.overscroll === 'block' && event.overscrollBlock === 0)
642
657
  ) {
643
- event.preventDefault();
644
- event.stopPropagation();
658
+ const element = event.target as HTMLElement;
659
+ const activeCell = element.closest('[data-dx-active]');
660
+ const contentEl = element.closest('.dx-grid__cell__content');
661
+ if (
662
+ !(
663
+ element &&
664
+ activeCell &&
665
+ contentEl &&
666
+ (contentEl.scrollWidth > contentEl.clientWidth || contentEl.scrollHeight > contentEl.clientHeight)
667
+ )
668
+ ) {
669
+ event.preventDefault();
670
+ event.stopPropagation();
671
+ }
645
672
  }
646
673
  }
647
674
  };
648
675
 
649
676
  private handleWheel = (event: DxGridAnnotatedPanEvent) => {
650
677
  if (this.mode === 'browse') {
651
- const nextPosInline = this.posInline + event.deltaX;
652
- const nextPosBlock = this.posBlock + event.deltaY;
678
+ const { deltaX, deltaY } = this.getOverflowingCellModifiedDeltas(event);
679
+ const nextPosInline = this.posInline + deltaX;
680
+ const nextPosBlock = this.posBlock + deltaY;
653
681
  const maxPosInline = this.maxPosInline();
654
682
  const maxPosBlock = this.maxPosBlock();
655
683
  this.updatePos(nextPosInline, nextPosBlock, maxPosInline, maxPosBlock);
@@ -708,6 +736,15 @@ export class DxGrid extends LitElement {
708
736
  this.templatefrozenColsEnd = [...Array(this.frozen.frozenColsEnd ?? 0)]
709
737
  .map((_, c0) => `${this.colSize(c0, 'frozenColsEnd')}px`)
710
738
  .join(' ');
739
+
740
+ this.frozenColsSize =
741
+ [...Array(this.frozen.frozenColsStart ?? 0)].reduce(
742
+ (sum, _, c0) => sum + this.colSize(c0, 'frozenColsStart'),
743
+ 0,
744
+ ) +
745
+ gap * Math.max(0, this.frozen.frozenColsStart ?? 0 - 1) +
746
+ [...Array(this.frozen.frozenColsEnd ?? 0)].reduce((sum, _, c0) => sum + this.colSize(c0, 'frozenColsEnd'), 0) +
747
+ gap * Math.max(0, this.frozen.frozenColsEnd ?? 0 - 1);
711
748
  }
712
749
 
713
750
  private updateVisBlock(): void {
@@ -757,6 +794,15 @@ export class DxGrid extends LitElement {
757
794
  this.templatefrozenRowsEnd = [...Array(this.frozen.frozenRowsEnd ?? 0)]
758
795
  .map((_, r0) => `${this.rowSize(r0, 'frozenRowsEnd')}px`)
759
796
  .join(' ');
797
+
798
+ this.frozenRowsSize =
799
+ [...Array(this.frozen.frozenRowsStart ?? 0)].reduce(
800
+ (sum, _, r0) => sum + this.rowSize(r0, 'frozenRowsStart'),
801
+ 0,
802
+ ) +
803
+ gap * Math.max(0, this.frozen.frozenRowsStart ?? 0 - 1) +
804
+ [...Array(this.frozen.frozenRowsEnd ?? 0)].reduce((sum, _, r0) => sum + this.rowSize(r0, 'frozenRowsEnd'), 0) +
805
+ gap * Math.max(0, this.frozen.frozenRowsEnd ?? 0 - 1);
760
806
  }
761
807
 
762
808
  private updateVis(): void {
@@ -1055,16 +1101,40 @@ export class DxGrid extends LitElement {
1055
1101
  : !!(this.rows[plane]?.[index]?.resizeable ?? this.rowDefault[plane as DxGridFrozenRowsPlane]?.resizeable);
1056
1102
  }
1057
1103
 
1104
+ private clampAxisSize(
1105
+ plane: 'grid' | DxGridFrozenPlane,
1106
+ axis: DxGridAxis,
1107
+ index: number | string,
1108
+ requestedSize: number,
1109
+ ): number {
1110
+ const minSize =
1111
+ axis === 'col'
1112
+ ? (this.columns[plane]?.[index]?.minSize ??
1113
+ this.columnDefault[plane as DxGridFrozenColsPlane]?.minSize ??
1114
+ sizeColMin)
1115
+ : (this.rows[plane]?.[index]?.minSize ??
1116
+ this.rowDefault[plane as DxGridFrozenRowsPlane]?.minSize ??
1117
+ sizeRowMin);
1118
+ const maxSize =
1119
+ axis === 'col'
1120
+ ? (this.columns[plane]?.[index]?.maxSize ??
1121
+ this.columnDefault[plane as DxGridFrozenColsPlane]?.maxSize ??
1122
+ sizeColMax)
1123
+ : (this.rows[plane]?.[index]?.maxSize ??
1124
+ this.rowDefault[plane as DxGridFrozenRowsPlane]?.maxSize ??
1125
+ sizeRowMax);
1126
+ return Math.max(minSize, Math.min(maxSize, requestedSize));
1127
+ }
1128
+
1058
1129
  private handleAxisResizeInternal(event: DxAxisResizeInternal): void {
1059
1130
  event.stopPropagation();
1060
1131
  const { plane, axis, delta, size, index, state } = event;
1132
+ const nextSize = this.clampAxisSize(plane, axis, index, size + delta);
1061
1133
  if (axis === 'col') {
1062
- const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, size + delta));
1063
1134
  this.colSizes = { ...this.colSizes, [plane]: { ...this.colSizes[plane], [index]: nextSize } };
1064
1135
  this.updateVisInline();
1065
1136
  this.updateIntrinsicInlineSize();
1066
1137
  } else {
1067
- const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, size + delta));
1068
1138
  this.rowSizes = { ...this.colSizes, [plane]: { ...this.rowSizes[plane], [index]: nextSize } };
1069
1139
  this.updateVisBlock();
1070
1140
  this.updateIntrinsicBlockSize();
@@ -1165,7 +1235,7 @@ export class DxGrid extends LitElement {
1165
1235
  ) {
1166
1236
  const rowPlane = resolveRowPlane(plane) as DxGridFrozenPlane;
1167
1237
  const rows = this.frozen[rowPlane];
1168
- return (rows ?? 0) > 0
1238
+ return (rows ?? 0) > 0 && this.limitColumns > 0
1169
1239
  ? html`<div
1170
1240
  role="none"
1171
1241
  class="dx-grid__plane--frozen-row"
@@ -1199,7 +1269,7 @@ export class DxGrid extends LitElement {
1199
1269
  ) {
1200
1270
  const colPlane = resolveColPlane(plane) as DxGridFrozenPlane;
1201
1271
  const cols = this.frozen[colPlane];
1202
- return (cols ?? 0) > 0
1272
+ return (cols ?? 0) > 0 && this.limitRows > 0
1203
1273
  ? html`<div
1204
1274
  role="none"
1205
1275
  class="dx-grid__plane--frozen-col"
@@ -1225,6 +1295,40 @@ export class DxGrid extends LitElement {
1225
1295
  : null;
1226
1296
  }
1227
1297
 
1298
+ private renderMainGrid(
1299
+ visibleCols: number,
1300
+ visibleRows: number,
1301
+ offsetInline: number,
1302
+ offsetBlock: number,
1303
+ selection: DxGridSelectionProps,
1304
+ ) {
1305
+ return this.limitRows > 0 && this.limitColumns > 0
1306
+ ? html`<div
1307
+ role="grid"
1308
+ class="dx-grid__plane--grid"
1309
+ tabindex="0"
1310
+ data-dx-grid-plane="grid"
1311
+ data-dx-grid-plane-row="1"
1312
+ data-dx-grid-plane-col="1"
1313
+ >
1314
+ <div
1315
+ role="none"
1316
+ class="dx-grid__plane--grid__content"
1317
+ style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
1318
+ .templateGridColumns};grid-template-rows:${this.templateGridRows};"
1319
+ >
1320
+ ${[...Array(visibleRows)].map((_, r0) => {
1321
+ return [...Array(visibleCols)].map((_, c0) => {
1322
+ const c = c0 + this.visColMin;
1323
+ const r = r0 + this.visRowMin;
1324
+ return this.renderCell(c, r, 'grid', cellSelected(c, r, 'grid', selection), c0, r0);
1325
+ });
1326
+ })}
1327
+ </div>
1328
+ </div>`
1329
+ : null;
1330
+ }
1331
+
1228
1332
  private cellReadonly(col: number, row: number, plane: DxGridPlane): boolean {
1229
1333
  const colPlane = resolveColPlane(plane);
1230
1334
  const rowPlane = resolveRowPlane(plane);
@@ -1242,6 +1346,23 @@ export class DxGrid extends LitElement {
1242
1346
  return isReadonly(colReadOnly) || isReadonly(rowReadOnly);
1243
1347
  }
1244
1348
 
1349
+ private cellFocusUnfurl(col: number, row: number, plane: DxGridPlane): boolean {
1350
+ const colPlane = resolveColPlane(plane);
1351
+ const rowPlane = resolveRowPlane(plane);
1352
+
1353
+ // Check cell-specific setting first.
1354
+ const cellUnfurl = this.cell(col, row, plane)?.focusUnfurl;
1355
+ if (cellUnfurl !== undefined) {
1356
+ return cellUnfurl;
1357
+ }
1358
+
1359
+ // Check column/row defaults.
1360
+ const colUnfurl = this.columns?.[colPlane]?.[col]?.focusUnfurl ?? this.columnDefault?.[colPlane]?.focusUnfurl;
1361
+ const rowUnfurl = this.rows?.[rowPlane]?.[row]?.focusUnfurl ?? this.rowDefault?.[rowPlane]?.focusUnfurl;
1362
+
1363
+ return colUnfurl ?? rowUnfurl ?? focusUnfurlDefault;
1364
+ }
1365
+
1245
1366
  /**
1246
1367
  * Determines if the cell's text content should be selectable based on its readonly value.
1247
1368
  * @returns true if the cells text content is selectable, false otherwise.
@@ -1262,10 +1383,49 @@ export class DxGrid extends LitElement {
1262
1383
  return colReadonly === 'text-select' || rowReadonly === 'text-select';
1263
1384
  }
1264
1385
 
1386
+ private getOverflowingCellModifiedDeltas(
1387
+ event: DxGridAnnotatedPanEvent,
1388
+ ): Pick<DxGridAnnotatedPanEvent, 'deltaX' | 'deltaY'> {
1389
+ if (!event.target) {
1390
+ return event;
1391
+ }
1392
+ const element = event.target as HTMLElement;
1393
+ const activeCell = element.closest('[data-dx-active]');
1394
+ const contentEl = element.closest('.dx-grid__cell__content');
1395
+
1396
+ if (!activeCell || !contentEl || !document.activeElement?.contains(element)) {
1397
+ return event;
1398
+ }
1399
+
1400
+ // Commented-out code will let the event delta through unmodified if the cell can scroll but is scrolled to the end
1401
+ // in the same direction as the wheel event, a.k.a. “overscroll”; this is probably undesirable, though.
1402
+
1403
+ const { scrollWidth, clientWidth, scrollHeight, clientHeight /*, scrollLeft, scrollTop */ } = contentEl;
1404
+
1405
+ if (scrollWidth <= clientWidth && scrollHeight <= clientHeight) {
1406
+ return event;
1407
+ }
1408
+
1409
+ const deltaX =
1410
+ scrollWidth > clientWidth /* &&
1411
+ ((event.deltaX < 0 && scrollLeft > 0) || (event.deltaX > 0 && scrollLeft < scrollWidth - clientWidth)) */
1412
+ ? 0
1413
+ : event.deltaX;
1414
+
1415
+ const deltaY =
1416
+ scrollHeight > clientHeight /* &&
1417
+ ((event.deltaY < 0 && scrollTop > 0) || (event.deltaY > 0 && scrollTop < scrollHeight - clientHeight)) */
1418
+ ? 0
1419
+ : event.deltaY;
1420
+
1421
+ return { deltaX, deltaY };
1422
+ }
1423
+
1265
1424
  private renderCell(col: number, row: number, plane: DxGridPlane, selected?: boolean, visCol = col, visRow = row) {
1266
1425
  const cell = this.cell(col, row, plane);
1267
1426
  const active = this.cellActive(col, row, plane);
1268
1427
  const readonly = this.cellReadonly(col, row, plane);
1428
+ const focusUnfurl = this.cellFocusUnfurl(col, row, plane);
1269
1429
  const textSelectable = this.cellTextSelectable(col, row, plane);
1270
1430
  const resizeIndex = cell?.resizeHandle ? (cell.resizeHandle === 'col' ? col : row) : undefined;
1271
1431
  const resizePlane = cell?.resizeHandle ? resolveFrozenPlane(cell.resizeHandle, plane) : undefined;
@@ -1277,6 +1437,7 @@ export class DxGrid extends LitElement {
1277
1437
  aria-readonly=${readonly ? 'true' : nothing}
1278
1438
  class=${cell?.className ?? nothing}
1279
1439
  data-refs=${cell?.dataRefs ?? nothing}
1440
+ data-focus-unfurl=${focusUnfurl ? nothing : 'false'}
1280
1441
  ?data-dx-active=${active}
1281
1442
  data-text-selectable=${textSelectable ? 'true' : 'false'}
1282
1443
  data-dx-grid-action="cell"
@@ -1284,9 +1445,8 @@ export class DxGrid extends LitElement {
1284
1445
  aria-rowindex=${row}
1285
1446
  style="grid-column:${visCol + 1};grid-row:${visRow + 1}"
1286
1447
  >
1287
- ${this.mode !== 'browse' && active ? null : cell?.value}${this.mode !== 'browse' && active
1288
- ? null
1289
- : accessory}${cell?.resizeHandle &&
1448
+ <div role="none" class="dx-grid__cell__content">${cell?.value}${accessory}</div>
1449
+ ${cell?.resizeHandle &&
1290
1450
  this.mode === 'browse' &&
1291
1451
  this.axisResizeable(resizePlane!, cell.resizeHandle, resizeIndex!)
1292
1452
  ? html`<dx-grid-axis-resize-handle
@@ -1320,13 +1480,24 @@ export class DxGrid extends LitElement {
1320
1480
  <div
1321
1481
  role="none"
1322
1482
  class="dx-grid"
1483
+ data-arrow-keys="all"
1323
1484
  style=${styleMap({
1324
- 'grid-template-columns': `${this.templatefrozenColsStart ? 'min-content ' : ''}minmax(0, ${
1325
- Number.isFinite(this.limitColumns) ? `${Math.max(0, this.intrinsicInlineSize)}px` : '1fr'
1326
- })${this.templatefrozenColsEnd ? ' min-content' : ''}`,
1327
- 'grid-template-rows': `${this.templatefrozenRowsStart ? 'min-content ' : ''}minmax(0, ${
1328
- Number.isFinite(this.limitRows) ? `${Math.max(0, this.intrinsicBlockSize)}px` : '1fr'
1329
- })${this.templatefrozenRowsEnd ? ' min-content' : ''}`,
1485
+ 'grid-template-columns': [
1486
+ this.templatefrozenColsStart ? 'min-content' : false,
1487
+ this.limitColumns > 0 &&
1488
+ `minmax(0, ${Number.isFinite(this.limitColumns) ? `${Math.max(0, this.intrinsicInlineSize)}px` : '1fr'})`,
1489
+ this.templatefrozenColsEnd ? 'min-content' : false,
1490
+ ]
1491
+ .filter(Boolean)
1492
+ .join(' '),
1493
+ 'grid-template-rows': [
1494
+ this.templatefrozenRowsStart ? 'min-content' : false,
1495
+ this.limitRows > 0 &&
1496
+ `minmax(0, ${Number.isFinite(this.limitRows) ? `${Math.max(0, this.intrinsicBlockSize)}px` : '1fr'})`,
1497
+ this.templatefrozenRowsEnd ? ' min-content' : false,
1498
+ ]
1499
+ .filter(Boolean)
1500
+ .join(' '),
1330
1501
  '--dx-grid-content-inline-size': Number.isFinite(this.limitColumns)
1331
1502
  ? `${Math.max(0, this.totalIntrinsicInlineSize)}px`
1332
1503
  : 'max-content',
@@ -1350,30 +1521,7 @@ export class DxGrid extends LitElement {
1350
1521
  offsetBlock,
1351
1522
  selection,
1352
1523
  )}
1353
- <div
1354
- role="grid"
1355
- class="dx-grid__plane--grid"
1356
- tabindex="0"
1357
- data-dx-grid-plane="grid"
1358
- data-dx-grid-plane-row="1"
1359
- data-dx-grid-plane-col="1"
1360
- ${ref(this.viewportRef)}
1361
- >
1362
- <div
1363
- role="none"
1364
- class="dx-grid__plane--grid__content"
1365
- style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
1366
- .templateGridColumns};grid-template-rows:${this.templateGridRows};"
1367
- >
1368
- ${[...Array(visibleRows)].map((_, r0) => {
1369
- return [...Array(visibleCols)].map((_, c0) => {
1370
- const c = c0 + this.visColMin;
1371
- const r = r0 + this.visRowMin;
1372
- return this.renderCell(c, r, 'grid', cellSelected(c, r, 'grid', selection), c0, r0);
1373
- });
1374
- })}
1375
- </div>
1376
- </div>
1524
+ ${this.renderMainGrid(visibleCols, visibleRows, offsetInline, offsetBlock, selection)}
1377
1525
  ${this.renderFrozenColumns('frozenColsEnd', visibleRows, offsetBlock, selection)}${this.renderFixed(
1378
1526
  'fixedEndStart',
1379
1527
  selection,
@@ -1387,37 +1535,19 @@ export class DxGrid extends LitElement {
1387
1535
  private updateIntrinsicInlineSize(): void {
1388
1536
  this.intrinsicInlineSize = Number.isFinite(this.limitColumns)
1389
1537
  ? [...Array(this.limitColumns)].reduce((acc, _, c0) => acc + this.colSize(c0, 'grid'), 0) +
1390
- gap * (this.limitColumns - 1)
1538
+ gap * Math.max(0, this.limitColumns - 1)
1391
1539
  : Infinity;
1392
1540
  this.totalIntrinsicInlineSize =
1393
- this.intrinsicInlineSize +
1394
- (Number.isFinite(this.frozen.frozenColsStart)
1395
- ? [...Array(this.frozen.frozenColsStart)].reduce(
1396
- (acc, _, c0) => acc + gap + this.colSize(c0, 'frozenColsStart'),
1397
- 0,
1398
- )
1399
- : 0) +
1400
- (Number.isFinite(this.frozen.frozenColsEnd)
1401
- ? [...Array(this.frozen.frozenColsEnd)].reduce((acc, _, c0) => acc + gap + this.colSize(c0, 'frozenColsEnd'), 0)
1402
- : 0);
1541
+ this.limitColumns > 0 ? this.intrinsicInlineSize + this.frozenColsSize : this.frozenColsSize - gap;
1403
1542
  }
1404
1543
 
1405
1544
  private updateIntrinsicBlockSize(): void {
1406
1545
  this.intrinsicBlockSize = Number.isFinite(this.limitRows)
1407
1546
  ? [...Array(this.limitRows)].reduce((acc, _, r0) => acc + this.rowSize(r0, 'grid'), 0) +
1408
- gap * (this.limitRows - 1)
1547
+ gap * Math.max(0, this.limitRows - 1)
1409
1548
  : Infinity;
1410
1549
  this.totalIntrinsicBlockSize =
1411
- this.intrinsicBlockSize +
1412
- (Number.isFinite(this.frozen.frozenRowsStart)
1413
- ? [...Array(this.frozen.frozenRowsStart)].reduce(
1414
- (acc, _, r0) => acc + gap + this.rowSize(r0, 'frozenRowsStart'),
1415
- 0,
1416
- )
1417
- : 0) +
1418
- (Number.isFinite(this.frozen.frozenRowsEnd)
1419
- ? [...Array(this.frozen.frozenRowsEnd)].reduce((acc, _, r0) => acc + gap + this.rowSize(r0, 'frozenRowsEnd'), 0)
1420
- : 0);
1550
+ this.limitRows > 0 ? this.intrinsicBlockSize + this.frozenRowsSize : this.frozenRowsSize - gap;
1421
1551
  }
1422
1552
 
1423
1553
  private updateIntrinsicSizes(): void {
@@ -1465,7 +1595,7 @@ export class DxGrid extends LitElement {
1465
1595
  if (this.getCells) {
1466
1596
  this.updateCells(true);
1467
1597
  }
1468
- this.observer.observe(this.viewportRef.value!);
1598
+ this.observer.observe(this.gridRef.value!);
1469
1599
  this.computeColSizes();
1470
1600
  this.computeRowSizes();
1471
1601
  this.updateIntrinsicSizes();
@@ -1490,6 +1620,13 @@ export class DxGrid extends LitElement {
1490
1620
  this.updateVisInline();
1491
1621
  }
1492
1622
 
1623
+ if (changedProperties.has('frozen')) {
1624
+ this.updateIntrinsicBlockSize();
1625
+ this.updateIntrinsicInlineSize();
1626
+ this.updateVisBlock();
1627
+ this.updateVisInline();
1628
+ }
1629
+
1493
1630
  if (
1494
1631
  this.getCells &&
1495
1632
  (changedProperties.has('initialCells') ||
@@ -1500,7 +1637,8 @@ export class DxGrid extends LitElement {
1500
1637
  changedProperties.has('columns') ||
1501
1638
  changedProperties.has('rows') ||
1502
1639
  changedProperties.has('limitColumns') ||
1503
- changedProperties.has('limitRows'))
1640
+ changedProperties.has('limitRows') ||
1641
+ changedProperties.has('frozen'))
1504
1642
  ) {
1505
1643
  this.updateCells(true);
1506
1644
  }
@@ -1520,8 +1658,9 @@ export class DxGrid extends LitElement {
1520
1658
  }
1521
1659
  }
1522
1660
 
1523
- public updateIfWithinBounds({ col, row }: { col: number; row: number }): boolean {
1661
+ public updateIfWithinBounds({ col, row }: { col: number; row: number }, includeFixed?: boolean): boolean {
1524
1662
  if (col >= this.visColMin && col <= this.visColMax && row >= this.visRowMin && row <= this.visRowMax) {
1663
+ this.updateCells(includeFixed);
1525
1664
  this.requestUpdate();
1526
1665
  return true;
1527
1666
  }
@@ -1530,8 +1669,8 @@ export class DxGrid extends LitElement {
1530
1669
 
1531
1670
  override disconnectedCallback(): void {
1532
1671
  super.disconnectedCallback();
1533
- if (this.viewportRef.value) {
1534
- this.observer.unobserve(this.viewportRef.value);
1672
+ if (this.gridRef.value) {
1673
+ this.observer.unobserve(this.gridRef.value);
1535
1674
  }
1536
1675
  document.defaultView?.removeEventListener('wheel', this.handleTopLevelWheel);
1537
1676
  }
@@ -1549,6 +1688,7 @@ export {
1549
1688
  parseCellIndex,
1550
1689
  toPlaneCellIndex,
1551
1690
  cellQuery,
1691
+ accessoryHandlesPointerdownAttrs,
1552
1692
  } from './util';
1553
1693
 
1554
1694
  export const commentedClassName = 'dx-grid__cell--commented';
@@ -2,13 +2,13 @@
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
 
9
- import { DxGridManager } from './dx-grid-manager';
10
- import { type DxGridCellsSelect } from '../../types';
11
- import { toPlaneCellIndex } from '../../util';
9
+ import { DxGridManager } from '../testing';
10
+ import { type DxGridCellsSelect } from '../types';
11
+ import { toPlaneCellIndex } from '../util';
12
12
 
13
13
  const gridPlaneCellSize = 31;
14
14
  const gap = 1;
@@ -21,7 +21,7 @@ test.describe('dx-grid', () => {
21
21
 
22
22
  test.beforeEach(async ({ browser }) => {
23
23
  const setup = await setupPage(browser, {
24
- url: storybookUrl('dx-grid--spec'),
24
+ url: storybookUrl('dx-grid--spec', 9002),
25
25
  viewportSize: {
26
26
  width: (gridPlaneCellSize + gap) * (nCols + 1.5),
27
27
  height: (gridPlaneCellSize + gap) * (nRows + 1.5),
@@ -0,0 +1,17 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { defineConfig } from '@playwright/test';
6
+
7
+ import { e2ePreset } from '@dxos/test-utils/playwright';
8
+
9
+ export default defineConfig({
10
+ ...e2ePreset(import.meta.dirname),
11
+ // TODO(wittjosiah): Avoid hard-coding ports.
12
+ webServer: {
13
+ command: 'moon run lit-storybook:serve-e2e -- --port=9002',
14
+ port: 9002,
15
+ reuseExistingServer: false,
16
+ },
17
+ });
@@ -2,9 +2,9 @@
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
- import type { DxGridPlanePosition } from '../../types';
7
+ import type { DxGridPlanePosition } from '../types';
8
8
 
9
9
  /**
10
10
  * Test helper for managing dx-grid interactions and assertions in Playwright tests.
@@ -2,4 +2,4 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export { DxGridManager } from './playwright/dx-grid-manager';
5
+ export { DxGridManager } from './dx-grid-manager';
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>>;