@densedash/core 0.1.0 → 0.2.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/README.md +59 -0
- package/dist/bare.d.ts +3 -3
- package/dist/bare.js +2 -2
- package/dist/collections/types.d.ts +0 -46
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +152 -39
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/keys.js +4 -3
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/scopes.js +30 -3
- package/dist/commands/scopes.js.map +1 -1
- package/dist/commands/trading-preset.d.ts +5 -1
- package/dist/commands/trading-preset.js +22 -43
- package/dist/commands/trading-preset.js.map +1 -1
- package/dist/commands/types.d.ts +32 -5
- package/dist/defaults/keyboard.js +55 -10
- package/dist/defaults/keyboard.js.map +1 -1
- package/dist/index.d.ts +7 -10
- package/dist/index.js +3 -7
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js.map +1 -1
- package/dist/navigation/commands.js +200 -0
- package/dist/navigation/commands.js.map +1 -0
- package/dist/navigation/index.d.ts +31 -0
- package/dist/navigation/index.js +148 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/{collections → navigation}/move.js +1 -1
- package/dist/navigation/move.js.map +1 -0
- package/dist/{tree → navigation}/props.js +25 -9
- package/dist/navigation/props.js.map +1 -0
- package/dist/{collections → navigation}/reconcile.js +1 -1
- package/dist/navigation/reconcile.js.map +1 -0
- package/dist/navigation/selection.js +21 -0
- package/dist/navigation/selection.js.map +1 -0
- package/dist/navigation/state.d.ts +3 -0
- package/dist/navigation/state.js +265 -0
- package/dist/navigation/state.js.map +1 -0
- package/dist/navigation/types.d.ts +86 -0
- package/dist/ports/keyboard.d.ts +3 -2
- package/dist/ports/keyboard.js +9 -0
- package/dist/ports/keyboard.js.map +1 -0
- package/dist/tree/types.d.ts +7 -36
- package/package.json +4 -3
- package/dist/collections/flatten.d.ts +0 -8
- package/dist/collections/flatten.js +0 -59
- package/dist/collections/flatten.js.map +0 -1
- package/dist/collections/move.d.ts +0 -7
- package/dist/collections/move.js.map +0 -1
- package/dist/collections/reconcile.d.ts +0 -9
- package/dist/collections/reconcile.js.map +0 -1
- package/dist/collections/selection.d.ts +0 -12
- package/dist/collections/selection.js +0 -39
- package/dist/collections/selection.js.map +0 -1
- package/dist/tree/commands.js +0 -119
- package/dist/tree/commands.js.map +0 -1
- package/dist/tree/index.d.ts +0 -6
- package/dist/tree/index.js +0 -71
- package/dist/tree/index.js.map +0 -1
- package/dist/tree/props.js.map +0 -1
- package/dist/tree/state.js +0 -126
- package/dist/tree/state.js.map +0 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { move } from "./move.js";
|
|
2
|
+
import { selectRange } from "./selection.js";
|
|
3
|
+
import { changeDetails, keyState } from "./state.js";
|
|
4
|
+
// src/navigation/commands.ts
|
|
5
|
+
function createNavigationCommands(model) {
|
|
6
|
+
function target(action) {
|
|
7
|
+
const state = model.get();
|
|
8
|
+
const key = action.key ?? state.highlighted;
|
|
9
|
+
return state.rows.find((row) => row.key === key && !row.disabled);
|
|
10
|
+
}
|
|
11
|
+
function navigate(movement, action = {}) {
|
|
12
|
+
if (!model.active()) return;
|
|
13
|
+
const state = model.get();
|
|
14
|
+
const highlighted = move(state.rows, action.key ?? state.highlighted, movement, { loop: model.options().loop ?? false });
|
|
15
|
+
if (action.event && "shiftKey" in action.event && action.event.shiftKey && model.options().selectionMode === "multiple" && highlighted !== null) {
|
|
16
|
+
const anchor = state.anchor ?? state.highlighted ?? highlighted;
|
|
17
|
+
model.change({
|
|
18
|
+
highlighted,
|
|
19
|
+
anchor,
|
|
20
|
+
selected: selectRange(state.rows, anchor, highlighted)
|
|
21
|
+
}, action);
|
|
22
|
+
} else model.change({ highlighted }, action);
|
|
23
|
+
}
|
|
24
|
+
function setExpanded(expanded, action = {}) {
|
|
25
|
+
if (!model.active()) return;
|
|
26
|
+
const row = target(action);
|
|
27
|
+
if (!row?.hasChildren) return;
|
|
28
|
+
const engineRow = model.table.getRow(row.key);
|
|
29
|
+
if (engineRow.getIsExpanded() === expanded) return;
|
|
30
|
+
model.run(action, () => engineRow.toggleExpanded(expanded));
|
|
31
|
+
}
|
|
32
|
+
const commands = {
|
|
33
|
+
next: (action) => navigate({ type: "next" }, action),
|
|
34
|
+
prev: (action) => navigate({ type: "prev" }, action),
|
|
35
|
+
first: (action) => navigate({ type: "first" }, action),
|
|
36
|
+
last: (action) => navigate({ type: "last" }, action),
|
|
37
|
+
page: (direction, action) => navigate({
|
|
38
|
+
type: "page",
|
|
39
|
+
direction,
|
|
40
|
+
size: model.options().pageSize ?? 10
|
|
41
|
+
}, action),
|
|
42
|
+
parent: (action) => navigate({ type: "parent" }, action),
|
|
43
|
+
expand(action = {}) {
|
|
44
|
+
if (!model.active()) return;
|
|
45
|
+
setExpanded(true, action);
|
|
46
|
+
},
|
|
47
|
+
collapse(action = {}) {
|
|
48
|
+
if (!model.active()) return;
|
|
49
|
+
setExpanded(false, action);
|
|
50
|
+
},
|
|
51
|
+
toggle(action = {}) {
|
|
52
|
+
setExpanded(!target(action)?.expanded, action);
|
|
53
|
+
},
|
|
54
|
+
select(action = {}) {
|
|
55
|
+
if (!model.active()) return;
|
|
56
|
+
const row = target(action);
|
|
57
|
+
if (!row) return;
|
|
58
|
+
model.run(action, () => {
|
|
59
|
+
model.change({ anchor: row.key }, action);
|
|
60
|
+
const engineRow = model.table.getRow(row.key);
|
|
61
|
+
engineRow.toggleSelected(model.options().selectionMode === "multiple" ? !engineRow.getIsSelected() : true, { selectChildren: false });
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
extendSelection(action = {}) {
|
|
65
|
+
if (!model.active()) return;
|
|
66
|
+
const row = target(action);
|
|
67
|
+
if (!row) return;
|
|
68
|
+
if (model.options().selectionMode !== "multiple") {
|
|
69
|
+
commands.select(action);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const state = model.get();
|
|
73
|
+
const anchor = state.anchor ?? state.highlighted ?? row.key;
|
|
74
|
+
model.run(action, () => {
|
|
75
|
+
model.change({ anchor }, action);
|
|
76
|
+
model.table.setRowSelection(keyState(selectRange(state.rows, anchor, row.key)));
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
sort(column, action = {}) {
|
|
80
|
+
if (!model.active()) return;
|
|
81
|
+
const sortable = model.table.getAllLeafColumns().filter((each) => each.getCanSort());
|
|
82
|
+
const id = column ?? model.state.get().chosenColumn ?? model.state.get().sorting[0]?.id ?? sortable[0]?.id;
|
|
83
|
+
const chosen = sortable.find((each) => each.id === id);
|
|
84
|
+
if (!chosen) return;
|
|
85
|
+
const first = chosen.columnDef.sortDescFirst === true;
|
|
86
|
+
model.run(action, () => {
|
|
87
|
+
model.request("chosenColumn", chosen.id);
|
|
88
|
+
const sorted = model.state.get().sorting[0];
|
|
89
|
+
model.table.setSorting(sorted?.id !== chosen.id ? [{
|
|
90
|
+
id: chosen.id,
|
|
91
|
+
desc: first
|
|
92
|
+
}] : sorted.desc !== first ? [] : [{
|
|
93
|
+
id: chosen.id,
|
|
94
|
+
desc: !first
|
|
95
|
+
}]);
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
chooseColumn(direction = 1, action = {}) {
|
|
99
|
+
if (!model.active()) return;
|
|
100
|
+
const columns = model.table.getAllLeafColumns().filter((each) => each.getCanSort());
|
|
101
|
+
if (!columns.length) return;
|
|
102
|
+
const state = model.state.get();
|
|
103
|
+
const current = state.chosenColumn ?? state.sorting[0]?.id ?? columns[0].id;
|
|
104
|
+
const chosen = columns[(columns.findIndex((each) => each.id === current) + direction + columns.length) % columns.length];
|
|
105
|
+
model.run(action, () => {
|
|
106
|
+
model.request("chosenColumn", chosen.id);
|
|
107
|
+
if (state.sorting.length) model.table.setSorting([{
|
|
108
|
+
id: chosen.id,
|
|
109
|
+
desc: state.sorting[0].desc
|
|
110
|
+
}]);
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
open(action = {}) {
|
|
114
|
+
if (!model.active()) return;
|
|
115
|
+
const row = target(action);
|
|
116
|
+
if (row) model.options().onOpen?.(row.item, changeDetails(action));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const groups = {
|
|
120
|
+
move: {
|
|
121
|
+
group: "tree.move",
|
|
122
|
+
groupLabel: "move"
|
|
123
|
+
},
|
|
124
|
+
expand: {
|
|
125
|
+
group: "tree.expand",
|
|
126
|
+
groupLabel: "open, close"
|
|
127
|
+
},
|
|
128
|
+
open: {
|
|
129
|
+
group: "tree.open",
|
|
130
|
+
groupLabel: "open",
|
|
131
|
+
legend: "secondary"
|
|
132
|
+
},
|
|
133
|
+
jump: {
|
|
134
|
+
group: "tree.jump",
|
|
135
|
+
groupLabel: "jump",
|
|
136
|
+
legend: "secondary"
|
|
137
|
+
},
|
|
138
|
+
page: {
|
|
139
|
+
group: "tree.page",
|
|
140
|
+
groupLabel: "page",
|
|
141
|
+
legend: "secondary"
|
|
142
|
+
},
|
|
143
|
+
parent: {
|
|
144
|
+
group: "tree.parent",
|
|
145
|
+
groupLabel: "parent",
|
|
146
|
+
legend: "secondary"
|
|
147
|
+
},
|
|
148
|
+
toggle: {
|
|
149
|
+
group: "tree.toggle",
|
|
150
|
+
groupLabel: "toggle",
|
|
151
|
+
legend: "secondary"
|
|
152
|
+
},
|
|
153
|
+
select: {
|
|
154
|
+
group: "tree.select",
|
|
155
|
+
groupLabel: "select",
|
|
156
|
+
legend: "secondary"
|
|
157
|
+
},
|
|
158
|
+
sort: {
|
|
159
|
+
group: "table.sort",
|
|
160
|
+
groupLabel: "sort"
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function define(id, label, group, run) {
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
label,
|
|
167
|
+
...group,
|
|
168
|
+
run: ({ event }) => run(event ? {
|
|
169
|
+
reason: "keyboard",
|
|
170
|
+
event
|
|
171
|
+
} : { reason: "programmatic" })
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
commands,
|
|
176
|
+
definitions: [
|
|
177
|
+
define("tree.next", "Next row", groups.move, commands.next),
|
|
178
|
+
define("tree.prev", "Previous row", groups.move, commands.prev),
|
|
179
|
+
define("tree.first", "First row", groups.jump, commands.first),
|
|
180
|
+
define("tree.last", "Last row", groups.jump, commands.last),
|
|
181
|
+
define("tree.page-up", "Previous page", groups.page, (action) => commands.page(-1, action)),
|
|
182
|
+
define("tree.page-down", "Next page", groups.page, (action) => commands.page(1, action)),
|
|
183
|
+
define("tree.parent", "Parent row", groups.parent, commands.parent),
|
|
184
|
+
define("tree.expand", "Expand row", groups.expand, commands.expand),
|
|
185
|
+
define("tree.collapse", "Collapse row", groups.expand, commands.collapse),
|
|
186
|
+
define("tree.toggle", "Toggle expansion", groups.toggle, commands.toggle),
|
|
187
|
+
define("tree.select", "Select row", groups.select, commands.select),
|
|
188
|
+
define("tree.extend-selection", "Extend selection", groups.select, commands.extendSelection),
|
|
189
|
+
{
|
|
190
|
+
...define("tree.open", "Open row", groups.open, commands.open),
|
|
191
|
+
enabled: () => model.options().onOpen !== void 0
|
|
192
|
+
},
|
|
193
|
+
define("table.sort", "Sort column", groups.sort, (action) => commands.sort(void 0, action)),
|
|
194
|
+
define("table.next-column", "Next sort column", groups.sort, (action) => commands.chooseColumn(1, action))
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
export { createNavigationCommands };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","names":[],"sources":["../../src/navigation/commands.ts"],"sourcesContent":["import type { RowData } from \"@tanstack/table-core\";\nimport { move } from \"./move.js\";\nimport { selectRange } from \"./selection.js\";\nimport type { Move } from \"../collections/types.js\";\nimport type { Command } from \"../commands/types.js\";\nimport { changeDetails, keyState } from \"./state.js\";\nimport type { NavigationModel } from \"./state.js\";\nimport type { TreeAction } from \"../tree/types.js\";\nimport type { NavigationCommands } from \"./types.js\";\n\nexport function createNavigationCommands<T extends RowData>(model: NavigationModel<T>) {\n function target(action: TreeAction) {\n const state = model.get();\n const key = action.key ?? state.highlighted;\n return state.rows.find((row) => row.key === key && !row.disabled);\n }\n\n function navigate(movement: Move, action: TreeAction = {}): void {\n if (!model.active()) return;\n const state = model.get();\n const highlighted = move(state.rows, action.key ?? state.highlighted, movement, {\n loop: model.options().loop ?? false,\n });\n if (\n action.event &&\n \"shiftKey\" in action.event &&\n action.event.shiftKey &&\n model.options().selectionMode === \"multiple\" &&\n highlighted !== null\n ) {\n const anchor = state.anchor ?? state.highlighted ?? highlighted;\n model.change(\n { highlighted, anchor, selected: selectRange(state.rows, anchor, highlighted) },\n action,\n );\n } else model.change({ highlighted }, action);\n }\n\n function setExpanded(expanded: boolean, action: TreeAction = {}): void {\n if (!model.active()) return;\n const row = target(action);\n if (!row?.hasChildren) return;\n const engineRow = model.table.getRow(row.key);\n if (engineRow.getIsExpanded() === expanded) return;\n model.run(action, () => engineRow.toggleExpanded(expanded));\n }\n\n const commands: NavigationCommands = {\n next: (action) => navigate({ type: \"next\" }, action),\n prev: (action) => navigate({ type: \"prev\" }, action),\n first: (action) => navigate({ type: \"first\" }, action),\n last: (action) => navigate({ type: \"last\" }, action),\n page: (direction, action) =>\n navigate({ type: \"page\", direction, size: model.options().pageSize ?? 10 }, action),\n parent: (action) => navigate({ type: \"parent\" }, action),\n expand(action = {}) {\n if (!model.active()) return;\n setExpanded(true, action);\n },\n collapse(action = {}) {\n if (!model.active()) return;\n setExpanded(false, action);\n },\n toggle(action = {}) {\n setExpanded(!target(action)?.expanded, action);\n },\n select(action = {}) {\n if (!model.active()) return;\n const row = target(action);\n if (!row) return;\n model.run(action, () => {\n model.change({ anchor: row.key }, action);\n const engineRow = model.table.getRow(row.key);\n engineRow.toggleSelected(\n model.options().selectionMode === \"multiple\" ? !engineRow.getIsSelected() : true,\n { selectChildren: false },\n );\n });\n },\n extendSelection(action = {}) {\n if (!model.active()) return;\n const row = target(action);\n if (!row) return;\n if (model.options().selectionMode !== \"multiple\") {\n commands.select(action);\n return;\n }\n const state = model.get();\n const anchor = state.anchor ?? state.highlighted ?? row.key;\n model.run(action, () => {\n model.change({ anchor }, action);\n model.table.setRowSelection(keyState(selectRange(state.rows, anchor, row.key)));\n });\n },\n sort(column, action = {}) {\n if (!model.active()) return;\n const sortable = model.table.getAllLeafColumns().filter((each) => each.getCanSort());\n const id =\n column ??\n model.state.get().chosenColumn ??\n model.state.get().sorting[0]?.id ??\n sortable[0]?.id;\n const chosen = sortable.find((each) => each.id === id);\n if (!chosen) return;\n // Ascending, descending, unsorted; a `sortDescFirst` column starts descending.\n const first = chosen.columnDef.sortDescFirst === true;\n model.run(action, () => {\n model.request(\"chosenColumn\", chosen.id);\n const sorted = model.state.get().sorting[0];\n model.table.setSorting(\n sorted?.id !== chosen.id\n ? [{ id: chosen.id, desc: first }]\n : sorted.desc !== first\n ? []\n : [{ id: chosen.id, desc: !first }],\n );\n });\n },\n chooseColumn(direction = 1, action = {}) {\n if (!model.active()) return;\n const columns = model.table.getAllLeafColumns().filter((each) => each.getCanSort());\n if (!columns.length) return;\n const state = model.state.get();\n const current = state.chosenColumn ?? state.sorting[0]?.id ?? columns[0]!.id;\n const index = columns.findIndex((each) => each.id === current);\n const chosen = columns[(index + direction + columns.length) % columns.length]!;\n model.run(action, () => {\n model.request(\"chosenColumn\", chosen.id);\n if (state.sorting.length)\n model.table.setSorting([{ id: chosen.id, desc: state.sorting[0]!.desc }]);\n });\n },\n open(action = {}) {\n if (!model.active()) return;\n const row = target(action);\n if (row) model.options().onOpen?.(row.item, changeDetails(action));\n },\n };\n\n // Labels belong to the group, rather than to whichever command happens to appear first.\n const groups = {\n move: { group: \"tree.move\", groupLabel: \"move\" },\n expand: { group: \"tree.expand\", groupLabel: \"open, close\" },\n open: { group: \"tree.open\", groupLabel: \"open\", legend: \"secondary\" },\n jump: { group: \"tree.jump\", groupLabel: \"jump\", legend: \"secondary\" },\n page: { group: \"tree.page\", groupLabel: \"page\", legend: \"secondary\" },\n parent: { group: \"tree.parent\", groupLabel: \"parent\", legend: \"secondary\" },\n toggle: { group: \"tree.toggle\", groupLabel: \"toggle\", legend: \"secondary\" },\n select: { group: \"tree.select\", groupLabel: \"select\", legend: \"secondary\" },\n sort: { group: \"table.sort\", groupLabel: \"sort\" },\n } as const;\n\n function define(\n id: Command[\"id\"],\n label: string,\n group: Pick<Command, \"group\" | \"groupLabel\" | \"legend\">,\n run: (action: TreeAction) => void,\n ): Command {\n return {\n id,\n label,\n ...group,\n run: ({ event }) => run(event ? { reason: \"keyboard\", event } : { reason: \"programmatic\" }),\n };\n }\n\n const definitions = [\n define(\"tree.next\", \"Next row\", groups.move, commands.next),\n define(\"tree.prev\", \"Previous row\", groups.move, commands.prev),\n define(\"tree.first\", \"First row\", groups.jump, commands.first),\n define(\"tree.last\", \"Last row\", groups.jump, commands.last),\n define(\"tree.page-up\", \"Previous page\", groups.page, (action) => commands.page(-1, action)),\n define(\"tree.page-down\", \"Next page\", groups.page, (action) => commands.page(1, action)),\n define(\"tree.parent\", \"Parent row\", groups.parent, commands.parent),\n define(\"tree.expand\", \"Expand row\", groups.expand, commands.expand),\n define(\"tree.collapse\", \"Collapse row\", groups.expand, commands.collapse),\n define(\"tree.toggle\", \"Toggle expansion\", groups.toggle, commands.toggle),\n define(\"tree.select\", \"Select row\", groups.select, commands.select),\n define(\"tree.extend-selection\", \"Extend selection\", groups.select, commands.extendSelection),\n {\n ...define(\"tree.open\", \"Open row\", groups.open, commands.open),\n enabled: () => model.options().onOpen !== undefined,\n },\n define(\"table.sort\", \"Sort column\", groups.sort, (action) => commands.sort(undefined, action)),\n define(\"table.next-column\", \"Next sort column\", groups.sort, (action) =>\n commands.chooseColumn(1, action),\n ),\n ];\n return { commands, definitions };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Kernel } from "../kernel.js";
|
|
2
|
+
import "./state.js";
|
|
3
|
+
import { NavigableTable, NavigationCommands, NavigationFeatures, NavigationOptions, NavigationRow, NavigationSnapshot, NavigationState, NavigationTable } from "./types.js";
|
|
4
|
+
import { RowData, TableFeature } from "@tanstack/table-core";
|
|
5
|
+
/** State and APIs are opt-in through the v9 feature registry, without v8's `_features` option. */
|
|
6
|
+
export declare const navigationFeature: TableFeature;
|
|
7
|
+
/** The deliberately small registry shared by Table, List and Tree, with per-table atoms. */
|
|
8
|
+
export declare const navigationFeatures: {
|
|
9
|
+
coreReactivityFeature: import("@tanstack/table-core/reactivity").TableReactivityBindings;
|
|
10
|
+
columnFilteringFeature: TableFeature;
|
|
11
|
+
globalFilteringFeature: TableFeature;
|
|
12
|
+
columnOrderingFeature: TableFeature;
|
|
13
|
+
columnSizingFeature: TableFeature;
|
|
14
|
+
columnVisibilityFeature: TableFeature;
|
|
15
|
+
rowExpandingFeature: TableFeature;
|
|
16
|
+
rowSelectionFeature: TableFeature;
|
|
17
|
+
rowSortingFeature: TableFeature;
|
|
18
|
+
filteredRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
19
|
+
sortedRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
20
|
+
expandedRowModel: (table: import("@tanstack/table-core").Table<any, any>) => () => import("@tanstack/table-core").RowModel<any, any>;
|
|
21
|
+
sortFns: {
|
|
22
|
+
alphanumeric: import("@tanstack/table-core").CreatedSortFn<any, any>;
|
|
23
|
+
};
|
|
24
|
+
filterFns: {
|
|
25
|
+
includesString: import("@tanstack/table-core").CreatedFilterFn<any, any>;
|
|
26
|
+
};
|
|
27
|
+
navigationFeature: TableFeature;
|
|
28
|
+
};
|
|
29
|
+
/** Builds table-core with densedash navigation. Dispose it before its kernel. */
|
|
30
|
+
export declare function createNavigableTable<T extends RowData>(kernel: Kernel, options: NavigationOptions<T>): NavigableTable<T>;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createNavigationCommands } from "./commands.js";
|
|
2
|
+
import { createNavigationProps } from "./props.js";
|
|
3
|
+
import { createNavigationModel } from "./state.js";
|
|
4
|
+
import { assignTableAPIs, columnFilteringFeature, columnOrderingFeature, columnSizingFeature, columnVisibilityFeature, constructTable, createExpandedRowModel, createFilteredRowModel, createSortedRowModel, filterFn_includesString, globalFilteringFeature, rowExpandingFeature, rowSelectionFeature, rowSortingFeature, sortFn_alphanumeric, tableFeatures } from "@tanstack/table-core";
|
|
5
|
+
import { storeReactivityBindings } from "@tanstack/table-core/store-reactivity-bindings";
|
|
6
|
+
// src/navigation/index.ts
|
|
7
|
+
let nextInstance = 0;
|
|
8
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
/** State and APIs are opt-in through the v9 feature registry, without v8's `_features` option. */
|
|
10
|
+
const navigationFeature = {
|
|
11
|
+
getInitialState: (state) => ({
|
|
12
|
+
highlighted: null,
|
|
13
|
+
anchor: null,
|
|
14
|
+
chosenColumn: null,
|
|
15
|
+
...state
|
|
16
|
+
}),
|
|
17
|
+
getDefaultTableOptions: (table) => ({
|
|
18
|
+
onHighlightedChange: (updater) => getNavigationRuntime(table).model.request("highlighted", updater),
|
|
19
|
+
onExpandedChange: (updater) => getNavigationRuntime(table).model.request("expanded", updater),
|
|
20
|
+
onRowSelectionChange: (updater) => getNavigationRuntime(table).model.request("rowSelection", updater),
|
|
21
|
+
onSortingChange: (updater) => getNavigationRuntime(table).model.request("sorting", updater)
|
|
22
|
+
}),
|
|
23
|
+
constructTableAPIs: (table) => {
|
|
24
|
+
assignTableAPIs("navigationFeature", table, {
|
|
25
|
+
table_getNavigation: { fn: () => getNavigationRuntime(table).controller },
|
|
26
|
+
table_setHighlighted: { fn: (updater) => table.options.onHighlightedChange?.(updater) }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/** The deliberately small registry shared by Table, List and Tree, with per-table atoms. */
|
|
31
|
+
const navigationFeatures = tableFeatures({
|
|
32
|
+
coreReactivityFeature: storeReactivityBindings(),
|
|
33
|
+
columnFilteringFeature,
|
|
34
|
+
globalFilteringFeature,
|
|
35
|
+
columnOrderingFeature,
|
|
36
|
+
columnSizingFeature,
|
|
37
|
+
columnVisibilityFeature,
|
|
38
|
+
rowExpandingFeature,
|
|
39
|
+
rowSelectionFeature,
|
|
40
|
+
rowSortingFeature,
|
|
41
|
+
filteredRowModel: createFilteredRowModel(),
|
|
42
|
+
sortedRowModel: createSortedRowModel(),
|
|
43
|
+
expandedRowModel: createExpandedRowModel(),
|
|
44
|
+
sortFns: { alphanumeric: sortFn_alphanumeric },
|
|
45
|
+
filterFns: { includesString: filterFn_includesString },
|
|
46
|
+
navigationFeature
|
|
47
|
+
});
|
|
48
|
+
/** Internal: builds the controller that `table.getNavigation()` returns, once per table. */
|
|
49
|
+
function getNavigationRuntime(table) {
|
|
50
|
+
const existing = instances.get(table);
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
const { kernel, options } = table.options.navigation;
|
|
53
|
+
const instance = nextInstance++;
|
|
54
|
+
const id = options.id ?? `dd-tree-${instance}`;
|
|
55
|
+
const scope = `densedash:tree:${instance}`;
|
|
56
|
+
const model = createNavigationModel(kernel, table, options);
|
|
57
|
+
const { commands, definitions } = createNavigationCommands(model);
|
|
58
|
+
let root;
|
|
59
|
+
let unregister;
|
|
60
|
+
const commandDefinitions = () => table.getAllLeafColumns().some((column) => column.getCanSort()) ? definitions : definitions.filter((command) => !command.id.startsWith("table."));
|
|
61
|
+
function register() {
|
|
62
|
+
unregister?.();
|
|
63
|
+
unregister = void 0;
|
|
64
|
+
const keys = model.options().keys;
|
|
65
|
+
unregister = kernel.commands.register(scope, commandDefinitions(), {
|
|
66
|
+
...model.options().scope,
|
|
67
|
+
...keys === void 0 ? {} : { keys },
|
|
68
|
+
...root === void 0 ? {} : { target: root }
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const props = createNavigationProps({
|
|
72
|
+
model,
|
|
73
|
+
commands,
|
|
74
|
+
clock: kernel.ports.clock,
|
|
75
|
+
id,
|
|
76
|
+
mount(element) {
|
|
77
|
+
if (root !== element) {
|
|
78
|
+
root = element;
|
|
79
|
+
register();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const controller = {
|
|
84
|
+
table,
|
|
85
|
+
state: model.state,
|
|
86
|
+
commands,
|
|
87
|
+
scope,
|
|
88
|
+
setHighlighted: (updater) => table.setHighlighted(updater),
|
|
89
|
+
getRootProps: props.getRootProps,
|
|
90
|
+
getRowProps: props.getRowProps,
|
|
91
|
+
getToggleProps: props.getToggleProps,
|
|
92
|
+
update(next) {
|
|
93
|
+
if (!model.active()) return;
|
|
94
|
+
const previous = model.options();
|
|
95
|
+
model.update(next);
|
|
96
|
+
if (model.active() && (next.scope !== void 0 && next.scope !== previous.scope || "keys" in next && JSON.stringify(next.keys) !== JSON.stringify(previous.keys) || next.columns !== void 0 && next.columns !== previous.columns)) register();
|
|
97
|
+
else if (model.active() && "onOpen" in next) kernel.commands.update(scope, commandDefinitions());
|
|
98
|
+
},
|
|
99
|
+
dispose() {
|
|
100
|
+
if (!model.active()) return;
|
|
101
|
+
model.dispose();
|
|
102
|
+
props.dispose();
|
|
103
|
+
root = void 0;
|
|
104
|
+
unregister?.();
|
|
105
|
+
unregister = void 0;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const runtime = {
|
|
109
|
+
controller,
|
|
110
|
+
model
|
|
111
|
+
};
|
|
112
|
+
instances.set(table, runtime);
|
|
113
|
+
try {
|
|
114
|
+
model.change({});
|
|
115
|
+
register();
|
|
116
|
+
} catch (cause) {
|
|
117
|
+
controller.dispose();
|
|
118
|
+
throw cause;
|
|
119
|
+
}
|
|
120
|
+
return runtime;
|
|
121
|
+
}
|
|
122
|
+
/** Builds table-core with densedash navigation. Dispose it before its kernel. */
|
|
123
|
+
function createNavigableTable(kernel, options) {
|
|
124
|
+
return constructTable({
|
|
125
|
+
features: navigationFeatures,
|
|
126
|
+
data: options.data,
|
|
127
|
+
columns: options.columns ?? [],
|
|
128
|
+
...options.getRowId === void 0 ? {} : { getRowId: options.getRowId },
|
|
129
|
+
...options.getSubRows === void 0 ? {} : { getSubRows: options.getSubRows },
|
|
130
|
+
navigation: {
|
|
131
|
+
kernel,
|
|
132
|
+
options
|
|
133
|
+
},
|
|
134
|
+
autoResetAll: false,
|
|
135
|
+
enableSubRowSelection: false,
|
|
136
|
+
enableMultiRowSelection: options.selectionMode === "multiple",
|
|
137
|
+
enableRowSelection: (row) => !(options.isDisabled?.(row.original) ?? false),
|
|
138
|
+
initialState: {
|
|
139
|
+
highlighted: options.highlighted?.value !== void 0 ? options.highlighted.value : options.highlighted?.defaultValue ?? null,
|
|
140
|
+
expanded: options.expanded?.value ?? options.expanded?.defaultValue ?? {},
|
|
141
|
+
rowSelection: options.rowSelection?.value ?? options.rowSelection?.defaultValue ?? {},
|
|
142
|
+
sorting: options.sorting?.value ?? options.sorting?.defaultValue ?? []
|
|
143
|
+
}
|
|
144
|
+
}).getNavigation();
|
|
145
|
+
}
|
|
146
|
+
export { createNavigableTable, getNavigationRuntime, navigationFeature, navigationFeatures };
|
|
147
|
+
|
|
148
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/navigation/index.ts"],"sourcesContent":["import type { RowData } from \"@tanstack/table-core\";\nimport {\n assignTableAPIs,\n columnFilteringFeature,\n columnOrderingFeature,\n columnSizingFeature,\n columnVisibilityFeature,\n constructTable,\n createExpandedRowModel,\n createFilteredRowModel,\n createSortedRowModel,\n filterFn_includesString,\n globalFilteringFeature,\n rowExpandingFeature,\n rowSelectionFeature,\n rowSortingFeature,\n sortFn_alphanumeric,\n tableFeatures,\n} from \"@tanstack/table-core\";\nimport type {\n ExpandedState,\n RowSelectionState,\n SortingState,\n TableFeature,\n Updater,\n} from \"@tanstack/table-core\";\nimport { storeReactivityBindings } from \"@tanstack/table-core/store-reactivity-bindings\";\nimport type { Kernel } from \"../kernel.js\";\nimport { createNavigationCommands } from \"./commands.js\";\nimport { createNavigationProps } from \"./props.js\";\nimport { createNavigationModel } from \"./state.js\";\nimport type {\n NavigableTable,\n NavigationOptions,\n NavigationFeatures,\n NavigationTable,\n} from \"./types.js\";\n\nexport type {\n NavigableTable,\n NavigationCommands,\n NavigationFeatures,\n NavigationOptions,\n NavigationRow,\n NavigationSnapshot,\n NavigationState,\n NavigationTable,\n} from \"./types.js\";\n\nlet nextInstance = 0;\nconst instances = new WeakMap<\n object,\n { controller: NavigableTable<RowData>; model: ReturnType<typeof createNavigationModel<RowData>> }\n>();\n\n/** State and APIs are opt-in through the v9 feature registry, without v8's `_features` option. */\nexport const navigationFeature: TableFeature = {\n getInitialState: (state) => ({ highlighted: null, anchor: null, chosenColumn: null, ...state }),\n getDefaultTableOptions: (table) => ({\n onHighlightedChange: (updater: Updater<string | null>) =>\n getNavigationRuntime(table as unknown as NavigationTable<RowData>).model.request(\n \"highlighted\",\n updater,\n ),\n onExpandedChange: (updater: Updater<ExpandedState>) =>\n getNavigationRuntime(table as unknown as NavigationTable<RowData>).model.request(\n \"expanded\",\n updater,\n ),\n onRowSelectionChange: (updater: Updater<RowSelectionState>) =>\n getNavigationRuntime(table as unknown as NavigationTable<RowData>).model.request(\n \"rowSelection\",\n updater,\n ),\n onSortingChange: (updater: Updater<SortingState>) =>\n getNavigationRuntime(table as unknown as NavigationTable<RowData>).model.request(\n \"sorting\",\n updater,\n ),\n }),\n constructTableAPIs: (table) => {\n assignTableAPIs(\"navigationFeature\", table, {\n table_getNavigation: {\n fn: () => getNavigationRuntime(table as unknown as NavigationTable<RowData>).controller,\n },\n table_setHighlighted: {\n fn: (updater: Updater<string | null>) =>\n (\n table.options as import(\"./types.js\").NavigationFeatureOptions<RowData>\n ).onHighlightedChange?.(updater),\n },\n });\n },\n};\n\n/** The deliberately small registry shared by Table, List and Tree, with per-table atoms. */\nexport const navigationFeatures = tableFeatures({\n coreReactivityFeature: storeReactivityBindings(),\n columnFilteringFeature,\n globalFilteringFeature,\n columnOrderingFeature,\n columnSizingFeature,\n columnVisibilityFeature,\n rowExpandingFeature,\n rowSelectionFeature,\n rowSortingFeature,\n filteredRowModel: createFilteredRowModel(),\n sortedRowModel: createSortedRowModel(),\n expandedRowModel: createExpandedRowModel(),\n sortFns: { alphanumeric: sortFn_alphanumeric },\n filterFns: { includesString: filterFn_includesString },\n navigationFeature,\n});\n\n/** Internal: builds the controller that `table.getNavigation()` returns, once per table. */\nexport function getNavigationRuntime<T extends RowData>(table: NavigationTable<T>) {\n const existing = instances.get(table);\n if (existing) {\n // The WeakMap preserves the table's TData identity, which a heterogeneous map cannot express.\n return existing as unknown as {\n controller: NavigableTable<T>;\n model: ReturnType<typeof createNavigationModel<T>>;\n };\n }\n const { kernel, options } = table.options.navigation;\n const instance = nextInstance++;\n const id = options.id ?? `dd-tree-${instance}`;\n const scope = `densedash:tree:${instance}`;\n const model = createNavigationModel(kernel, table, options);\n const { commands, definitions } = createNavigationCommands(model);\n let root: HTMLElement | undefined;\n let unregister: (() => void) | undefined;\n const commandDefinitions = () =>\n table.getAllLeafColumns().some((column) => column.getCanSort())\n ? definitions\n : definitions.filter((command) => !command.id.startsWith(\"table.\"));\n function register(): void {\n unregister?.();\n unregister = undefined;\n const keys = model.options().keys;\n unregister = kernel.commands.register(scope, commandDefinitions(), {\n ...model.options().scope,\n ...(keys === undefined ? {} : { keys }),\n ...(root === undefined ? {} : { target: root }),\n });\n }\n const props = createNavigationProps({\n model,\n commands,\n clock: kernel.ports.clock,\n id,\n mount(element) {\n if (root !== element) {\n root = element;\n register();\n }\n },\n });\n const controller: NavigableTable<T> = {\n table,\n state: model.state,\n commands,\n scope,\n setHighlighted: (updater) => table.setHighlighted(updater),\n getRootProps: props.getRootProps,\n getRowProps: props.getRowProps,\n getToggleProps: props.getToggleProps,\n update(next) {\n if (!model.active()) return;\n const previous = model.options();\n model.update(next);\n if (\n model.active() &&\n ((next.scope !== undefined && next.scope !== previous.scope) ||\n (\"keys\" in next && JSON.stringify(next.keys) !== JSON.stringify(previous.keys)) ||\n (next.columns !== undefined && next.columns !== previous.columns))\n )\n register();\n else if (model.active() && \"onOpen\" in next)\n kernel.commands.update(scope, commandDefinitions());\n },\n dispose() {\n if (!model.active()) return;\n model.dispose();\n props.dispose();\n root = undefined;\n unregister?.();\n unregister = undefined;\n },\n };\n const runtime = { controller, model };\n instances.set(\n table,\n runtime as unknown as {\n controller: NavigableTable<RowData>;\n model: ReturnType<typeof createNavigationModel<RowData>>;\n },\n );\n try {\n model.change({});\n register();\n } catch (cause) {\n controller.dispose();\n throw cause;\n }\n return runtime;\n}\n\n/** Builds table-core with densedash navigation. Dispose it before its kernel. */\nexport function createNavigableTable<T extends RowData>(\n kernel: Kernel,\n options: NavigationOptions<T>,\n): NavigableTable<T> {\n const table = constructTable<NavigationFeatures, T>({\n features: navigationFeatures,\n data: options.data,\n columns: options.columns ?? [],\n ...(options.getRowId === undefined ? {} : { getRowId: options.getRowId }),\n ...(options.getSubRows === undefined ? {} : { getSubRows: options.getSubRows }),\n navigation: { kernel, options },\n autoResetAll: false,\n enableSubRowSelection: false,\n enableMultiRowSelection: options.selectionMode === \"multiple\",\n enableRowSelection: (row) => !(options.isDisabled?.(row.original) ?? false),\n initialState: {\n highlighted:\n options.highlighted?.value !== undefined\n ? options.highlighted.value\n : (options.highlighted?.defaultValue ?? null),\n expanded: options.expanded?.value ?? options.expanded?.defaultValue ?? {},\n rowSelection: options.rowSelection?.value ?? options.rowSelection?.defaultValue ?? {},\n sorting: options.sorting?.value ?? options.sorting?.defaultValue ?? [],\n },\n });\n return table.getNavigation();\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/navigation/move.ts
|
|
2
2
|
/** Move by enabled rows without iterating count times, even for huge counts. */
|
|
3
3
|
function step(rows, fromIndex, count, direction, loop) {
|
|
4
4
|
if (!Number.isSafeInteger(count) || count < 0) throw new RangeError("Collection move count/size must be a non-negative safe integer.");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"move.js","names":[],"sources":["../../src/navigation/move.ts"],"sourcesContent":["import type { Key, Move, MoveOptions, TreeRow } from \"../collections/types.js\";\n\n/** Move by enabled rows without iterating count times, even for huge counts. */\nfunction step<T>(\n rows: readonly TreeRow<T>[],\n fromIndex: number,\n count: number,\n direction: 1 | -1,\n loop: boolean,\n): Key | null {\n if (!Number.isSafeInteger(count) || count < 0) {\n throw new RangeError(\"Collection move count/size must be a non-negative safe integer.\");\n }\n\n const keys: Key[] = [];\n let before = 0;\n let current = -1;\n for (let index = 0; index < rows.length; index++) {\n const row = rows[index]!;\n if (row.disabled) continue;\n if (index < fromIndex) before++;\n if (index === fromIndex) current = keys.length;\n keys.push(row.key);\n }\n if (keys.length === 0) return null;\n if (count === 0) return current < 0 ? null : keys[current]!;\n\n let start = current;\n if (current < 0) {\n start =\n fromIndex < 0 ? (direction === 1 ? -1 : keys.length) : direction === 1 ? before - 1 : before;\n }\n // Reduce before addition so even MAX_SAFE_INTEGER stays arithmetically exact.\n const distance = loop ? count % keys.length : Math.min(count, keys.length);\n const target = start + direction * distance;\n const bounded = loop\n ? ((target % keys.length) + keys.length) % keys.length\n : Math.max(0, Math.min(keys.length - 1, target));\n return keys[bounded]!;\n}\n\n/**\n * Return a key; never mutate rows or state. No target leaves the current enabled\n * key in place (or null). Counts and pages count enabled rows. Empty rows return null.\n */\nexport function move<T>(\n rows: readonly TreeRow<T>[],\n from: Key | null,\n movement: Move,\n { loop = false }: MoveOptions = {},\n): Key | null {\n const fromIndex = from === null ? -1 : rows.findIndex((row) => row.key === from);\n const current = rows[fromIndex];\n const unchanged = current && !current.disabled ? current.key : null;\n\n switch (movement.type) {\n case \"next\":\n case \"prev\":\n return step(rows, fromIndex, movement.count ?? 1, movement.type === \"next\" ? 1 : -1, loop);\n case \"page\":\n return step(rows, fromIndex, movement.size, movement.direction, loop);\n case \"first\":\n return rows.find((row) => !row.disabled)?.key ?? null;\n case \"last\":\n for (let index = rows.length - 1; index >= 0; index--) {\n const row = rows[index]!;\n if (!row.disabled) return row.key;\n }\n return null;\n case \"parent\": {\n let parentKey = current?.parentKey;\n for (let index = fromIndex - 1; index >= 0 && parentKey != null; index--) {\n const row = rows[index]!;\n if (row.key !== parentKey) continue;\n if (!row.disabled) return row.key;\n parentKey = row.parentKey;\n }\n return unchanged;\n }\n case \"firstChild\":\n if (!current) return null;\n for (let index = fromIndex + 1; index < rows.length; index++) {\n const row = rows[index]!;\n if (row.depth <= current.depth) break;\n if (row.parentKey === current.key && !row.disabled) return row.key;\n }\n return unchanged;\n case \"typeahead\": {\n const text = movement.text.toLowerCase();\n if (text.length === 0) return unchanged;\n const end = loop ? rows.length + fromIndex + 1 : rows.length;\n for (let index = fromIndex + 1; index < end; index++) {\n const row = rows[index % rows.length]!;\n if (!row.disabled && row.textValue.toLowerCase().startsWith(text)) return row.key;\n }\n return unchanged;\n }\n }\n}\n"],"mappings":";;AAGA,SAAS,KACP,MACA,WACA,OACA,WACA,MACY;CACZ,IAAI,CAAC,OAAO,cAAc,KAAK,KAAK,QAAQ,GAC1C,MAAM,IAAI,WAAW,iEAAiE;CAGxF,MAAM,OAAc,CAAC;CACrB,IAAI,SAAS;CACb,IAAI,UAAU;CACd,KAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS;EAChD,MAAM,MAAM,KAAK;EACjB,IAAI,IAAI,UAAU;EAClB,IAAI,QAAQ,WAAW;EACvB,IAAI,UAAU,WAAW,UAAU,KAAK;EACxC,KAAK,KAAK,IAAI,GAAG;CACnB;CACA,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,IAAI,UAAU,GAAG,OAAO,UAAU,IAAI,OAAO,KAAK;CAElD,IAAI,QAAQ;CACZ,IAAI,UAAU,GACZ,QACE,YAAY,IAAK,cAAc,IAAI,KAAK,KAAK,SAAU,cAAc,IAAI,SAAS,IAAI;CAG1F,MAAM,WAAW,OAAO,QAAQ,KAAK,SAAS,KAAK,IAAI,OAAO,KAAK,MAAM;CACzE,MAAM,SAAS,QAAQ,YAAY;CAInC,OAAO,KAHS,QACV,SAAS,KAAK,SAAU,KAAK,UAAU,KAAK,SAC9C,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,SAAS,GAAG,MAAM,CAAC;AAEnD;;;;;AAMA,SAAgB,KACd,MACA,MACA,UACA,EAAE,OAAO,UAAuB,CAAC,GACrB;CACZ,MAAM,YAAY,SAAS,OAAO,KAAK,KAAK,WAAW,QAAQ,IAAI,QAAQ,IAAI;CAC/E,MAAM,UAAU,KAAK;CACrB,MAAM,YAAY,WAAW,CAAC,QAAQ,WAAW,QAAQ,MAAM;CAE/D,QAAQ,SAAS,MAAjB;EACE,KAAK;EACL,KAAK,QACH,OAAO,KAAK,MAAM,WAAW,SAAS,SAAS,GAAG,SAAS,SAAS,SAAS,IAAI,IAAI,IAAI;EAC3F,KAAK,QACH,OAAO,KAAK,MAAM,WAAW,SAAS,MAAM,SAAS,WAAW,IAAI;EACtE,KAAK,SACH,OAAO,KAAK,MAAM,QAAQ,CAAC,IAAI,QAAQ,CAAC,EAAE,OAAO;EACnD,KAAK;GACH,KAAK,IAAI,QAAQ,KAAK,SAAS,GAAG,SAAS,GAAG,SAAS;IACrD,MAAM,MAAM,KAAK;IACjB,IAAI,CAAC,IAAI,UAAU,OAAO,IAAI;GAChC;GACA,OAAO;EACT,KAAK,UAAU;GACb,IAAI,YAAY,SAAS;GACzB,KAAK,IAAI,QAAQ,YAAY,GAAG,SAAS,KAAK,aAAa,MAAM,SAAS;IACxE,MAAM,MAAM,KAAK;IACjB,IAAI,IAAI,QAAQ,WAAW;IAC3B,IAAI,CAAC,IAAI,UAAU,OAAO,IAAI;IAC9B,YAAY,IAAI;GAClB;GACA,OAAO;EACT;EACA,KAAK;GACH,IAAI,CAAC,SAAS,OAAO;GACrB,KAAK,IAAI,QAAQ,YAAY,GAAG,QAAQ,KAAK,QAAQ,SAAS;IAC5D,MAAM,MAAM,KAAK;IACjB,IAAI,IAAI,SAAS,QAAQ,OAAO;IAChC,IAAI,IAAI,cAAc,QAAQ,OAAO,CAAC,IAAI,UAAU,OAAO,IAAI;GACjE;GACA,OAAO;EACT,KAAK,aAAa;GAChB,MAAM,OAAO,SAAS,KAAK,YAAY;GACvC,IAAI,KAAK,WAAW,GAAG,OAAO;GAC9B,MAAM,MAAM,OAAO,KAAK,SAAS,YAAY,IAAI,KAAK;GACtD,KAAK,IAAI,QAAQ,YAAY,GAAG,QAAQ,KAAK,SAAS;IACpD,MAAM,MAAM,KAAK,QAAQ,KAAK;IAC9B,IAAI,CAAC,IAAI,YAAY,IAAI,UAAU,YAAY,CAAC,CAAC,WAAW,IAAI,GAAG,OAAO,IAAI;GAChF;GACA,OAAO;EACT;CACF;AACF"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { move } from "
|
|
2
|
-
// src/
|
|
3
|
-
function
|
|
1
|
+
import { move } from "./move.js";
|
|
2
|
+
// src/navigation/props.ts
|
|
3
|
+
function createNavigationProps({ model, commands, clock, id, mount }) {
|
|
4
4
|
let root;
|
|
5
5
|
let text = "";
|
|
6
6
|
let lastTyped = -Infinity;
|
|
7
|
+
const typeahead = [];
|
|
8
|
+
let frame;
|
|
7
9
|
const rowId = (key) => `${id}-row-${Array.from(key, (character) => character.codePointAt(0).toString(16)).join("-")}`;
|
|
8
10
|
const roving = () => model.options().focusStrategy === "roving";
|
|
9
11
|
const mode = () => model.options().mode ?? "tree";
|
|
@@ -61,7 +63,8 @@ function createTreeProps({ model, commands, clock, id, mount }) {
|
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
function onKeyDown(event) {
|
|
64
|
-
|
|
66
|
+
const nativeEvent = event.nativeEvent ?? event;
|
|
67
|
+
if (!model.active() || event.defaultPrevented || nativeEvent.defaultPrevented || nativeEvent.isComposing || event.ctrlKey || event.metaKey || event.altKey || event.key.length !== 1 || event.key.trim() === "") return;
|
|
65
68
|
const target = event.target;
|
|
66
69
|
if (target && "closest" in target && typeof target.closest === "function") {
|
|
67
70
|
if (target.closest("input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])")) return;
|
|
@@ -69,8 +72,8 @@ function createTreeProps({ model, commands, clock, id, mount }) {
|
|
|
69
72
|
}
|
|
70
73
|
const at = clock.now();
|
|
71
74
|
const character = event.key;
|
|
72
|
-
|
|
73
|
-
if (!model.active() || event.defaultPrevented) return;
|
|
75
|
+
const apply = () => {
|
|
76
|
+
if (!model.active() || event.defaultPrevented || nativeEvent.defaultPrevented) return;
|
|
74
77
|
text = at - lastTyped <= (model.options().typeaheadMs ?? 500) ? text + character : character;
|
|
75
78
|
lastTyped = at;
|
|
76
79
|
const state = model.get();
|
|
@@ -82,7 +85,17 @@ function createTreeProps({ model, commands, clock, id, mount }) {
|
|
|
82
85
|
reason: "typeahead",
|
|
83
86
|
event
|
|
84
87
|
});
|
|
85
|
-
}
|
|
88
|
+
};
|
|
89
|
+
const view = root?.ownerDocument.defaultView;
|
|
90
|
+
if (!view) return;
|
|
91
|
+
typeahead.push(apply);
|
|
92
|
+
if (!frame) frame = {
|
|
93
|
+
view,
|
|
94
|
+
id: view.requestAnimationFrame(() => {
|
|
95
|
+
frame = void 0;
|
|
96
|
+
for (const apply of typeahead.splice(0)) apply();
|
|
97
|
+
})
|
|
98
|
+
};
|
|
86
99
|
}
|
|
87
100
|
return {
|
|
88
101
|
getRootProps() {
|
|
@@ -173,7 +186,7 @@ function createTreeProps({ model, commands, clock, id, mount }) {
|
|
|
173
186
|
type: "button",
|
|
174
187
|
tabIndex: -1,
|
|
175
188
|
disabled: !row || row.disabled || !row.hasChildren,
|
|
176
|
-
"aria-label": row?.expanded ? "Collapse" : "Expand",
|
|
189
|
+
"aria-label": row?.expanded ? model.options().labels?.collapse ?? "Collapse" : model.options().labels?.expand ?? "Expand",
|
|
177
190
|
"aria-expanded": row?.hasChildren ? row.expanded : void 0,
|
|
178
191
|
onClick(event) {
|
|
179
192
|
if (!interactive(key) || !current(key)?.hasChildren || event.defaultPrevented) return;
|
|
@@ -188,11 +201,14 @@ function createTreeProps({ model, commands, clock, id, mount }) {
|
|
|
188
201
|
};
|
|
189
202
|
},
|
|
190
203
|
dispose() {
|
|
204
|
+
if (frame) frame.view.cancelAnimationFrame(frame.id);
|
|
205
|
+
frame = void 0;
|
|
206
|
+
typeahead.length = 0;
|
|
191
207
|
root = void 0;
|
|
192
208
|
text = "";
|
|
193
209
|
}
|
|
194
210
|
};
|
|
195
211
|
}
|
|
196
|
-
export {
|
|
212
|
+
export { createNavigationProps };
|
|
197
213
|
|
|
198
214
|
//# sourceMappingURL=props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"props.js","names":[],"sources":["../../src/navigation/props.ts"],"sourcesContent":["import type { RowData } from \"@tanstack/table-core\";\nimport { move } from \"./move.js\";\nimport type { Key, TreeRow } from \"../collections/types.js\";\nimport type { ClockPort } from \"../ports/clock.js\";\nimport type { NavigationModel } from \"./state.js\";\nimport type {\n TreeCommands,\n TreePartProps,\n TreeRootProps,\n TreeRowProps,\n TreeToggleProps,\n} from \"../tree/types.js\";\n\nexport function createNavigationProps<T extends RowData>({\n model,\n commands,\n clock,\n id,\n mount,\n}: {\n model: NavigationModel<T>;\n commands: TreeCommands;\n clock: ClockPort;\n id: string;\n mount(root: HTMLElement): void;\n}) {\n let root: HTMLElement | undefined;\n let text = \"\";\n let lastTyped = -Infinity;\n const typeahead: (() => void)[] = [];\n let frame: { view: Window; id: number } | undefined;\n const rowId = (key: Key) =>\n `${id}-row-${Array.from(key, (character) => character.codePointAt(0)!.toString(16)).join(\"-\")}`;\n const roving = () => model.options().focusStrategy === \"roving\";\n const mode = () => model.options().mode ?? \"tree\";\n const grid = () => mode() === \"grid\" || mode() === \"treegrid\";\n const slot = (part: \"root\" | \"row\" | \"toggle\"): TreePartProps[\"data-slot\"] =>\n grid() ? `table-${part}` : `tree-${part}`;\n const rootSelector = () => `[data-slot=\"${slot(\"root\")}\"]`;\n // Prop getters run once per visible row, so look rows up by key in O(1) per snapshot.\n const indexes = new WeakMap<readonly TreeRow<T>[], Map<Key, TreeRow<T>>>();\n const current = (key: Key) => {\n const rows = model.get().rows;\n let index = indexes.get(rows);\n if (index === undefined) {\n index = new Map(rows.map((row) => [row.key, row]));\n indexes.set(rows, index);\n }\n return index.get(key);\n };\n const enabledHighlight = () => {\n const key = model.get().highlighted;\n const row = key === null ? undefined : current(key);\n return row?.disabled === false ? row : undefined;\n };\n const interactive = (key: Key) => model.active() && current(key)?.disabled === false;\n\n function focusHighlighted(): void {\n if (!model.active() || !roving() || !root?.contains(root.ownerDocument.activeElement)) return;\n const key = model.get().highlighted;\n const element = key === null ? null : root.ownerDocument.getElementById(rowId(key));\n if (element && root.contains(element) && element !== root.ownerDocument.activeElement)\n element.focus();\n }\n model.subscribe(focusHighlighted);\n\n function focusFromPointer(event: MouseEvent): void {\n const target = event.currentTarget;\n if (target && \"closest\" in target && typeof target.closest === \"function\") {\n const element = target.closest(rootSelector());\n if (element && \"focus\" in element) {\n root = element as HTMLElement;\n mount(root);\n }\n }\n if (!root) return;\n if (!roving() || !root.contains(root.ownerDocument.activeElement)) root.focus();\n focusHighlighted();\n }\n\n function part(slot: TreePartProps[\"data-slot\"], row?: TreeRow<T>): TreePartProps {\n const state = model.get();\n const depth = row?.depth ?? 0;\n return {\n \"data-slot\": slot,\n \"data-highlighted\": (row ? state.highlighted === row.key : state.highlighted !== null)\n ? \"\"\n : undefined,\n \"data-selected\": (row ? state.selected.has(row.key) : state.selected.size > 0)\n ? \"\"\n : undefined,\n \"data-expanded\": (row ? row.expanded : state.expanded.size > 0) ? \"\" : undefined,\n \"data-depth\": depth,\n \"data-disabled\": row?.disabled ? \"\" : undefined,\n \"data-leaf\": row && !row.hasChildren ? \"\" : undefined,\n style: { \"--dd-depth\": depth },\n };\n }\n\n function onKeyDown(event: KeyboardEvent & { readonly nativeEvent?: KeyboardEvent }): void {\n // React copies defaultPrevented before the document's keyboard listener runs.\n // Read the underlying event again after bubbling, including sequence prefixes.\n const nativeEvent = event.nativeEvent ?? event;\n if (\n !model.active() ||\n event.defaultPrevented ||\n nativeEvent.defaultPrevented ||\n nativeEvent.isComposing ||\n event.ctrlKey ||\n event.metaKey ||\n event.altKey ||\n event.key.length !== 1 ||\n event.key.trim() === \"\"\n )\n return;\n const target = event.target;\n if (target && \"closest\" in target && typeof target.closest === \"function\") {\n if (\n target.closest('input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])')\n )\n return;\n if (target.closest(rootSelector()) !== event.currentTarget) return;\n }\n const at = clock.now();\n const character = event.key;\n const apply = () => {\n if (!model.active() || event.defaultPrevented || nativeEvent.defaultPrevented) return;\n text = at - lastTyped <= (model.options().typeaheadMs ?? 500) ? text + character : character;\n lastTyped = at;\n const state = model.get();\n const highlighted = move(\n state.rows,\n state.highlighted,\n { type: \"typeahead\", text },\n { loop: true },\n );\n model.change({ highlighted }, { reason: \"typeahead\", event });\n };\n const view = root?.ownerDocument.defaultView;\n if (!view) return;\n // Browsers can drain microtasks between bubbling listeners. Wait for the frame so\n // document/window command handlers have finished before checking native prevention.\n typeahead.push(apply);\n if (!frame) {\n frame = {\n view,\n id: view.requestAnimationFrame(() => {\n frame = undefined;\n for (const apply of typeahead.splice(0)) apply();\n }),\n };\n }\n }\n\n return {\n getRootProps(): TreeRootProps {\n const state = model.get();\n const highlighted = enabledHighlight();\n return {\n ...part(slot(\"root\")),\n id,\n role: mode(),\n tabIndex: roving() && state.rows.some((row) => !row.disabled) ? -1 : 0,\n \"aria-activedescendant\": !roving() && highlighted ? rowId(highlighted.key) : undefined,\n \"aria-multiselectable\": model.options().selectionMode === \"multiple\",\n onKeyDown,\n onFocus(event) {\n if (!model.active()) return;\n const target = event.currentTarget;\n if (target && \"ownerDocument\" in target && \"focus\" in target) {\n root = target as HTMLElement;\n mount(root);\n }\n if (model.get().highlighted === null) commands.first();\n focusHighlighted();\n },\n };\n },\n getRowProps(key: Key): TreeRowProps {\n const state = model.get();\n const row = current(key);\n const rowMode = mode();\n // Flat grids have no levels, sets or expansion; their rows are plain grid rows.\n const leveled = rowMode === \"tree\" || rowMode === \"treegrid\";\n const focused =\n enabledHighlight()?.key ?? state.rows.find((candidate) => !candidate.disabled)?.key;\n return {\n ...part(slot(\"row\"), row),\n id: rowId(key),\n role: rowMode === \"listbox\" ? \"option\" : grid() ? \"row\" : \"treeitem\",\n tabIndex: roving() && row && !row.disabled && focused === key ? 0 : -1,\n \"aria-level\": leveled && row ? row.depth + 1 : undefined,\n \"aria-posinset\": rowMode === \"grid\" ? undefined : row?.posInSet,\n \"aria-setsize\": rowMode === \"grid\" ? undefined : row?.setSize,\n \"aria-rowindex\": grid() && row ? row.index + 1 : undefined,\n \"aria-expanded\": leveled && row?.hasChildren ? row.expanded : undefined,\n \"aria-selected\": state.selected.has(key),\n \"aria-disabled\": row?.disabled ?? true,\n onPointerMove(event) {\n if (interactive(key) && !event.defaultPrevented && event.pointerType !== \"touch\")\n model.change({ highlighted: key }, { reason: \"pointer\", event });\n },\n onClick(event) {\n if (!interactive(key) || event.defaultPrevented) return;\n const before = model.get();\n model.change(\n {\n highlighted: key,\n ...(event.shiftKey && before.anchor === null ? { anchor: before.highlighted } : {}),\n },\n { reason: \"pointer\", event },\n );\n const action = { key, reason: \"pointer\" as const, event };\n if (event.shiftKey) commands.extendSelection(action);\n else commands.select(action);\n if (model.active()) focusFromPointer(event);\n },\n onDoubleClick(event) {\n if (interactive(key) && !event.defaultPrevented)\n commands.open({ key, reason: \"pointer\", event });\n },\n onFocus(event) {\n if (interactive(key) && roving())\n model.change({ highlighted: key }, { reason: \"programmatic\", event });\n },\n };\n },\n getToggleProps(key: Key): TreeToggleProps {\n const row = current(key);\n return {\n ...part(slot(\"toggle\"), row),\n type: \"button\",\n tabIndex: -1,\n disabled: !row || row.disabled || !row.hasChildren,\n \"aria-label\": row?.expanded\n ? (model.options().labels?.collapse ?? \"Collapse\")\n : (model.options().labels?.expand ?? \"Expand\"),\n \"aria-expanded\": row?.hasChildren ? row.expanded : undefined,\n onClick(event) {\n if (!interactive(key) || !current(key)?.hasChildren || event.defaultPrevented) return;\n event.stopPropagation();\n commands.toggle({ key, reason: \"pointer\", event });\n if (model.active()) focusFromPointer(event);\n },\n };\n },\n dispose() {\n if (frame) frame.view.cancelAnimationFrame(frame.id);\n frame = undefined;\n typeahead.length = 0;\n root = undefined;\n text = \"\";\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","names":[],"sources":["../../src/navigation/reconcile.ts"],"sourcesContent":["import type { Key, TreeRow, TreeState } from \"../collections/types.js\";\n\n/**\n * Reconcile against supplied visible rows in O(prev + next + selected).\n * Keep a live enabled cursor by key. Otherwise prefer the nearest enabled survivor\n * in the old order (following wins ties), then the first enabled new row.\n * Prune invisible selection/anchor; retain expansion for hidden descendants.\n */\nexport function reconcile<T>(\n prev: readonly TreeRow<T>[],\n next: readonly TreeRow<T>[],\n state: TreeState,\n): TreeState {\n const existing = new Set<Key>();\n const enabled = new Set<Key>();\n for (const row of next) {\n existing.add(row.key);\n if (!row.disabled) enabled.add(row.key);\n }\n\n let highlighted = state.highlighted;\n if (highlighted !== null && !enabled.has(highlighted)) {\n const previousIndex = prev.findIndex((row) => row.key === highlighted);\n highlighted = null;\n if (previousIndex >= 0) {\n for (let distance = 1; distance < prev.length; distance++) {\n const following = prev[previousIndex + distance];\n const preceding = prev[previousIndex - distance];\n if (following && enabled.has(following.key)) {\n highlighted = following.key;\n break;\n }\n if (preceding && enabled.has(preceding.key)) {\n highlighted = preceding.key;\n break;\n }\n }\n }\n highlighted ??= next.find((row) => !row.disabled)?.key ?? null;\n }\n\n const selected = new Set<Key>();\n for (const key of state.selected) {\n if (existing.has(key)) selected.add(key);\n }\n\n return {\n highlighted,\n selected,\n anchor: state.anchor !== null && existing.has(state.anchor) ? state.anchor : null,\n expanded: state.expanded,\n };\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,UACd,MACA,MACA,OACW;CACX,MAAM,2BAAW,IAAI,IAAS;CAC9B,MAAM,0BAAU,IAAI,IAAS;CAC7B,KAAK,MAAM,OAAO,MAAM;EACtB,SAAS,IAAI,IAAI,GAAG;EACpB,IAAI,CAAC,IAAI,UAAU,QAAQ,IAAI,IAAI,GAAG;CACxC;CAEA,IAAI,cAAc,MAAM;CACxB,IAAI,gBAAgB,QAAQ,CAAC,QAAQ,IAAI,WAAW,GAAG;EACrD,MAAM,gBAAgB,KAAK,WAAW,QAAQ,IAAI,QAAQ,WAAW;EACrE,cAAc;EACd,IAAI,iBAAiB,GACnB,KAAK,IAAI,WAAW,GAAG,WAAW,KAAK,QAAQ,YAAY;GACzD,MAAM,YAAY,KAAK,gBAAgB;GACvC,MAAM,YAAY,KAAK,gBAAgB;GACvC,IAAI,aAAa,QAAQ,IAAI,UAAU,GAAG,GAAG;IAC3C,cAAc,UAAU;IACxB;GACF;GACA,IAAI,aAAa,QAAQ,IAAI,UAAU,GAAG,GAAG;IAC3C,cAAc,UAAU;IACxB;GACF;EACF;EAEF,gBAAgB,KAAK,MAAM,QAAQ,CAAC,IAAI,QAAQ,CAAC,EAAE,OAAO;CAC5D;CAEA,MAAM,2BAAW,IAAI,IAAS;CAC9B,KAAK,MAAM,OAAO,MAAM,UACtB,IAAI,SAAS,IAAI,GAAG,GAAG,SAAS,IAAI,GAAG;CAGzC,OAAO;EACL;EACA;EACA,QAAQ,MAAM,WAAW,QAAQ,SAAS,IAAI,MAAM,MAAM,IAAI,MAAM,SAAS;EAC7E,UAAU,MAAM;CAClB;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/navigation/selection.ts
|
|
2
|
+
/**
|
|
3
|
+
* Replace selection with an inclusive visible range, excluding disabled rows.
|
|
4
|
+
* A missing anchor selects only the target; a missing target yields an empty set.
|
|
5
|
+
*/
|
|
6
|
+
function selectRange(rows, anchor, to) {
|
|
7
|
+
const targetIndex = rows.findIndex((row) => row.key === to);
|
|
8
|
+
const selected = /* @__PURE__ */ new Set();
|
|
9
|
+
if (targetIndex < 0) return selected;
|
|
10
|
+
const anchorIndex = anchor === null ? -1 : rows.findIndex((row) => row.key === anchor);
|
|
11
|
+
const start = anchorIndex < 0 ? targetIndex : Math.min(anchorIndex, targetIndex);
|
|
12
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
13
|
+
for (let index = start; index <= end; index++) {
|
|
14
|
+
const row = rows[index];
|
|
15
|
+
if (!row.disabled) selected.add(row.key);
|
|
16
|
+
}
|
|
17
|
+
return selected;
|
|
18
|
+
}
|
|
19
|
+
export { selectRange };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","names":[],"sources":["../../src/navigation/selection.ts"],"sourcesContent":["import type { Key, TreeRow } from \"../collections/types.js\";\n\n/**\n * Replace selection with an inclusive visible range, excluding disabled rows.\n * A missing anchor selects only the target; a missing target yields an empty set.\n */\nexport function selectRange<T>(\n rows: readonly TreeRow<T>[],\n anchor: Key | null,\n to: Key,\n): ReadonlySet<Key> {\n const targetIndex = rows.findIndex((row) => row.key === to);\n const selected = new Set<Key>();\n if (targetIndex < 0) return selected;\n const anchorIndex = anchor === null ? -1 : rows.findIndex((row) => row.key === anchor);\n const start = anchorIndex < 0 ? targetIndex : Math.min(anchorIndex, targetIndex);\n const end = Math.max(anchorIndex, targetIndex);\n for (let index = start; index <= end; index++) {\n const row = rows[index]!;\n if (!row.disabled) selected.add(row.key);\n }\n return selected;\n}\n"],"mappings":";;;;;AAMA,SAAgB,YACd,MACA,QACA,IACkB;CAClB,MAAM,cAAc,KAAK,WAAW,QAAQ,IAAI,QAAQ,EAAE;CAC1D,MAAM,2BAAW,IAAI,IAAS;CAC9B,IAAI,cAAc,GAAG,OAAO;CAC5B,MAAM,cAAc,WAAW,OAAO,KAAK,KAAK,WAAW,QAAQ,IAAI,QAAQ,MAAM;CACrF,MAAM,QAAQ,cAAc,IAAI,cAAc,KAAK,IAAI,aAAa,WAAW;CAC/E,MAAM,MAAM,KAAK,IAAI,aAAa,WAAW;CAC7C,KAAK,IAAI,QAAQ,OAAO,SAAS,KAAK,SAAS;EAC7C,MAAM,MAAM,KAAK;EACjB,IAAI,CAAC,IAAI,UAAU,SAAS,IAAI,IAAI,GAAG;CACzC;CACA,OAAO;AACT"}
|