@alaarab/ogrid-js 2.0.15 → 2.0.17
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.
|
@@ -39,6 +39,26 @@ export class InlineCellEditor {
|
|
|
39
39
|
this.editor = editor;
|
|
40
40
|
this.container.appendChild(editor);
|
|
41
41
|
editor.focus();
|
|
42
|
+
// Position dropdown with fixed positioning to escape container overflow
|
|
43
|
+
const dropdownEl = editor.querySelector('[role="listbox"]');
|
|
44
|
+
if (dropdownEl) {
|
|
45
|
+
const maxH = 200;
|
|
46
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
47
|
+
const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
|
|
48
|
+
dropdownEl.style.position = 'fixed';
|
|
49
|
+
dropdownEl.style.left = `${rect.left}px`;
|
|
50
|
+
dropdownEl.style.width = `${rect.width}px`;
|
|
51
|
+
dropdownEl.style.maxHeight = `${maxH}px`;
|
|
52
|
+
dropdownEl.style.zIndex = '9999';
|
|
53
|
+
dropdownEl.style.right = 'auto';
|
|
54
|
+
if (flipUp) {
|
|
55
|
+
dropdownEl.style.top = 'auto';
|
|
56
|
+
dropdownEl.style.bottom = `${window.innerHeight - rect.top}px`;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
dropdownEl.style.top = `${rect.bottom}px`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
42
62
|
}
|
|
43
63
|
/** Returns the cell currently being edited, or null if no editor is open. */
|
|
44
64
|
getEditingCell() {
|
|
@@ -205,31 +225,121 @@ export class InlineCellEditor {
|
|
|
205
225
|
return input;
|
|
206
226
|
}
|
|
207
227
|
createSelectEditor(value, column) {
|
|
208
|
-
const select = document.createElement('select');
|
|
209
228
|
const values = column.cellEditorParams?.values ?? [];
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
229
|
+
const formatValue = column.cellEditorParams?.formatValue;
|
|
230
|
+
const getDisplayText = (v) => formatValue ? formatValue(v) : (v != null ? String(v) : '');
|
|
231
|
+
const wrapper = document.createElement('div');
|
|
232
|
+
Object.assign(wrapper.style, EDITOR_STYLE);
|
|
233
|
+
wrapper.style.padding = '6px 10px';
|
|
234
|
+
wrapper.style.display = 'flex';
|
|
235
|
+
wrapper.style.alignItems = 'center';
|
|
236
|
+
wrapper.tabIndex = 0;
|
|
237
|
+
// Display current value + chevron
|
|
238
|
+
const display = document.createElement('div');
|
|
239
|
+
display.style.display = 'flex';
|
|
240
|
+
display.style.alignItems = 'center';
|
|
241
|
+
display.style.justifyContent = 'space-between';
|
|
242
|
+
display.style.width = '100%';
|
|
243
|
+
display.style.cursor = 'pointer';
|
|
244
|
+
display.style.fontSize = '13px';
|
|
245
|
+
const valueSpan = document.createElement('span');
|
|
246
|
+
valueSpan.textContent = getDisplayText(value);
|
|
247
|
+
display.appendChild(valueSpan);
|
|
248
|
+
const chevron = document.createElement('span');
|
|
249
|
+
chevron.textContent = '\u25BE';
|
|
250
|
+
chevron.style.marginLeft = '4px';
|
|
251
|
+
chevron.style.fontSize = '10px';
|
|
252
|
+
chevron.style.opacity = '0.5';
|
|
253
|
+
display.appendChild(chevron);
|
|
254
|
+
wrapper.appendChild(display);
|
|
255
|
+
// Dropdown list
|
|
256
|
+
const dropdown = document.createElement('div');
|
|
257
|
+
dropdown.setAttribute('role', 'listbox');
|
|
258
|
+
dropdown.style.position = 'absolute';
|
|
259
|
+
dropdown.style.top = '100%';
|
|
260
|
+
dropdown.style.left = '0';
|
|
261
|
+
dropdown.style.right = '0';
|
|
262
|
+
dropdown.style.maxHeight = '200px';
|
|
263
|
+
dropdown.style.overflowY = 'auto';
|
|
264
|
+
dropdown.style.backgroundColor = 'var(--ogrid-bg, #fff)';
|
|
265
|
+
dropdown.style.border = '1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12))';
|
|
266
|
+
dropdown.style.zIndex = '1001';
|
|
267
|
+
dropdown.style.boxShadow = '0 4px 16px rgba(0,0,0,0.2)';
|
|
268
|
+
wrapper.appendChild(dropdown);
|
|
269
|
+
let highlightedIndex = Math.max(values.findIndex((v) => String(v) === String(value)), 0);
|
|
270
|
+
const renderOptions = () => {
|
|
271
|
+
dropdown.innerHTML = '';
|
|
272
|
+
for (let i = 0; i < values.length; i++) {
|
|
273
|
+
const val = values[i];
|
|
274
|
+
const option = document.createElement('div');
|
|
275
|
+
option.setAttribute('role', 'option');
|
|
276
|
+
option.setAttribute('aria-selected', String(i === highlightedIndex));
|
|
277
|
+
option.textContent = getDisplayText(val);
|
|
278
|
+
option.style.padding = '6px 8px';
|
|
279
|
+
option.style.cursor = 'pointer';
|
|
280
|
+
option.style.color = 'var(--ogrid-fg, #242424)';
|
|
281
|
+
if (i === highlightedIndex) {
|
|
282
|
+
option.style.background = 'var(--ogrid-bg-hover, #e8f0fe)';
|
|
283
|
+
}
|
|
284
|
+
option.addEventListener('mousedown', (e) => {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
if (this.editingCell) {
|
|
287
|
+
this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, val);
|
|
288
|
+
}
|
|
289
|
+
this.closeEditor();
|
|
290
|
+
});
|
|
291
|
+
dropdown.appendChild(option);
|
|
221
292
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
293
|
+
};
|
|
294
|
+
const scrollHighlightedIntoView = () => {
|
|
295
|
+
const highlighted = dropdown.children[highlightedIndex];
|
|
296
|
+
highlighted?.scrollIntoView({ block: 'nearest' });
|
|
297
|
+
};
|
|
298
|
+
renderOptions();
|
|
299
|
+
wrapper.addEventListener('keydown', (e) => {
|
|
300
|
+
switch (e.key) {
|
|
301
|
+
case 'ArrowDown':
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
highlightedIndex = Math.min(highlightedIndex + 1, values.length - 1);
|
|
304
|
+
renderOptions();
|
|
305
|
+
scrollHighlightedIntoView();
|
|
306
|
+
break;
|
|
307
|
+
case 'ArrowUp':
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
highlightedIndex = Math.max(highlightedIndex - 1, 0);
|
|
310
|
+
renderOptions();
|
|
311
|
+
scrollHighlightedIntoView();
|
|
312
|
+
break;
|
|
313
|
+
case 'Enter':
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
e.stopPropagation();
|
|
316
|
+
if (values.length > 0 && highlightedIndex < values.length) {
|
|
317
|
+
if (this.editingCell) {
|
|
318
|
+
this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, values[highlightedIndex]);
|
|
319
|
+
}
|
|
320
|
+
const afterCommit = this.onAfterCommit;
|
|
321
|
+
this.closeEditor();
|
|
322
|
+
afterCommit?.();
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
case 'Tab':
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
if (values.length > 0 && highlightedIndex < values.length) {
|
|
328
|
+
if (this.editingCell) {
|
|
329
|
+
this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, values[highlightedIndex]);
|
|
330
|
+
}
|
|
331
|
+
this.closeEditor();
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
case 'Escape':
|
|
335
|
+
e.preventDefault();
|
|
336
|
+
e.stopPropagation();
|
|
337
|
+
this.onCancel?.();
|
|
338
|
+
this.closeEditor();
|
|
339
|
+
break;
|
|
230
340
|
}
|
|
231
341
|
});
|
|
232
|
-
return
|
|
342
|
+
return wrapper;
|
|
233
343
|
}
|
|
234
344
|
createRichSelectEditor(value, column) {
|
|
235
345
|
const wrapper = document.createElement('div');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { injectGlobalStyles, measureRange as measureRangeCore } from '@alaarab/ogrid-core';
|
|
1
|
+
import { injectGlobalStyles, measureRange as measureRangeCore, rangesEqual } from '@alaarab/ogrid-core';
|
|
2
2
|
/**
|
|
3
3
|
* Measure the bounding rect of a range within a container, with scroll offsets.
|
|
4
4
|
* This variant adds scroll offsets for the JS implementation's scrollable container.
|
|
@@ -15,14 +15,6 @@ function measureRange(container, range, colOffset) {
|
|
|
15
15
|
height: rect.height,
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
-
function rangesEqual(a, b) {
|
|
19
|
-
if (a === b)
|
|
20
|
-
return true;
|
|
21
|
-
if (!a || !b)
|
|
22
|
-
return false;
|
|
23
|
-
return a.startRow === b.startRow && a.endRow === b.endRow &&
|
|
24
|
-
a.startCol === b.startCol && a.endCol === b.endCol;
|
|
25
|
-
}
|
|
26
18
|
/**
|
|
27
19
|
* MarchingAntsOverlay — renders SVG overlays on top of the grid:
|
|
28
20
|
* 1. Selection range: solid green border
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeSelectionRange, getCellValue } from '@alaarab/ogrid-core';
|
|
1
|
+
import { normalizeSelectionRange, getCellValue, formatSelectionAsTsv, parseTsvClipboard } from '@alaarab/ogrid-core';
|
|
2
2
|
import { parseValue } from '@alaarab/ogrid-core';
|
|
3
3
|
import { EventEmitter } from './EventEmitter';
|
|
4
4
|
export class ClipboardState {
|
|
@@ -33,21 +33,7 @@ export class ClipboardState {
|
|
|
33
33
|
return;
|
|
34
34
|
const norm = normalizeSelectionRange(range);
|
|
35
35
|
const { items, visibleCols } = this.params;
|
|
36
|
-
const
|
|
37
|
-
for (let r = norm.startRow; r <= norm.endRow; r++) {
|
|
38
|
-
const cells = [];
|
|
39
|
-
for (let c = norm.startCol; c <= norm.endCol; c++) {
|
|
40
|
-
if (r >= items.length || c >= visibleCols.length)
|
|
41
|
-
break;
|
|
42
|
-
const item = items[r];
|
|
43
|
-
const col = visibleCols[c];
|
|
44
|
-
const raw = getCellValue(item, col);
|
|
45
|
-
const val = col.valueFormatter ? col.valueFormatter(raw, item) : raw;
|
|
46
|
-
cells.push(val != null && val !== '' ? String(val).replace(/\t/g, ' ').replace(/\n/g, ' ') : '');
|
|
47
|
-
}
|
|
48
|
-
rows.push(cells.join('\t'));
|
|
49
|
-
}
|
|
50
|
-
const tsv = rows.join('\r\n');
|
|
36
|
+
const tsv = formatSelectionAsTsv(items, visibleCols, norm);
|
|
51
37
|
this.internalClipboard = tsv;
|
|
52
38
|
this._copyRange = norm;
|
|
53
39
|
this._cutRange = null;
|
|
@@ -98,9 +84,9 @@ export class ClipboardState {
|
|
|
98
84
|
const anchorRow = norm ? norm.startRow : 0;
|
|
99
85
|
const anchorCol = norm ? norm.startCol : 0;
|
|
100
86
|
const { items, visibleCols } = this.params;
|
|
101
|
-
const lines = text
|
|
87
|
+
const lines = parseTsvClipboard(text);
|
|
102
88
|
for (let r = 0; r < lines.length; r++) {
|
|
103
|
-
const cells = lines[r]
|
|
89
|
+
const cells = lines[r];
|
|
104
90
|
for (let c = 0; c < cells.length; c++) {
|
|
105
91
|
const targetRow = anchorRow + r;
|
|
106
92
|
const targetCol = anchorCol + c;
|
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
import { normalizeSelectionRange, getCellValue } from '@alaarab/ogrid-core';
|
|
1
|
+
import { normalizeSelectionRange, getCellValue, findCtrlArrowTarget as findCtrlTarget, computeTabNavigation } from '@alaarab/ogrid-core';
|
|
2
2
|
import { parseValue } from '@alaarab/ogrid-core';
|
|
3
|
-
/**
|
|
4
|
-
* Excel-style Ctrl+Arrow: find the target position along a 1D axis.
|
|
5
|
-
* - Non-empty current + non-empty next → scan through non-empties, stop at last before empty/edge.
|
|
6
|
-
* - Otherwise → skip empties, land on next non-empty or edge.
|
|
7
|
-
*/
|
|
8
|
-
function findCtrlTarget(pos, edge, step, isEmpty) {
|
|
9
|
-
if (pos === edge)
|
|
10
|
-
return pos;
|
|
11
|
-
const next = pos + step;
|
|
12
|
-
if (!isEmpty(pos) && !isEmpty(next)) {
|
|
13
|
-
let p = next;
|
|
14
|
-
while (p !== edge) {
|
|
15
|
-
if (isEmpty(p + step))
|
|
16
|
-
return p;
|
|
17
|
-
p += step;
|
|
18
|
-
}
|
|
19
|
-
return edge;
|
|
20
|
-
}
|
|
21
|
-
let p = next;
|
|
22
|
-
while (p !== edge) {
|
|
23
|
-
if (!isEmpty(p))
|
|
24
|
-
return p;
|
|
25
|
-
p += step;
|
|
26
|
-
}
|
|
27
|
-
return edge;
|
|
28
|
-
}
|
|
29
3
|
export class KeyboardNavState {
|
|
30
4
|
constructor(params, getActiveCell, getSelectionRange, setActiveCell, setSelectionRange) {
|
|
31
5
|
this.wrapperRef = null;
|
|
@@ -184,32 +158,13 @@ export class KeyboardNavState {
|
|
|
184
158
|
}
|
|
185
159
|
case 'Tab': {
|
|
186
160
|
e.preventDefault();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (columnIndex > colOffset) {
|
|
191
|
-
newColTab = columnIndex - 1;
|
|
192
|
-
}
|
|
193
|
-
else if (rowIndex > 0) {
|
|
194
|
-
newRowTab = rowIndex - 1;
|
|
195
|
-
newColTab = maxColIndex;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
if (columnIndex < maxColIndex) {
|
|
200
|
-
newColTab = columnIndex + 1;
|
|
201
|
-
}
|
|
202
|
-
else if (rowIndex < maxRowIndex) {
|
|
203
|
-
newRowTab = rowIndex + 1;
|
|
204
|
-
newColTab = colOffset;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
const newDataColTab = newColTab - colOffset;
|
|
208
|
-
this.setActiveCell({ rowIndex: newRowTab, columnIndex: newColTab });
|
|
161
|
+
const tabResult = computeTabNavigation(rowIndex, columnIndex, maxRowIndex, maxColIndex, colOffset, e.shiftKey);
|
|
162
|
+
const newDataColTab = tabResult.columnIndex - colOffset;
|
|
163
|
+
this.setActiveCell({ rowIndex: tabResult.rowIndex, columnIndex: tabResult.columnIndex });
|
|
209
164
|
this.setSelectionRange({
|
|
210
|
-
startRow:
|
|
165
|
+
startRow: tabResult.rowIndex,
|
|
211
166
|
startCol: newDataColTab,
|
|
212
|
-
endRow:
|
|
167
|
+
endRow: tabResult.rowIndex,
|
|
213
168
|
endCol: newDataColTab,
|
|
214
169
|
});
|
|
215
170
|
break;
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
+
import { rangesEqual } from '@alaarab/ogrid-core';
|
|
1
2
|
import { EventEmitter } from './EventEmitter';
|
|
2
|
-
/** Compares two selection ranges by value to avoid redundant RAF work. */
|
|
3
|
-
function rangesEqual(a, b) {
|
|
4
|
-
if (a === b)
|
|
5
|
-
return true;
|
|
6
|
-
if (!a || !b)
|
|
7
|
-
return false;
|
|
8
|
-
return a.startRow === b.startRow && a.endRow === b.endRow &&
|
|
9
|
-
a.startCol === b.startCol && a.endCol === b.endCol;
|
|
10
|
-
}
|
|
11
3
|
export class SelectionState {
|
|
12
4
|
constructor() {
|
|
13
5
|
this.emitter = new EventEmitter();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-js",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.17",
|
|
4
4
|
"description": "OGrid vanilla JS – framework-free data grid with sorting, filtering, pagination, and spreadsheet-style editing.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@alaarab/ogrid-core": "2.0.
|
|
39
|
+
"@alaarab/ogrid-core": "2.0.17"
|
|
40
40
|
},
|
|
41
41
|
"sideEffects": false,
|
|
42
42
|
"publishConfig": {
|