@design.estate/dees-catalog 3.64.0 → 3.65.0
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_bundle/bundle.js +360 -101
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.d.ts +46 -2
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.demo.js +39 -9
- package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.js +328 -68
- package/dist_ts_web/elements/00group-dataview/dees-table/styles.js +23 -26
- package/dist_ts_web/elements/00group-dataview/dees-table/types.d.ts +31 -0
- package/dist_watch/bundle.js +358 -99
- package/dist_watch/bundle.js.map +3 -3
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-dataview/dees-table/dees-table.demo.ts +38 -8
- package/ts_web/elements/00group-dataview/dees-table/dees-table.ts +338 -56
- package/ts_web/elements/00group-dataview/dees-table/styles.ts +26 -26
- package/ts_web/elements/00group-dataview/dees-table/types.ts +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design.estate/dees-catalog",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.65.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
|
6
6
|
"main": "dist_ts_web/index.js",
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@design.estate/dees-catalog',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.65.0',
|
|
7
7
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
|
8
8
|
}
|
|
@@ -55,36 +55,66 @@ export const demoFunc = () => html`
|
|
|
55
55
|
<div class="demo-container">
|
|
56
56
|
<div class="demo-section">
|
|
57
57
|
<h2 class="demo-title">Basic Table with Actions</h2>
|
|
58
|
-
<p class="demo-description">A standard table with row actions, editable
|
|
58
|
+
<p class="demo-description">A standard table with row actions, editable cells, and context menu support. Double-click any cell to edit. Tab moves to the next editable cell, Enter to the row below, Esc cancels.</p>
|
|
59
59
|
<dees-table
|
|
60
60
|
heading1="Current Account Statement"
|
|
61
61
|
heading2="Bunq - Payment Account 2 - April 2021"
|
|
62
|
-
.
|
|
62
|
+
.columns=${[
|
|
63
|
+
{ key: 'date', header: 'Date', sortable: true, editable: true, editor: 'date' },
|
|
64
|
+
{ key: 'amount', header: 'Amount', editable: true, editor: 'text' },
|
|
65
|
+
{
|
|
66
|
+
key: 'category',
|
|
67
|
+
header: 'Category',
|
|
68
|
+
editable: true,
|
|
69
|
+
editor: 'dropdown',
|
|
70
|
+
editorOptions: {
|
|
71
|
+
options: [
|
|
72
|
+
{ option: 'Office Supplies', key: 'office' },
|
|
73
|
+
{ option: 'Hardware', key: 'hardware' },
|
|
74
|
+
{ option: 'Software', key: 'software' },
|
|
75
|
+
{ option: 'Travel', key: 'travel' },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{ key: 'description', header: 'Description', editable: true },
|
|
80
|
+
{ key: 'reconciled', header: 'OK', editable: true, editor: 'checkbox' },
|
|
81
|
+
]}
|
|
82
|
+
@cellEdit=${(e: CustomEvent) => console.log('cellEdit', e.detail)}
|
|
63
83
|
.data=${[
|
|
64
84
|
{
|
|
65
85
|
date: '2021-04-01',
|
|
66
86
|
amount: '2464.65 €',
|
|
67
|
-
|
|
87
|
+
category: 'office',
|
|
88
|
+
description: 'Printing Paper - STAPLES BREMEN',
|
|
89
|
+
reconciled: true,
|
|
68
90
|
},
|
|
69
91
|
{
|
|
70
92
|
date: '2021-04-02',
|
|
71
93
|
amount: '165.65 €',
|
|
72
|
-
|
|
94
|
+
category: 'hardware',
|
|
95
|
+
description: 'Logitech Mouse - logi.com OnlineShop',
|
|
96
|
+
reconciled: false,
|
|
73
97
|
},
|
|
74
98
|
{
|
|
75
99
|
date: '2021-04-03',
|
|
76
100
|
amount: '2999,00 €',
|
|
77
|
-
|
|
101
|
+
category: 'hardware',
|
|
102
|
+
description: 'Macbook Pro 16inch - Apple.de OnlineShop',
|
|
103
|
+
reconciled: false,
|
|
78
104
|
},
|
|
79
105
|
{
|
|
80
106
|
date: '2021-04-01',
|
|
81
107
|
amount: '2464.65 €',
|
|
108
|
+
category: 'office',
|
|
82
109
|
description: 'Office-Supplies - STAPLES BREMEN',
|
|
110
|
+
reconciled: true,
|
|
83
111
|
},
|
|
84
112
|
{
|
|
85
113
|
date: '2021-04-01',
|
|
86
114
|
amount: '2464.65 €',
|
|
115
|
+
category: 'office',
|
|
87
116
|
description: 'Office-Supplies - STAPLES BREMEN',
|
|
117
|
+
reconciled: true,
|
|
88
118
|
},
|
|
89
119
|
]}
|
|
90
120
|
dataName="transactions"
|
|
@@ -510,13 +540,13 @@ export const demoFunc = () => html`
|
|
|
510
540
|
<h2 class="demo-title">Column Filters + Sticky Header (New)</h2>
|
|
511
541
|
<p class="demo-description">Per-column quick filters and sticky header with internal scroll. Try filtering the Name column. Uses --table-max-height var.</p>
|
|
512
542
|
<style>
|
|
513
|
-
dees-table[
|
|
543
|
+
dees-table[fixed-height] { --table-max-height: 220px; }
|
|
514
544
|
</style>
|
|
515
545
|
<dees-table
|
|
516
546
|
heading1="Employees"
|
|
517
547
|
heading2="Quick filter per column + sticky header"
|
|
518
548
|
.showColumnFilters=${true}
|
|
519
|
-
.
|
|
549
|
+
.fixedHeight=${true}
|
|
520
550
|
.columns=${[
|
|
521
551
|
{ key: 'name', header: 'Name', sortable: true },
|
|
522
552
|
{ key: 'email', header: 'Email', sortable: true },
|
|
@@ -669,7 +699,7 @@ export const demoFunc = () => html`
|
|
|
669
699
|
</style>
|
|
670
700
|
<dees-table
|
|
671
701
|
id="scrollSmallHeight"
|
|
672
|
-
.
|
|
702
|
+
.fixedHeight=${true}
|
|
673
703
|
heading1="People Directory (Scrollable)"
|
|
674
704
|
heading2="Forced scrolling with many items"
|
|
675
705
|
.columns=${[
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import * as plugins from '../../00plugins.js';
|
|
2
2
|
import { demoFunc } from './dees-table.demo.js';
|
|
3
|
-
import { customElement, html, DeesElement, property, type TemplateResult, directives } from '@design.estate/dees-element';
|
|
3
|
+
import { customElement, html, DeesElement, property, state, type TemplateResult, directives } from '@design.estate/dees-element';
|
|
4
4
|
|
|
5
5
|
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
|
6
6
|
import { DeesModal } from '../../00group-overlay/dees-modal/dees-modal.js';
|
|
7
7
|
import * as domtools from '@design.estate/dees-domtools';
|
|
8
8
|
import { type TIconKey } from '../../00group-utility/dees-icon/dees-icon.js';
|
|
9
9
|
import { tableStyles } from './styles.js';
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
Column,
|
|
12
|
+
ISortDescriptor,
|
|
13
|
+
ITableAction,
|
|
14
|
+
ITableActionDataArg,
|
|
15
|
+
TCellEditorType,
|
|
16
|
+
TDisplayFunction,
|
|
17
|
+
} from './types.js';
|
|
18
|
+
import '../../00group-input/dees-input-text/index.js';
|
|
19
|
+
import '../../00group-input/dees-input-checkbox/index.js';
|
|
20
|
+
import '../../00group-input/dees-input-dropdown/index.js';
|
|
21
|
+
import '../../00group-input/dees-input-datepicker/index.js';
|
|
22
|
+
import '../../00group-input/dees-input-tags/index.js';
|
|
11
23
|
import {
|
|
12
24
|
computeColumnsFromDisplayFunction as computeColumnsFromDisplayFunctionFn,
|
|
13
25
|
computeEffectiveColumns as computeEffectiveColumnsFn,
|
|
@@ -138,11 +150,6 @@ export class DeesTable<T> extends DeesElement {
|
|
|
138
150
|
})
|
|
139
151
|
accessor selectedDataRow!: T;
|
|
140
152
|
|
|
141
|
-
@property({
|
|
142
|
-
type: Array,
|
|
143
|
-
})
|
|
144
|
-
accessor editableFields: string[] = [];
|
|
145
|
-
|
|
146
153
|
@property({
|
|
147
154
|
type: Boolean,
|
|
148
155
|
reflect: true,
|
|
@@ -224,6 +231,20 @@ export class DeesTable<T> extends DeesElement {
|
|
|
224
231
|
*/
|
|
225
232
|
private __selectionAnchorId?: string;
|
|
226
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Cell currently focused for keyboard navigation. When set, the cell shows
|
|
236
|
+
* a focus ring and Enter/F2 enters edit mode. Independent from row selection.
|
|
237
|
+
*/
|
|
238
|
+
@state()
|
|
239
|
+
private accessor __focusedCell: { rowId: string; colKey: string } | undefined = undefined;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Cell currently being edited. When set, that cell renders an editor
|
|
243
|
+
* (dees-input-*) instead of its display content.
|
|
244
|
+
*/
|
|
245
|
+
@state()
|
|
246
|
+
private accessor __editingCell: { rowId: string; colKey: string } | undefined = undefined;
|
|
247
|
+
|
|
227
248
|
constructor() {
|
|
228
249
|
super();
|
|
229
250
|
// Make the host focusable so it can receive Ctrl/Cmd+C for copy.
|
|
@@ -238,24 +259,84 @@ export class DeesTable<T> extends DeesElement {
|
|
|
238
259
|
* receive the copy.
|
|
239
260
|
*/
|
|
240
261
|
private __handleHostKeydown = (eventArg: KeyboardEvent) => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Don't hijack copy when the user is selecting text in an input/textarea.
|
|
262
|
+
// Detect whether the keydown originated inside an editor (input/textarea
|
|
263
|
+
// or contenteditable). Used to skip both copy hijacking and grid nav.
|
|
244
264
|
const path = (eventArg.composedPath?.() || []) as EventTarget[];
|
|
265
|
+
let inEditor = false;
|
|
245
266
|
for (const t of path) {
|
|
246
267
|
const tag = (t as HTMLElement)?.tagName;
|
|
247
|
-
if (tag === 'INPUT' || tag === 'TEXTAREA')
|
|
248
|
-
|
|
268
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || (t as HTMLElement)?.isContentEditable) {
|
|
269
|
+
inEditor = true;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
249
272
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
273
|
+
|
|
274
|
+
// Ctrl/Cmd+C → copy selected rows as JSON (unless typing in an input).
|
|
275
|
+
const isCopy =
|
|
276
|
+
(eventArg.metaKey || eventArg.ctrlKey) && (eventArg.key === 'c' || eventArg.key === 'C');
|
|
277
|
+
if (isCopy) {
|
|
278
|
+
if (inEditor) return;
|
|
279
|
+
const rows: T[] = [];
|
|
280
|
+
if (this.selectedIds.size > 0) {
|
|
281
|
+
for (const r of this.data) if (this.selectedIds.has(this.getRowId(r))) rows.push(r);
|
|
282
|
+
} else if (this.selectedDataRow) {
|
|
283
|
+
rows.push(this.selectedDataRow);
|
|
284
|
+
}
|
|
285
|
+
if (rows.length === 0) return;
|
|
286
|
+
eventArg.preventDefault();
|
|
287
|
+
this.__writeRowsAsJson(rows);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Cell navigation only when no editor is open.
|
|
292
|
+
if (inEditor || this.__editingCell) return;
|
|
293
|
+
switch (eventArg.key) {
|
|
294
|
+
case 'ArrowLeft':
|
|
295
|
+
eventArg.preventDefault();
|
|
296
|
+
this.moveFocusedCell(-1, 0, false);
|
|
297
|
+
return;
|
|
298
|
+
case 'ArrowRight':
|
|
299
|
+
eventArg.preventDefault();
|
|
300
|
+
this.moveFocusedCell(+1, 0, false);
|
|
301
|
+
return;
|
|
302
|
+
case 'ArrowUp':
|
|
303
|
+
eventArg.preventDefault();
|
|
304
|
+
this.moveFocusedCell(0, -1, false);
|
|
305
|
+
return;
|
|
306
|
+
case 'ArrowDown':
|
|
307
|
+
eventArg.preventDefault();
|
|
308
|
+
this.moveFocusedCell(0, +1, false);
|
|
309
|
+
return;
|
|
310
|
+
case 'Enter':
|
|
311
|
+
case 'F2': {
|
|
312
|
+
if (!this.__focusedCell) return;
|
|
313
|
+
const view: T[] = (this as any)._lastViewData ?? [];
|
|
314
|
+
const item = view.find((r) => this.getRowId(r) === this.__focusedCell!.rowId);
|
|
315
|
+
if (!item) return;
|
|
316
|
+
const allCols: Column<T>[] =
|
|
317
|
+
Array.isArray(this.columns) && this.columns.length > 0
|
|
318
|
+
? computeEffectiveColumnsFn(
|
|
319
|
+
this.columns,
|
|
320
|
+
this.augmentFromDisplayFunction,
|
|
321
|
+
this.displayFunction,
|
|
322
|
+
this.data
|
|
323
|
+
)
|
|
324
|
+
: computeColumnsFromDisplayFunctionFn(this.displayFunction, this.data);
|
|
325
|
+
const col = allCols.find((c) => String(c.key) === this.__focusedCell!.colKey);
|
|
326
|
+
if (!col || !this.__isColumnEditable(col)) return;
|
|
327
|
+
eventArg.preventDefault();
|
|
328
|
+
this.startEditing(item, col);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
case 'Escape':
|
|
332
|
+
if (this.__focusedCell) {
|
|
333
|
+
this.__focusedCell = undefined;
|
|
334
|
+
this.requestUpdate();
|
|
335
|
+
}
|
|
336
|
+
return;
|
|
337
|
+
default:
|
|
338
|
+
return;
|
|
255
339
|
}
|
|
256
|
-
if (rows.length === 0) return;
|
|
257
|
-
eventArg.preventDefault();
|
|
258
|
-
this.__writeRowsAsJson(rows);
|
|
259
340
|
};
|
|
260
341
|
|
|
261
342
|
/**
|
|
@@ -492,20 +573,48 @@ export class DeesTable<T> extends DeesElement {
|
|
|
492
573
|
? col.renderer(value, itemArg, { rowIndex, colIndex, column: col })
|
|
493
574
|
: value;
|
|
494
575
|
const editKey = String(col.key);
|
|
576
|
+
const isEditable = !!(col.editable || col.editor);
|
|
577
|
+
const rowId = this.getRowId(itemArg);
|
|
578
|
+
const isFocused =
|
|
579
|
+
this.__focusedCell?.rowId === rowId &&
|
|
580
|
+
this.__focusedCell?.colKey === editKey;
|
|
581
|
+
const isEditing =
|
|
582
|
+
this.__editingCell?.rowId === rowId &&
|
|
583
|
+
this.__editingCell?.colKey === editKey;
|
|
584
|
+
const cellClasses = [
|
|
585
|
+
isEditable ? 'editable' : '',
|
|
586
|
+
isFocused && !isEditing ? 'focused' : '',
|
|
587
|
+
isEditing ? 'editingCell' : '',
|
|
588
|
+
]
|
|
589
|
+
.filter(Boolean)
|
|
590
|
+
.join(' ');
|
|
495
591
|
return html`
|
|
496
592
|
<td
|
|
593
|
+
class=${cellClasses}
|
|
594
|
+
@click=${(e: MouseEvent) => {
|
|
595
|
+
if (isEditing) {
|
|
596
|
+
e.stopPropagation();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (isEditable) {
|
|
600
|
+
this.__focusedCell = { rowId, colKey: editKey };
|
|
601
|
+
}
|
|
602
|
+
}}
|
|
497
603
|
@dblclick=${(e: Event) => {
|
|
498
604
|
const dblAction = this.dataActions.find((actionArg) =>
|
|
499
605
|
actionArg.type?.includes('doubleClick')
|
|
500
606
|
);
|
|
501
|
-
if (
|
|
502
|
-
|
|
607
|
+
if (isEditable) {
|
|
608
|
+
e.stopPropagation();
|
|
609
|
+
this.startEditing(itemArg, col);
|
|
503
610
|
} else if (dblAction) {
|
|
504
611
|
dblAction.actionFunc({ item: itemArg, table: this });
|
|
505
612
|
}
|
|
506
613
|
}}
|
|
507
614
|
>
|
|
508
|
-
<div class="innerCellContainer"
|
|
615
|
+
<div class="innerCellContainer">
|
|
616
|
+
${isEditing ? this.renderCellEditor(itemArg, col) : content}
|
|
617
|
+
</div>
|
|
509
618
|
</td>
|
|
510
619
|
`;
|
|
511
620
|
})}
|
|
@@ -1524,43 +1633,216 @@ export class DeesTable<T> extends DeesElement {
|
|
|
1524
1633
|
return actions;
|
|
1525
1634
|
}
|
|
1526
1635
|
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1636
|
+
// ─── Cell editing ─────────────────────────────────────────────────────
|
|
1637
|
+
|
|
1638
|
+
/** True if the column has any in-cell editor configured. */
|
|
1639
|
+
private __isColumnEditable(col: Column<T>): boolean {
|
|
1640
|
+
return !!(col.editable || col.editor);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** Effective columns filtered to those that can be edited (visible only). */
|
|
1644
|
+
private __editableColumns(effectiveColumns: Column<T>[]): Column<T>[] {
|
|
1645
|
+
return effectiveColumns.filter((c) => !c.hidden && this.__isColumnEditable(c));
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
/**
|
|
1649
|
+
* Opens the editor on the given cell. Sets focus + editing state and
|
|
1650
|
+
* focuses the freshly rendered editor on the next frame.
|
|
1651
|
+
*/
|
|
1652
|
+
public startEditing(item: T, col: Column<T>) {
|
|
1653
|
+
if (!this.__isColumnEditable(col)) return;
|
|
1654
|
+
const rowId = this.getRowId(item);
|
|
1655
|
+
const colKey = String(col.key);
|
|
1656
|
+
this.__focusedCell = { rowId, colKey };
|
|
1657
|
+
this.__editingCell = { rowId, colKey };
|
|
1658
|
+
this.requestUpdate();
|
|
1659
|
+
this.updateComplete.then(() => {
|
|
1660
|
+
const el = this.shadowRoot?.querySelector(
|
|
1661
|
+
'.editingCell dees-input-text, .editingCell dees-input-checkbox, ' +
|
|
1662
|
+
'.editingCell dees-input-dropdown, .editingCell dees-input-datepicker, ' +
|
|
1663
|
+
'.editingCell dees-input-tags'
|
|
1664
|
+
) as any;
|
|
1665
|
+
el?.focus?.();
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
/** Closes the editor without committing. */
|
|
1670
|
+
public cancelCellEdit() {
|
|
1671
|
+
this.__editingCell = undefined;
|
|
1672
|
+
this.requestUpdate();
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/**
|
|
1676
|
+
* Commits an editor value to the row. Runs `parse` then `validate`. On
|
|
1677
|
+
* validation failure, fires `cellEditError` and leaves the editor open.
|
|
1678
|
+
* On success, mutates `data` in place, fires `cellEdit`, and closes the
|
|
1679
|
+
* editor.
|
|
1680
|
+
*/
|
|
1681
|
+
public commitCellEdit(item: T, col: Column<T>, editorValue: any) {
|
|
1682
|
+
const key = String(col.key);
|
|
1683
|
+
const oldValue = (item as any)[col.key];
|
|
1684
|
+
const parsed = col.parse ? col.parse(editorValue, item) : editorValue;
|
|
1685
|
+
if (col.validate) {
|
|
1686
|
+
const result = col.validate(parsed, item);
|
|
1687
|
+
if (typeof result === 'string') {
|
|
1688
|
+
this.dispatchEvent(
|
|
1689
|
+
new CustomEvent('cellEditError', {
|
|
1690
|
+
detail: { row: item, key, value: parsed, message: result },
|
|
1691
|
+
bubbles: true,
|
|
1692
|
+
composed: true,
|
|
1693
|
+
})
|
|
1694
|
+
);
|
|
1695
|
+
return;
|
|
1542
1696
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1697
|
+
}
|
|
1698
|
+
if (parsed !== oldValue) {
|
|
1699
|
+
(item as any)[col.key] = parsed;
|
|
1700
|
+
this.dispatchEvent(
|
|
1701
|
+
new CustomEvent('cellEdit', {
|
|
1702
|
+
detail: { row: item, key, oldValue, newValue: parsed },
|
|
1703
|
+
bubbles: true,
|
|
1704
|
+
composed: true,
|
|
1705
|
+
})
|
|
1706
|
+
);
|
|
1707
|
+
this.changeSubject.next(this);
|
|
1708
|
+
}
|
|
1709
|
+
this.__editingCell = undefined;
|
|
1710
|
+
this.requestUpdate();
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/** Renders the appropriate dees-input-* component for this column. */
|
|
1714
|
+
private renderCellEditor(item: T, col: Column<T>): TemplateResult {
|
|
1715
|
+
const raw = (item as any)[col.key];
|
|
1716
|
+
const value = col.format ? col.format(raw, item) : raw;
|
|
1717
|
+
const editorType: TCellEditorType = col.editor ?? 'text';
|
|
1718
|
+
const onTextCommit = (target: any) => this.commitCellEdit(item, col, target.value);
|
|
1719
|
+
|
|
1720
|
+
switch (editorType) {
|
|
1721
|
+
case 'checkbox':
|
|
1722
|
+
return html`<dees-input-checkbox
|
|
1723
|
+
.value=${!!value}
|
|
1724
|
+
@newValue=${(e: CustomEvent<boolean>) => {
|
|
1725
|
+
e.stopPropagation();
|
|
1726
|
+
this.commitCellEdit(item, col, e.detail);
|
|
1727
|
+
}}
|
|
1728
|
+
></dees-input-checkbox>`;
|
|
1729
|
+
|
|
1730
|
+
case 'dropdown': {
|
|
1731
|
+
const options = (col.editorOptions?.options as any[]) ?? [];
|
|
1732
|
+
const selected =
|
|
1733
|
+
options.find((o: any) => (o?.option ?? o?.key ?? o) === value) ?? null;
|
|
1734
|
+
return html`<dees-input-dropdown
|
|
1735
|
+
.options=${options}
|
|
1736
|
+
.selectedOption=${selected}
|
|
1737
|
+
@selectedOption=${(e: CustomEvent<any>) => {
|
|
1738
|
+
e.stopPropagation();
|
|
1739
|
+
const detail = e.detail;
|
|
1740
|
+
const newRaw = detail?.option ?? detail?.key ?? detail;
|
|
1741
|
+
this.commitCellEdit(item, col, newRaw);
|
|
1742
|
+
}}
|
|
1743
|
+
></dees-input-dropdown>`;
|
|
1546
1744
|
}
|
|
1547
|
-
input.remove();
|
|
1548
|
-
target.style.color = originalColor;
|
|
1549
|
-
this.requestUpdate();
|
|
1550
|
-
};
|
|
1551
1745
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1746
|
+
case 'date':
|
|
1747
|
+
return html`<dees-input-datepicker
|
|
1748
|
+
.value=${value}
|
|
1749
|
+
@focusout=${(e: any) => onTextCommit(e.target)}
|
|
1750
|
+
@keydown=${(e: KeyboardEvent) => this.__handleEditorKey(e, item, col)}
|
|
1751
|
+
></dees-input-datepicker>`;
|
|
1752
|
+
|
|
1753
|
+
case 'tags':
|
|
1754
|
+
return html`<dees-input-tags
|
|
1755
|
+
.value=${(value as any) ?? []}
|
|
1756
|
+
@focusout=${(e: any) => onTextCommit(e.target)}
|
|
1757
|
+
@keydown=${(e: KeyboardEvent) => this.__handleEditorKey(e, item, col)}
|
|
1758
|
+
></dees-input-tags>`;
|
|
1759
|
+
|
|
1760
|
+
case 'number':
|
|
1761
|
+
case 'text':
|
|
1762
|
+
default:
|
|
1763
|
+
return html`<dees-input-text
|
|
1764
|
+
.value=${value == null ? '' : String(value)}
|
|
1765
|
+
@focusout=${(e: any) => onTextCommit(e.target)}
|
|
1766
|
+
@keydown=${(e: KeyboardEvent) => this.__handleEditorKey(e, item, col)}
|
|
1767
|
+
></dees-input-text>`;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
/**
|
|
1772
|
+
* Centralized keydown handler for text-style editors. Handles Esc (cancel),
|
|
1773
|
+
* Enter (commit + move down) and Tab/Shift+Tab (commit + move horizontally).
|
|
1774
|
+
*/
|
|
1775
|
+
private __handleEditorKey(eventArg: KeyboardEvent, item: T, col: Column<T>) {
|
|
1776
|
+
if (eventArg.key === 'Escape') {
|
|
1777
|
+
eventArg.preventDefault();
|
|
1778
|
+
eventArg.stopPropagation();
|
|
1779
|
+
this.cancelCellEdit();
|
|
1780
|
+
// Restore focus to the host so arrow-key navigation can resume.
|
|
1781
|
+
this.focus();
|
|
1782
|
+
} else if (eventArg.key === 'Enter') {
|
|
1783
|
+
eventArg.preventDefault();
|
|
1784
|
+
eventArg.stopPropagation();
|
|
1785
|
+
const target = eventArg.target as any;
|
|
1786
|
+
this.commitCellEdit(item, col, target.value);
|
|
1787
|
+
this.moveFocusedCell(0, +1, true);
|
|
1788
|
+
} else if (eventArg.key === 'Tab') {
|
|
1789
|
+
eventArg.preventDefault();
|
|
1790
|
+
eventArg.stopPropagation();
|
|
1791
|
+
const target = eventArg.target as any;
|
|
1792
|
+
this.commitCellEdit(item, col, target.value);
|
|
1793
|
+
this.moveFocusedCell(eventArg.shiftKey ? -1 : +1, 0, true);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Moves the focused cell by `dx` columns and `dy` rows along the editable
|
|
1799
|
+
* grid. Wraps row-end → next row when moving horizontally. If
|
|
1800
|
+
* `andStartEditing` is true, opens the editor on the new cell.
|
|
1801
|
+
*/
|
|
1802
|
+
public moveFocusedCell(dx: number, dy: number, andStartEditing: boolean) {
|
|
1803
|
+
const view: T[] = (this as any)._lastViewData ?? [];
|
|
1804
|
+
if (view.length === 0) return;
|
|
1805
|
+
// Recompute editable columns from the latest effective set.
|
|
1806
|
+
const allCols: Column<T>[] = Array.isArray(this.columns) && this.columns.length > 0
|
|
1807
|
+
? computeEffectiveColumnsFn(this.columns, this.augmentFromDisplayFunction, this.displayFunction, this.data)
|
|
1808
|
+
: computeColumnsFromDisplayFunctionFn(this.displayFunction, this.data);
|
|
1809
|
+
const editableCols = this.__editableColumns(allCols);
|
|
1810
|
+
if (editableCols.length === 0) return;
|
|
1811
|
+
|
|
1812
|
+
let rowIdx = 0;
|
|
1813
|
+
let colIdx = 0;
|
|
1814
|
+
if (this.__focusedCell) {
|
|
1815
|
+
rowIdx = view.findIndex((r) => this.getRowId(r) === this.__focusedCell!.rowId);
|
|
1816
|
+
colIdx = editableCols.findIndex((c) => String(c.key) === this.__focusedCell!.colKey);
|
|
1817
|
+
if (rowIdx < 0) rowIdx = 0;
|
|
1818
|
+
if (colIdx < 0) colIdx = 0;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
if (dx !== 0) {
|
|
1822
|
+
colIdx += dx;
|
|
1823
|
+
while (colIdx >= editableCols.length) {
|
|
1824
|
+
colIdx -= editableCols.length;
|
|
1825
|
+
rowIdx += 1;
|
|
1559
1826
|
}
|
|
1560
|
-
|
|
1827
|
+
while (colIdx < 0) {
|
|
1828
|
+
colIdx += editableCols.length;
|
|
1829
|
+
rowIdx -= 1;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (dy !== 0) rowIdx += dy;
|
|
1561
1833
|
|
|
1562
|
-
//
|
|
1563
|
-
|
|
1564
|
-
|
|
1834
|
+
// Clamp to grid bounds.
|
|
1835
|
+
if (rowIdx < 0 || rowIdx >= view.length) {
|
|
1836
|
+
this.cancelCellEdit();
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
const item = view[rowIdx];
|
|
1840
|
+
const col = editableCols[colIdx];
|
|
1841
|
+
this.__focusedCell = { rowId: this.getRowId(item), colKey: String(col.key) };
|
|
1842
|
+
if (andStartEditing) {
|
|
1843
|
+
this.startEditing(item, col);
|
|
1844
|
+
} else {
|
|
1845
|
+
this.requestUpdate();
|
|
1846
|
+
}
|
|
1565
1847
|
}
|
|
1566
1848
|
}
|
|
@@ -372,32 +372,32 @@ export const tableStyles: CSSResult[] = [
|
|
|
372
372
|
min-height: 24px;
|
|
373
373
|
line-height: 24px;
|
|
374
374
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
td input
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
375
|
+
|
|
376
|
+
/* Editable cell affordances */
|
|
377
|
+
td.editable {
|
|
378
|
+
cursor: text;
|
|
379
|
+
}
|
|
380
|
+
td.focused {
|
|
381
|
+
outline: 2px solid ${cssManager.bdTheme(
|
|
382
|
+
'hsl(222.2 47.4% 51.2% / 0.6)',
|
|
383
|
+
'hsl(217.2 91.2% 59.8% / 0.6)'
|
|
384
|
+
)};
|
|
385
|
+
outline-offset: -2px;
|
|
386
|
+
}
|
|
387
|
+
td.editingCell {
|
|
388
|
+
padding: 0;
|
|
389
|
+
}
|
|
390
|
+
td.editingCell .innerCellContainer {
|
|
391
|
+
padding: 0;
|
|
392
|
+
line-height: normal;
|
|
393
|
+
}
|
|
394
|
+
td.editingCell dees-input-text,
|
|
395
|
+
td.editingCell dees-input-checkbox,
|
|
396
|
+
td.editingCell dees-input-dropdown,
|
|
397
|
+
td.editingCell dees-input-datepicker,
|
|
398
|
+
td.editingCell dees-input-tags {
|
|
399
|
+
display: block;
|
|
400
|
+
width: 100%;
|
|
401
401
|
}
|
|
402
402
|
|
|
403
403
|
/* filter row */
|