@dxos/lit-grid 0.6.13 → 0.6.14-main.2b6a0f3
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/src/dx-grid-axis-resize-handle.d.ts +16 -0
- package/dist/src/dx-grid-axis-resize-handle.d.ts.map +1 -0
- package/dist/src/dx-grid-axis-resize-handle.js +96 -0
- package/dist/src/dx-grid-axis-resize-handle.js.map +1 -0
- package/dist/src/dx-grid.d.ts +138 -0
- package/dist/src/dx-grid.d.ts.map +1 -0
- package/dist/src/dx-grid.js +1224 -0
- package/dist/src/dx-grid.js.map +1 -0
- package/dist/src/dx-grid.lit-stories.d.ts +42 -0
- package/dist/src/dx-grid.lit-stories.d.ts.map +1 -0
- package/dist/src/dx-grid.lit-stories.js +166 -0
- package/dist/src/dx-grid.lit-stories.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/types.d.ts +119 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +44 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/util.d.ts +9 -0
- package/dist/src/util.d.ts.map +1 -0
- package/dist/src/util.js +19 -0
- package/dist/src/util.js.map +1 -0
- package/dist/types/src/dx-grid-axis-resize-handle.d.ts +16 -0
- package/dist/types/src/dx-grid-axis-resize-handle.d.ts.map +1 -0
- package/dist/types/src/dx-grid-axis-resize-handle.js +96 -0
- package/dist/types/src/dx-grid-axis-resize-handle.js.map +1 -0
- package/dist/types/src/dx-grid.d.ts +112 -57
- package/dist/types/src/dx-grid.d.ts.map +1 -1
- package/dist/types/src/dx-grid.js +1224 -0
- package/dist/types/src/dx-grid.js.map +1 -0
- package/dist/types/src/dx-grid.lit-stories.d.ts +27 -2
- package/dist/types/src/dx-grid.lit-stories.d.ts.map +1 -1
- package/dist/types/src/dx-grid.lit-stories.js +166 -0
- package/dist/types/src/dx-grid.lit-stories.js.map +1 -0
- package/dist/types/src/index.js +6 -0
- package/dist/types/src/index.js.map +1 -0
- package/dist/types/src/types.d.ts +111 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/types.js +44 -0
- package/dist/types/src/types.js.map +1 -0
- package/dist/types/src/util.d.ts +9 -0
- package/dist/types/src/util.d.ts.map +1 -0
- package/dist/types/src/util.js +19 -0
- package/dist/types/src/util.js.map +1 -0
- package/package.json +6 -6
- package/src/dx-grid-axis-resize-handle.ts +87 -0
- package/src/dx-grid.lit-stories.ts +148 -21
- package/src/dx-grid.pcss +68 -69
- package/src/dx-grid.ts +1116 -343
- package/src/types.ts +161 -1
- package/src/util.ts +28 -0
- package/dist/lib/browser/index.mjs +0 -578
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
package/src/dx-grid.ts
CHANGED
|
@@ -2,11 +2,43 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { LitElement, html } from 'lit';
|
|
6
|
-
import { customElement, state, property
|
|
5
|
+
import { LitElement, html, nothing } from 'lit';
|
|
6
|
+
import { customElement, state, property } from 'lit/decorators.js';
|
|
7
7
|
import { ref, createRef, type Ref } from 'lit/directives/ref.js';
|
|
8
|
-
|
|
9
|
-
import {
|
|
8
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
9
|
+
import { unsafeStatic, html as staticHtml } from 'lit/static-html.js';
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line unused-imports/no-unused-imports
|
|
12
|
+
import './dx-grid-axis-resize-handle';
|
|
13
|
+
import {
|
|
14
|
+
type DxGridAxisMetaProps,
|
|
15
|
+
type DxGridAxisSizes,
|
|
16
|
+
type DxGridCellIndex,
|
|
17
|
+
type DxGridCellValue,
|
|
18
|
+
DxAxisResize,
|
|
19
|
+
type DxAxisResizeInternal,
|
|
20
|
+
DxEditRequest,
|
|
21
|
+
type DxGridAxisMeta,
|
|
22
|
+
type DxGridCells,
|
|
23
|
+
DxGridCellsSelect,
|
|
24
|
+
type DxGridFixedPlane,
|
|
25
|
+
type DxGridFrozenAxes,
|
|
26
|
+
type DxGridFrozenColsPlane,
|
|
27
|
+
type DxGridFrozenPlane,
|
|
28
|
+
type DxGridFrozenRowsPlane,
|
|
29
|
+
type DxGridMode,
|
|
30
|
+
type DxGridPlane,
|
|
31
|
+
type DxGridPlaneCells,
|
|
32
|
+
type DxGridPlaneRange,
|
|
33
|
+
type DxGridPlaneRecord,
|
|
34
|
+
type DxGridPointer,
|
|
35
|
+
type DxGridPosition,
|
|
36
|
+
type DxGridPositionNullable,
|
|
37
|
+
type DxGridAxis,
|
|
38
|
+
type DxGridSelectionProps,
|
|
39
|
+
type DxGridAnnotatedWheelEvent,
|
|
40
|
+
} from './types';
|
|
41
|
+
import { separator, toCellIndex } from './util';
|
|
10
42
|
|
|
11
43
|
/**
|
|
12
44
|
* The size in pixels of the gap between cells
|
|
@@ -14,10 +46,14 @@ import { DxAxisResize, type DxAxisResizeProps, type DxGridAxis } from './types';
|
|
|
14
46
|
const gap = 1;
|
|
15
47
|
|
|
16
48
|
/**
|
|
17
|
-
*
|
|
18
|
-
* changes when scrolling vertically.
|
|
49
|
+
* ResizeObserver notices even subpixel changes, only respond to changes of at least 1px.
|
|
19
50
|
*/
|
|
20
|
-
const resizeTolerance =
|
|
51
|
+
const resizeTolerance = 1;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The amount of pixels the primary pointer has to move after PointerDown to engage in selection.
|
|
55
|
+
*/
|
|
56
|
+
const selectTolerance = 4;
|
|
21
57
|
|
|
22
58
|
//
|
|
23
59
|
// `overscan` is the number of columns or rows to render outside of the viewport
|
|
@@ -25,124 +61,245 @@ const resizeTolerance = 8;
|
|
|
25
61
|
const overscanCol = 1;
|
|
26
62
|
const overscanRow = 1;
|
|
27
63
|
|
|
64
|
+
//
|
|
65
|
+
// `defaultSize`, the final fallbacks
|
|
66
|
+
//
|
|
67
|
+
const defaultSizeRow = 32;
|
|
68
|
+
const defaultSizeCol = 180;
|
|
69
|
+
|
|
28
70
|
//
|
|
29
71
|
// `size`, when suffixed with ‘row’ or ‘col’, are limits on size applied when resizing
|
|
30
72
|
//
|
|
31
73
|
const sizeColMin = 32;
|
|
32
74
|
const sizeColMax = 1024;
|
|
33
|
-
const sizeRowMin =
|
|
75
|
+
const sizeRowMin = 32;
|
|
34
76
|
const sizeRowMax = 1024;
|
|
35
77
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
const shouldSelect = (pointer: DxGridPointer, { pageX, pageY }: PointerEvent) => {
|
|
79
|
+
if (pointer?.state === 'maybeSelecting') {
|
|
80
|
+
return Math.hypot(Math.abs(pointer.pageX - pageX), Math.abs(pointer.pageY - pageY)) >= selectTolerance;
|
|
81
|
+
} else {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
40
85
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
86
|
+
const selectionProps = (selectionStart: DxGridPosition, selectionEnd: DxGridPosition): DxGridSelectionProps => {
|
|
87
|
+
const colMin = Math.min(selectionStart.col, selectionEnd.col);
|
|
88
|
+
const colMax = Math.max(selectionStart.col, selectionEnd.col);
|
|
89
|
+
const rowMin = Math.min(selectionStart.row, selectionEnd.row);
|
|
90
|
+
const rowMax = Math.max(selectionStart.row, selectionEnd.row);
|
|
91
|
+
const plane = selectionStart.plane;
|
|
92
|
+
const visible = colMin !== colMax || rowMin !== rowMax;
|
|
93
|
+
return { colMin, colMax, rowMin, rowMax, plane, visible };
|
|
94
|
+
};
|
|
44
95
|
|
|
45
|
-
const
|
|
96
|
+
const cellSelected = (col: number, row: number, plane: DxGridPlane, selection: DxGridSelectionProps): boolean => {
|
|
46
97
|
return (
|
|
47
|
-
|
|
48
|
-
|
|
98
|
+
plane === selection.plane &&
|
|
99
|
+
col >= selection.colMin &&
|
|
100
|
+
col <= selection.colMax &&
|
|
101
|
+
row >= selection.rowMin &&
|
|
102
|
+
row <= selection.rowMax
|
|
49
103
|
);
|
|
50
104
|
};
|
|
51
105
|
|
|
52
|
-
const
|
|
53
|
-
|
|
106
|
+
const closestAction = (target: EventTarget | null): { action: string | null; actionEl: HTMLElement | null } => {
|
|
107
|
+
const actionEl: HTMLElement | null = (target as HTMLElement | null)?.closest('[data-dx-grid-action]') ?? null;
|
|
108
|
+
return { actionEl, action: actionEl?.getAttribute('data-dx-grid-action') ?? null };
|
|
54
109
|
};
|
|
55
110
|
|
|
56
|
-
export
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
111
|
+
export const closestCell = (target: EventTarget | null, actionEl?: HTMLElement | null): DxGridPositionNullable => {
|
|
112
|
+
let cellElement = actionEl;
|
|
113
|
+
if (!cellElement) {
|
|
114
|
+
const { action, actionEl } = closestAction(target);
|
|
115
|
+
if (action === 'cell') {
|
|
116
|
+
cellElement = actionEl as HTMLElement;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (cellElement) {
|
|
120
|
+
const col = parseInt(cellElement.getAttribute('aria-colindex') ?? 'never');
|
|
121
|
+
const row = parseInt(cellElement.getAttribute('aria-rowindex') ?? 'never');
|
|
122
|
+
const plane = (cellElement.closest('[data-dx-grid-plane]')?.getAttribute('data-dx-grid-plane') ??
|
|
123
|
+
'grid') as DxGridPlane;
|
|
124
|
+
return { plane, col, row };
|
|
125
|
+
} else {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
69
128
|
};
|
|
70
129
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
130
|
+
const resolveRowPlane = (plane: DxGridPlane): 'grid' | DxGridFrozenRowsPlane => {
|
|
131
|
+
switch (plane) {
|
|
132
|
+
case 'fixedStartStart':
|
|
133
|
+
case 'fixedStartEnd':
|
|
134
|
+
case 'frozenRowsStart':
|
|
135
|
+
return 'frozenRowsStart';
|
|
136
|
+
case 'fixedEndStart':
|
|
137
|
+
case 'fixedEndEnd':
|
|
138
|
+
case 'frozenRowsEnd':
|
|
139
|
+
return 'frozenRowsEnd';
|
|
140
|
+
default:
|
|
141
|
+
return 'grid';
|
|
142
|
+
}
|
|
75
143
|
};
|
|
76
144
|
|
|
77
|
-
|
|
145
|
+
const resolveColPlane = (plane: DxGridPlane): 'grid' | DxGridFrozenColsPlane => {
|
|
146
|
+
switch (plane) {
|
|
147
|
+
case 'fixedStartStart':
|
|
148
|
+
case 'fixedEndStart':
|
|
149
|
+
case 'frozenColsStart':
|
|
150
|
+
return 'frozenColsStart';
|
|
151
|
+
case 'fixedStartEnd':
|
|
152
|
+
case 'fixedEndEnd':
|
|
153
|
+
case 'frozenColsEnd':
|
|
154
|
+
return 'frozenColsEnd';
|
|
155
|
+
default:
|
|
156
|
+
return 'grid';
|
|
157
|
+
}
|
|
158
|
+
};
|
|
78
159
|
|
|
79
|
-
const
|
|
80
|
-
|
|
160
|
+
const resolveFrozenPlane = (axis: DxGridAxis, cellPlane: DxGridPlane): 'grid' | DxGridFrozenPlane => {
|
|
161
|
+
switch (cellPlane) {
|
|
162
|
+
case 'fixedStartStart':
|
|
163
|
+
return axis === 'col' ? 'frozenColsStart' : 'frozenRowsStart';
|
|
164
|
+
case 'fixedStartEnd':
|
|
165
|
+
return axis === 'col' ? 'frozenColsEnd' : 'frozenRowsStart';
|
|
166
|
+
case 'fixedEndStart':
|
|
167
|
+
return axis === 'col' ? 'frozenColsStart' : 'frozenRowsEnd';
|
|
168
|
+
case 'fixedEndEnd':
|
|
169
|
+
return axis === 'col' ? 'frozenColsEnd' : 'frozenRowsEnd';
|
|
170
|
+
case 'frozenColsStart':
|
|
171
|
+
case 'frozenColsEnd':
|
|
172
|
+
return axis === 'col' ? cellPlane : 'grid';
|
|
173
|
+
case 'frozenRowsStart':
|
|
174
|
+
case 'frozenRowsEnd':
|
|
175
|
+
return axis === 'row' ? cellPlane : 'grid';
|
|
176
|
+
default:
|
|
177
|
+
return cellPlane;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
81
180
|
|
|
82
|
-
const
|
|
181
|
+
const isSameCell = (a: DxGridPositionNullable, b: DxGridPositionNullable) =>
|
|
182
|
+
a &&
|
|
183
|
+
b &&
|
|
184
|
+
a.plane === b.plane &&
|
|
185
|
+
Number.isFinite(a.col) &&
|
|
186
|
+
Number.isFinite(a.row) &&
|
|
187
|
+
a.col === b.col &&
|
|
188
|
+
a.row === b.row;
|
|
83
189
|
|
|
84
190
|
@customElement('dx-grid')
|
|
85
191
|
export class DxGrid extends LitElement {
|
|
192
|
+
constructor() {
|
|
193
|
+
super();
|
|
194
|
+
// Wheel, top-level and element-level
|
|
195
|
+
document.defaultView?.addEventListener('wheel', this.handleTopLevelWheel, { passive: false });
|
|
196
|
+
this.addEventListener('wheel', this.handleWheel);
|
|
197
|
+
// Custom event(s)
|
|
198
|
+
this.addEventListener('dx-axis-resize-internal', this.handleAxisResizeInternal as EventListener);
|
|
199
|
+
// Standard events
|
|
200
|
+
this.addEventListener('pointerdown', this.handlePointerDown);
|
|
201
|
+
this.addEventListener('pointermove', this.handlePointerMove);
|
|
202
|
+
this.addEventListener('pointerup', this.handlePointerUp);
|
|
203
|
+
this.addEventListener('pointerleave', this.handlePointerUp);
|
|
204
|
+
this.addEventListener('focus', this.handleFocus, { capture: true });
|
|
205
|
+
this.addEventListener('blur', this.handleBlur, { capture: true });
|
|
206
|
+
this.addEventListener('keydown', this.handleKeydown);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@property({ type: String })
|
|
210
|
+
gridId: string = 'default-grid-id';
|
|
211
|
+
|
|
212
|
+
@property({ type: Object })
|
|
213
|
+
rowDefault: DxGridPlaneRecord<DxGridFrozenRowsPlane, DxGridAxisMetaProps> = {
|
|
214
|
+
grid: { size: defaultSizeRow },
|
|
215
|
+
};
|
|
216
|
+
|
|
86
217
|
@property({ type: Object })
|
|
87
|
-
|
|
218
|
+
columnDefault: DxGridPlaneRecord<DxGridFrozenColsPlane, DxGridAxisMetaProps> = {
|
|
219
|
+
grid: { size: defaultSizeCol },
|
|
220
|
+
};
|
|
88
221
|
|
|
89
222
|
@property({ type: Object })
|
|
90
|
-
|
|
223
|
+
rows: DxGridAxisMeta = { grid: {} };
|
|
91
224
|
|
|
92
225
|
@property({ type: Object })
|
|
93
|
-
|
|
226
|
+
columns: DxGridAxisMeta = { grid: {} };
|
|
94
227
|
|
|
95
228
|
@property({ type: Object })
|
|
96
|
-
|
|
229
|
+
initialCells: DxGridCells = { grid: {} };
|
|
230
|
+
|
|
231
|
+
@property({ type: String })
|
|
232
|
+
mode: DxGridMode = 'browse';
|
|
233
|
+
|
|
234
|
+
@property({ type: Number })
|
|
235
|
+
limitColumns: number = Infinity;
|
|
236
|
+
|
|
237
|
+
@property({ type: Number })
|
|
238
|
+
limitRows: number = Infinity;
|
|
97
239
|
|
|
98
240
|
@property({ type: Object })
|
|
99
|
-
|
|
241
|
+
frozen: DxGridFrozenAxes = {};
|
|
242
|
+
|
|
243
|
+
@property({ type: String })
|
|
244
|
+
overscroll: 'inline' | 'block' | undefined = undefined;
|
|
245
|
+
|
|
246
|
+
@property({ type: String })
|
|
247
|
+
activeRefs = '';
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* When this function is defined, it is used first to try to get a value for a cell, and otherwise will fall back
|
|
251
|
+
* to `cells`.
|
|
252
|
+
*/
|
|
253
|
+
getCells: ((nextRange: DxGridPlaneRange, plane: DxGridPlane) => DxGridPlaneCells) | null = null;
|
|
254
|
+
|
|
255
|
+
@state()
|
|
256
|
+
private cells: DxGridCells = { grid: {} };
|
|
100
257
|
|
|
101
258
|
//
|
|
102
259
|
// `pos`, short for ‘position’, is the position in pixels of the viewport from the origin.
|
|
103
260
|
//
|
|
104
261
|
|
|
105
262
|
@state()
|
|
106
|
-
posInline = 0;
|
|
263
|
+
private posInline = 0;
|
|
107
264
|
|
|
108
265
|
@state()
|
|
109
|
-
posBlock = 0;
|
|
266
|
+
private posBlock = 0;
|
|
110
267
|
|
|
111
268
|
//
|
|
112
269
|
// `size` (when not suffixed with ‘row’ or ‘col’, see above) is the size in pixels of the viewport.
|
|
113
270
|
//
|
|
114
271
|
|
|
115
272
|
@state()
|
|
116
|
-
sizeInline = 0;
|
|
273
|
+
private sizeInline = 0;
|
|
117
274
|
|
|
118
275
|
@state()
|
|
119
|
-
sizeBlock = 0;
|
|
276
|
+
private sizeBlock = 0;
|
|
120
277
|
|
|
121
278
|
//
|
|
122
279
|
// `overscan` is the amount in pixels to offset the grid content due to the number of overscanned columns or rows.
|
|
123
280
|
//
|
|
124
281
|
|
|
125
282
|
@state()
|
|
126
|
-
overscanInline = 0;
|
|
283
|
+
private overscanInline = 0;
|
|
127
284
|
|
|
128
285
|
@state()
|
|
129
|
-
overscanBlock = 0;
|
|
286
|
+
private overscanBlock = 0;
|
|
130
287
|
|
|
131
288
|
//
|
|
132
289
|
// `bin`, not short for anything, is the range in pixels within which virtualization does not need to reassess.
|
|
133
290
|
//
|
|
134
291
|
|
|
135
292
|
@state()
|
|
136
|
-
binInlineMin = 0;
|
|
293
|
+
private binInlineMin = 0;
|
|
137
294
|
|
|
138
295
|
@state()
|
|
139
|
-
binInlineMax =
|
|
296
|
+
private binInlineMax = defaultSizeCol;
|
|
140
297
|
|
|
141
298
|
@state()
|
|
142
|
-
binBlockMin = 0;
|
|
299
|
+
private binBlockMin = 0;
|
|
143
300
|
|
|
144
301
|
@state()
|
|
145
|
-
binBlockMax =
|
|
302
|
+
private binBlockMax = defaultSizeRow;
|
|
146
303
|
|
|
147
304
|
//
|
|
148
305
|
// `vis`, short for ‘visible’, is the range in numeric index of the columns or rows which should be rendered within
|
|
@@ -150,97 +307,317 @@ export class DxGrid extends LitElement {
|
|
|
150
307
|
//
|
|
151
308
|
|
|
152
309
|
@state()
|
|
153
|
-
visColMin = 0;
|
|
310
|
+
private visColMin = 0;
|
|
154
311
|
|
|
155
312
|
@state()
|
|
156
|
-
visColMax = 1;
|
|
313
|
+
private visColMax = 1;
|
|
157
314
|
|
|
158
315
|
@state()
|
|
159
|
-
visRowMin = 0;
|
|
316
|
+
private visRowMin = 0;
|
|
160
317
|
|
|
161
318
|
@state()
|
|
162
|
-
visRowMax = 1;
|
|
319
|
+
private visRowMax = 1;
|
|
163
320
|
|
|
164
321
|
//
|
|
165
322
|
// `template` is the rendered value of `grid-{axis}-template`.
|
|
166
323
|
//
|
|
167
324
|
@state()
|
|
168
|
-
|
|
325
|
+
private templateGridColumns = '0';
|
|
326
|
+
|
|
327
|
+
@state()
|
|
328
|
+
private templatefrozenColsStart = '';
|
|
329
|
+
|
|
330
|
+
@state()
|
|
331
|
+
private templatefrozenColsEnd = '';
|
|
169
332
|
|
|
170
333
|
@state()
|
|
171
|
-
|
|
334
|
+
private templateGridRows = '0';
|
|
335
|
+
|
|
336
|
+
@state()
|
|
337
|
+
private templatefrozenRowsStart = '';
|
|
338
|
+
|
|
339
|
+
@state()
|
|
340
|
+
private templatefrozenRowsEnd = '';
|
|
172
341
|
|
|
173
342
|
//
|
|
174
|
-
//
|
|
343
|
+
// Focus, selection, and resize states
|
|
175
344
|
//
|
|
176
345
|
|
|
177
346
|
@state()
|
|
178
|
-
|
|
347
|
+
private pointer: DxGridPointer = null;
|
|
179
348
|
|
|
180
349
|
@state()
|
|
181
|
-
|
|
350
|
+
private colSizes: DxGridAxisSizes = { grid: {} };
|
|
182
351
|
|
|
183
352
|
@state()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
353
|
+
private rowSizes: DxGridAxisSizes = { grid: {} };
|
|
354
|
+
|
|
355
|
+
@state()
|
|
356
|
+
private focusActive: boolean = false;
|
|
357
|
+
|
|
358
|
+
@state()
|
|
359
|
+
private focusedCell: DxGridPosition = { plane: 'grid', col: 0, row: 0 };
|
|
360
|
+
|
|
361
|
+
@state()
|
|
362
|
+
private selectionStart: DxGridPosition = { plane: 'grid', col: 0, row: 0 };
|
|
363
|
+
|
|
364
|
+
@state()
|
|
365
|
+
private selectionEnd: DxGridPosition = { plane: 'grid', col: 0, row: 0 };
|
|
366
|
+
|
|
367
|
+
//
|
|
368
|
+
// Limits
|
|
369
|
+
//
|
|
370
|
+
|
|
371
|
+
@state()
|
|
372
|
+
private intrinsicInlineSize: number = Infinity;
|
|
373
|
+
|
|
374
|
+
@state()
|
|
375
|
+
private intrinsicBlockSize: number = Infinity;
|
|
376
|
+
|
|
377
|
+
//
|
|
378
|
+
// Primary pointer and keyboard handlers
|
|
379
|
+
//
|
|
380
|
+
|
|
381
|
+
private dispatchEditRequest(initialContent?: string) {
|
|
382
|
+
this.snapPosToFocusedCell();
|
|
383
|
+
if (!this.cellReadonly(this.focusedCell.col, this.focusedCell.row, this.focusedCell.plane)) {
|
|
384
|
+
// Without deferring, the event dispatches before `focusedCellBox` can get updated bounds of the cell, hence:
|
|
385
|
+
queueMicrotask(() =>
|
|
386
|
+
this.dispatchEvent(
|
|
387
|
+
new DxEditRequest({
|
|
388
|
+
cellIndex: toCellIndex(this.focusedCell),
|
|
389
|
+
cellBox: this.focusedCellBox(),
|
|
390
|
+
initialContent,
|
|
391
|
+
}),
|
|
392
|
+
),
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private dispatchSelectionChange() {
|
|
398
|
+
return this.dispatchEvent(
|
|
399
|
+
new DxGridCellsSelect({
|
|
400
|
+
start: this.selectionStart,
|
|
401
|
+
end: this.selectionEnd,
|
|
402
|
+
}),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private handlePointerDown = (event: PointerEvent) => {
|
|
407
|
+
if (event.isPrimary) {
|
|
408
|
+
const { action, actionEl } = closestAction(event.target);
|
|
409
|
+
if (action && action === 'cell') {
|
|
410
|
+
if (event.shiftKey) {
|
|
411
|
+
// Prevent focus moving so the pointerup handler can move selectionEnd.
|
|
412
|
+
event.preventDefault();
|
|
413
|
+
} else {
|
|
414
|
+
const cellCoords = closestCell(event.target, actionEl);
|
|
415
|
+
if (cellCoords) {
|
|
416
|
+
this.pointer = { state: 'maybeSelecting', pageX: event.pageX, pageY: event.pageY };
|
|
417
|
+
this.selectionStart = cellCoords;
|
|
418
|
+
this.selectionEnd = cellCoords;
|
|
419
|
+
this.dispatchSelectionChange();
|
|
420
|
+
}
|
|
421
|
+
if (this.mode === 'edit') {
|
|
422
|
+
// Prevent focus moving when editing.
|
|
423
|
+
event.preventDefault();
|
|
424
|
+
} else {
|
|
425
|
+
if (this.focusActive && isSameCell(this.focusedCell, cellCoords)) {
|
|
426
|
+
this.dispatchEditRequest();
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
199
430
|
}
|
|
200
431
|
}
|
|
201
432
|
};
|
|
202
433
|
|
|
203
|
-
handlePointerUp = (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
index: this.resizing.index,
|
|
208
|
-
size: this[this.resizing.axis === 'col' ? 'colSize' : 'rowSize'](this.resizing.index),
|
|
209
|
-
});
|
|
210
|
-
this.dispatchEvent(resizeEvent);
|
|
211
|
-
this.resizing = null;
|
|
434
|
+
private handlePointerUp = (event: PointerEvent) => {
|
|
435
|
+
const cell = closestCell(event.target);
|
|
436
|
+
if (cell) {
|
|
437
|
+
this.setSelectionEnd(cell);
|
|
212
438
|
}
|
|
439
|
+
this.pointer = null;
|
|
213
440
|
};
|
|
214
441
|
|
|
215
|
-
handlePointerMove = (event: PointerEvent) => {
|
|
216
|
-
if (this.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.
|
|
442
|
+
private handlePointerMove = (event: PointerEvent) => {
|
|
443
|
+
if (shouldSelect(this.pointer, event)) {
|
|
444
|
+
this.pointer = { state: 'selecting' };
|
|
445
|
+
} else if (this.pointer?.state === 'selecting') {
|
|
446
|
+
const cell = closestCell(event.target);
|
|
447
|
+
if (
|
|
448
|
+
cell &&
|
|
449
|
+
cell.plane === this.selectionStart.plane &&
|
|
450
|
+
(cell.col !== this.selectionEnd.col || cell.row !== this.selectionEnd.row)
|
|
451
|
+
) {
|
|
452
|
+
this.setSelectionEnd(cell);
|
|
226
453
|
}
|
|
227
454
|
}
|
|
228
455
|
};
|
|
229
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Increments focus among all theoretically possible cells in a plane, cycling as tab would but accounting for the
|
|
459
|
+
* theoretical bounds of the grid plane (handling infinite planes heuristically).
|
|
460
|
+
*/
|
|
461
|
+
private incrementFocusWithinPlane(reverse?: boolean) {
|
|
462
|
+
const colPlane = resolveColPlane(this.focusedCell.plane);
|
|
463
|
+
const rowPlane = resolveRowPlane(this.focusedCell.plane);
|
|
464
|
+
const colMax = (colPlane === 'grid' ? this.limitColumns : this.frozen[colPlane]!) - 1;
|
|
465
|
+
const rowMax = (rowPlane === 'grid' ? this.limitRows : this.frozen[rowPlane]!) - 1;
|
|
466
|
+
if (reverse ? this.focusedCell.col - 1 < 0 : this.focusedCell.col + 1 > colMax) {
|
|
467
|
+
if (reverse ? this.focusedCell.row - 1 < 0 : this.focusedCell.row + 1 > rowMax) {
|
|
468
|
+
this.setFocusedCell({
|
|
469
|
+
plane: this.focusedCell.plane,
|
|
470
|
+
row: reverse && Number.isFinite(rowMax) ? rowMax : 0,
|
|
471
|
+
col: reverse && Number.isFinite(colMax) ? colMax : 0,
|
|
472
|
+
});
|
|
473
|
+
} else {
|
|
474
|
+
this.setFocusedCell({
|
|
475
|
+
plane: this.focusedCell.plane,
|
|
476
|
+
row: this.focusedCell.row + (reverse ? -1 : 1),
|
|
477
|
+
col: reverse && Number.isFinite(colMax) ? colMax : 0,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
this.setFocusedCell({ ...this.focusedCell, col: this.focusedCell.col + (reverse ? -1 : 1) });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Increments focus in a specific direction without cycling.
|
|
487
|
+
*/
|
|
488
|
+
private moveFocusOrSelectionEndWithinPlane(deltaCol: number, deltaRow: number, selectionEnd?: boolean) {
|
|
489
|
+
const current = selectionEnd ? this.selectionEnd : this.focusedCell;
|
|
490
|
+
|
|
491
|
+
const colPlane = resolveColPlane(current.plane);
|
|
492
|
+
const colMax = (colPlane === 'grid' ? this.limitColumns : this.frozen[colPlane]!) - 1;
|
|
493
|
+
const nextCol = Math.max(0, Math.min(colMax, current.col + deltaCol));
|
|
494
|
+
|
|
495
|
+
const rowPlane = resolveRowPlane(current.plane);
|
|
496
|
+
const rowMax = (rowPlane === 'grid' ? this.limitRows : this.frozen[rowPlane]!) - 1;
|
|
497
|
+
const nextRow = Math.max(0, Math.min(rowMax, current.row + deltaRow));
|
|
498
|
+
|
|
499
|
+
if (selectionEnd) {
|
|
500
|
+
this.setSelectionEnd({ ...this.selectionEnd, col: nextCol, row: nextRow });
|
|
501
|
+
} else {
|
|
502
|
+
this.setFocusedCell({ ...this.focusedCell, row: nextRow, col: nextCol });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private handleKeydown(event: KeyboardEvent) {
|
|
507
|
+
if (this.focusActive && this.mode === 'browse') {
|
|
508
|
+
// Adjust state
|
|
509
|
+
switch (event.key) {
|
|
510
|
+
case 'ArrowDown':
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
this.moveFocusOrSelectionEndWithinPlane(0, 1, event.shiftKey);
|
|
513
|
+
break;
|
|
514
|
+
case 'ArrowUp':
|
|
515
|
+
event.preventDefault();
|
|
516
|
+
this.moveFocusOrSelectionEndWithinPlane(0, -1, event.shiftKey);
|
|
517
|
+
break;
|
|
518
|
+
case 'ArrowRight':
|
|
519
|
+
event.preventDefault();
|
|
520
|
+
this.moveFocusOrSelectionEndWithinPlane(1, 0, event.shiftKey);
|
|
521
|
+
break;
|
|
522
|
+
case 'ArrowLeft':
|
|
523
|
+
event.preventDefault();
|
|
524
|
+
this.moveFocusOrSelectionEndWithinPlane(-1, 0, event.shiftKey);
|
|
525
|
+
break;
|
|
526
|
+
case 'Tab':
|
|
527
|
+
event.preventDefault();
|
|
528
|
+
this.incrementFocusWithinPlane(event.shiftKey);
|
|
529
|
+
break;
|
|
530
|
+
case 'Escape':
|
|
531
|
+
// Handle escape if selection is a superset of the focused cell.
|
|
532
|
+
if (this.selectionStart.col !== this.selectionEnd.col || this.selectionStart.row !== this.selectionEnd.row) {
|
|
533
|
+
event.preventDefault();
|
|
534
|
+
this.selectionStart = this.focusedCell;
|
|
535
|
+
this.selectionEnd = this.focusedCell;
|
|
536
|
+
this.dispatchSelectionChange();
|
|
537
|
+
}
|
|
538
|
+
break;
|
|
539
|
+
case 'Enter':
|
|
540
|
+
this.dispatchEditRequest();
|
|
541
|
+
break;
|
|
542
|
+
default:
|
|
543
|
+
if (event.key.length === 1 && event.key.match(/\P{Cc}/u) && !(event.metaKey || event.ctrlKey)) {
|
|
544
|
+
this.dispatchEditRequest(event.key);
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
230
551
|
//
|
|
231
552
|
// Accessors
|
|
232
553
|
//
|
|
233
554
|
|
|
234
|
-
private colSize(c: number | string) {
|
|
235
|
-
|
|
555
|
+
private colSize(c: number | string, plane: DxGridPlane) {
|
|
556
|
+
const resolvedPlane = resolveColPlane(plane);
|
|
557
|
+
return this.colSizes?.[resolvedPlane]?.[c] ?? this.columnDefault[resolvedPlane]?.size ?? defaultSizeCol;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private rowSize(r: number | string, plane: DxGridPlane) {
|
|
561
|
+
const resolvedPlane = resolveRowPlane(plane);
|
|
562
|
+
return this.rowSizes?.[resolvedPlane]?.[r] ?? this.rowDefault[resolvedPlane]?.size ?? defaultSizeRow;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private cell(c: number | string, r: number | string, plane: DxGridPlane): DxGridCellValue | undefined {
|
|
566
|
+
const index: DxGridCellIndex = `${c}${separator}${r}`;
|
|
567
|
+
return this.cells?.[plane]?.[index] ?? this.initialCells?.[plane]?.[index];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
private cellActive(c: number | string, r: number | string, plane: DxGridPlane): boolean {
|
|
571
|
+
return (
|
|
572
|
+
this.focusActive && this.focusedCell.plane === plane && this.focusedCell.col === c && this.focusedCell.row === r
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private setFocusedCell(nextCoords: DxGridPosition) {
|
|
577
|
+
if (
|
|
578
|
+
this.focusedCell.plane !== nextCoords.plane ||
|
|
579
|
+
this.focusedCell.col !== nextCoords.col ||
|
|
580
|
+
this.focusedCell.row !== nextCoords.row
|
|
581
|
+
) {
|
|
582
|
+
this.focusedCell = nextCoords;
|
|
583
|
+
this.selectionStart = nextCoords;
|
|
584
|
+
this.selectionEnd = nextCoords;
|
|
585
|
+
this.snapPosToFocusedCell();
|
|
586
|
+
this.dispatchSelectionChange();
|
|
587
|
+
}
|
|
236
588
|
}
|
|
237
589
|
|
|
238
|
-
private
|
|
239
|
-
|
|
590
|
+
private setSelectionEnd(nextCoords: DxGridPosition) {
|
|
591
|
+
if (
|
|
592
|
+
this.selectionEnd.plane !== nextCoords.plane ||
|
|
593
|
+
this.selectionEnd.col !== nextCoords.col ||
|
|
594
|
+
this.selectionEnd.row !== nextCoords.row
|
|
595
|
+
) {
|
|
596
|
+
this.selectionEnd = nextCoords;
|
|
597
|
+
this.dispatchSelectionChange();
|
|
598
|
+
}
|
|
240
599
|
}
|
|
241
600
|
|
|
242
|
-
private
|
|
243
|
-
|
|
601
|
+
private focusedCellBox(): DxEditRequest['cellBox'] {
|
|
602
|
+
const cellElement = this.focusedCellElement();
|
|
603
|
+
const cellSize = {
|
|
604
|
+
inlineSize: this.colSize(this.focusedCell.col, this.focusedCell.plane),
|
|
605
|
+
blockSize: this.rowSize(this.focusedCell.row, this.focusedCell.plane),
|
|
606
|
+
};
|
|
607
|
+
if (!cellElement) {
|
|
608
|
+
return { insetInlineStart: NaN, insetBlockStart: NaN, ...cellSize };
|
|
609
|
+
}
|
|
610
|
+
const contentElement = cellElement.offsetParent as HTMLElement;
|
|
611
|
+
// Note that storing `offset` in state causes performance issues, so instead the transform is parsed here.
|
|
612
|
+
const [_translate3d, inlineStr, blockStr] = contentElement.style.transform.split(/[()]|px,?\s?/);
|
|
613
|
+
const contentOffsetInline = parseFloat(inlineStr);
|
|
614
|
+
const contentOffsetBlock = parseFloat(blockStr);
|
|
615
|
+
const offsetParent = contentElement.offsetParent as HTMLElement;
|
|
616
|
+
return {
|
|
617
|
+
insetInlineStart: cellElement.offsetLeft + contentOffsetInline + offsetParent.offsetLeft,
|
|
618
|
+
insetBlockStart: cellElement.offsetTop + contentOffsetBlock + offsetParent.offsetTop,
|
|
619
|
+
...cellSize,
|
|
620
|
+
};
|
|
244
621
|
}
|
|
245
622
|
|
|
246
623
|
//
|
|
@@ -248,7 +625,7 @@ export class DxGrid extends LitElement {
|
|
|
248
625
|
//
|
|
249
626
|
|
|
250
627
|
@state()
|
|
251
|
-
observer = new ResizeObserver((entries) => {
|
|
628
|
+
private observer = new ResizeObserver((entries) => {
|
|
252
629
|
const { inlineSize, blockSize } = entries?.[0]?.contentBoxSize?.[0] ?? {
|
|
253
630
|
inlineSize: 0,
|
|
254
631
|
blockSize: 0,
|
|
@@ -261,97 +638,152 @@ export class DxGrid extends LitElement {
|
|
|
261
638
|
this.sizeInline = inlineSize;
|
|
262
639
|
this.sizeBlock = blockSize;
|
|
263
640
|
this.updateVis();
|
|
641
|
+
queueMicrotask(() => this.updatePos());
|
|
264
642
|
}
|
|
265
643
|
});
|
|
266
644
|
|
|
267
|
-
viewportRef: Ref<HTMLDivElement> = createRef();
|
|
645
|
+
private viewportRef: Ref<HTMLDivElement> = createRef();
|
|
646
|
+
|
|
647
|
+
private maybeUpdateVisInline = () => {
|
|
648
|
+
if (this.posInline < this.binInlineMin || this.posInline >= this.binInlineMax) {
|
|
649
|
+
this.updateVisInline();
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
private maybeUpdateVisBlock = () => {
|
|
654
|
+
if (this.posBlock < this.binBlockMin || this.posBlock >= this.binBlockMax) {
|
|
655
|
+
this.updateVisBlock();
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
private maxPosInline() {
|
|
660
|
+
return this.intrinsicInlineSize - this.sizeInline;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private maxPosBlock() {
|
|
664
|
+
return this.intrinsicBlockSize - this.sizeBlock;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
private updatePosInline(inline?: number, maxInline: number = this.maxPosInline()) {
|
|
668
|
+
this.posInline = Math.max(0, Math.min(maxInline, inline ?? this.posInline));
|
|
669
|
+
this.maybeUpdateVisInline();
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private updatePosBlock(block?: number, maxBlock: number = this.maxPosBlock()) {
|
|
673
|
+
this.posBlock = Math.max(0, Math.min(maxBlock, block ?? this.posBlock));
|
|
674
|
+
this.maybeUpdateVisBlock();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private updatePos(inline?: number, block?: number, maxInline?: number, maxBlock?: number) {
|
|
678
|
+
this.updatePosInline(inline, maxInline);
|
|
679
|
+
this.updatePosBlock(block, maxBlock);
|
|
680
|
+
}
|
|
268
681
|
|
|
269
|
-
|
|
270
|
-
this.posInline = Math.max(0, this.posInline + deltaX);
|
|
271
|
-
this.posBlock = Math.max(0, this.posBlock + deltaY);
|
|
682
|
+
private handleTopLevelWheel = (event: DxGridAnnotatedWheelEvent) => {
|
|
272
683
|
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
this.posBlock >= this.binBlockMin &&
|
|
276
|
-
this.posBlock < this.binBlockMax
|
|
684
|
+
(Number.isFinite(event.overscrollInline) && this.overscroll === 'inline' && event.overscrollInline === 0) ||
|
|
685
|
+
(Number.isFinite(event.overscrollBlock) && this.overscroll === 'block' && event.overscrollBlock === 0)
|
|
277
686
|
) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
this.
|
|
687
|
+
event.preventDefault();
|
|
688
|
+
event.stopPropagation();
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
private handleWheel = (event: DxGridAnnotatedWheelEvent) => {
|
|
693
|
+
if (this.mode === 'browse') {
|
|
694
|
+
const nextPosInline = this.posInline + event.deltaX;
|
|
695
|
+
const nextPosBlock = this.posBlock + event.deltaY;
|
|
696
|
+
const maxPosInline = this.maxPosInline();
|
|
697
|
+
const maxPosBlock = this.maxPosBlock();
|
|
698
|
+
this.updatePos(nextPosInline, nextPosBlock, maxPosInline, maxPosBlock);
|
|
699
|
+
event.overscrollInline =
|
|
700
|
+
nextPosInline <= 0 ? nextPosInline : nextPosInline > maxPosInline ? nextPosInline - maxPosInline : 0;
|
|
701
|
+
event.overscrollBlock =
|
|
702
|
+
nextPosBlock <= 0 ? nextPosBlock : nextPosBlock > maxPosBlock ? nextPosBlock - maxPosBlock : 0;
|
|
287
703
|
}
|
|
288
704
|
};
|
|
289
705
|
|
|
290
706
|
private updateVisInline() {
|
|
291
707
|
// todo: avoid starting from zero
|
|
292
708
|
let colIndex = 0;
|
|
293
|
-
let pxInline = this.colSize(colIndex);
|
|
709
|
+
let pxInline = this.colSize(colIndex, 'grid');
|
|
294
710
|
|
|
295
711
|
while (pxInline < this.posInline) {
|
|
296
712
|
colIndex += 1;
|
|
297
|
-
pxInline += this.colSize(colIndex) + gap;
|
|
713
|
+
pxInline += this.colSize(colIndex, 'grid') + gap;
|
|
298
714
|
}
|
|
299
715
|
|
|
300
716
|
this.visColMin = colIndex - overscanCol;
|
|
301
717
|
|
|
302
|
-
this.binInlineMin = pxInline - this.colSize(colIndex) - gap;
|
|
718
|
+
this.binInlineMin = pxInline - this.colSize(colIndex, 'grid') - gap;
|
|
303
719
|
this.binInlineMax = pxInline + gap;
|
|
304
720
|
|
|
305
721
|
this.overscanInline =
|
|
306
722
|
[...Array(overscanCol)].reduce((acc, _, c0) => {
|
|
307
|
-
acc += this.colSize(this.visColMin + c0);
|
|
723
|
+
acc += this.colSize(this.visColMin + c0, 'grid');
|
|
308
724
|
return acc;
|
|
309
725
|
}, 0) +
|
|
310
726
|
gap * (overscanCol - 1);
|
|
311
727
|
|
|
312
|
-
while (pxInline < this.binInlineMax + this.sizeInline
|
|
728
|
+
while (pxInline < this.binInlineMax + this.sizeInline - gap * 2) {
|
|
313
729
|
colIndex += 1;
|
|
314
|
-
pxInline += this.colSize(colIndex) + gap;
|
|
730
|
+
pxInline += this.colSize(colIndex, 'grid') + gap;
|
|
315
731
|
}
|
|
316
732
|
|
|
317
|
-
this.visColMax = colIndex + overscanCol;
|
|
733
|
+
this.visColMax = Math.min(this.limitColumns, colIndex + overscanCol);
|
|
318
734
|
|
|
319
|
-
this.
|
|
320
|
-
.map((_, c0) => `${this.colSize(this.visColMin + c0)}px`)
|
|
735
|
+
this.templateGridColumns = [...Array(this.visColMax - this.visColMin)]
|
|
736
|
+
.map((_, c0) => `${this.colSize(this.visColMin + c0, 'grid')}px`)
|
|
737
|
+
.join(' ');
|
|
738
|
+
|
|
739
|
+
this.templatefrozenColsStart = [...Array(this.frozen.frozenColsStart ?? 0)]
|
|
740
|
+
.map((_, c0) => `${this.colSize(c0, 'frozenColsStart')}px`)
|
|
741
|
+
.join(' ');
|
|
742
|
+
|
|
743
|
+
this.templatefrozenColsEnd = [...Array(this.frozen.frozenColsEnd ?? 0)]
|
|
744
|
+
.map((_, c0) => `${this.colSize(c0, 'frozenColsEnd')}px`)
|
|
321
745
|
.join(' ');
|
|
322
746
|
}
|
|
323
747
|
|
|
324
748
|
private updateVisBlock() {
|
|
325
749
|
// todo: avoid starting from zero
|
|
326
750
|
let rowIndex = 0;
|
|
327
|
-
let pxBlock = this.rowSize(rowIndex);
|
|
751
|
+
let pxBlock = this.rowSize(rowIndex, 'grid');
|
|
328
752
|
|
|
329
753
|
while (pxBlock < this.posBlock) {
|
|
330
754
|
rowIndex += 1;
|
|
331
|
-
pxBlock += this.rowSize(rowIndex) + gap;
|
|
755
|
+
pxBlock += this.rowSize(rowIndex, 'grid') + gap;
|
|
332
756
|
}
|
|
333
757
|
|
|
334
758
|
this.visRowMin = rowIndex - overscanRow;
|
|
335
759
|
|
|
336
|
-
this.binBlockMin = pxBlock - this.rowSize(rowIndex) - gap;
|
|
760
|
+
this.binBlockMin = pxBlock - this.rowSize(rowIndex, 'grid') - gap;
|
|
337
761
|
this.binBlockMax = pxBlock + gap;
|
|
338
762
|
|
|
339
763
|
this.overscanBlock =
|
|
340
764
|
[...Array(overscanRow)].reduce((acc, _, r0) => {
|
|
341
|
-
acc += this.rowSize(this.visRowMin + r0);
|
|
765
|
+
acc += this.rowSize(this.visRowMin + r0, 'grid');
|
|
342
766
|
return acc;
|
|
343
767
|
}, 0) +
|
|
344
768
|
gap * (overscanRow - 1);
|
|
345
769
|
|
|
346
|
-
while (pxBlock < this.binBlockMax + this.sizeBlock) {
|
|
770
|
+
while (pxBlock < this.binBlockMax + this.sizeBlock - gap * 2) {
|
|
347
771
|
rowIndex += 1;
|
|
348
|
-
pxBlock += this.rowSize(rowIndex) + gap;
|
|
772
|
+
pxBlock += this.rowSize(rowIndex, 'grid') + gap;
|
|
349
773
|
}
|
|
350
774
|
|
|
351
|
-
this.visRowMax = rowIndex + overscanRow;
|
|
775
|
+
this.visRowMax = Math.min(this.limitRows, rowIndex + overscanRow);
|
|
776
|
+
|
|
777
|
+
this.templateGridRows = [...Array(this.visRowMax - this.visRowMin)]
|
|
778
|
+
.map((_, r0) => `${this.rowSize(this.visRowMin + r0, 'grid')}px`)
|
|
779
|
+
.join(' ');
|
|
780
|
+
|
|
781
|
+
this.templatefrozenRowsStart = [...Array(this.frozen.frozenRowsStart ?? 0)]
|
|
782
|
+
.map((_, r0) => `${this.rowSize(r0, 'frozenRowsStart')}px`)
|
|
783
|
+
.join(' ');
|
|
352
784
|
|
|
353
|
-
this.
|
|
354
|
-
.map((_, r0) => `${this.rowSize(
|
|
785
|
+
this.templatefrozenRowsEnd = [...Array(this.frozen.frozenRowsEnd ?? 0)]
|
|
786
|
+
.map((_, r0) => `${this.rowSize(r0, 'frozenRowsEnd')}px`)
|
|
355
787
|
.join(' ');
|
|
356
788
|
}
|
|
357
789
|
|
|
@@ -360,130 +792,276 @@ export class DxGrid extends LitElement {
|
|
|
360
792
|
this.updateVisBlock();
|
|
361
793
|
}
|
|
362
794
|
|
|
363
|
-
|
|
795
|
+
private updateCells(includeFixed?: boolean) {
|
|
796
|
+
this.cells.grid = this.getCells!(
|
|
797
|
+
{
|
|
798
|
+
start: { col: this.visColMin, row: this.visRowMin },
|
|
799
|
+
end: { col: this.visColMax, row: this.visRowMax },
|
|
800
|
+
},
|
|
801
|
+
'grid',
|
|
802
|
+
);
|
|
803
|
+
Object.entries(this.frozen)
|
|
804
|
+
.filter(([_, limit]) => limit && limit > 0)
|
|
805
|
+
.forEach(([plane, limit]) => {
|
|
806
|
+
this.cells[plane as DxGridFrozenPlane] = this.getCells!(
|
|
807
|
+
plane.startsWith('frozenRows')
|
|
808
|
+
? {
|
|
809
|
+
start: { col: this.visColMin, row: 0 },
|
|
810
|
+
end: { col: this.visColMax, row: limit },
|
|
811
|
+
}
|
|
812
|
+
: {
|
|
813
|
+
start: { col: 0, row: this.visRowMin },
|
|
814
|
+
end: { col: limit, row: this.visRowMax },
|
|
815
|
+
},
|
|
816
|
+
plane as DxGridFrozenPlane,
|
|
817
|
+
);
|
|
818
|
+
});
|
|
819
|
+
if (includeFixed) {
|
|
820
|
+
if ((this.frozen.frozenColsStart ?? 0) > 0 && (this.frozen.frozenRowsStart ?? 0) > 0) {
|
|
821
|
+
this.cells.fixedStartStart = this.getCells!(
|
|
822
|
+
{
|
|
823
|
+
start: { col: 0, row: 0 },
|
|
824
|
+
end: { col: this.frozen.frozenColsStart!, row: this.frozen.frozenRowsStart! },
|
|
825
|
+
},
|
|
826
|
+
'fixedStartStart',
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if ((this.frozen.frozenColsEnd ?? 0) > 0 && (this.frozen.frozenRowsStart ?? 0) > 0) {
|
|
830
|
+
this.cells.fixedStartEnd = this.getCells!(
|
|
831
|
+
{
|
|
832
|
+
start: { col: 0, row: 0 },
|
|
833
|
+
end: { col: this.frozen.frozenColsEnd!, row: this.frozen.frozenRowsStart! },
|
|
834
|
+
},
|
|
835
|
+
'fixedStartEnd',
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
if ((this.frozen.frozenColsStart ?? 0) > 0 && (this.frozen.frozenRowsEnd ?? 0) > 0) {
|
|
839
|
+
this.cells.fixedEndStart = this.getCells!(
|
|
840
|
+
{
|
|
841
|
+
start: { col: 0, row: 0 },
|
|
842
|
+
end: { col: this.frozen.frozenColsStart!, row: this.frozen.frozenRowsEnd! },
|
|
843
|
+
},
|
|
844
|
+
'fixedEndStart',
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if ((this.frozen.frozenColsEnd ?? 0) > 0 && (this.frozen.frozenRowsEnd ?? 0) > 0) {
|
|
848
|
+
this.cells.fixedEndEnd = this.getCells!(
|
|
849
|
+
{
|
|
850
|
+
start: { col: 0, row: 0 },
|
|
851
|
+
end: { col: this.frozen.frozenColsEnd!, row: this.frozen.frozenRowsEnd! },
|
|
852
|
+
},
|
|
853
|
+
'fixedEndEnd',
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
364
858
|
|
|
365
|
-
|
|
366
|
-
focusedCell: Record<DxGridAxis, number> = { col: 0, row: 0 };
|
|
859
|
+
// Focus handlers
|
|
367
860
|
|
|
368
|
-
|
|
369
|
-
|
|
861
|
+
setFocus(coords: DxGridPosition, snap = true) {
|
|
862
|
+
this.setFocusedCell(coords);
|
|
863
|
+
this.focusActive = true;
|
|
864
|
+
if (snap) {
|
|
865
|
+
this.snapPosToFocusedCell();
|
|
866
|
+
}
|
|
867
|
+
}
|
|
370
868
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const action = target.getAttribute('data-dx-grid-action');
|
|
375
|
-
if (action === 'cell') {
|
|
376
|
-
const c = parseInt(target.getAttribute('aria-colindex') ?? 'never');
|
|
377
|
-
const r = parseInt(target.getAttribute('aria-rowindex') ?? 'never');
|
|
378
|
-
this.focusedCell = { col: c, row: r };
|
|
869
|
+
private handleFocus(event: FocusEvent) {
|
|
870
|
+
const cellCoords = closestCell(event.target);
|
|
871
|
+
if (cellCoords) {
|
|
379
872
|
this.focusActive = true;
|
|
873
|
+
this.setFocusedCell(cellCoords);
|
|
380
874
|
}
|
|
381
875
|
}
|
|
382
876
|
|
|
383
|
-
|
|
384
|
-
handleBlur(event: FocusEvent) {
|
|
877
|
+
private handleBlur(event: FocusEvent) {
|
|
385
878
|
// Only unset `focusActive` if focus is not moving to an element within the grid.
|
|
386
|
-
if (
|
|
387
|
-
!event.relatedTarget ||
|
|
388
|
-
(event.relatedTarget as HTMLElement).closest('.dx-grid__viewport') !== this.viewportRef.value
|
|
389
|
-
) {
|
|
879
|
+
if (!event.relatedTarget || !(event.relatedTarget as HTMLElement).closest(`[data-grid="${this.gridId}"]`)) {
|
|
390
880
|
this.focusActive = false;
|
|
391
881
|
}
|
|
392
882
|
}
|
|
393
883
|
|
|
884
|
+
private focusedCellQuery() {
|
|
885
|
+
return `[data-dx-grid-plane=${this.focusedCell.plane}] > [aria-colindex="${this.focusedCell.col}"][aria-rowindex="${this.focusedCell.row}"]`;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
private focusedCellElement() {
|
|
889
|
+
return this.viewportRef.value?.querySelector(this.focusedCellQuery()) as HTMLElement | null;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
//
|
|
893
|
+
// `outOfVis` returns by how many rows/cols the focused cell is outside of the `vis` range for an axis, inset by a
|
|
894
|
+
// `delta`, otherwise zero if it is within that range.
|
|
895
|
+
//
|
|
896
|
+
|
|
897
|
+
private focusedCellRowOutOfVis(minDelta = 0, maxDelta = minDelta) {
|
|
898
|
+
return this.focusedCell.row <= this.visRowMin + minDelta
|
|
899
|
+
? this.focusedCell.row - (this.visRowMin + minDelta)
|
|
900
|
+
: this.focusedCell.row >= this.visRowMax - maxDelta
|
|
901
|
+
? -(this.focusedCell.row - this.visRowMax - maxDelta)
|
|
902
|
+
: 0;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
private focusedCellColOutOfVis(minDelta = 0, maxDelta = minDelta) {
|
|
906
|
+
return this.focusedCell.col <= this.visColMin + minDelta
|
|
907
|
+
? this.focusedCell.col - (this.visColMin + minDelta)
|
|
908
|
+
: this.focusedCell.col >= this.visColMax - maxDelta
|
|
909
|
+
? -(this.focusedCell.col - this.visColMax - maxDelta)
|
|
910
|
+
: 0;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
private focusedCellOutOfVis(colDelta = 0, rowDelta = colDelta): { col: number; row: number } {
|
|
914
|
+
switch (this.focusedCell.plane) {
|
|
915
|
+
case 'grid':
|
|
916
|
+
return { row: this.focusedCellRowOutOfVis(rowDelta), col: this.focusedCellColOutOfVis(colDelta) };
|
|
917
|
+
case 'frozenRowsStart':
|
|
918
|
+
case 'frozenRowsEnd':
|
|
919
|
+
return { col: this.focusedCellColOutOfVis(colDelta), row: 0 };
|
|
920
|
+
case 'frozenColsStart':
|
|
921
|
+
case 'frozenColsEnd':
|
|
922
|
+
return { col: 0, row: this.focusedCellRowOutOfVis(rowDelta) };
|
|
923
|
+
default:
|
|
924
|
+
return { col: 0, row: 0 };
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
394
928
|
/**
|
|
395
929
|
* Moves focus to the cell with actual focus, otherwise moves focus to the viewport.
|
|
396
930
|
*/
|
|
397
|
-
refocus() {
|
|
398
|
-
(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
931
|
+
refocus(increment?: 'col' | 'row', delta: 1 | -1 = 1) {
|
|
932
|
+
if (increment) {
|
|
933
|
+
switch (increment) {
|
|
934
|
+
case 'col': {
|
|
935
|
+
this.focusedCell.col += delta;
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
case 'row': {
|
|
939
|
+
this.focusedCell.row += delta;
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
this.snapPosToFocusedCell();
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
queueMicrotask(() => {
|
|
947
|
+
const outOfVis = this.focusedCellOutOfVis();
|
|
948
|
+
const cellVisible = outOfVis.col === 0 && outOfVis.row === 0;
|
|
949
|
+
if (cellVisible) {
|
|
950
|
+
const cellElement = this.focusedCellElement();
|
|
951
|
+
if (cellElement && cellElement !== document.activeElement) {
|
|
952
|
+
cellElement.focus({ preventScroll: true });
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
this.viewportRef.value?.focus({ preventScroll: true });
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
private findPosInlineFromVisColMin(deltaCols: number) {
|
|
961
|
+
return [...Array(deltaCols)].reduce(
|
|
962
|
+
(acc, _, c0) => acc - this.colSize(this.visColMin - c0, 'grid') - gap,
|
|
963
|
+
this.binInlineMin + gap,
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
private findPosBlockFromVisRowMin(deltaRows: number) {
|
|
968
|
+
return [...Array(deltaRows)].reduce(
|
|
969
|
+
(acc, _, r0) => acc - this.rowSize(this.visRowMin - r0, 'grid') - gap,
|
|
970
|
+
this.binBlockMin + gap,
|
|
971
|
+
);
|
|
407
972
|
}
|
|
408
973
|
|
|
409
974
|
/**
|
|
410
975
|
* Updates `pos` so that a cell in focus is fully within the viewport
|
|
411
976
|
*/
|
|
412
977
|
snapPosToFocusedCell() {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
this.
|
|
416
|
-
this.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
this.
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
// this.focusedCell,
|
|
429
|
-
// [this.visColMin, this.visColMax, overscanCol],
|
|
430
|
-
// [this.visRowMin, this.visRowMax, overscanRow],
|
|
431
|
-
// );
|
|
432
|
-
} else {
|
|
433
|
-
if (this.focusedCell.col <= this.visColMin + overscanCol) {
|
|
434
|
-
this.posInline = this.binInlineMin;
|
|
435
|
-
this.updateVisInline();
|
|
436
|
-
} else if (this.focusedCell.col >= this.visColMax - overscanCol - 1) {
|
|
437
|
-
const sizeSumCol = [...Array(this.focusedCell.col - this.visColMin)].reduce((acc, _, c0) => {
|
|
438
|
-
acc += this.colSize(this.visColMin + overscanCol + c0) + gap;
|
|
439
|
-
return acc;
|
|
440
|
-
}, 0);
|
|
441
|
-
this.posInline = this.binInlineMin + sizeSumCol + gap * 2 - this.sizeInline;
|
|
442
|
-
this.updateVisInline();
|
|
443
|
-
}
|
|
978
|
+
const outOfVis = this.focusedCellOutOfVis(overscanCol, overscanRow);
|
|
979
|
+
if (outOfVis.col < 0) {
|
|
980
|
+
this.posInline = this.findPosInlineFromVisColMin(-outOfVis.col);
|
|
981
|
+
this.updateVisInline();
|
|
982
|
+
} else if (outOfVis.col > 0) {
|
|
983
|
+
const sizeSumCol = [...Array(this.focusedCell.col - this.visColMin)].reduce((acc, _, c0) => {
|
|
984
|
+
acc += this.colSize(this.visColMin + overscanCol + c0, 'grid') + gap;
|
|
985
|
+
return acc;
|
|
986
|
+
}, 0);
|
|
987
|
+
this.posInline = Math.max(
|
|
988
|
+
0,
|
|
989
|
+
Math.min(this.intrinsicInlineSize - this.sizeInline, this.binInlineMin + sizeSumCol - this.sizeInline),
|
|
990
|
+
);
|
|
991
|
+
this.updateVisInline();
|
|
992
|
+
}
|
|
444
993
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
994
|
+
if (outOfVis.row < 0) {
|
|
995
|
+
this.posBlock = this.findPosBlockFromVisRowMin(-outOfVis.row);
|
|
996
|
+
this.updateVisBlock();
|
|
997
|
+
} else if (outOfVis.row > 0) {
|
|
998
|
+
const sizeSumRow = [...Array(this.focusedCell.row - this.visRowMin)].reduce((acc, _, r0) => {
|
|
999
|
+
acc += this.rowSize(this.visRowMin + overscanRow + r0, 'grid') + gap;
|
|
1000
|
+
return acc;
|
|
1001
|
+
}, 0);
|
|
1002
|
+
this.posBlock = Math.max(
|
|
1003
|
+
0,
|
|
1004
|
+
Math.min(this.intrinsicBlockSize - this.sizeBlock, this.binBlockMin + sizeSumRow - this.sizeBlock),
|
|
1005
|
+
);
|
|
1006
|
+
this.updateVisBlock();
|
|
456
1007
|
}
|
|
457
1008
|
}
|
|
458
1009
|
|
|
459
|
-
//
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1010
|
+
//
|
|
1011
|
+
// Map scroll DOM methods to virtualized value.
|
|
1012
|
+
//
|
|
1013
|
+
|
|
1014
|
+
override get scrollLeft() {
|
|
1015
|
+
return this.posInline;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
override set scrollLeft(nextValue: number) {
|
|
1019
|
+
this.posInline = nextValue;
|
|
1020
|
+
this.maybeUpdateVisInline();
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
override get scrollTop() {
|
|
1024
|
+
return this.posBlock;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
override set scrollTop(nextValue: number) {
|
|
1028
|
+
this.posBlock = nextValue;
|
|
1029
|
+
this.maybeUpdateVisBlock();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
//
|
|
1033
|
+
// Resize handlers
|
|
1034
|
+
//
|
|
1035
|
+
|
|
1036
|
+
private axisResizeable(plane: 'grid' | DxGridFrozenPlane, axis: DxGridAxis, index: number | string) {
|
|
1037
|
+
return axis === 'col'
|
|
1038
|
+
? !!(this.columns[plane]?.[index]?.resizeable ?? this.columnDefault[plane as DxGridFrozenColsPlane]?.resizeable)
|
|
1039
|
+
: !!(this.rows[plane]?.[index]?.resizeable ?? this.rowDefault[plane as DxGridFrozenRowsPlane]?.resizeable);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
private handleAxisResizeInternal(event: DxAxisResizeInternal) {
|
|
1043
|
+
event.stopPropagation();
|
|
1044
|
+
const { plane, axis, delta, size, index, state } = event;
|
|
1045
|
+
if (axis === 'col') {
|
|
1046
|
+
const nextSize = Math.max(sizeColMin, Math.min(sizeColMax, size + delta));
|
|
1047
|
+
this.colSizes = { ...this.colSizes, [plane]: { ...this.colSizes[plane], [index]: nextSize } };
|
|
1048
|
+
this.updateVisInline();
|
|
1049
|
+
this.updateIntrinsicInlineSize();
|
|
1050
|
+
} else {
|
|
1051
|
+
const nextSize = Math.max(sizeRowMin, Math.min(sizeRowMax, size + delta));
|
|
1052
|
+
this.rowSizes = { ...this.colSizes, [plane]: { ...this.rowSizes[plane], [index]: nextSize } };
|
|
1053
|
+
this.updateVisBlock();
|
|
1054
|
+
this.updateIntrinsicBlockSize();
|
|
1055
|
+
}
|
|
1056
|
+
if (state === 'dropped') {
|
|
1057
|
+
this.dispatchEvent(
|
|
1058
|
+
new DxAxisResize({
|
|
1059
|
+
plane,
|
|
1060
|
+
axis,
|
|
1061
|
+
index,
|
|
1062
|
+
size: this[`${axis}Size`](index, plane),
|
|
1063
|
+
}),
|
|
1064
|
+
);
|
|
487
1065
|
}
|
|
488
1066
|
}
|
|
489
1067
|
|
|
@@ -491,116 +1069,300 @@ export class DxGrid extends LitElement {
|
|
|
491
1069
|
// Render and other lifecycle methods
|
|
492
1070
|
//
|
|
493
1071
|
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
role="none"
|
|
502
|
-
class="dx-grid"
|
|
503
|
-
@pointerdown=${this.handlePointerDown}
|
|
504
|
-
@pointerup=${this.handlePointerUp}
|
|
505
|
-
@pointermove=${this.handlePointerMove}
|
|
506
|
-
@focus=${this.handleFocus}
|
|
507
|
-
@blur=${this.handleBlur}
|
|
508
|
-
@keydown=${this.handleKeydown}
|
|
509
|
-
>
|
|
510
|
-
<div role="none" class="dx-grid__corner"></div>
|
|
511
|
-
<div role="none" class="dx-grid__columnheader">
|
|
512
|
-
<div
|
|
513
|
-
role="none"
|
|
514
|
-
class="dx-grid__columnheader__content"
|
|
515
|
-
style="transform:translate3d(${offsetInline}px,0,0);grid-template-columns:${this.templateColumns};"
|
|
516
|
-
>
|
|
517
|
-
${[...Array(visibleCols)].map((_, c0) => {
|
|
518
|
-
const c = this.visColMin + c0;
|
|
519
|
-
return html`<div
|
|
520
|
-
role="columnheader"
|
|
521
|
-
?inert=${c < 0}
|
|
522
|
-
style="block-size:${this.rowDefault.size}px;grid-column:${c0 + 1}/${c0 + 2};"
|
|
523
|
-
>
|
|
524
|
-
<span id=${localChId(c0)}>${colToA1Notation(c)}</span>
|
|
525
|
-
${(this.columns[c]?.resizeable ?? this.columnDefault.resizeable) &&
|
|
526
|
-
html`<button class="dx-grid__resize-handle" data-dx-grid-action=${`resize-col,${c}`}>
|
|
527
|
-
<span class="sr-only">Resize</span>
|
|
528
|
-
</button>`}
|
|
529
|
-
</div>`;
|
|
530
|
-
})}
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
533
|
-
<div role="none" class="dx-grid__corner"></div>
|
|
534
|
-
<div role="none" class="dx-grid__rowheader">
|
|
535
|
-
<div
|
|
1072
|
+
private renderFixed(plane: DxGridFixedPlane, selection: DxGridSelectionProps) {
|
|
1073
|
+
const colPlane = resolveColPlane(plane) as DxGridFrozenPlane;
|
|
1074
|
+
const rowPlane = resolveRowPlane(plane) as DxGridFrozenPlane;
|
|
1075
|
+
const cols = this.frozen[colPlane];
|
|
1076
|
+
const rows = this.frozen[rowPlane];
|
|
1077
|
+
return (cols ?? 0) > 0 && (rows ?? 0) > 0
|
|
1078
|
+
? html`<div
|
|
536
1079
|
role="none"
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return html`<div role="rowheader" ?inert=${r < 0} style="grid-row:${r0 + 1}/${r0 + 2}">
|
|
543
|
-
<span id=${localRhId(r0)}>${rowToA1Notation(r)}</span>
|
|
544
|
-
${(this.rows[r]?.resizeable ?? this.rowDefault.resizeable) &&
|
|
545
|
-
html`<button class="dx-grid__resize-handle" data-dx-grid-action=${`resize-row,${r}`}>
|
|
546
|
-
<span class="sr-only">Resize</span>
|
|
547
|
-
</button>`}
|
|
548
|
-
</div>`;
|
|
1080
|
+
data-dx-grid-plane=${plane}
|
|
1081
|
+
class="dx-grid__plane--fixed"
|
|
1082
|
+
style=${styleMap({
|
|
1083
|
+
'grid-template-columns': this[`template${colPlane}`],
|
|
1084
|
+
'grid-template-rows': this[`template${rowPlane}`],
|
|
549
1085
|
})}
|
|
550
|
-
</div>
|
|
551
|
-
</div>
|
|
552
|
-
<div role="grid" class="dx-grid__viewport" tabindex="0" @wheel=${this.handleWheel} ${ref(this.viewportRef)}>
|
|
553
|
-
<div
|
|
554
|
-
role="none"
|
|
555
|
-
class="dx-grid__content"
|
|
556
|
-
style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
|
|
557
|
-
.templateColumns};grid-template-rows:${this.templateRows};"
|
|
558
1086
|
>
|
|
559
|
-
${[...Array(
|
|
560
|
-
return [...Array(
|
|
561
|
-
|
|
562
|
-
const r = r0 + this.visRowMin;
|
|
563
|
-
const cell = this.getCell(c, r);
|
|
564
|
-
return html`<div
|
|
565
|
-
role="gridcell"
|
|
566
|
-
tabindex="0"
|
|
567
|
-
?inert=${c < 0 || r < 0}
|
|
568
|
-
aria-rowindex=${r}
|
|
569
|
-
aria-colindex=${c}
|
|
570
|
-
data-dx-grid-action="cell"
|
|
571
|
-
style="grid-column:${c0 + 1};grid-row:${r0 + 1}"
|
|
572
|
-
>
|
|
573
|
-
${cell?.value}
|
|
574
|
-
</div>`;
|
|
1087
|
+
${[...Array(rows)].map((_, r) => {
|
|
1088
|
+
return [...Array(cols)].map((_, c) => {
|
|
1089
|
+
return this.renderCell(c, r, plane, cellSelected(c, r, plane, selection));
|
|
575
1090
|
});
|
|
576
1091
|
})}
|
|
577
|
-
</div
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
1092
|
+
</div>`
|
|
1093
|
+
: null;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
private renderFrozenRows(
|
|
1097
|
+
plane: DxGridFrozenRowsPlane,
|
|
1098
|
+
visibleCols: number,
|
|
1099
|
+
offsetInline: number,
|
|
1100
|
+
selection: DxGridSelectionProps,
|
|
1101
|
+
) {
|
|
1102
|
+
const rowPlane = resolveRowPlane(plane) as DxGridFrozenPlane;
|
|
1103
|
+
const rows = this.frozen[rowPlane];
|
|
1104
|
+
return (rows ?? 0) > 0
|
|
1105
|
+
? html`<div role="none" class="dx-grid__plane--frozen-row">
|
|
1106
|
+
<div
|
|
1107
|
+
role="none"
|
|
1108
|
+
data-dx-grid-plane=${plane}
|
|
1109
|
+
class="dx-grid__plane--frozen-row__content"
|
|
1110
|
+
style="transform:translate3d(${offsetInline}px,0,0);grid-template-columns:${this
|
|
1111
|
+
.templateGridColumns};grid-template-rows:${this[`template${rowPlane}`]}"
|
|
1112
|
+
>
|
|
1113
|
+
${[...Array(rows)].map((_, r) => {
|
|
1114
|
+
return [...Array(visibleCols)].map((_, c0) => {
|
|
1115
|
+
const c = this.visColMin + c0;
|
|
1116
|
+
return this.renderCell(c, r, plane, cellSelected(c, r, plane, selection), c0, r);
|
|
1117
|
+
});
|
|
1118
|
+
})}
|
|
1119
|
+
</div>
|
|
1120
|
+
</div>`
|
|
1121
|
+
: null;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private renderFrozenColumns(
|
|
1125
|
+
plane: DxGridFrozenColsPlane,
|
|
1126
|
+
visibleRows: number,
|
|
1127
|
+
offsetBlock: number,
|
|
1128
|
+
selection: DxGridSelectionProps,
|
|
1129
|
+
) {
|
|
1130
|
+
const colPlane = resolveColPlane(plane) as DxGridFrozenPlane;
|
|
1131
|
+
const cols = this.frozen[colPlane];
|
|
1132
|
+
return (cols ?? 0) > 0
|
|
1133
|
+
? html`<div role="none" class="dx-grid__plane--frozen-col">
|
|
1134
|
+
<div
|
|
1135
|
+
role="none"
|
|
1136
|
+
data-dx-grid-plane=${plane}
|
|
1137
|
+
class="dx-grid__plane--frozen-col__content"
|
|
1138
|
+
style="transform:translate3d(0,${offsetBlock}px,0);grid-template-rows:${this
|
|
1139
|
+
.templateGridRows};grid-template-columns:${this[`template${colPlane}`]}"
|
|
1140
|
+
>
|
|
1141
|
+
${[...Array(visibleRows)].map((_, r0) => {
|
|
1142
|
+
return [...Array(cols)].map((_, c) => {
|
|
1143
|
+
const r = this.visRowMin + r0;
|
|
1144
|
+
return this.renderCell(c, r, plane, cellSelected(c, r, plane, selection), c, r0);
|
|
1145
|
+
});
|
|
1146
|
+
})}
|
|
1147
|
+
</div>
|
|
1148
|
+
</div>`
|
|
1149
|
+
: null;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
private cellReadonly(col: number, row: number, plane: DxGridPlane) {
|
|
1153
|
+
const colPlane = resolveColPlane(plane);
|
|
1154
|
+
const rowPlane = resolveRowPlane(plane);
|
|
1155
|
+
return (
|
|
1156
|
+
(this.columns?.[colPlane]?.[col]?.readonly ?? this.columnDefault?.[colPlane]?.readonly) ||
|
|
1157
|
+
(this.rows?.[rowPlane]?.[row]?.readonly ?? this.rowDefault?.[rowPlane]?.readonly)
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
private renderCell(col: number, row: number, plane: DxGridPlane, selected?: boolean, visCol = col, visRow = row) {
|
|
1162
|
+
const cell = this.cell(col, row, plane);
|
|
1163
|
+
const active = this.cellActive(col, row, plane);
|
|
1164
|
+
const readonly = this.cellReadonly(col, row, plane);
|
|
1165
|
+
const resizeIndex = cell?.resizeHandle ? (cell.resizeHandle === 'col' ? col : row) : undefined;
|
|
1166
|
+
const resizePlane = cell?.resizeHandle ? resolveFrozenPlane(cell.resizeHandle, plane) : undefined;
|
|
1167
|
+
const accessory = cell?.accessoryHtml ? staticHtml`${unsafeStatic(cell.accessoryHtml)}` : null;
|
|
1168
|
+
return html`<div
|
|
1169
|
+
role="gridcell"
|
|
1170
|
+
tabindex="0"
|
|
1171
|
+
?inert=${col < 0 || row < 0}
|
|
1172
|
+
aria-selected=${selected ? 'true' : nothing}
|
|
1173
|
+
aria-readonly=${readonly ? 'true' : nothing}
|
|
1174
|
+
class=${cell?.className ?? nothing}
|
|
1175
|
+
data-refs=${cell?.dataRefs ?? nothing}
|
|
1176
|
+
?data-dx-active=${active}
|
|
1177
|
+
data-dx-grid-action="cell"
|
|
1178
|
+
aria-colindex=${col}
|
|
1179
|
+
aria-rowindex=${row}
|
|
1180
|
+
style="grid-column:${visCol + 1};grid-row:${visRow + 1}"
|
|
1181
|
+
>
|
|
1182
|
+
${this.mode === 'edit' && active ? null : cell?.value}${this.mode === 'edit' && active
|
|
1183
|
+
? null
|
|
1184
|
+
: accessory}${cell?.resizeHandle && this.axisResizeable(resizePlane!, cell.resizeHandle, resizeIndex!)
|
|
1185
|
+
? html`<dx-grid-axis-resize-handle
|
|
1186
|
+
axis=${cell.resizeHandle}
|
|
1187
|
+
plane=${resizePlane}
|
|
1188
|
+
index=${resizeIndex}
|
|
1189
|
+
size=${this[`${cell.resizeHandle}Size`](resizeIndex!, plane)}
|
|
1190
|
+
></dx-grid-axis-resize-handle>`
|
|
1191
|
+
: null}
|
|
587
1192
|
</div>`;
|
|
588
1193
|
}
|
|
589
1194
|
|
|
1195
|
+
override render() {
|
|
1196
|
+
const visibleCols = this.visColMax - this.visColMin;
|
|
1197
|
+
const visibleRows = this.visRowMax - this.visRowMin;
|
|
1198
|
+
const offsetInline = this.binInlineMin - this.posInline - this.overscanInline;
|
|
1199
|
+
const offsetBlock = this.binBlockMin - this.posBlock - this.overscanBlock;
|
|
1200
|
+
const selection = selectionProps(this.selectionStart, this.selectionEnd);
|
|
1201
|
+
|
|
1202
|
+
return html`<style>
|
|
1203
|
+
${this.activeRefs
|
|
1204
|
+
.split(' ')
|
|
1205
|
+
.filter((value) => value)
|
|
1206
|
+
.map(
|
|
1207
|
+
(activeRef) =>
|
|
1208
|
+
`[data-refs~="${activeRef}"] { background: var(--dx-grid-commented-active, var(--dx-gridCommentedActive)) !important; }`,
|
|
1209
|
+
)
|
|
1210
|
+
.join('\n')}
|
|
1211
|
+
</style>
|
|
1212
|
+
<div
|
|
1213
|
+
role="none"
|
|
1214
|
+
class="dx-grid"
|
|
1215
|
+
style=${styleMap({
|
|
1216
|
+
'grid-template-columns': `${this.templatefrozenColsStart ? 'min-content ' : ''}minmax(0, ${
|
|
1217
|
+
Number.isFinite(this.limitColumns) ? `${this.intrinsicInlineSize}px` : '1fr'
|
|
1218
|
+
})${this.templatefrozenColsEnd ? ' min-content' : ''}`,
|
|
1219
|
+
'grid-template-rows': `${this.templatefrozenRowsStart ? 'min-content ' : ''}minmax(0, ${
|
|
1220
|
+
Number.isFinite(this.limitRows) ? `${this.intrinsicBlockSize}px` : '1fr'
|
|
1221
|
+
})${this.templatefrozenRowsEnd ? ' min-content' : ''}`,
|
|
1222
|
+
})}
|
|
1223
|
+
data-grid=${this.gridId}
|
|
1224
|
+
data-grid-mode=${this.mode}
|
|
1225
|
+
?data-grid-select=${selection.visible}
|
|
1226
|
+
>
|
|
1227
|
+
${this.renderFixed('fixedStartStart', selection)}${this.renderFrozenRows(
|
|
1228
|
+
'frozenRowsStart',
|
|
1229
|
+
visibleCols,
|
|
1230
|
+
offsetInline,
|
|
1231
|
+
selection,
|
|
1232
|
+
)}${this.renderFixed('fixedStartEnd', selection)}${this.renderFrozenColumns(
|
|
1233
|
+
'frozenColsStart',
|
|
1234
|
+
visibleRows,
|
|
1235
|
+
offsetBlock,
|
|
1236
|
+
selection,
|
|
1237
|
+
)}
|
|
1238
|
+
<div role="grid" class="dx-grid__plane--grid" tabindex="0" ${ref(this.viewportRef)}>
|
|
1239
|
+
<div
|
|
1240
|
+
role="none"
|
|
1241
|
+
class="dx-grid__plane--grid__content"
|
|
1242
|
+
data-dx-grid-plane="grid"
|
|
1243
|
+
style="transform:translate3d(${offsetInline}px,${offsetBlock}px,0);grid-template-columns:${this
|
|
1244
|
+
.templateGridColumns};grid-template-rows:${this.templateGridRows};"
|
|
1245
|
+
>
|
|
1246
|
+
${[...Array(visibleRows)].map((_, r0) => {
|
|
1247
|
+
return [...Array(visibleCols)].map((_, c0) => {
|
|
1248
|
+
const c = c0 + this.visColMin;
|
|
1249
|
+
const r = r0 + this.visRowMin;
|
|
1250
|
+
return this.renderCell(c, r, 'grid', cellSelected(c, r, 'grid', selection), c0, r0);
|
|
1251
|
+
});
|
|
1252
|
+
})}
|
|
1253
|
+
</div>
|
|
1254
|
+
</div>
|
|
1255
|
+
${this.renderFrozenColumns('frozenColsEnd', visibleRows, offsetBlock, selection)}${this.renderFixed(
|
|
1256
|
+
'fixedEndStart',
|
|
1257
|
+
selection,
|
|
1258
|
+
)}${this.renderFrozenRows('frozenRowsEnd', visibleCols, offsetInline, selection)}${this.renderFixed(
|
|
1259
|
+
'fixedEndEnd',
|
|
1260
|
+
selection,
|
|
1261
|
+
)}
|
|
1262
|
+
</div>`;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
private updateIntrinsicInlineSize() {
|
|
1266
|
+
this.intrinsicInlineSize = Number.isFinite(this.limitColumns)
|
|
1267
|
+
? [...Array(this.limitColumns)].reduce((acc, _, c0) => acc + this.colSize(c0, 'grid'), 0) +
|
|
1268
|
+
gap * (this.limitColumns - 1)
|
|
1269
|
+
: Infinity;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
private updateIntrinsicBlockSize() {
|
|
1273
|
+
this.intrinsicBlockSize = Number.isFinite(this.limitRows)
|
|
1274
|
+
? [...Array(this.limitRows)].reduce((acc, _, r0) => acc + this.rowSize(r0, 'grid'), 0) +
|
|
1275
|
+
gap * (this.limitRows - 1)
|
|
1276
|
+
: Infinity;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
private updateIntrinsicSizes() {
|
|
1280
|
+
this.updateIntrinsicInlineSize();
|
|
1281
|
+
this.updateIntrinsicBlockSize();
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
private computeColSizes() {
|
|
1285
|
+
this.colSizes = Object.entries(this.columns ?? {}).reduce(
|
|
1286
|
+
(acc: DxGridAxisSizes, [plane, planeColMeta]) => {
|
|
1287
|
+
acc[plane as 'grid' | DxGridFrozenPlane] = Object.entries(planeColMeta).reduce(
|
|
1288
|
+
(planeAcc: Record<string, number>, [col, colMeta]) => {
|
|
1289
|
+
if (colMeta?.size) {
|
|
1290
|
+
planeAcc[col] = colMeta.size;
|
|
1291
|
+
}
|
|
1292
|
+
return planeAcc;
|
|
1293
|
+
},
|
|
1294
|
+
{},
|
|
1295
|
+
);
|
|
1296
|
+
return acc;
|
|
1297
|
+
},
|
|
1298
|
+
{ grid: {} },
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
private computeRowSizes() {
|
|
1303
|
+
this.rowSizes = Object.entries(this.rows ?? {}).reduce(
|
|
1304
|
+
(acc: DxGridAxisSizes, [plane, planeRowMeta]) => {
|
|
1305
|
+
acc[plane as 'grid' | DxGridFrozenPlane] = Object.entries(planeRowMeta).reduce(
|
|
1306
|
+
(planeAcc: Record<string, number>, [row, rowMeta]) => {
|
|
1307
|
+
if (rowMeta?.size) {
|
|
1308
|
+
planeAcc[row] = rowMeta.size;
|
|
1309
|
+
}
|
|
1310
|
+
return planeAcc;
|
|
1311
|
+
},
|
|
1312
|
+
{},
|
|
1313
|
+
);
|
|
1314
|
+
return acc;
|
|
1315
|
+
},
|
|
1316
|
+
{ grid: {} },
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
590
1320
|
override firstUpdated() {
|
|
1321
|
+
if (this.getCells) {
|
|
1322
|
+
this.updateCells(true);
|
|
1323
|
+
}
|
|
591
1324
|
this.observer.observe(this.viewportRef.value!);
|
|
592
|
-
this.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
1325
|
+
this.computeColSizes();
|
|
1326
|
+
this.computeRowSizes();
|
|
1327
|
+
this.updateIntrinsicSizes();
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
override willUpdate(changedProperties: Map<string, any>) {
|
|
1331
|
+
if (
|
|
1332
|
+
this.getCells &&
|
|
1333
|
+
(changedProperties.has('initialCells') ||
|
|
1334
|
+
changedProperties.has('visColMin') ||
|
|
1335
|
+
changedProperties.has('visColMax') ||
|
|
1336
|
+
changedProperties.has('visRowMin') ||
|
|
1337
|
+
changedProperties.has('visRowMax'))
|
|
1338
|
+
) {
|
|
1339
|
+
this.updateCells();
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
if (changedProperties.has('rowDefault') || changedProperties.has('rows') || changedProperties.has('limitRows')) {
|
|
1343
|
+
this.updateIntrinsicBlockSize();
|
|
1344
|
+
this.updatePosBlock();
|
|
1345
|
+
this.updateVisBlock();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (
|
|
1349
|
+
changedProperties.has('colDefault') ||
|
|
1350
|
+
changedProperties.has('columns') ||
|
|
1351
|
+
changedProperties.has('limitColumns')
|
|
1352
|
+
) {
|
|
1353
|
+
this.updateIntrinsicInlineSize();
|
|
1354
|
+
this.updatePosInline();
|
|
1355
|
+
this.updateVisInline();
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
if (changedProperties.has('columns')) {
|
|
1359
|
+
this.computeColSizes();
|
|
1360
|
+
this.updateIntrinsicInlineSize();
|
|
1361
|
+
}
|
|
1362
|
+
if (changedProperties.has('rows')) {
|
|
1363
|
+
this.computeRowSizes();
|
|
1364
|
+
this.updateIntrinsicBlockSize();
|
|
1365
|
+
}
|
|
604
1366
|
}
|
|
605
1367
|
|
|
606
1368
|
override updated(changedProperties: Map<string, any>) {
|
|
@@ -613,16 +1375,27 @@ export class DxGrid extends LitElement {
|
|
|
613
1375
|
}
|
|
614
1376
|
}
|
|
615
1377
|
|
|
1378
|
+
public updateIfWithinBounds({ col, row }: { col: number; row: number }): boolean {
|
|
1379
|
+
if (col >= this.visColMin && col <= this.visColMax && row >= this.visRowMin && row <= this.visRowMax) {
|
|
1380
|
+
this.requestUpdate();
|
|
1381
|
+
return true;
|
|
1382
|
+
}
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
616
1386
|
override disconnectedCallback() {
|
|
617
1387
|
super.disconnectedCallback();
|
|
618
|
-
// console.log('[disconnected]', this.viewportRef.value);
|
|
619
|
-
// TODO(thure): Will this even work?
|
|
620
1388
|
if (this.viewportRef.value) {
|
|
621
1389
|
this.observer.unobserve(this.viewportRef.value);
|
|
622
1390
|
}
|
|
1391
|
+
document.defaultView?.removeEventListener('wheel', this.handleTopLevelWheel);
|
|
623
1392
|
}
|
|
624
1393
|
|
|
625
1394
|
override createRenderRoot() {
|
|
626
1395
|
return this;
|
|
627
1396
|
}
|
|
628
1397
|
}
|
|
1398
|
+
|
|
1399
|
+
export { rowToA1Notation, colToA1Notation } from './util';
|
|
1400
|
+
|
|
1401
|
+
export const commentedClassName = 'dx-grid__cell--commented';
|