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

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 +11 -6
  12. package/dist/src/dx-grid.d.ts.map +1 -1
  13. package/dist/src/dx-grid.js +186 -153
  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 +179 -100
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/lit-grid",
3
- "version": "0.8.4-main.67995b8",
3
+ "version": "0.8.4-main.a4bbb77",
4
4
  "description": "A grid Web Component using Lit",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -36,10 +36,10 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
39
- "lit": "^3.2.0"
39
+ "lit": "^3.3.1"
40
40
  },
41
41
  "devDependencies": {
42
- "@dxos/random": "0.8.4-main.67995b8",
43
- "@dxos/test-utils": "0.8.4-main.67995b8"
42
+ "@dxos/random": "0.8.4-main.a4bbb77",
43
+ "@dxos/test-utils": "0.8.4-main.a4bbb77"
44
44
  }
45
45
  }
package/src/defs.ts CHANGED
@@ -4,3 +4,4 @@
4
4
 
5
5
  export const defaultColSize = 180;
6
6
  export const defaultRowSize = 30;
7
+ export const focusUnfurlDefault = true;
@@ -21,4 +21,10 @@ dx-grid-axis-resize-handle {
21
21
  block-size: .5rem;
22
22
  cursor: row-resize;
23
23
  }
24
+ &:focus {
25
+ outline: none;
26
+ }
27
+ &:focus-visible {
28
+ background: var(--dx-grid-resizeHandleFocus, var(--dx-accentSurface));
29
+ }
24
30
  }
@@ -6,7 +6,7 @@ import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
6
  import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
7
7
  import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
8
8
  import { type CleanupFn, type DragLocationHistory } from '@atlaskit/pragmatic-drag-and-drop/types';
9
- import { html, LitElement } from 'lit';
9
+ import { LitElement, html } from 'lit';
10
10
  import { customElement, property } from 'lit/decorators.js';
11
11
  import { ref } from 'lit/directives/ref.js';
12
12
 
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { html, LitElement } from 'lit';
5
+ import { LitElement, html } from 'lit';
6
6
  import { customElement, property } from 'lit/decorators.js';
7
7
 
8
8
  export type DxGridSelectValue = {
@@ -31,6 +31,7 @@ export class DxGridMultiselectCell extends LitElement {
31
31
  aria-haspopup="dialog"
32
32
  class="dx-grid__cell__multiselect"
33
33
  data-dx-grid-accessory="invoke-multiselect"
34
+ data-dx-grid-action="accessory"
34
35
  >
35
36
  ${this.values.length > 0
36
37
  ? this.values.map(({ label }) => html`<span class="dx-grid__cell__multiselect__value">${label}</span>`)
package/src/dx-grid.pcss CHANGED
@@ -4,10 +4,10 @@
4
4
  @layer dx-tokens {
5
5
  :root {
6
6
  --dx-grid-cell-padding-block: 0.2rem;
7
- --dx-grid-cell-content-padding-block: calc(var(--dx-grid-cell-padding-block) - 1px);
7
+ --dx-grid-cell-content-padding-block: calc(var(--dx-grid-cell-padding-block) - var(--dx-gridFocusIndicatorWidth));
8
8
  --dx-grid-cell-editor-padding-block: var(--dx-grid-cell-content-padding-block);
9
9
  --dx-grid-cell-padding-inline: 0.25rem;
10
- --dx-grid-cell-content-padding-inline: calc(var(--dx-grid-cell-padding-inline) - 1px);
10
+ --dx-grid-cell-content-padding-inline: calc(var(--dx-grid-cell-padding-inline) - var(--dx-gridFocusIndicatorWidth));
11
11
  --dx-grid-cell-editor-padding-inline: var(--dx-grid-cell-content-padding-inline);
12
12
  }
13
13
  }
@@ -54,7 +54,7 @@
54
54
  position: absolute;
55
55
  inset: 0;
56
56
  pointer-events: none;
57
- border: 1px solid var(--dx-accentSurface);
57
+ border: var(--dx-gridFocusIndicatorWidth) solid var(--dx-gridFocusIndicatorColor);
58
58
  }
59
59
  }
60
60
 
@@ -98,7 +98,7 @@
98
98
  white-space: nowrap;
99
99
  block-size: 100%;
100
100
  position: relative;
101
- border: 1px solid transparent;
101
+ border: var(--dx-gridFocusIndicatorWidth) solid transparent;
102
102
  border-radius: 2px;
103
103
 
104
104
  &:has(.dx-tag), &:has(dx-tag-picker-item) {
@@ -130,6 +130,18 @@
130
130
  }
131
131
  }
132
132
  }
133
+
134
+ .dx-grid__row--cta__cell {
135
+ transition: background-color 100ms linear;
136
+ & > .dx-grid__cell__content {
137
+ cursor: pointer;
138
+ background: transparent !important;
139
+ }
140
+ }
141
+
142
+ &:has(.dx-grid__row--cta__cell:hover) .dx-grid__row--cta__cell {
143
+ background: var(--dx-hoverOverlay);
144
+ }
133
145
  }
134
146
 
135
147
  /* Editor and focused cell styles; be sure to keep these two blocks in-sync. */
@@ -137,7 +149,7 @@
137
149
  [role='gridcell'],
138
150
  [role='columnheader'],
139
151
  [role='rowheader'] {
140
- &:not(.dx-grid__cell--no-focus-unfurl){
152
+ &:not([data-focus-unfurl="false"]):not(.dx-grid__cell--no-focus-unfurl) {
141
153
  &:focus,
142
154
  &:focus-within {
143
155
  & > .dx-grid__cell__content {
@@ -163,13 +175,13 @@
163
175
  }
164
176
 
165
177
  &:not([aria-readonly='true']) > .dx-grid__cell__content {
166
- border-color: var(--dx-accentSurface);
178
+ border-color: var(--dx-gridFocusIndicatorColor);
167
179
  }
168
180
  }
169
181
  }
170
182
 
171
183
  &:focus-visible > .dx-grid__cell__content {
172
- border-color: var(--dx-accentSurface);
184
+ border-color: var(--dx-gridFocusIndicatorColor);
173
185
  }
174
186
  }
175
187
  }
@@ -181,7 +193,7 @@
181
193
  overflow-wrap: break-word;
182
194
 
183
195
  background: var(--dx-grid-base, var(--dx-baseSurface));
184
- border: 1px solid var(--dx-accentSurface);
196
+ border: var(--dx-gridFocusIndicatorWidth) solid var(--dx-gridFocusIndicatorColor);
185
197
  border-radius: 2px;
186
198
 
187
199
  --dx-grid-cell-editor-max-block-size: min(12em, 50vh);
@@ -206,7 +218,7 @@
206
218
  [role='columnheader'],
207
219
  [role='rowheader'] {
208
220
  &[data-dx-active]:not([aria-readonly='true']) > .dx-grid__cell__content {
209
- border-color: var(--dx-accentSurface);
221
+ border-color: var(--dx-gridFocusIndicatorColor);
210
222
  }
211
223
  }
212
224
  }
@@ -235,6 +247,7 @@
235
247
  block-size: 0;
236
248
  border-inline-start: 0.5em solid transparent;
237
249
  border-block-start: 0.5em solid var(--dx-warningText);
250
+ z-index: 1;
238
251
  }
239
252
  }
240
253
  }
package/src/dx-grid.ts CHANGED
@@ -3,61 +3,63 @@
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')
@@ -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`.
@@ -212,6 +217,16 @@ export class DxGrid extends LitElement {
212
217
  @state()
213
218
  private templatefrozenRowsEnd = '';
214
219
 
220
+ //
221
+ // `frozen…Size` is used to measure space available for the non-fixed planes
222
+ //
223
+
224
+ @state()
225
+ private frozenColsSize = 0;
226
+
227
+ @state()
228
+ private frozenRowsSize = 0;
229
+
215
230
  //
216
231
  // Focus, selection, and resize states
217
232
  //
@@ -415,15 +430,13 @@ export class DxGrid extends LitElement {
415
430
  }
416
431
 
417
432
  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
- }
433
+ const colPlane = resolveColPlane(plane);
434
+ const rowPlane = resolveRowPlane(plane);
435
+ this.focusedCell = {
436
+ plane,
437
+ col: colPlane === 'grid' ? this.visColMin : 0,
438
+ row: rowPlane === 'grid' ? this.visRowMin : 0,
439
+ };
427
440
  this.focusedCellElement()?.focus({ preventScroll: true });
428
441
  }
429
442
 
@@ -590,19 +603,18 @@ export class DxGrid extends LitElement {
590
603
  blockSize: 0,
591
604
  };
592
605
  if (
593
- Math.abs(inlineSize - this.sizeInline) > resizeTolerance ||
594
- Math.abs(blockSize - this.sizeBlock) > resizeTolerance
606
+ Math.abs(inlineSize - this.frozenColsSize - this.sizeInline) > resizeTolerance ||
607
+ Math.abs(blockSize - this.frozenRowsSize - this.sizeBlock) > resizeTolerance
595
608
  ) {
596
609
  // console.info('[updating bounds]', 'resize', [inlineSize - this.sizeInline, blockSize - this.sizeBlock]);
597
- this.sizeInline = inlineSize;
598
- this.sizeBlock = blockSize;
610
+ this.sizeInline = inlineSize - this.frozenColsSize;
611
+ this.sizeBlock = blockSize - this.frozenRowsSize;
599
612
  this.updateVis();
600
613
  queueMicrotask(() => this.updatePos());
601
614
  }
602
615
  });
603
616
 
604
617
  private gridRef: Ref<HTMLDivElement> = createRef();
605
- private viewportRef: Ref<HTMLDivElement> = createRef();
606
618
 
607
619
  private maybeUpdateVisInline = () => {
608
620
  if (this.posInline < this.binInlineMin || this.posInline >= this.binInlineMax) {
@@ -727,6 +739,15 @@ export class DxGrid extends LitElement {
727
739
  this.templatefrozenColsEnd = [...Array(this.frozen.frozenColsEnd ?? 0)]
728
740
  .map((_, c0) => `${this.colSize(c0, 'frozenColsEnd')}px`)
729
741
  .join(' ');
742
+
743
+ this.frozenColsSize =
744
+ [...Array(this.frozen.frozenColsStart ?? 0)].reduce(
745
+ (sum, _, c0) => sum + this.colSize(c0, 'frozenColsStart'),
746
+ 0,
747
+ ) +
748
+ gap * Math.max(0, this.frozen.frozenColsStart ?? 0 - 1) +
749
+ [...Array(this.frozen.frozenColsEnd ?? 0)].reduce((sum, _, c0) => sum + this.colSize(c0, 'frozenColsEnd'), 0) +
750
+ gap * Math.max(0, this.frozen.frozenColsEnd ?? 0 - 1);
730
751
  }
731
752
 
732
753
  private updateVisBlock(): void {
@@ -776,6 +797,15 @@ export class DxGrid extends LitElement {
776
797
  this.templatefrozenRowsEnd = [...Array(this.frozen.frozenRowsEnd ?? 0)]
777
798
  .map((_, r0) => `${this.rowSize(r0, 'frozenRowsEnd')}px`)
778
799
  .join(' ');
800
+
801
+ this.frozenRowsSize =
802
+ [...Array(this.frozen.frozenRowsStart ?? 0)].reduce(
803
+ (sum, _, r0) => sum + this.rowSize(r0, 'frozenRowsStart'),
804
+ 0,
805
+ ) +
806
+ gap * Math.max(0, this.frozen.frozenRowsStart ?? 0 - 1) +
807
+ [...Array(this.frozen.frozenRowsEnd ?? 0)].reduce((sum, _, r0) => sum + this.rowSize(r0, 'frozenRowsEnd'), 0) +
808
+ gap * Math.max(0, this.frozen.frozenRowsEnd ?? 0 - 1);
779
809
  }
780
810
 
781
811
  private updateVis(): void {
@@ -1074,16 +1104,40 @@ export class DxGrid extends LitElement {
1074
1104
  : !!(this.rows[plane]?.[index]?.resizeable ?? this.rowDefault[plane as DxGridFrozenRowsPlane]?.resizeable);
1075
1105
  }
1076
1106
 
1107
+ private clampAxisSize(
1108
+ plane: 'grid' | DxGridFrozenPlane,
1109
+ axis: DxGridAxis,
1110
+ index: number | string,
1111
+ requestedSize: number,
1112
+ ): number {
1113
+ const minSize =
1114
+ axis === 'col'
1115
+ ? (this.columns[plane]?.[index]?.minSize ??
1116
+ this.columnDefault[plane as DxGridFrozenColsPlane]?.minSize ??
1117
+ sizeColMin)
1118
+ : (this.rows[plane]?.[index]?.minSize ??
1119
+ this.rowDefault[plane as DxGridFrozenRowsPlane]?.minSize ??
1120
+ sizeRowMin);
1121
+ const maxSize =
1122
+ axis === 'col'
1123
+ ? (this.columns[plane]?.[index]?.maxSize ??
1124
+ this.columnDefault[plane as DxGridFrozenColsPlane]?.maxSize ??
1125
+ sizeColMax)
1126
+ : (this.rows[plane]?.[index]?.maxSize ??
1127
+ this.rowDefault[plane as DxGridFrozenRowsPlane]?.maxSize ??
1128
+ sizeRowMax);
1129
+ return Math.max(minSize, Math.min(maxSize, requestedSize));
1130
+ }
1131
+
1077
1132
  private handleAxisResizeInternal(event: DxAxisResizeInternal): void {
1078
1133
  event.stopPropagation();
1079
1134
  const { plane, axis, delta, size, index, state } = event;
1135
+ const nextSize = this.clampAxisSize(plane, axis, index, size + delta);
1080
1136
  if (axis === 'col') {
1081
- const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, size + delta));
1082
1137
  this.colSizes = { ...this.colSizes, [plane]: { ...this.colSizes[plane], [index]: nextSize } };
1083
1138
  this.updateVisInline();
1084
1139
  this.updateIntrinsicInlineSize();
1085
1140
  } else {
1086
- const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, size + delta));
1087
1141
  this.rowSizes = { ...this.colSizes, [plane]: { ...this.rowSizes[plane], [index]: nextSize } };
1088
1142
  this.updateVisBlock();
1089
1143
  this.updateIntrinsicBlockSize();
@@ -1184,7 +1238,7 @@ export class DxGrid extends LitElement {
1184
1238
  ) {
1185
1239
  const rowPlane = resolveRowPlane(plane) as DxGridFrozenPlane;
1186
1240
  const rows = this.frozen[rowPlane];
1187
- return (rows ?? 0) > 0
1241
+ return (rows ?? 0) > 0 && this.limitColumns > 0
1188
1242
  ? html`<div
1189
1243
  role="none"
1190
1244
  class="dx-grid__plane--frozen-row"
@@ -1218,7 +1272,7 @@ export class DxGrid extends LitElement {
1218
1272
  ) {
1219
1273
  const colPlane = resolveColPlane(plane) as DxGridFrozenPlane;
1220
1274
  const cols = this.frozen[colPlane];
1221
- return (cols ?? 0) > 0
1275
+ return (cols ?? 0) > 0 && this.limitRows > 0
1222
1276
  ? html`<div
1223
1277
  role="none"
1224
1278
  class="dx-grid__plane--frozen-col"
@@ -1244,6 +1298,40 @@ export class DxGrid extends LitElement {
1244
1298
  : null;
1245
1299
  }
1246
1300
 
1301
+ private renderMainGrid(
1302
+ visibleCols: number,
1303
+ visibleRows: number,
1304
+ offsetInline: number,
1305
+ offsetBlock: number,
1306
+ selection: DxGridSelectionProps,
1307
+ ) {
1308
+ return this.limitRows > 0 && this.limitColumns > 0
1309
+ ? html`<div
1310
+ role="grid"
1311
+ class="dx-grid__plane--grid"
1312
+ tabindex="0"
1313
+ data-dx-grid-plane="grid"
1314
+ data-dx-grid-plane-row="1"
1315
+ data-dx-grid-plane-col="1"
1316
+ >
1317
+ <div
1318
+ role="none"
1319
+ class="dx-grid__plane--grid__content"
1320
+ style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
1321
+ .templateGridColumns};grid-template-rows:${this.templateGridRows};"
1322
+ >
1323
+ ${[...Array(visibleRows)].map((_, r0) => {
1324
+ return [...Array(visibleCols)].map((_, c0) => {
1325
+ const c = c0 + this.visColMin;
1326
+ const r = r0 + this.visRowMin;
1327
+ return this.renderCell(c, r, 'grid', cellSelected(c, r, 'grid', selection), c0, r0);
1328
+ });
1329
+ })}
1330
+ </div>
1331
+ </div>`
1332
+ : null;
1333
+ }
1334
+
1247
1335
  private cellReadonly(col: number, row: number, plane: DxGridPlane): boolean {
1248
1336
  const colPlane = resolveColPlane(plane);
1249
1337
  const rowPlane = resolveRowPlane(plane);
@@ -1261,6 +1349,23 @@ export class DxGrid extends LitElement {
1261
1349
  return isReadonly(colReadOnly) || isReadonly(rowReadOnly);
1262
1350
  }
1263
1351
 
1352
+ private cellFocusUnfurl(col: number, row: number, plane: DxGridPlane): boolean {
1353
+ const colPlane = resolveColPlane(plane);
1354
+ const rowPlane = resolveRowPlane(plane);
1355
+
1356
+ // Check cell-specific setting first.
1357
+ const cellUnfurl = this.cell(col, row, plane)?.focusUnfurl;
1358
+ if (cellUnfurl !== undefined) {
1359
+ return cellUnfurl;
1360
+ }
1361
+
1362
+ // Check column/row defaults.
1363
+ const colUnfurl = this.columns?.[colPlane]?.[col]?.focusUnfurl ?? this.columnDefault?.[colPlane]?.focusUnfurl;
1364
+ const rowUnfurl = this.rows?.[rowPlane]?.[row]?.focusUnfurl ?? this.rowDefault?.[rowPlane]?.focusUnfurl;
1365
+
1366
+ return colUnfurl ?? rowUnfurl ?? focusUnfurlDefault;
1367
+ }
1368
+
1264
1369
  /**
1265
1370
  * Determines if the cell's text content should be selectable based on its readonly value.
1266
1371
  * @returns true if the cells text content is selectable, false otherwise.
@@ -1323,6 +1428,7 @@ export class DxGrid extends LitElement {
1323
1428
  const cell = this.cell(col, row, plane);
1324
1429
  const active = this.cellActive(col, row, plane);
1325
1430
  const readonly = this.cellReadonly(col, row, plane);
1431
+ const focusUnfurl = this.cellFocusUnfurl(col, row, plane);
1326
1432
  const textSelectable = this.cellTextSelectable(col, row, plane);
1327
1433
  const resizeIndex = cell?.resizeHandle ? (cell.resizeHandle === 'col' ? col : row) : undefined;
1328
1434
  const resizePlane = cell?.resizeHandle ? resolveFrozenPlane(cell.resizeHandle, plane) : undefined;
@@ -1334,6 +1440,7 @@ export class DxGrid extends LitElement {
1334
1440
  aria-readonly=${readonly ? 'true' : nothing}
1335
1441
  class=${cell?.className ?? nothing}
1336
1442
  data-refs=${cell?.dataRefs ?? nothing}
1443
+ data-focus-unfurl=${focusUnfurl ? nothing : 'false'}
1337
1444
  ?data-dx-active=${active}
1338
1445
  data-text-selectable=${textSelectable ? 'true' : 'false'}
1339
1446
  data-dx-grid-action="cell"
@@ -1376,13 +1483,24 @@ export class DxGrid extends LitElement {
1376
1483
  <div
1377
1484
  role="none"
1378
1485
  class="dx-grid"
1486
+ data-arrow-keys="all"
1379
1487
  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' : ''}`,
1488
+ 'grid-template-columns': [
1489
+ this.templatefrozenColsStart ? 'min-content' : false,
1490
+ this.limitColumns > 0 &&
1491
+ `minmax(0, ${Number.isFinite(this.limitColumns) ? `${Math.max(0, this.intrinsicInlineSize)}px` : '1fr'})`,
1492
+ this.templatefrozenColsEnd ? 'min-content' : false,
1493
+ ]
1494
+ .filter(Boolean)
1495
+ .join(' '),
1496
+ 'grid-template-rows': [
1497
+ this.templatefrozenRowsStart ? 'min-content' : false,
1498
+ this.limitRows > 0 &&
1499
+ `minmax(0, ${Number.isFinite(this.limitRows) ? `${Math.max(0, this.intrinsicBlockSize)}px` : '1fr'})`,
1500
+ this.templatefrozenRowsEnd ? ' min-content' : false,
1501
+ ]
1502
+ .filter(Boolean)
1503
+ .join(' '),
1386
1504
  '--dx-grid-content-inline-size': Number.isFinite(this.limitColumns)
1387
1505
  ? `${Math.max(0, this.totalIntrinsicInlineSize)}px`
1388
1506
  : 'max-content',
@@ -1392,6 +1510,7 @@ export class DxGrid extends LitElement {
1392
1510
  })}
1393
1511
  data-grid=${this.gridId}
1394
1512
  data-grid-mode=${this.mode}
1513
+ data-grid-focus-indicator-variant=${this.focusIndicatorVariant}
1395
1514
  ?data-grid-select=${selection.visible}
1396
1515
  ${ref(this.gridRef)}
1397
1516
  >
@@ -1406,30 +1525,7 @@ export class DxGrid extends LitElement {
1406
1525
  offsetBlock,
1407
1526
  selection,
1408
1527
  )}
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>
1528
+ ${this.renderMainGrid(visibleCols, visibleRows, offsetInline, offsetBlock, selection)}
1433
1529
  ${this.renderFrozenColumns('frozenColsEnd', visibleRows, offsetBlock, selection)}${this.renderFixed(
1434
1530
  'fixedEndStart',
1435
1531
  selection,
@@ -1443,37 +1539,19 @@ export class DxGrid extends LitElement {
1443
1539
  private updateIntrinsicInlineSize(): void {
1444
1540
  this.intrinsicInlineSize = Number.isFinite(this.limitColumns)
1445
1541
  ? [...Array(this.limitColumns)].reduce((acc, _, c0) => acc + this.colSize(c0, 'grid'), 0) +
1446
- gap * (this.limitColumns - 1)
1542
+ gap * Math.max(0, this.limitColumns - 1)
1447
1543
  : Infinity;
1448
1544
  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);
1545
+ this.limitColumns > 0 ? this.intrinsicInlineSize + this.frozenColsSize : this.frozenColsSize - gap;
1459
1546
  }
1460
1547
 
1461
1548
  private updateIntrinsicBlockSize(): void {
1462
1549
  this.intrinsicBlockSize = Number.isFinite(this.limitRows)
1463
1550
  ? [...Array(this.limitRows)].reduce((acc, _, r0) => acc + this.rowSize(r0, 'grid'), 0) +
1464
- gap * (this.limitRows - 1)
1551
+ gap * Math.max(0, this.limitRows - 1)
1465
1552
  : Infinity;
1466
1553
  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);
1554
+ this.limitRows > 0 ? this.intrinsicBlockSize + this.frozenRowsSize : this.frozenRowsSize - gap;
1477
1555
  }
1478
1556
 
1479
1557
  private updateIntrinsicSizes(): void {
@@ -1521,7 +1599,7 @@ export class DxGrid extends LitElement {
1521
1599
  if (this.getCells) {
1522
1600
  this.updateCells(true);
1523
1601
  }
1524
- this.observer.observe(this.viewportRef.value!);
1602
+ this.observer.observe(this.gridRef.value!);
1525
1603
  this.computeColSizes();
1526
1604
  this.computeRowSizes();
1527
1605
  this.updateIntrinsicSizes();
@@ -1595,8 +1673,8 @@ export class DxGrid extends LitElement {
1595
1673
 
1596
1674
  override disconnectedCallback(): void {
1597
1675
  super.disconnectedCallback();
1598
- if (this.viewportRef.value) {
1599
- this.observer.unobserve(this.viewportRef.value);
1676
+ if (this.gridRef.value) {
1677
+ this.observer.unobserve(this.gridRef.value);
1600
1678
  }
1601
1679
  document.defaultView?.removeEventListener('wheel', this.handleTopLevelWheel);
1602
1680
  }
@@ -1614,6 +1692,7 @@ export {
1614
1692
  parseCellIndex,
1615
1693
  toPlaneCellIndex,
1616
1694
  cellQuery,
1695
+ accessoryHandlesPointerdownAttrs,
1617
1696
  } from './util';
1618
1697
 
1619
1698
  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