@guiexpert/table 1.1.96 → 1.1.97
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/README.md +4 -0
- package/css/main.css +107 -0
- package/css/table-color-vars.css +12 -0
- package/css/table-master-detail.css +95 -0
- package/index.cjs +39 -39
- package/index.d.ts +6 -0
- package/index.js +3579 -2397
- package/lib/table/aria/aria-ids.d.ts +7 -0
- package/lib/table/aria/aria-texts.d.ts +2 -0
- package/lib/table/aria/aria-texts.if.d.ts +14 -0
- package/lib/table/data/common/event/ge-detail-event.d.ts +40 -0
- package/lib/table/data/options/auto-restore-options.d.ts +2 -0
- package/lib/table/data/options/auto-restore-options.if.d.ts +6 -0
- package/lib/table/data/options/table-options.d.ts +2 -0
- package/lib/table/data/options/table-options.if.d.ts +18 -0
- package/lib/table/ele-scope.d.ts +14 -0
- package/lib/table/event-adapter.d.ts +3 -0
- package/lib/table/event-listener.if.d.ts +7 -0
- package/lib/table/master-detail/detail-renderer.if.d.ts +99 -0
- package/lib/table/master-detail/master-detail-options.d.ts +30 -0
- package/lib/table/master-detail/master-detail-options.if.d.ts +60 -0
- package/lib/table/master-detail/master-detail-service.d.ts +223 -0
- package/lib/table/render-scope.d.ts +73 -0
- package/lib/table/row-layout/row-layout.d.ts +120 -0
- package/lib/table/state/table-state.if.d.ts +4 -1
- package/lib/table/state/table-state.service.d.ts +4 -0
- package/lib/table/table-api.d.ts +101 -0
- package/lib/table/table-scope.d.ts +64 -0
- package/package.json +1 -1
|
@@ -47,6 +47,13 @@ export declare class AriaIds {
|
|
|
47
47
|
* @returns {string} e.g. `"ge-t1-rg-body-center"`
|
|
48
48
|
*/
|
|
49
49
|
rowGroup(area: AreaIdent, side: SideIdent): string;
|
|
50
|
+
/**
|
|
51
|
+
* The ID of the row group of the detail rows (master/detail), referenced by `aria-owns` of the host.
|
|
52
|
+
*
|
|
53
|
+
* @returns {string} e.g. `"ge-t1-rg-detail"`
|
|
54
|
+
* @since 1.1.97
|
|
55
|
+
*/
|
|
56
|
+
detailRowGroup(): string;
|
|
50
57
|
/**
|
|
51
58
|
* The ID of the live region (the element that speaks announcements, see {@link LiveAnnouncer}).
|
|
52
59
|
*
|
|
@@ -36,6 +36,8 @@ export declare class AriaTexts implements AriaTextsIf {
|
|
|
36
36
|
rowMoveNotPossible(): string;
|
|
37
37
|
rowDragCancelled(): string;
|
|
38
38
|
cellsFilled(cellCount: number): string;
|
|
39
|
+
detailExpanded(): string;
|
|
40
|
+
detailCollapsed(): string;
|
|
39
41
|
fillTooLarge(maxCells: number): string;
|
|
40
42
|
pendingChanges(pendingCount: number): string;
|
|
41
43
|
changesCommitted(cellCount: number): string;
|
|
@@ -172,4 +172,18 @@ export interface AriaTextsIf {
|
|
|
172
172
|
* @since 1.1.96
|
|
173
173
|
*/
|
|
174
174
|
invalidValue?(message: string): string;
|
|
175
|
+
/**
|
|
176
|
+
* Announced after a detail row was expanded (master/detail).
|
|
177
|
+
*
|
|
178
|
+
* @returns {string} default: `"Details expanded"`
|
|
179
|
+
* @since 1.1.97
|
|
180
|
+
*/
|
|
181
|
+
detailExpanded?(): string;
|
|
182
|
+
/**
|
|
183
|
+
* Announced after a detail row was collapsed (master/detail).
|
|
184
|
+
*
|
|
185
|
+
* @returns {string} default: `"Details collapsed"`
|
|
186
|
+
* @since 1.1.97
|
|
187
|
+
*/
|
|
188
|
+
detailCollapsed?(): string;
|
|
175
189
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What opened or closed a detail row: a click on the icon (`"mouse"`), Enter (`"keyboard"`), the {@link TableApi}
|
|
3
|
+
* (`"api"`) or a restored table state (`"state"`).
|
|
4
|
+
*
|
|
5
|
+
* @category Master/detail
|
|
6
|
+
* @since 1.1.97
|
|
7
|
+
*/
|
|
8
|
+
export type GeDetailEventSource = "mouse" | "keyboard" | "api" | "state";
|
|
9
|
+
/**
|
|
10
|
+
* Reported by {@link EventListenerIf.onDetailExpandedChanged}: a detail row was expanded or collapsed (master/detail,
|
|
11
|
+
* {@link MasterDetailOptionsIf}).
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const listener = new EventAdapter();
|
|
15
|
+
* listener.onDetailExpandedChanged = (evt: GeDetailEvent) => {
|
|
16
|
+
* console.log(`order ${evt.row.id} ${evt.expanded ? "opened" : "closed"} (${evt.source})`);
|
|
17
|
+
* };
|
|
18
|
+
*
|
|
19
|
+
* @category Master/detail
|
|
20
|
+
* @since 1.1.97
|
|
21
|
+
*/
|
|
22
|
+
export declare class GeDetailEvent<T = any> {
|
|
23
|
+
/** The master row. */
|
|
24
|
+
readonly row: T;
|
|
25
|
+
/** Its display index (`-1` if it is not shown, e.g. filtered out). */
|
|
26
|
+
readonly rowIndex: number;
|
|
27
|
+
/** `true` = expanded, `false` = collapsed. */
|
|
28
|
+
readonly expanded: boolean;
|
|
29
|
+
/** What changed it. */
|
|
30
|
+
readonly source: GeDetailEventSource;
|
|
31
|
+
constructor(
|
|
32
|
+
/** The master row. */
|
|
33
|
+
row: T,
|
|
34
|
+
/** Its display index (`-1` if it is not shown, e.g. filtered out). */
|
|
35
|
+
rowIndex: number,
|
|
36
|
+
/** `true` = expanded, `false` = collapsed. */
|
|
37
|
+
expanded: boolean,
|
|
38
|
+
/** What changed it. */
|
|
39
|
+
source: GeDetailEventSource);
|
|
40
|
+
}
|
|
@@ -44,6 +44,8 @@ export declare class AutoRestoreOptions<T> implements AutoRestoreOptionsIf<T> {
|
|
|
44
44
|
autoRestoreHeaderGroupState: boolean;
|
|
45
45
|
/** @since 1.1.92 */
|
|
46
46
|
autoRestoreCustomState: boolean;
|
|
47
|
+
/** @since 1.1.97 */
|
|
48
|
+
autoRestoreDetailState: boolean;
|
|
47
49
|
getStorageKeyFn: undefined;
|
|
48
50
|
/** @deprecated since 1.1.92 – never used by the table. */
|
|
49
51
|
isSame: (r1: T, r2: T, options: AutoRestoreOptionsIf<T>) => boolean;
|
|
@@ -51,6 +51,7 @@ export type GetStorageKeyFn = (() => string);
|
|
|
51
51
|
* | {@link autoRestoreCheckedState} | checked rows | 1.1.92 (declared before, without effect) |
|
|
52
52
|
* | {@link autoRestoreSelectedState} | selection | 1.1.92 (declared before, without effect) |
|
|
53
53
|
* | {@link autoRestoreCustomState} | your data ({@link TableApi.setCustomState}) | 1.1.92 |
|
|
54
|
+
* | {@link autoRestoreDetailState} | expanded detail rows (master/detail) | 1.1.97 |
|
|
54
55
|
*
|
|
55
56
|
* Before 1.1.92 sorting, scroll position and tree were stored under three keys (`<key>sortingState`,
|
|
56
57
|
* `<key>scrollState`, `<key>collapsedExpandedState`); they are read once and removed with the next save.
|
|
@@ -103,6 +104,11 @@ export interface AutoRestoreOptionsIf<T> {
|
|
|
103
104
|
* @since 1.1.92
|
|
104
105
|
*/
|
|
105
106
|
autoRestoreCustomState?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Remember the expanded detail rows of master/detail ({@link TableOptionsIf.masterDetail}, by {@link getRowId}).
|
|
109
|
+
* @since 1.1.97
|
|
110
|
+
*/
|
|
111
|
+
autoRestoreDetailState?: boolean;
|
|
106
112
|
/**
|
|
107
113
|
* A unique, stable id per row (tree: per node). With the default of {@link AutoRestoreOptions} the table uses the
|
|
108
114
|
* property `id`, then `uuid`; rows without them are not stored (since 1.1.92 – the JSON of the row would write
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MasterDetailOptionsIf } from '../../master-detail/master-detail-options.if.js';
|
|
1
2
|
import { DefaultRowHeightsIf } from './default-row-heights.if.js';
|
|
2
3
|
import { TableOptionsIf } from './table-options.if.js';
|
|
3
4
|
import { TreeOptions } from './tree-options.js';
|
|
@@ -85,5 +86,6 @@ export declare class TableOptions implements TableOptionsIf {
|
|
|
85
86
|
overlayOptions?: Partial<OverlayOptionsIf>;
|
|
86
87
|
rowDragOptions?: Partial<RowDragOptionsIf>;
|
|
87
88
|
fillHandle?: boolean | Partial<FillHandleOptionsIf>;
|
|
89
|
+
masterDetail?: Partial<MasterDetailOptionsIf>;
|
|
88
90
|
batchEdit?: boolean | Partial<BatchEditOptionsIf>;
|
|
89
91
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MasterDetailOptionsIf } from '../../master-detail/master-detail-options.if.js';
|
|
1
2
|
import { RowDragOptionsIf } from '../../row-drag/row-drag-options.if.js';
|
|
2
3
|
import { FillHandleOptionsIf } from '../../fill/fill-handle-options.if.js';
|
|
3
4
|
import { BatchEditOptionsIf } from '../../batch-edit/batch-edit-options.if.js';
|
|
@@ -361,6 +362,23 @@ export interface TableOptionsIf {
|
|
|
361
362
|
* @since 1.1.96
|
|
362
363
|
*/
|
|
363
364
|
batchEdit?: boolean | Partial<BatchEditOptionsIf>;
|
|
365
|
+
/**
|
|
366
|
+
* Master/detail: master rows get an expand icon; below an expanded row a detail row over the full width shows any
|
|
367
|
+
* content (a nested table, a form, a framework component – see {@link MasterDetailOptionsIf}). Only for flat body
|
|
368
|
+
* models. Fields you leave out keep their default ({@link MasterDetailOptions}); `detailRenderer` is required.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* const tableOptions: TableOptionsIf = {
|
|
372
|
+
* ...new TableOptions(),
|
|
373
|
+
* masterDetail: {
|
|
374
|
+
* detailHeight: "auto",
|
|
375
|
+
* detailRenderer: (container, row) => { container.textContent = `Notes: ${row.notes}`; }
|
|
376
|
+
* }
|
|
377
|
+
* };
|
|
378
|
+
*
|
|
379
|
+
* @since 1.1.97
|
|
380
|
+
*/
|
|
381
|
+
masterDetail?: Partial<MasterDetailOptionsIf>;
|
|
364
382
|
/**
|
|
365
383
|
* The state the table starts with: column layout, sorting, tree, checked and selected rows … – e.g. loaded from
|
|
366
384
|
* your server ({@link TableApi.getState}, {@link EventListenerIf.onStateChanged}). A state stored by
|
package/lib/table/ele-scope.d.ts
CHANGED
|
@@ -98,6 +98,12 @@ export declare class EleScope {
|
|
|
98
98
|
* @return {void}
|
|
99
99
|
*/
|
|
100
100
|
adjustContainersAndRows(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Position and size of the detail layer (master/detail) in the host – overwritten in {@link RenderScope}.
|
|
103
|
+
*
|
|
104
|
+
* @since 1.1.97
|
|
105
|
+
*/
|
|
106
|
+
protected adjustDetailLayerBounds(_left: number, _top: number, _width: number, _height: number): void;
|
|
101
107
|
/**
|
|
102
108
|
* Adjusts the position or appearance of elements after scrolling.
|
|
103
109
|
* This method must be overwritten in child classes.
|
|
@@ -105,6 +111,14 @@ export declare class EleScope {
|
|
|
105
111
|
* @return {void}
|
|
106
112
|
*/
|
|
107
113
|
adjustAfterScrolling(): void;
|
|
114
|
+
/**
|
|
115
|
+
* The height of all body rows in px – the model's content height plus, in {@link RenderScope}, the heights of open
|
|
116
|
+
* detail rows (master/detail).
|
|
117
|
+
*
|
|
118
|
+
* @returns {number} px
|
|
119
|
+
* @since 1.1.97
|
|
120
|
+
*/
|
|
121
|
+
protected getBodyContentHeight(): number;
|
|
108
122
|
/**
|
|
109
123
|
* Resets the size of the wrapper div based on the content dimensions.
|
|
110
124
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GeDetailEvent } from './data/common/event/ge-detail-event.js';
|
|
1
2
|
import { GeModelChangeEvent } from './data/common/event/ge-model-change-event.js';
|
|
2
3
|
import { GeMouseEvent } from './data/common/event/ge-mouse-event.js';
|
|
3
4
|
import { GeScrollEvent } from './data/common/event/ge-scroll-event.js';
|
|
@@ -40,4 +41,6 @@ export declare class EventAdapter implements EventListenerIf {
|
|
|
40
41
|
onRowDragEnd(_evt: GeRowDragEvent): void;
|
|
41
42
|
/** @since 1.1.96 */
|
|
42
43
|
onBatchEditChanged(_evt: GeBatchEditEvent): void;
|
|
44
|
+
/** @since 1.1.97 */
|
|
45
|
+
onDetailExpandedChanged(_evt: GeDetailEvent): void;
|
|
43
46
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GeDetailEvent } from './data/common/event/ge-detail-event.js';
|
|
1
2
|
import { GeMouseEvent } from './data/common/event/ge-mouse-event.js';
|
|
2
3
|
import { GeModelChangeEvent } from './data/common/event/ge-model-change-event.js';
|
|
3
4
|
import { GeScrollEvent } from './data/common/event/ge-scroll-event.js';
|
|
@@ -95,4 +96,10 @@ export interface EventListenerIf extends EventFocusChangedListenerIf, EventSelec
|
|
|
95
96
|
* @since 1.1.96
|
|
96
97
|
*/
|
|
97
98
|
onBatchEditChanged?(evt: GeBatchEditEvent): void;
|
|
99
|
+
/**
|
|
100
|
+
* A detail row was expanded or collapsed (master/detail, {@link TableOptionsIf.masterDetail}).
|
|
101
|
+
*
|
|
102
|
+
* @since 1.1.97
|
|
103
|
+
*/
|
|
104
|
+
onDetailExpandedChanged?(evt: GeDetailEvent): void;
|
|
98
105
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TableApi } from '../table-api.js';
|
|
2
|
+
import { TableModelIf } from '../data/tablemodel/table-model.if.js';
|
|
3
|
+
import { TableOptionsIf } from '../data/options/table-options.if.js';
|
|
4
|
+
import { EventListenerIf } from '../event-listener.if.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* What {@link DetailRenderContextIf.createTable} needs for a nested table in a detail row.
|
|
8
|
+
*
|
|
9
|
+
* @category Master/detail
|
|
10
|
+
* @since 1.1.97
|
|
11
|
+
*/
|
|
12
|
+
export interface DetailTableParamsIf {
|
|
13
|
+
/** The model of the nested table, e.g. `TableFactory.createTableModel({ rows: order.items, properties: [...] })`. */
|
|
14
|
+
tableModel: TableModelIf;
|
|
15
|
+
/** Its options (default: `new TableOptions()`). */
|
|
16
|
+
tableOptions?: TableOptionsIf;
|
|
17
|
+
/** Its event listener (default: none). */
|
|
18
|
+
eventListener?: EventListenerIf;
|
|
19
|
+
/**
|
|
20
|
+
* The height of the nested table: px or a CSS length. Default: `"100%"` with a fixed
|
|
21
|
+
* {@link MasterDetailOptionsIf.detailHeight}, otherwise {@link MasterDetailOptionsIf.estimatedDetailHeight} px.
|
|
22
|
+
*/
|
|
23
|
+
height?: number | string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The context a {@link DetailRendererIf} gets: the master row, the outer table and helpers whose resources are released
|
|
27
|
+
* together with the detail row.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const renderDetail: DetailRenderFnType<Order> = (container, order, ctx) => {
|
|
31
|
+
* const button = document.createElement("button");
|
|
32
|
+
* button.textContent = `Open order ${order.id}`;
|
|
33
|
+
* button.addEventListener("click", () => ctx.tableApi.announce(`Order ${order.id}`), { signal: ctx.signal });
|
|
34
|
+
* container.appendChild(button);
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* @category Master/detail
|
|
38
|
+
* @since 1.1.97
|
|
39
|
+
*/
|
|
40
|
+
export interface DetailRenderContextIf<T = any> {
|
|
41
|
+
/** The master row (the row object of the body model). */
|
|
42
|
+
readonly row: T;
|
|
43
|
+
/** The display index of the master row when the detail was created (it changes with sorting and filtering). */
|
|
44
|
+
readonly rowIndex: number;
|
|
45
|
+
/** The API of the outer (master) table. */
|
|
46
|
+
readonly tableApi: TableApi;
|
|
47
|
+
/**
|
|
48
|
+
* `true` if the content of this detail row existed before and was destroyed (too many rendered details, see
|
|
49
|
+
* {@link MasterDetailOptionsIf.maxRenderedDetails}, or {@link TableApi.refreshDetail}) – state that lives only in the
|
|
50
|
+
* DOM (inputs, scroll positions) is gone then.
|
|
51
|
+
*/
|
|
52
|
+
readonly recreated: boolean;
|
|
53
|
+
/** Aborted when the detail row is destroyed – pass it to `addEventListener(…, { signal })`. */
|
|
54
|
+
readonly signal: AbortSignal;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a GuiExpert table inside the detail row; it is destroyed (`TableScope.destroy()`) together with the detail
|
|
57
|
+
* row. The CSS variables of the page (light/dark theme) apply to it as well.
|
|
58
|
+
*
|
|
59
|
+
* @param {DetailTableParamsIf} params - model, options, listener, height
|
|
60
|
+
* @returns {TableApi} the API of the nested table
|
|
61
|
+
*/
|
|
62
|
+
createTable(params: DetailTableParamsIf): TableApi;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Renders the content of a detail row – a function (see {@link DetailRendererIf}). It may return a cleanup function,
|
|
66
|
+
* called when the detail row is destroyed (collapsed, too many rendered details, new table model).
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const renderDetail: DetailRenderFnType<Order> = (container, order) => {
|
|
70
|
+
* container.textContent = `${order.items.length} items, ordered on ${order.date}`; // textContent: no HTML injection
|
|
71
|
+
* };
|
|
72
|
+
*
|
|
73
|
+
* @category Master/detail
|
|
74
|
+
* @since 1.1.97
|
|
75
|
+
*/
|
|
76
|
+
export type DetailRenderFnType<T = any> = (container: HTMLDivElement, row: T, context: DetailRenderContextIf<T>) => (() => void) | void;
|
|
77
|
+
/**
|
|
78
|
+
* Renders the content of a detail row – the object form of {@link DetailRenderFnType}, e.g. the
|
|
79
|
+
* `DetailComponentRendererWrapper` of the framework wrappers (React, Vue, Svelte …).
|
|
80
|
+
*
|
|
81
|
+
* The content is created once, when the detail row becomes visible for the first time, and lives until the row is
|
|
82
|
+
* collapsed – scrolling only hides and shows it (inputs, selections and scroll positions stay).
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* class OrderNotesRenderer implements DetailRendererIf<Order> {
|
|
86
|
+
* render(container: HTMLDivElement, order: Order): () => void {
|
|
87
|
+
* const textarea = document.createElement("textarea");
|
|
88
|
+
* textarea.value = order.notes ?? "";
|
|
89
|
+
* container.appendChild(textarea);
|
|
90
|
+
* return () => { order.notes = textarea.value; }; // cleanup: keep the text
|
|
91
|
+
* }
|
|
92
|
+
* }
|
|
93
|
+
*
|
|
94
|
+
* @category Master/detail
|
|
95
|
+
* @since 1.1.97
|
|
96
|
+
*/
|
|
97
|
+
export interface DetailRendererIf<T = any> {
|
|
98
|
+
render(container: HTMLDivElement, row: T, context: DetailRenderContextIf<T>): (() => void) | void;
|
|
99
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { DetailHeightType, MasterDetailOptionsIf } from './master-detail-options.if.js';
|
|
2
|
+
import { DetailRendererIf, DetailRenderFnType } from './detail-renderer.if.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The defaults of {@link MasterDetailOptionsIf}.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const options = MasterDetailOptions.create({ detailRenderer: (el, row) => { el.textContent = String(row.id); } });
|
|
9
|
+
* options.detailHeight; // "auto"
|
|
10
|
+
* options.maxRenderedDetails; // 50
|
|
11
|
+
*
|
|
12
|
+
* @category Master/detail
|
|
13
|
+
* @since 1.1.97
|
|
14
|
+
*/
|
|
15
|
+
export declare class MasterDetailOptions<T = any> implements MasterDetailOptionsIf<T> {
|
|
16
|
+
detailRenderer: DetailRendererIf<T> | DetailRenderFnType<T>;
|
|
17
|
+
isMaster?: (row: T, rowIndex: number) => boolean;
|
|
18
|
+
detailHeight: DetailHeightType | ((row: T, rowIndex: number) => DetailHeightType);
|
|
19
|
+
estimatedDetailHeight: number;
|
|
20
|
+
toggleColumn?: number | string;
|
|
21
|
+
maxRenderedDetails: number;
|
|
22
|
+
getRowId?: (row: T) => string | number;
|
|
23
|
+
/**
|
|
24
|
+
* The defaults, overridden by the given values (`undefined` keeps the default).
|
|
25
|
+
*
|
|
26
|
+
* @param {Partial<MasterDetailOptionsIf>} [options] - e.g. `tableOptions.masterDetail`
|
|
27
|
+
* @returns {MasterDetailOptionsIf} complete options
|
|
28
|
+
*/
|
|
29
|
+
static create<T = any>(options?: Partial<MasterDetailOptionsIf<T>>): MasterDetailOptionsIf<T>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { DetailRendererIf, DetailRenderFnType } from './detail-renderer.if.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The height of a detail row: px, or `"auto"` = measured from its content (a `ResizeObserver` follows changes).
|
|
5
|
+
*
|
|
6
|
+
* @category Master/detail
|
|
7
|
+
* @since 1.1.97
|
|
8
|
+
*/
|
|
9
|
+
export type DetailHeightType = number | "auto";
|
|
10
|
+
/**
|
|
11
|
+
* Master/detail: a master row can be expanded; below it a detail row over the full width of the table (fixed columns
|
|
12
|
+
* included) shows any content – a nested GuiExpert table, a form, a chart, a framework component. Works with virtual
|
|
13
|
+
* scrolling and millions of rows: the content is created when the detail row becomes visible and lives until it is
|
|
14
|
+
* collapsed (scrolling only hides and shows it). Only for flat body models (not for trees, row groups and pivot tables).
|
|
15
|
+
*
|
|
16
|
+
* Fields you leave out keep their default ({@link MasterDetailOptions}).
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const tableOptions: TableOptionsIf = {
|
|
20
|
+
* ...new TableOptions(),
|
|
21
|
+
* masterDetail: {
|
|
22
|
+
* detailHeight: 180,
|
|
23
|
+
* detailRenderer: (_container, order: Order, ctx) => {
|
|
24
|
+
* ctx.createTable({
|
|
25
|
+
* tableModel: TableFactory.createTableModel({ rows: order.items, properties: ["sku", "name", "qty", "price"] })
|
|
26
|
+
* });
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* };
|
|
30
|
+
*
|
|
31
|
+
* @see Training: [Master/detail](https://gui.expert/training/master-detail/)
|
|
32
|
+
*
|
|
33
|
+
* @category Master/detail
|
|
34
|
+
* @since 1.1.97
|
|
35
|
+
*/
|
|
36
|
+
export interface MasterDetailOptionsIf<T = any> {
|
|
37
|
+
/** Renders the content of a detail row (function or object with `render()`). */
|
|
38
|
+
detailRenderer: DetailRendererIf<T> | DetailRenderFnType<T>;
|
|
39
|
+
/** Which rows can be expanded. Default: every row. */
|
|
40
|
+
isMaster?: (row: T, rowIndex: number) => boolean;
|
|
41
|
+
/** Height of the detail rows: px, `"auto"` (measured) or a function per row. Default `"auto"`. */
|
|
42
|
+
detailHeight: DetailHeightType | ((row: T, rowIndex: number) => DetailHeightType);
|
|
43
|
+
/** Height of an `"auto"` detail row before its content was measured (px). Default 200. */
|
|
44
|
+
estimatedDetailHeight: number;
|
|
45
|
+
/**
|
|
46
|
+
* The column with the expand/collapse icon: column index or property. Default: the first data column (behind a row
|
|
47
|
+
* number and a row checkbox column – where a tree has its arrow).
|
|
48
|
+
*/
|
|
49
|
+
toggleColumn?: number | string;
|
|
50
|
+
/**
|
|
51
|
+
* At most this many detail contents exist at the same time. Beyond it the detail row that was out of view the
|
|
52
|
+
* longest is destroyed and created again when it scrolls back into view (`context.recreated`). Default 50.
|
|
53
|
+
*/
|
|
54
|
+
maxRenderedDetails: number;
|
|
55
|
+
/**
|
|
56
|
+
* The id of a master row – keeps detail rows open when the rows are replaced by new objects with the same ids
|
|
57
|
+
* (`setRows`). Default: `autoRestoreOptions.getRowId`, otherwise the row object itself.
|
|
58
|
+
*/
|
|
59
|
+
getRowId?: (row: T) => string | number;
|
|
60
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { TableScope } from '../table-scope.js';
|
|
2
|
+
import { TableApi } from '../table-api.js';
|
|
3
|
+
import { TableModelIf } from '../data/tablemodel/table-model.if.js';
|
|
4
|
+
import { TableOptionsIf } from '../data/options/table-options.if.js';
|
|
5
|
+
import { EventListenerIf } from '../event-listener.if.js';
|
|
6
|
+
import { GeDetailEventSource } from '../data/common/event/ge-detail-event.js';
|
|
7
|
+
import { MasterDetailOptionsIf } from './master-detail-options.if.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The part of a nested table the master/detail service needs (a {@link TableScope}).
|
|
11
|
+
*
|
|
12
|
+
* @category Internals
|
|
13
|
+
* @since 1.1.97
|
|
14
|
+
*/
|
|
15
|
+
export interface NestedTableScopeIf {
|
|
16
|
+
firstInit(): unknown;
|
|
17
|
+
getApi(): TableApi;
|
|
18
|
+
destroy(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates the {@link TableScope} of a nested table (given by `TableScope` – no import cycle).
|
|
22
|
+
*
|
|
23
|
+
* @category Internals
|
|
24
|
+
* @since 1.1.97
|
|
25
|
+
*/
|
|
26
|
+
export type NestedTableScopeFactory = (host: HTMLDivElement, tableModel: TableModelIf, tableOptions: TableOptionsIf | undefined, eventListener: EventListenerIf | undefined) => NestedTableScopeIf;
|
|
27
|
+
/**
|
|
28
|
+
* Master/detail of a table ({@link TableOptionsIf.masterDetail}): the expanded master rows, the detail layer and the
|
|
29
|
+
* life cycle of the detail contents. Created by {@link TableScope} (`tableScope.masterDetailService`); use the
|
|
30
|
+
* {@link TableApi} (`setDetailExpanded`, `toggleDetail` …).
|
|
31
|
+
*
|
|
32
|
+
* - **Layer**: one div `ge-table-detail-layer` in the host, over the body (full width, fixed columns included, without
|
|
33
|
+
* the scroll bars). It is never cleared by the virtual rendering and does not scroll horizontally.
|
|
34
|
+
* - **Positions**: the heights of the detail rows are extras of the body {@link RowLayout} (behind their master row);
|
|
35
|
+
* `RenderScope` reports the detail rows in view after every render pass ({@link layoutVisible}).
|
|
36
|
+
* - **Life cycle**: the content is created when the detail row becomes visible, hidden (not destroyed) when it scrolls
|
|
37
|
+
* out of view, destroyed when it is collapsed, beyond `maxRenderedDetails` or when the table is destroyed.
|
|
38
|
+
* - **Events**: mouse, keyboard and pointer events from a detail row are ignored by the outer table
|
|
39
|
+
* ({@link isInLayer}); the mouse wheel over a detail row scrolls the outer table when nothing in the detail scrolls.
|
|
40
|
+
*
|
|
41
|
+
* @category Internals
|
|
42
|
+
* @since 1.1.97
|
|
43
|
+
*/
|
|
44
|
+
export declare class MasterDetailService {
|
|
45
|
+
private readonly scope;
|
|
46
|
+
private readonly createNestedScope;
|
|
47
|
+
/** The complete options (defaults + `tableOptions.masterDetail`). */
|
|
48
|
+
readonly options: MasterDetailOptionsIf;
|
|
49
|
+
private readonly active;
|
|
50
|
+
private layer?;
|
|
51
|
+
private readonly entries;
|
|
52
|
+
private byIndex;
|
|
53
|
+
private visibleNow;
|
|
54
|
+
private renderPass;
|
|
55
|
+
private renderedCount;
|
|
56
|
+
private toggleColumnIndex;
|
|
57
|
+
private resizeFrame;
|
|
58
|
+
private destroyed;
|
|
59
|
+
constructor(scope: TableScope, createNestedScope: NestedTableScopeFactory);
|
|
60
|
+
/** True if master/detail is configured (and the body is flat). */
|
|
61
|
+
isActive(): boolean;
|
|
62
|
+
/** True if at least one master row is expanded. */
|
|
63
|
+
hasExpanded(): boolean;
|
|
64
|
+
/** The detail layer (`undefined` without master/detail). */
|
|
65
|
+
getLayer(): HTMLDivElement | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* True if an event comes from a detail row (or a nested table in it) – the outer table ignores it.
|
|
68
|
+
*
|
|
69
|
+
* @param {Event} evt - a DOM event while it is dispatched
|
|
70
|
+
* @returns {boolean} whether its path contains the detail layer
|
|
71
|
+
*/
|
|
72
|
+
isInLayer(evt: Event): boolean;
|
|
73
|
+
/** The id of a master row: `options.getRowId`, `autoRestoreOptions.getRowId`, the row object or its index. */
|
|
74
|
+
keyOf(row: unknown, rowIndex: number): unknown;
|
|
75
|
+
/**
|
|
76
|
+
* True if the row at a display index can be expanded ({@link MasterDetailOptionsIf.isMaster}).
|
|
77
|
+
*
|
|
78
|
+
* @param {number} rowIndex - display index
|
|
79
|
+
* @returns {boolean} whether it is a master row
|
|
80
|
+
*/
|
|
81
|
+
isMasterAt(rowIndex: number): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* True if the master row at a display index is expanded.
|
|
84
|
+
*
|
|
85
|
+
* @param {number} rowIndex - display index
|
|
86
|
+
* @returns {boolean} whether its detail row is open
|
|
87
|
+
*/
|
|
88
|
+
isExpandedAt(rowIndex: number): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* True if a row (display index or row object) is expanded.
|
|
91
|
+
*
|
|
92
|
+
* @param {number | object} rowOrIndex - display index or row object
|
|
93
|
+
* @returns {boolean} whether its detail row is open
|
|
94
|
+
*/
|
|
95
|
+
isExpanded(rowOrIndex: unknown): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Expands or collapses a master row. The table repaints and reports the change ({@link TableScope}).
|
|
98
|
+
*
|
|
99
|
+
* @param {number | object} rowOrIndex - display index or row object
|
|
100
|
+
* @param {boolean} expanded - the new state
|
|
101
|
+
* @param {GeDetailEventSource} [source] - what changed it (for the event)
|
|
102
|
+
* @returns {boolean} false if nothing changed (no master row, already in that state, no master/detail)
|
|
103
|
+
*/
|
|
104
|
+
setExpanded(rowOrIndex: unknown, expanded: boolean, source?: GeDetailEventSource): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Expands all master rows that are shown (their contents are created only when they scroll into view).
|
|
107
|
+
* No event per row.
|
|
108
|
+
*
|
|
109
|
+
* @returns {number} the number of rows that were expanded
|
|
110
|
+
*/
|
|
111
|
+
expandAll(): number;
|
|
112
|
+
/**
|
|
113
|
+
* Collapses all detail rows (their contents are destroyed). No event per row.
|
|
114
|
+
*
|
|
115
|
+
* @returns {number} the number of rows that were collapsed
|
|
116
|
+
*/
|
|
117
|
+
collapseAll(): number;
|
|
118
|
+
/** The expanded master rows (row objects; for models without row objects: `undefined`). */
|
|
119
|
+
getExpandedRows(): unknown[];
|
|
120
|
+
/**
|
|
121
|
+
* Expands exactly these rows and collapses all others – the restore of the table state. Rows that are no master
|
|
122
|
+
* rows are skipped. No event per row.
|
|
123
|
+
*
|
|
124
|
+
* @param {unknown[]} rows - row objects of the body model
|
|
125
|
+
* @param {GeDetailEventSource} [source] - what changed it
|
|
126
|
+
* @returns {boolean} true if something changed
|
|
127
|
+
*/
|
|
128
|
+
setExpandedRows(rows: readonly unknown[], source?: GeDetailEventSource): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Destroys the content of a detail row and creates it again (e.g. after the row's data changed).
|
|
131
|
+
*
|
|
132
|
+
* @param {number | object} rowOrIndex - display index or row object
|
|
133
|
+
* @returns {boolean} false if the row is not expanded
|
|
134
|
+
*/
|
|
135
|
+
refresh(rowOrIndex: unknown): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* The container of a detail row (the element `detailRenderer` renders into) – only while its content exists.
|
|
138
|
+
*
|
|
139
|
+
* @param {number | object} rowOrIndex - display index or row object
|
|
140
|
+
* @returns {HTMLDivElement | undefined} the container
|
|
141
|
+
*/
|
|
142
|
+
getDetailElement(rowOrIndex: unknown): HTMLDivElement | undefined;
|
|
143
|
+
/**
|
|
144
|
+
* The display index of the master row a DOM node belongs to (a detail row or something in it).
|
|
145
|
+
*
|
|
146
|
+
* @param {Node} node - a node in the detail layer
|
|
147
|
+
* @returns {number} the display index, -1 if none
|
|
148
|
+
*/
|
|
149
|
+
getMasterRowIndexOf(node: Node | null): number;
|
|
150
|
+
/**
|
|
151
|
+
* The display indices and heights of the expanded master rows that are shown – the extras of the body row layout.
|
|
152
|
+
* Called by `RenderScope` when the row layout is built (after filtering, sorting, editing, a detail toggle …): one
|
|
153
|
+
* pass over the shown rows. Removes expanded rows that no longer exist in the model (`getAllRows()`).
|
|
154
|
+
*
|
|
155
|
+
* @returns {{ rowIndices: number[], heights: number[] } | undefined} the extras, `undefined` without expanded rows
|
|
156
|
+
*/
|
|
157
|
+
computeExtras(): {
|
|
158
|
+
rowIndices: number[];
|
|
159
|
+
heights: number[];
|
|
160
|
+
} | undefined;
|
|
161
|
+
/**
|
|
162
|
+
* Called at the start of the body render pass: the column of the expand icon.
|
|
163
|
+
*/
|
|
164
|
+
startRenderPass(): void;
|
|
165
|
+
/**
|
|
166
|
+
* Shows the detail rows in view (called by `RenderScope` after the body rows were rendered): creates missing
|
|
167
|
+
* contents, sets their positions, hides the ones that left the view and destroys the oldest hidden contents beyond
|
|
168
|
+
* {@link MasterDetailOptionsIf.maxRenderedDetails}.
|
|
169
|
+
*
|
|
170
|
+
* @param {number[]} triples - `rowIndex, top, height` of each detail row in view; `top` relative to the body top
|
|
171
|
+
*/
|
|
172
|
+
layoutVisible(triples: number[]): void;
|
|
173
|
+
/**
|
|
174
|
+
* Position and size of the layer: the visible body including the fixed columns, without the scroll bars.
|
|
175
|
+
*
|
|
176
|
+
* @param {number} left - px in the host
|
|
177
|
+
* @param {number} top - px in the host (below the header)
|
|
178
|
+
* @param {number} width - px
|
|
179
|
+
* @param {number} height - px
|
|
180
|
+
*/
|
|
181
|
+
setBounds(left: number, top: number, width: number, height: number): void;
|
|
182
|
+
/**
|
|
183
|
+
* Decorates a body cell of the toggle column of a master row with the expand icon (`ge-table-detail-toggle`).
|
|
184
|
+
*
|
|
185
|
+
* @param {HTMLDivElement} cell - the rendered cell
|
|
186
|
+
* @param {number} rowIndex - display index
|
|
187
|
+
* @param {number} columnIndex - model column index
|
|
188
|
+
*/
|
|
189
|
+
decorateCell(cell: HTMLDivElement, rowIndex: number, columnIndex: number): void;
|
|
190
|
+
/**
|
|
191
|
+
* Called when the table is destroyed: destroys all detail contents and removes the layer.
|
|
192
|
+
*/
|
|
193
|
+
destroy(): void;
|
|
194
|
+
private getBody;
|
|
195
|
+
private getAllRows;
|
|
196
|
+
/** isMaster for a row that is not shown (filtered out) – the index is unknown (-1). */
|
|
197
|
+
private isMasterRow;
|
|
198
|
+
/** `[row, rowIndex]` of a display index or a row object (index -1 if the row is not shown). */
|
|
199
|
+
private resolve;
|
|
200
|
+
private heightOf;
|
|
201
|
+
private detailHeightOf;
|
|
202
|
+
private computeToggleColumnIndex;
|
|
203
|
+
private pruneRemovedRows;
|
|
204
|
+
private createLayer;
|
|
205
|
+
private createContent;
|
|
206
|
+
private createNestedTable;
|
|
207
|
+
private place;
|
|
208
|
+
private park;
|
|
209
|
+
/** The focus is inside a detail row that is hidden or removed: the master table (row) takes it. */
|
|
210
|
+
private releaseFocus;
|
|
211
|
+
private onResize;
|
|
212
|
+
private enforceLimit;
|
|
213
|
+
private destroyContent;
|
|
214
|
+
private clearAll;
|
|
215
|
+
/** The mouse wheel over a detail row: scrolls the outer table if nothing in the detail row takes the movement. */
|
|
216
|
+
private onWheel;
|
|
217
|
+
private canScrollInside;
|
|
218
|
+
/** Tab on a master row with an open detail row: the focus moves to the first focusable element in it. */
|
|
219
|
+
private onHostKeyDown;
|
|
220
|
+
/** The host got the focus back from a detail row (Shift+Tab): its master row gets the focus. */
|
|
221
|
+
private onHostFocus;
|
|
222
|
+
private findFocusable;
|
|
223
|
+
}
|