@dxos/lit-grid 0.6.12-main.5cc132e → 0.6.12-main.78ddbdf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +406 -143
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/dx-grid-axis-resize-handle.d.ts +15 -0
- package/dist/types/src/dx-grid-axis-resize-handle.d.ts.map +1 -0
- package/dist/types/src/dx-grid.d.ts +55 -39
- package/dist/types/src/dx-grid.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +38 -4
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +7 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/dx-grid-axis-resize-handle.ts +83 -0
- package/src/dx-grid.lit-stories.ts +2 -2
- package/src/dx-grid.pcss +32 -12
- package/src/dx-grid.ts +278 -138
- package/src/types.ts +50 -4
- package/src/util.ts +12 -0
package/src/dx-grid.ts
CHANGED
|
@@ -2,21 +2,29 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { LitElement, html } from 'lit';
|
|
5
|
+
import { LitElement, html, nothing } from 'lit';
|
|
6
6
|
import { customElement, state, property, eventOptions } from 'lit/decorators.js';
|
|
7
7
|
import { ref, createRef, type Ref } from 'lit/directives/ref.js';
|
|
8
8
|
|
|
9
|
+
// eslint-disable-next-line unused-imports/no-unused-imports
|
|
10
|
+
import './dx-grid-axis-resize-handle';
|
|
9
11
|
import {
|
|
10
12
|
type AxisMeta,
|
|
11
13
|
type CellIndex,
|
|
12
|
-
type CellValue,
|
|
13
14
|
DxAxisResize,
|
|
14
|
-
type
|
|
15
|
+
type DxAxisResizeInternal,
|
|
15
16
|
DxEditRequest,
|
|
16
17
|
type DxGridAxis,
|
|
18
|
+
type DxGridAxisMeta,
|
|
19
|
+
type DxGridCells,
|
|
20
|
+
DxGridCellsSelect,
|
|
17
21
|
type DxGridMode,
|
|
22
|
+
type DxGridPointer,
|
|
23
|
+
type DxGridPosition,
|
|
18
24
|
type DxGridPositionNullable,
|
|
25
|
+
type DxGridRange,
|
|
19
26
|
} from './types';
|
|
27
|
+
import { separator, toCellIndex } from './util';
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* The size in pixels of the gap between cells
|
|
@@ -43,11 +51,6 @@ const sizeColMax = 1024;
|
|
|
43
51
|
const sizeRowMin = 16;
|
|
44
52
|
const sizeRowMax = 1024;
|
|
45
53
|
|
|
46
|
-
/**
|
|
47
|
-
* Separator for serializing cell position vectors
|
|
48
|
-
*/
|
|
49
|
-
const separator = ',';
|
|
50
|
-
|
|
51
54
|
//
|
|
52
55
|
// A1 notation is the fallback for numbering columns and rows.
|
|
53
56
|
//
|
|
@@ -68,7 +71,7 @@ const closestAction = (target: EventTarget | null): { action: string | null; act
|
|
|
68
71
|
return { actionEl, action: actionEl?.getAttribute('data-dx-grid-action') ?? null };
|
|
69
72
|
};
|
|
70
73
|
|
|
71
|
-
const closestCell = (target: EventTarget | null, actionEl?: HTMLElement | null):
|
|
74
|
+
const closestCell = (target: EventTarget | null, actionEl?: HTMLElement | null): DxGridPositionNullable => {
|
|
72
75
|
let cellElement = actionEl;
|
|
73
76
|
if (!cellElement) {
|
|
74
77
|
const { action, actionEl } = closestAction(target);
|
|
@@ -88,9 +91,6 @@ const closestCell = (target: EventTarget | null, actionEl?: HTMLElement | null):
|
|
|
88
91
|
const isSameCell = (a: DxGridPositionNullable, b: DxGridPositionNullable) =>
|
|
89
92
|
a && b && Number.isFinite(a.col) && Number.isFinite(a.row) && a.col === b.col && a.row === b.row;
|
|
90
93
|
|
|
91
|
-
const toCellIndex = (cellCoords: Record<DxGridAxis, number>): CellIndex =>
|
|
92
|
-
`${cellCoords.col}${separator}${cellCoords.row}`;
|
|
93
|
-
|
|
94
94
|
const localChId = (c0: number) => `ch--${c0}`;
|
|
95
95
|
const localRhId = (r0: number) => `rh--${r0}`;
|
|
96
96
|
|
|
@@ -98,6 +98,22 @@ const getPage = (axis: string, event: PointerEvent) => (axis === 'col' ? event.p
|
|
|
98
98
|
|
|
99
99
|
@customElement('dx-grid')
|
|
100
100
|
export class DxGrid extends LitElement {
|
|
101
|
+
constructor() {
|
|
102
|
+
super();
|
|
103
|
+
this.addEventListener('dx-axis-resize-internal', this.handleAxisResizeInternal as EventListener);
|
|
104
|
+
this.addEventListener('wheel', this.handleWheel);
|
|
105
|
+
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
106
|
+
this.addEventListener('pointermove', this.handlePointerMove);
|
|
107
|
+
this.addEventListener('pointerup', this.handlePointerUp);
|
|
108
|
+
this.addEventListener('pointerleave', this.handlePointerUp);
|
|
109
|
+
this.addEventListener('focus', this.handleFocus, { capture: true });
|
|
110
|
+
this.addEventListener('blur', this.handleBlur, { capture: true });
|
|
111
|
+
this.addEventListener('keydown', this.handleKeydown);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@property({ type: String })
|
|
115
|
+
gridId: string = 'default-grid-id';
|
|
116
|
+
|
|
101
117
|
@property({ type: Object })
|
|
102
118
|
rowDefault: AxisMeta = { size: 32 };
|
|
103
119
|
|
|
@@ -105,62 +121,71 @@ export class DxGrid extends LitElement {
|
|
|
105
121
|
columnDefault: AxisMeta = { size: 180 };
|
|
106
122
|
|
|
107
123
|
@property({ type: Object })
|
|
108
|
-
rows:
|
|
124
|
+
rows: DxGridAxisMeta = {};
|
|
109
125
|
|
|
110
126
|
@property({ type: Object })
|
|
111
|
-
columns:
|
|
127
|
+
columns: DxGridAxisMeta = {};
|
|
112
128
|
|
|
113
129
|
@property({ type: Object })
|
|
114
|
-
|
|
130
|
+
initialCells: DxGridCells = {};
|
|
115
131
|
|
|
116
132
|
@property({ type: String })
|
|
117
133
|
mode: DxGridMode = 'browse';
|
|
118
134
|
|
|
135
|
+
/**
|
|
136
|
+
* When this function is defined, it is used first to try to get a value for a cell, and otherwise will fall back
|
|
137
|
+
* to `cells`.
|
|
138
|
+
*/
|
|
139
|
+
getCells: ((nextRange: DxGridRange) => DxGridCells) | null = null;
|
|
140
|
+
|
|
141
|
+
@state()
|
|
142
|
+
private cells: DxGridCells = {};
|
|
143
|
+
|
|
119
144
|
//
|
|
120
145
|
// `pos`, short for ‘position’, is the position in pixels of the viewport from the origin.
|
|
121
146
|
//
|
|
122
147
|
|
|
123
148
|
@state()
|
|
124
|
-
posInline = 0;
|
|
149
|
+
private posInline = 0;
|
|
125
150
|
|
|
126
151
|
@state()
|
|
127
|
-
posBlock = 0;
|
|
152
|
+
private posBlock = 0;
|
|
128
153
|
|
|
129
154
|
//
|
|
130
155
|
// `size` (when not suffixed with ‘row’ or ‘col’, see above) is the size in pixels of the viewport.
|
|
131
156
|
//
|
|
132
157
|
|
|
133
158
|
@state()
|
|
134
|
-
sizeInline = 0;
|
|
159
|
+
private sizeInline = 0;
|
|
135
160
|
|
|
136
161
|
@state()
|
|
137
|
-
sizeBlock = 0;
|
|
162
|
+
private sizeBlock = 0;
|
|
138
163
|
|
|
139
164
|
//
|
|
140
165
|
// `overscan` is the amount in pixels to offset the grid content due to the number of overscanned columns or rows.
|
|
141
166
|
//
|
|
142
167
|
|
|
143
168
|
@state()
|
|
144
|
-
overscanInline = 0;
|
|
169
|
+
private overscanInline = 0;
|
|
145
170
|
|
|
146
171
|
@state()
|
|
147
|
-
overscanBlock = 0;
|
|
172
|
+
private overscanBlock = 0;
|
|
148
173
|
|
|
149
174
|
//
|
|
150
175
|
// `bin`, not short for anything, is the range in pixels within which virtualization does not need to reassess.
|
|
151
176
|
//
|
|
152
177
|
|
|
153
178
|
@state()
|
|
154
|
-
binInlineMin = 0;
|
|
179
|
+
private binInlineMin = 0;
|
|
155
180
|
|
|
156
181
|
@state()
|
|
157
|
-
binInlineMax = this.colSize(0);
|
|
182
|
+
private binInlineMax = this.colSize(0);
|
|
158
183
|
|
|
159
184
|
@state()
|
|
160
|
-
binBlockMin = 0;
|
|
185
|
+
private binBlockMin = 0;
|
|
161
186
|
|
|
162
187
|
@state()
|
|
163
|
-
binBlockMax = this.rowSize(0);
|
|
188
|
+
private binBlockMax = this.rowSize(0);
|
|
164
189
|
|
|
165
190
|
//
|
|
166
191
|
// `vis`, short for ‘visible’, is the range in numeric index of the columns or rows which should be rendered within
|
|
@@ -168,97 +193,129 @@ export class DxGrid extends LitElement {
|
|
|
168
193
|
//
|
|
169
194
|
|
|
170
195
|
@state()
|
|
171
|
-
visColMin = 0;
|
|
196
|
+
private visColMin = 0;
|
|
172
197
|
|
|
173
198
|
@state()
|
|
174
|
-
visColMax = 1;
|
|
199
|
+
private visColMax = 1;
|
|
175
200
|
|
|
176
201
|
@state()
|
|
177
|
-
visRowMin = 0;
|
|
202
|
+
private visRowMin = 0;
|
|
178
203
|
|
|
179
204
|
@state()
|
|
180
|
-
visRowMax = 1;
|
|
205
|
+
private visRowMax = 1;
|
|
181
206
|
|
|
182
207
|
//
|
|
183
208
|
// `template` is the rendered value of `grid-{axis}-template`.
|
|
184
209
|
//
|
|
185
210
|
@state()
|
|
186
|
-
templateColumns = `${this.colSize(0)}px`;
|
|
211
|
+
private templateColumns = `${this.colSize(0)}px`;
|
|
187
212
|
|
|
188
213
|
@state()
|
|
189
|
-
templateRows = `${this.rowSize(0)}px`;
|
|
214
|
+
private templateRows = `${this.rowSize(0)}px`;
|
|
190
215
|
|
|
191
216
|
//
|
|
192
|
-
//
|
|
217
|
+
// Focus, selection, and resize states
|
|
193
218
|
//
|
|
194
219
|
|
|
195
220
|
@state()
|
|
196
|
-
|
|
221
|
+
private pointer: DxGridPointer = null;
|
|
222
|
+
|
|
223
|
+
@state()
|
|
224
|
+
private colSizes: Record<string, number> = {};
|
|
225
|
+
|
|
226
|
+
@state()
|
|
227
|
+
private rowSizes: Record<string, number> = {};
|
|
228
|
+
|
|
229
|
+
@state()
|
|
230
|
+
private focusActive: boolean = false;
|
|
197
231
|
|
|
198
232
|
@state()
|
|
199
|
-
|
|
233
|
+
private focusedCell: DxGridPosition = { col: 0, row: 0 };
|
|
200
234
|
|
|
201
235
|
@state()
|
|
202
|
-
|
|
236
|
+
private selectionStart: DxGridPosition = { col: 0, row: 0 };
|
|
237
|
+
|
|
238
|
+
@state()
|
|
239
|
+
private selectionEnd: DxGridPosition = { col: 0, row: 0 };
|
|
203
240
|
|
|
204
241
|
//
|
|
205
242
|
// Primary pointer and keyboard handlers
|
|
206
243
|
//
|
|
207
244
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
);
|
|
245
|
+
private dispatchEditRequest(initialContent?: string) {
|
|
246
|
+
this.snapPosToFocusedCell();
|
|
247
|
+
// Without deferring, the event dispatches before `focusedCellBox` can get updated bounds of the cell, hence:
|
|
248
|
+
queueMicrotask(() =>
|
|
249
|
+
this.dispatchEvent(
|
|
250
|
+
new DxEditRequest({
|
|
251
|
+
cellIndex: toCellIndex(this.focusedCell),
|
|
252
|
+
cellBox: this.focusedCellBox(),
|
|
253
|
+
initialContent,
|
|
254
|
+
}),
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private handlePointerDown = (event: PointerEvent) => {
|
|
260
|
+
if (event.isPrimary) {
|
|
261
|
+
const { action, actionEl } = closestAction(event.target);
|
|
262
|
+
if (action) {
|
|
263
|
+
if (action.startsWith('resize') && this.mode === 'browse') {
|
|
264
|
+
const [resize, index] = action.split(',');
|
|
265
|
+
const [_, axis] = resize.split('-');
|
|
266
|
+
this.pointer = {
|
|
267
|
+
state: 'resizing',
|
|
268
|
+
axis: axis as DxGridAxis,
|
|
269
|
+
size: axis === 'col' ? this.colSize(index) : this.rowSize(index),
|
|
270
|
+
page: getPage(axis, event),
|
|
271
|
+
index,
|
|
272
|
+
};
|
|
273
|
+
} else if (action === 'cell') {
|
|
274
|
+
const cellCoords = closestCell(event.target, actionEl);
|
|
275
|
+
if (cellCoords) {
|
|
276
|
+
this.pointer = { state: 'selecting' };
|
|
277
|
+
this.selectionStart = cellCoords;
|
|
278
|
+
}
|
|
279
|
+
if (this.mode === 'edit') {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
} else {
|
|
282
|
+
if (this.focusActive && isSameCell(this.focusedCell, cellCoords)) {
|
|
283
|
+
this.dispatchEditRequest();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
229
286
|
}
|
|
230
287
|
}
|
|
231
288
|
}
|
|
232
289
|
};
|
|
233
290
|
|
|
234
|
-
handlePointerUp = (
|
|
235
|
-
if (this.resizing) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
291
|
+
private handlePointerUp = (event: PointerEvent) => {
|
|
292
|
+
if (this.pointer?.state === 'resizing') {
|
|
293
|
+
// do nothing, todo: remove
|
|
294
|
+
} else {
|
|
295
|
+
const cell = closestCell(event.target);
|
|
296
|
+
if (cell) {
|
|
297
|
+
this.selectionEnd = cell;
|
|
298
|
+
this.dispatchEvent(
|
|
299
|
+
new DxGridCellsSelect({
|
|
300
|
+
start: this.selectionStart,
|
|
301
|
+
end: this.selectionEnd,
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
243
305
|
}
|
|
306
|
+
this.pointer = null;
|
|
244
307
|
};
|
|
245
308
|
|
|
246
|
-
handlePointerMove = (event: PointerEvent) => {
|
|
247
|
-
if (this.
|
|
248
|
-
const
|
|
249
|
-
if (this.
|
|
250
|
-
|
|
251
|
-
this.colSizes = { ...this.colSizes, [this.resizing.index]: nextSize };
|
|
252
|
-
this.updateVisInline();
|
|
253
|
-
} else {
|
|
254
|
-
const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, this.resizing.size + delta));
|
|
255
|
-
this.rowSizes = { ...this.rowSizes, [this.resizing.index]: nextSize };
|
|
256
|
-
this.updateVisBlock();
|
|
309
|
+
private handlePointerMove = (event: PointerEvent) => {
|
|
310
|
+
if (this.pointer?.state === 'selecting') {
|
|
311
|
+
const cell = closestCell(event.target);
|
|
312
|
+
if (cell && (cell.col !== this.selectionEnd.col || cell.row !== this.selectionEnd.row)) {
|
|
313
|
+
this.selectionEnd = cell;
|
|
257
314
|
}
|
|
258
315
|
}
|
|
259
316
|
};
|
|
260
317
|
|
|
261
|
-
handleKeydown(event: KeyboardEvent) {
|
|
318
|
+
private handleKeydown(event: KeyboardEvent) {
|
|
262
319
|
if (this.focusActive && this.mode === 'browse') {
|
|
263
320
|
// Adjust state
|
|
264
321
|
switch (event.key) {
|
|
@@ -275,15 +332,15 @@ export class DxGrid extends LitElement {
|
|
|
275
332
|
this.focusedCell = { ...this.focusedCell, col: Math.max(0, this.focusedCell.col - 1) };
|
|
276
333
|
break;
|
|
277
334
|
}
|
|
278
|
-
// Emit
|
|
335
|
+
// Emit edit request if relevant
|
|
279
336
|
switch (event.key) {
|
|
280
337
|
case 'Enter':
|
|
281
|
-
this.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
338
|
+
this.dispatchEditRequest();
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
if (event.key.length === 1 && event.key.match(/\P{Cc}/u)) {
|
|
342
|
+
this.dispatchEditRequest(event.key);
|
|
343
|
+
}
|
|
287
344
|
break;
|
|
288
345
|
}
|
|
289
346
|
// Handle virtualization & focus consequences
|
|
@@ -312,7 +369,8 @@ export class DxGrid extends LitElement {
|
|
|
312
369
|
}
|
|
313
370
|
|
|
314
371
|
private cell(c: number | string, r: number | string) {
|
|
315
|
-
|
|
372
|
+
const index: CellIndex = `${c}${separator}${r}`;
|
|
373
|
+
return this.cells[index] ?? this.initialCells[index];
|
|
316
374
|
}
|
|
317
375
|
|
|
318
376
|
private focusedCellBox(): DxEditRequest['cellBox'] {
|
|
@@ -339,7 +397,7 @@ export class DxGrid extends LitElement {
|
|
|
339
397
|
//
|
|
340
398
|
|
|
341
399
|
@state()
|
|
342
|
-
observer = new ResizeObserver((entries) => {
|
|
400
|
+
private observer = new ResizeObserver((entries) => {
|
|
343
401
|
const { inlineSize, blockSize } = entries?.[0]?.contentBoxSize?.[0] ?? {
|
|
344
402
|
inlineSize: 0,
|
|
345
403
|
blockSize: 0,
|
|
@@ -355,28 +413,32 @@ export class DxGrid extends LitElement {
|
|
|
355
413
|
}
|
|
356
414
|
});
|
|
357
415
|
|
|
358
|
-
viewportRef: Ref<HTMLDivElement> = createRef();
|
|
416
|
+
private viewportRef: Ref<HTMLDivElement> = createRef();
|
|
417
|
+
|
|
418
|
+
private maybeUpdateVis = () => {
|
|
419
|
+
if (
|
|
420
|
+
this.posInline >= this.binInlineMin &&
|
|
421
|
+
this.posInline < this.binInlineMax &&
|
|
422
|
+
this.posBlock >= this.binBlockMin &&
|
|
423
|
+
this.posBlock < this.binBlockMax
|
|
424
|
+
) {
|
|
425
|
+
// do nothing
|
|
426
|
+
} else {
|
|
427
|
+
// console.info(
|
|
428
|
+
// '[updating bounds]',
|
|
429
|
+
// 'wheel',
|
|
430
|
+
// [this.binInlineMin, this.posInline, this.binInlineMax],
|
|
431
|
+
// [this.binBlockMin, this.posBlock, this.binBlockMax],
|
|
432
|
+
// );
|
|
433
|
+
this.updateVis();
|
|
434
|
+
}
|
|
435
|
+
};
|
|
359
436
|
|
|
360
|
-
handleWheel = ({ deltaX, deltaY }: WheelEvent) => {
|
|
437
|
+
private handleWheel = ({ deltaX, deltaY }: WheelEvent) => {
|
|
361
438
|
if (this.mode === 'browse') {
|
|
362
439
|
this.posInline = Math.max(0, this.posInline + deltaX);
|
|
363
440
|
this.posBlock = Math.max(0, this.posBlock + deltaY);
|
|
364
|
-
|
|
365
|
-
this.posInline >= this.binInlineMin &&
|
|
366
|
-
this.posInline < this.binInlineMax &&
|
|
367
|
-
this.posBlock >= this.binBlockMin &&
|
|
368
|
-
this.posBlock < this.binBlockMax
|
|
369
|
-
) {
|
|
370
|
-
// do nothing
|
|
371
|
-
} else {
|
|
372
|
-
// console.info(
|
|
373
|
-
// '[updating bounds]',
|
|
374
|
-
// 'wheel',
|
|
375
|
-
// [this.binInlineMin, this.posInline, this.binInlineMax],
|
|
376
|
-
// [this.binBlockMin, this.posBlock, this.binBlockMax],
|
|
377
|
-
// );
|
|
378
|
-
this.updateVis();
|
|
379
|
-
}
|
|
441
|
+
this.maybeUpdateVis();
|
|
380
442
|
}
|
|
381
443
|
};
|
|
382
444
|
|
|
@@ -455,14 +517,8 @@ export class DxGrid extends LitElement {
|
|
|
455
517
|
|
|
456
518
|
// Focus handlers
|
|
457
519
|
|
|
458
|
-
@state()
|
|
459
|
-
focusedCell: Record<DxGridAxis, number> = { col: 0, row: 0 };
|
|
460
|
-
|
|
461
|
-
@state()
|
|
462
|
-
focusActive: boolean = false;
|
|
463
|
-
|
|
464
520
|
@eventOptions({ capture: true })
|
|
465
|
-
handleFocus(event: FocusEvent) {
|
|
521
|
+
private handleFocus(event: FocusEvent) {
|
|
466
522
|
const cellCoords = closestCell(event.target);
|
|
467
523
|
if (cellCoords) {
|
|
468
524
|
this.focusedCell = cellCoords;
|
|
@@ -471,17 +527,14 @@ export class DxGrid extends LitElement {
|
|
|
471
527
|
}
|
|
472
528
|
|
|
473
529
|
@eventOptions({ capture: true })
|
|
474
|
-
handleBlur(event: FocusEvent) {
|
|
530
|
+
private handleBlur(event: FocusEvent) {
|
|
475
531
|
// Only unset `focusActive` if focus is not moving to an element within the grid.
|
|
476
|
-
if (
|
|
477
|
-
!event.relatedTarget ||
|
|
478
|
-
(event.relatedTarget as HTMLElement).closest('.dx-grid__viewport') !== this.viewportRef.value
|
|
479
|
-
) {
|
|
532
|
+
if (!event.relatedTarget || !(event.relatedTarget as HTMLElement).closest(`[data-grid="${this.gridId}"]`)) {
|
|
480
533
|
this.focusActive = false;
|
|
481
534
|
}
|
|
482
535
|
}
|
|
483
536
|
|
|
484
|
-
focusedCellElement() {
|
|
537
|
+
private focusedCellElement() {
|
|
485
538
|
return this.viewportRef.value?.querySelector(
|
|
486
539
|
`[aria-colindex="${this.focusedCell.col}"][aria-rowindex="${this.focusedCell.row}"]`,
|
|
487
540
|
) as HTMLElement | null;
|
|
@@ -490,13 +543,13 @@ export class DxGrid extends LitElement {
|
|
|
490
543
|
/**
|
|
491
544
|
* Moves focus to the cell with actual focus, otherwise moves focus to the viewport.
|
|
492
545
|
*/
|
|
493
|
-
refocus(increment?: '
|
|
546
|
+
refocus(increment?: 'col' | 'row', delta: 1 | -1 = 1) {
|
|
494
547
|
switch (increment) {
|
|
495
|
-
case '
|
|
496
|
-
this.focusedCell = { ...this.focusedCell, row: this.focusedCell.row +
|
|
548
|
+
case 'row':
|
|
549
|
+
this.focusedCell = { ...this.focusedCell, row: this.focusedCell.row + delta };
|
|
497
550
|
break;
|
|
498
|
-
case '
|
|
499
|
-
this.focusedCell = { ...this.focusedCell, col: this.focusedCell.col +
|
|
551
|
+
case 'col':
|
|
552
|
+
this.focusedCell = { ...this.focusedCell, col: this.focusedCell.col + delta };
|
|
500
553
|
}
|
|
501
554
|
(this.focusedCell.row < this.visRowMin ||
|
|
502
555
|
this.focusedCell.row > this.visRowMax ||
|
|
@@ -505,6 +558,9 @@ export class DxGrid extends LitElement {
|
|
|
505
558
|
? this.viewportRef.value
|
|
506
559
|
: this.focusedCellElement()
|
|
507
560
|
)?.focus({ preventScroll: true });
|
|
561
|
+
if (increment) {
|
|
562
|
+
this.snapPosToFocusedCell();
|
|
563
|
+
}
|
|
508
564
|
}
|
|
509
565
|
|
|
510
566
|
/**
|
|
@@ -539,7 +595,7 @@ export class DxGrid extends LitElement {
|
|
|
539
595
|
acc += this.colSize(this.visColMin + overscanCol + c0) + gap;
|
|
540
596
|
return acc;
|
|
541
597
|
}, 0);
|
|
542
|
-
this.posInline = this.binInlineMin + sizeSumCol + gap * 2 - this.sizeInline;
|
|
598
|
+
this.posInline = Math.max(0, this.binInlineMin + sizeSumCol + gap * 2 - this.sizeInline);
|
|
543
599
|
this.updateVisInline();
|
|
544
600
|
}
|
|
545
601
|
|
|
@@ -551,12 +607,60 @@ export class DxGrid extends LitElement {
|
|
|
551
607
|
acc += this.rowSize(this.visRowMin + overscanRow + r0) + gap;
|
|
552
608
|
return acc;
|
|
553
609
|
}, 0);
|
|
554
|
-
this.posBlock = this.binBlockMin + sizeSumRow + gap * 2 - this.sizeBlock;
|
|
610
|
+
this.posBlock = Math.max(0, this.binBlockMin + sizeSumRow + gap * 2 - this.sizeBlock);
|
|
555
611
|
this.updateVisBlock();
|
|
556
612
|
}
|
|
557
613
|
}
|
|
558
614
|
}
|
|
559
615
|
|
|
616
|
+
//
|
|
617
|
+
// Map scroll DOM methods to virtualized value.
|
|
618
|
+
//
|
|
619
|
+
|
|
620
|
+
override get scrollLeft() {
|
|
621
|
+
return this.posInline;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
override set scrollLeft(nextValue: number) {
|
|
625
|
+
this.posInline = nextValue;
|
|
626
|
+
this.maybeUpdateVis();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
override get scrollTop() {
|
|
630
|
+
return this.posBlock;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
override set scrollTop(nextValue: number) {
|
|
634
|
+
this.posBlock = nextValue;
|
|
635
|
+
this.maybeUpdateVis();
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
//
|
|
639
|
+
// Resize handlers
|
|
640
|
+
//
|
|
641
|
+
private handleAxisResizeInternal(event: DxAxisResizeInternal) {
|
|
642
|
+
event.stopPropagation();
|
|
643
|
+
const { axis, delta, size, index, type } = event;
|
|
644
|
+
if (axis === 'col') {
|
|
645
|
+
const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, size + delta));
|
|
646
|
+
this.colSizes = { ...this.colSizes, [index]: nextSize };
|
|
647
|
+
this.updateVisInline();
|
|
648
|
+
} else {
|
|
649
|
+
const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, size + delta));
|
|
650
|
+
this.rowSizes = { ...this.rowSizes, [index]: nextSize };
|
|
651
|
+
this.updateVisBlock();
|
|
652
|
+
}
|
|
653
|
+
if (type === 'dropped') {
|
|
654
|
+
this.dispatchEvent(
|
|
655
|
+
new DxAxisResize({
|
|
656
|
+
axis,
|
|
657
|
+
index,
|
|
658
|
+
size: this[axis === 'col' ? 'colSize' : 'rowSize'](index),
|
|
659
|
+
}),
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
560
664
|
//
|
|
561
665
|
// Render and other lifecycle methods
|
|
562
666
|
//
|
|
@@ -564,18 +668,21 @@ export class DxGrid extends LitElement {
|
|
|
564
668
|
override render() {
|
|
565
669
|
const visibleCols = this.visColMax - this.visColMin;
|
|
566
670
|
const visibleRows = this.visRowMax - this.visRowMin;
|
|
567
|
-
const offsetInline =
|
|
568
|
-
const offsetBlock =
|
|
671
|
+
const offsetInline = this.binInlineMin - this.posInline - this.overscanInline;
|
|
672
|
+
const offsetBlock = this.binBlockMin - this.posBlock - this.overscanBlock;
|
|
673
|
+
|
|
674
|
+
const selectColMin = Math.min(this.selectionStart.col, this.selectionEnd.col);
|
|
675
|
+
const selectColMax = Math.max(this.selectionStart.col, this.selectionEnd.col);
|
|
676
|
+
const selectRowMin = Math.min(this.selectionStart.row, this.selectionEnd.row);
|
|
677
|
+
const selectRowMax = Math.max(this.selectionStart.row, this.selectionEnd.row);
|
|
678
|
+
const selectVisible = selectColMin !== selectColMax || selectRowMin !== selectRowMax;
|
|
569
679
|
|
|
570
680
|
return html`<div
|
|
571
681
|
role="none"
|
|
572
682
|
class="dx-grid"
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
@focus=${this.handleFocus}
|
|
577
|
-
@blur=${this.handleBlur}
|
|
578
|
-
@keydown=${this.handleKeydown}
|
|
683
|
+
data-grid=${this.gridId}
|
|
684
|
+
data-grid-mode=${this.mode}
|
|
685
|
+
?data-grid-select=${selectVisible}
|
|
579
686
|
>
|
|
580
687
|
<div role="none" class="dx-grid__corner"></div>
|
|
581
688
|
<div role="none" class="dx-grid__columnheader">
|
|
@@ -593,9 +700,12 @@ export class DxGrid extends LitElement {
|
|
|
593
700
|
>
|
|
594
701
|
<span id=${localChId(c0)}>${colToA1Notation(c)}</span>
|
|
595
702
|
${(this.columns[c]?.resizeable ?? this.columnDefault.resizeable) &&
|
|
596
|
-
html`<
|
|
597
|
-
|
|
598
|
-
|
|
703
|
+
html`<dx-grid-axis-resize-handle
|
|
704
|
+
axis="col"
|
|
705
|
+
index=${c}
|
|
706
|
+
size=${this.colSize(c)}
|
|
707
|
+
@dxaxisresizeinternal=${this.handleAxisResizeInternal}
|
|
708
|
+
></dx-grid-axis-resize-handle>`}
|
|
599
709
|
</div>`;
|
|
600
710
|
})}
|
|
601
711
|
</div>
|
|
@@ -612,9 +722,11 @@ export class DxGrid extends LitElement {
|
|
|
612
722
|
return html`<div role="rowheader" ?inert=${r < 0} style="grid-row:${r0 + 1}/${r0 + 2}">
|
|
613
723
|
<span id=${localRhId(r0)}>${rowToA1Notation(r)}</span>
|
|
614
724
|
${(this.rows[r]?.resizeable ?? this.rowDefault.resizeable) &&
|
|
615
|
-
html`<
|
|
616
|
-
|
|
617
|
-
|
|
725
|
+
html`<dx-grid-axis-resize-handle
|
|
726
|
+
axis="row"
|
|
727
|
+
index=${r}
|
|
728
|
+
size=${this.rowSize(r)}
|
|
729
|
+
></dx-grid-axis-resize-handle>`}
|
|
618
730
|
</div>`;
|
|
619
731
|
})}
|
|
620
732
|
</div>
|
|
@@ -631,10 +743,16 @@ export class DxGrid extends LitElement {
|
|
|
631
743
|
const c = c0 + this.visColMin;
|
|
632
744
|
const r = r0 + this.visRowMin;
|
|
633
745
|
const cell = this.cell(c, r);
|
|
746
|
+
const active = this.focusActive && this.focusedCell.col === c && this.focusedCell.row === r;
|
|
747
|
+
const selected = c >= selectColMin && c <= selectColMax && r >= selectRowMin && r <= selectRowMax;
|
|
634
748
|
return html`<div
|
|
635
749
|
role="gridcell"
|
|
636
750
|
tabindex="0"
|
|
637
751
|
?inert=${c < 0 || r < 0}
|
|
752
|
+
?aria-selected=${selected}
|
|
753
|
+
class=${cell || active
|
|
754
|
+
? (cell?.className ? cell.className + ' ' : '') + (active ? 'dx-grid__cell--active' : '')
|
|
755
|
+
: nothing}
|
|
638
756
|
aria-rowindex=${r}
|
|
639
757
|
aria-colindex=${c}
|
|
640
758
|
data-dx-grid-action="cell"
|
|
@@ -658,6 +776,12 @@ export class DxGrid extends LitElement {
|
|
|
658
776
|
}
|
|
659
777
|
|
|
660
778
|
override firstUpdated() {
|
|
779
|
+
if (this.getCells) {
|
|
780
|
+
this.cells = this.getCells({
|
|
781
|
+
start: { col: this.visColMin, row: this.visRowMin },
|
|
782
|
+
end: { col: this.visColMax, row: this.visRowMax },
|
|
783
|
+
});
|
|
784
|
+
}
|
|
661
785
|
this.observer.observe(this.viewportRef.value!);
|
|
662
786
|
this.colSizes = Object.entries(this.columns).reduce((acc: Record<string, number>, [colId, colMeta]) => {
|
|
663
787
|
if (colMeta?.size) {
|
|
@@ -673,6 +797,22 @@ export class DxGrid extends LitElement {
|
|
|
673
797
|
}, {});
|
|
674
798
|
}
|
|
675
799
|
|
|
800
|
+
override willUpdate(changedProperties: Map<string, any>) {
|
|
801
|
+
if (
|
|
802
|
+
this.getCells &&
|
|
803
|
+
(changedProperties.has('initialCells') ||
|
|
804
|
+
changedProperties.has('visColMin') ||
|
|
805
|
+
changedProperties.has('visColMax') ||
|
|
806
|
+
changedProperties.has('visRowMin') ||
|
|
807
|
+
changedProperties.has('visRowMax'))
|
|
808
|
+
) {
|
|
809
|
+
this.cells = this.getCells({
|
|
810
|
+
start: { col: this.visColMin, row: this.visRowMin },
|
|
811
|
+
end: { col: this.visColMax, row: this.visRowMax },
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
676
816
|
override updated(changedProperties: Map<string, any>) {
|
|
677
817
|
// Update the focused element if there is a change in bounds (otherwise Lit keeps focus on the relative element).
|
|
678
818
|
if (
|