nuxt-ui-tools 1.16.9 → 1.17.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.
Files changed (43) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/i18n/locales/en.js +8 -0
  3. package/dist/runtime/i18n/locales/fr.js +8 -0
  4. package/dist/runtime/i18n/types.d.ts +8 -0
  5. package/dist/runtime/shared/types/query.d.ts +4 -3
  6. package/dist/runtime/table/components/table/table-layout.d.ts +2 -0
  7. package/dist/runtime/table/components/table/table-layout.js +1 -0
  8. package/dist/runtime/table/components/table/table-renderer.d.vue.ts +6 -6
  9. package/dist/runtime/table/components/table/table-renderer.vue +75 -11
  10. package/dist/runtime/table/components/table/table-renderer.vue.d.ts +6 -6
  11. package/dist/runtime/table/components/table/table-row.d.vue.ts +15 -2
  12. package/dist/runtime/table/components/table/table-row.vue +23 -2
  13. package/dist/runtime/table/components/table/table-row.vue.d.ts +15 -2
  14. package/dist/runtime/table/components/table/table-tree-control.d.vue.ts +19 -0
  15. package/dist/runtime/table/components/table/table-tree-control.vue +112 -0
  16. package/dist/runtime/table/components/table/table-tree-control.vue.d.ts +19 -0
  17. package/dist/runtime/table/components/table/table-tree-header.d.vue.ts +3 -0
  18. package/dist/runtime/table/components/table/table-tree-header.vue +66 -0
  19. package/dist/runtime/table/components/table/table-tree-header.vue.d.ts +3 -0
  20. package/dist/runtime/table/composables/use-table-api.d.ts +2 -0
  21. package/dist/runtime/table/composables/use-table-api.js +2 -0
  22. package/dist/runtime/table/composables/use-table-data.js +3 -2
  23. package/dist/runtime/table/composables/use-table-filter-options.js +1 -0
  24. package/dist/runtime/table/composables/use-table-internals.d.ts +48 -0
  25. package/dist/runtime/table/composables/use-table-internals.js +13 -4
  26. package/dist/runtime/table/composables/use-table-selection.d.ts +2 -0
  27. package/dist/runtime/table/composables/use-table-selection.js +15 -6
  28. package/dist/runtime/table/composables/use-table-tree.d.ts +36 -0
  29. package/dist/runtime/table/composables/use-table-tree.js +199 -0
  30. package/dist/runtime/table/types/index.d.ts +1 -0
  31. package/dist/runtime/table/types/layout.d.ts +3 -2
  32. package/dist/runtime/table/types/source.d.ts +2 -1
  33. package/dist/runtime/table/types/table-api.d.ts +6 -0
  34. package/dist/runtime/table/types/tree.d.ts +100 -0
  35. package/dist/runtime/table/types/tree.js +0 -0
  36. package/dist/runtime/table/utils/columns/defs.js +19 -2
  37. package/dist/runtime/table/utils/columns/types.d.ts +6 -1
  38. package/dist/runtime/table/utils/columns/types.js +7 -0
  39. package/dist/runtime/table/utils/index.d.ts +1 -0
  40. package/dist/runtime/table/utils/index.js +1 -0
  41. package/dist/runtime/table/utils/tree.d.ts +64 -0
  42. package/dist/runtime/table/utils/tree.js +112 -0
  43. package/package.json +1 -1
@@ -0,0 +1,100 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import type { GenericObject } from './utils.js';
3
+ /**
4
+ * Keys of a row that hold an array of child rows.
5
+ *
6
+ * A row type with no such key (or an untyped one) falls back to `string`, so the option never
7
+ * becomes unusable.
8
+ */
9
+ export type TableChildrenKey<TRow extends GenericObject> = [ChildrenKeys<TRow>] extends [never] ? string : ChildrenKeys<TRow>;
10
+ type ChildrenKeys<TRow extends GenericObject> = {
11
+ [TKey in keyof TRow & string]-?: NonNullable<TRow[TKey]> extends readonly GenericObject[] ? TKey : never;
12
+ }[keyof TRow & string];
13
+ /**
14
+ * Turns the table into a tree: a row can hold child rows that open beneath it.
15
+ *
16
+ * The children travel with their parent in the source payload. Row ids (`rowKey`) must be unique
17
+ * across the whole tree, not only among siblings. Pagination, sorting, filters, search and
18
+ * summaries act on the top-level rows; a search or filter field may reach into the children with a
19
+ * dotted path such as `creditNotes.number`, which keeps the parent row.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * table: {
24
+ * tree: { children: 'creditNotes', defaultExpanded: false },
25
+ * }
26
+ * ```
27
+ */
28
+ export interface TableTreeSchema<TRow extends GenericObject = GenericObject> {
29
+ /** Key of each row that holds its child rows. A row with no children shows no chevron. */
30
+ children: TableChildrenKey<TRow>;
31
+ /**
32
+ * Which branches start open. `true` opens every branch, a number opens that many levels
33
+ * (`1` opens the top-level rows), `false` (the default) opens none. A branch the user or
34
+ * `table.expansion` has toggled keeps that state.
35
+ */
36
+ defaultExpanded?: boolean | number;
37
+ /**
38
+ * Which rows can be selected when the table has selection. `'all'` (the default) lets every row
39
+ * carry a checkbox, children included, each one independent of its parent; `'roots'` limits the
40
+ * checkboxes to the top-level rows. A row without a checkbox keeps its width, so nothing moves.
41
+ */
42
+ selectable?: 'all' | 'roots';
43
+ }
44
+ /** One loaded row with its place in the tree, resolved once per data change. */
45
+ export interface TableTreeNode {
46
+ readonly id: string;
47
+ readonly row: GenericObject;
48
+ /** Top-level rows are depth `0`. */
49
+ readonly depth: number;
50
+ readonly parent: TableTreeNode | null;
51
+ /** Position of the top-level row this node sits under. */
52
+ readonly rootIndex: number;
53
+ readonly childNodes: readonly TableTreeNode[];
54
+ readonly hasChildren: boolean;
55
+ /** No later sibling follows: the rail stops at this row's elbow. */
56
+ readonly isLast: boolean;
57
+ }
58
+ /** The motion a row plays while a branch opens or closes. */
59
+ export interface TableTreeMotion {
60
+ kind: 'enter' | 'leave';
61
+ /** Position in the stagger, so rows arrive one after another instead of all at once. */
62
+ order: number;
63
+ }
64
+ export interface TableTreeIndex {
65
+ readonly roots: readonly TableTreeNode[];
66
+ readonly byId: ReadonlyMap<string, TableTreeNode>;
67
+ /** Every node that has children. */
68
+ readonly branches: readonly TableTreeNode[];
69
+ readonly maxDepth: number;
70
+ /** Ids met more than once; the later rows are left out of the tree. */
71
+ readonly duplicateIds: readonly string[];
72
+ }
73
+ export interface TableExpansionState {
74
+ /** Whether the schema configured `table.tree`. */
75
+ enabled: boolean;
76
+ /** Ids of the loaded rows that are open right now, defaults included. */
77
+ expandedKeys: string[];
78
+ expandedCount: number;
79
+ /** Loaded rows that have children. */
80
+ branchCount: number;
81
+ allExpanded: boolean;
82
+ }
83
+ export interface TableExpansionApi {
84
+ state: ComputedRef<TableExpansionState>;
85
+ isExpanded: (rowId: string) => boolean;
86
+ /** Opens the given rows; ids without children are ignored. */
87
+ expand: (rowIds: string[]) => void;
88
+ collapse: (rowIds: string[]) => void;
89
+ /** Flips a row, or sets it when `expanded` is given. */
90
+ toggle: (options: {
91
+ rowId: string;
92
+ expanded?: boolean;
93
+ }) => void;
94
+ /** Opens every loaded branch, and every branch that loads later, until collapsed again. */
95
+ expandAll: () => void;
96
+ collapseAll: () => void;
97
+ /** Returns every branch to the schema's `defaultExpanded`. */
98
+ reset: () => void;
99
+ }
100
+ export {};
File without changes
@@ -12,7 +12,9 @@ import {
12
12
  ROW_ACTIONS_COLUMN_ID,
13
13
  ROW_ACTIONS_COLUMN_WIDTH,
14
14
  SELECT_COLUMN_ID,
15
- SELECT_COLUMN_WIDTH
15
+ SELECT_COLUMN_WIDTH,
16
+ TREE_COLUMN_ID,
17
+ TREE_COLUMN_WIDTH
16
18
  } from "./types.js";
17
19
  const LABEL_CHAR_WIDTH = 7.2;
18
20
  const LABEL_CHROME_WIDTH = 64;
@@ -21,7 +23,22 @@ function headerFloor(label, sortable) {
21
23
  }
22
24
  export function createColumnDefs(options) {
23
25
  const defs = [];
24
- if (options.params.selection.selectionEnabled.value) {
26
+ if (options.params.tree.enabled.value) {
27
+ defs.push({
28
+ enableResizing: false,
29
+ id: TREE_COLUMN_ID,
30
+ maxSize: MAX_COLUMN_SIZE,
31
+ meta: {
32
+ canHide: false,
33
+ internal: "tree",
34
+ label: "",
35
+ render: () => null,
36
+ sortable: false
37
+ },
38
+ minSize: TREE_COLUMN_WIDTH,
39
+ size: TREE_COLUMN_WIDTH
40
+ });
41
+ } else if (options.params.selection.selectionEnabled.value) {
25
42
  const selection = createSelectionColumn({ params: options.params });
26
43
  defs.push({
27
44
  enableResizing: false,
@@ -1,9 +1,13 @@
1
1
  import type { ComputedRef, ShallowRef } from 'vue';
2
2
  import type { UseTableDataReturn } from '../../composables/use-table-data.js';
3
3
  import type { useTableState } from '../../composables/use-table-state.js';
4
+ import type { useTableTree } from '../../composables/use-table-tree.js';
4
5
  import type { TableApi, TableColumnAlign, TableColumnPinned, GenericObject, TableLayout, TableSchemaView, TableRuntimeRecord, TableColumnSkeleton } from '../../types/index.js';
5
6
  export declare const SELECT_COLUMN_ID = "__select";
6
7
  export declare const SELECT_COLUMN_WIDTH = 44;
8
+ export declare const TREE_COLUMN_ID = "__tree";
9
+ /** The control column at depth zero; it grows from here by one gutter per level of depth. */
10
+ export declare const TREE_COLUMN_WIDTH: number;
7
11
  export declare const ROW_ACTIONS_COLUMN_ID = "__row-actions";
8
12
  export declare const ROW_ACTIONS_COLUMN_WIDTH = 56;
9
13
  export type SchemaTableColumn = NonNullable<NonNullable<TableSchemaView['table']>['columns']>[number];
@@ -74,6 +78,7 @@ export interface UseTableColumnsParams {
74
78
  selection: TableColumnsSelectionState;
75
79
  tableLayout: ComputedRef<TableLayout>;
76
80
  tableApi: ShallowRef<TableApi<unknown> | null>;
81
+ tree: ReturnType<typeof useTableTree>;
77
82
  }
78
83
  export interface TableColumnRenderParams {
79
84
  column: SchemaTableColumn;
@@ -89,7 +94,7 @@ export interface TableCellRenderContext {
89
94
  tableApi: TableApi<unknown>;
90
95
  layout: TableLayout;
91
96
  }
92
- export type DataListColumnInternalKind = 'selection' | 'actions';
97
+ export type DataListColumnInternalKind = 'selection' | 'tree' | 'actions';
93
98
  export interface DataListColumnMeta {
94
99
  label: string;
95
100
  icon?: string;
@@ -1,5 +1,12 @@
1
+ import { TREE_CHECK_WIDTH, TREE_LEAD, getTreeColumnWidth } from "../tree.js";
1
2
  export const SELECT_COLUMN_ID = "__select";
2
3
  export const SELECT_COLUMN_WIDTH = 44;
4
+ export const TREE_COLUMN_ID = "__tree";
5
+ export const TREE_COLUMN_WIDTH = getTreeColumnWidth({
6
+ check: TREE_CHECK_WIDTH.md,
7
+ lead: TREE_LEAD,
8
+ maxDepth: 0
9
+ });
3
10
  export const ROW_ACTIONS_COLUMN_ID = "__row-actions";
4
11
  export const ROW_ACTIONS_COLUMN_WIDTH = 56;
5
12
  export const DEFAULT_COLUMN_SIZE = 150;
@@ -13,4 +13,5 @@ export * from './resolved-filters.js';
13
13
  export * from './rows.js';
14
14
  export * from './schema.js';
15
15
  export * from './source-request.js';
16
+ export * from './tree.js';
16
17
  export { tableSource } from './builders/table-source.js';
@@ -13,4 +13,5 @@ export * from "./resolved-filters.js";
13
13
  export * from "./rows.js";
14
14
  export * from "./schema.js";
15
15
  export * from "./source-request.js";
16
+ export * from "./tree.js";
16
17
  export { tableSource } from "./builders/table-source.js";
@@ -0,0 +1,64 @@
1
+ import type { GenericObject, TableTreeIndex, TableTreeNode } from '../types/index.js';
2
+ /** Pixels one level of the tree adds: one rail gutter. The renderer hands the same number to CSS. */
3
+ export declare const TREE_GUTTER_WIDTH = 16;
4
+ /** The chevron's footprint, which a childless row keeps free so its rail lines up. */
5
+ export declare const TREE_TOGGLE_WIDTH = 16;
6
+ /** Between the chevron and the checkbox. */
7
+ export declare const TREE_GAP = 6;
8
+ /** Least space before the first gutter; the table's own left gutter wins when it is wider. */
9
+ export declare const TREE_LEAD = 6;
10
+ /** Clear space after the checkbox of the deepest row, so it never meets the column's edge. */
11
+ export declare const TREE_TRAIL = 6;
12
+ /** Width of a Nuxt UI checkbox box at each control size, which the tree cell always reserves. */
13
+ export declare const TREE_CHECK_WIDTH: {
14
+ readonly lg: 18;
15
+ readonly md: 16;
16
+ readonly sm: 14;
17
+ readonly xl: 20;
18
+ readonly xs: 12;
19
+ };
20
+ /**
21
+ * The control column's width: lead, one gutter per level of the deepest loaded row, the chevron,
22
+ * the checkbox and a little room after it. Every row shares it, so the first data column starts at
23
+ * the same x on all of them and indentation happens inside the cell.
24
+ */
25
+ export declare function getTreeColumnWidth(params: {
26
+ lead: number;
27
+ maxDepth: number;
28
+ check: number;
29
+ }): number;
30
+ /** Every row under `rows`, the rows themselves first, however deep and whether open or not. */
31
+ export declare function flattenTreeRows(rows: readonly GenericObject[], childrenPath: string): GenericObject[];
32
+ /**
33
+ * Indexes the loaded rows once per data change: every node knows its depth, its parent and its
34
+ * children, so toggling a branch never walks the source again. A node holds no per-row arrays,
35
+ * which keeps the index linear however deep the tree goes.
36
+ *
37
+ * Built with an explicit stack, so a deep tree cannot overflow the call stack, and the nested
38
+ * source rows are left untouched: nothing is copied, mapped or tagged.
39
+ */
40
+ export declare function buildTableTree(params: {
41
+ rows: readonly GenericObject[];
42
+ childrenPath: string;
43
+ getRowId: (row: GenericObject, index: number) => string;
44
+ }): TableTreeIndex;
45
+ /** The rows from the top level down to `node`, inclusive. */
46
+ export declare function getTreeLineage(node: TableTreeNode): TableTreeNode[];
47
+ /**
48
+ * One flag per gutter between the top level and the row's own: whether the ancestor that gutter
49
+ * belongs to still has a sibling below, so its rail runs through the row. A rail is only drawn
50
+ * through a row where the answer is yes, which is what stops a branch at its last child.
51
+ */
52
+ export declare function getTreeRails(node: TableTreeNode): boolean[];
53
+ /**
54
+ * The rows that are on screen: every top-level row, then the children of each open branch.
55
+ *
56
+ * This replaces TanStack's expanded row model on purpose. That model materialises every
57
+ * descendant before the virtualizer sees a row; here only the visible rows exist, so closed
58
+ * branches cost nothing however large they are.
59
+ */
60
+ export declare function flattenVisibleTree(roots: readonly TableTreeNode[], isExpanded: (node: TableTreeNode) => boolean): TableTreeNode[];
61
+ /** The rows currently showing beneath `branch`, which follow it contiguously in `visible`. */
62
+ export declare function getVisibleDescendants(visible: readonly TableTreeNode[], branch: TableTreeNode, limit?: number): TableTreeNode[];
63
+ /** Whether `branch` starts open under a `defaultExpanded` setting. */
64
+ export declare function isOpenByDefault(defaultExpanded: boolean | number, depth: number): boolean;
@@ -0,0 +1,112 @@
1
+ import { isObject } from "../../shared/utils/predicate.js";
2
+ import { getTableRowValue } from "./rows.js";
3
+ export const TREE_GUTTER_WIDTH = 16;
4
+ export const TREE_TOGGLE_WIDTH = 16;
5
+ export const TREE_GAP = 6;
6
+ export const TREE_LEAD = 6;
7
+ export const TREE_TRAIL = 6;
8
+ export const TREE_CHECK_WIDTH = { lg: 18, md: 16, sm: 14, xl: 20, xs: 12 };
9
+ export function getTreeColumnWidth(params) {
10
+ return params.lead + params.maxDepth * TREE_GUTTER_WIDTH + TREE_TOGGLE_WIDTH + TREE_GAP + params.check + TREE_TRAIL;
11
+ }
12
+ export function flattenTreeRows(rows, childrenPath) {
13
+ const flat = [];
14
+ const seen = /* @__PURE__ */ new Set();
15
+ const stack = [...rows].reverse();
16
+ for (let row = stack.pop(); row; row = stack.pop()) {
17
+ if (seen.has(row)) continue;
18
+ seen.add(row);
19
+ flat.push(row);
20
+ const children = readChildren(row, childrenPath);
21
+ for (let position = children.length - 1; position >= 0; position -= 1) {
22
+ const child = children[position];
23
+ if (child) stack.push(child);
24
+ }
25
+ }
26
+ return flat;
27
+ }
28
+ function readChildren(row, childrenPath) {
29
+ const value = getTableRowValue({ path: childrenPath, row });
30
+ if (!Array.isArray(value)) return [];
31
+ const children = [];
32
+ for (const item of value) if (isObject(item)) children.push(item);
33
+ return children;
34
+ }
35
+ export function buildTableTree(params) {
36
+ const byId = /* @__PURE__ */ new Map();
37
+ const branches = [];
38
+ const duplicateIds = [];
39
+ const roots = [];
40
+ let maxDepth = 0;
41
+ const stack = [{ parent: null, rows: params.rows }];
42
+ for (let frame = stack.pop(); frame; frame = stack.pop()) {
43
+ const depth = frame.parent ? frame.parent.depth + 1 : 0;
44
+ const siblings = frame.parent ? frame.parent.childNodes : roots;
45
+ const created = [];
46
+ const rootIndexBase = frame.parent?.rootIndex;
47
+ for (const [index, row] of frame.rows.entries()) {
48
+ const id = params.getRowId(row, index);
49
+ if (byId.has(id)) {
50
+ duplicateIds.push(id);
51
+ continue;
52
+ }
53
+ const children = readChildren(row, params.childrenPath);
54
+ const node = {
55
+ childNodes: [],
56
+ depth,
57
+ hasChildren: children.length > 0,
58
+ id,
59
+ isLast: index === frame.rows.length - 1,
60
+ parent: frame.parent,
61
+ rootIndex: rootIndexBase ?? index,
62
+ row
63
+ };
64
+ byId.set(id, node);
65
+ created.push({ children, node });
66
+ siblings.push(node);
67
+ if (depth > maxDepth) maxDepth = depth;
68
+ if (node.hasChildren) branches.push(node);
69
+ }
70
+ for (let position = created.length - 1; position >= 0; position -= 1) {
71
+ const entry = created[position];
72
+ if (entry?.node.hasChildren) stack.push({ parent: entry.node, rows: entry.children });
73
+ }
74
+ }
75
+ return { branches, byId, duplicateIds, maxDepth, roots };
76
+ }
77
+ export function getTreeLineage(node) {
78
+ const lineage = [];
79
+ for (let current = node; current; current = current.parent)
80
+ lineage.unshift(current);
81
+ return lineage;
82
+ }
83
+ export function getTreeRails(node) {
84
+ return getTreeLineage(node).slice(1, -1).map((ancestor) => !ancestor.isLast);
85
+ }
86
+ export function flattenVisibleTree(roots, isExpanded) {
87
+ const visible = [];
88
+ const stack = [...roots].reverse();
89
+ for (let node = stack.pop(); node; node = stack.pop()) {
90
+ visible.push(node);
91
+ if (!node.hasChildren || !isExpanded(node)) continue;
92
+ for (let position = node.childNodes.length - 1; position >= 0; position -= 1) {
93
+ const child = node.childNodes[position];
94
+ if (child) stack.push(child);
95
+ }
96
+ }
97
+ return visible;
98
+ }
99
+ export function getVisibleDescendants(visible, branch, limit = Number.POSITIVE_INFINITY) {
100
+ const start = visible.indexOf(branch);
101
+ if (start === -1) return [];
102
+ const descendants = [];
103
+ for (let index = start + 1; index < visible.length && descendants.length < limit; index += 1) {
104
+ const node = visible[index];
105
+ if (!node || node.depth <= branch.depth) break;
106
+ descendants.push(node);
107
+ }
108
+ return descendants;
109
+ }
110
+ export function isOpenByDefault(defaultExpanded, depth) {
111
+ return defaultExpanded === true || defaultExpanded !== false && depth < defaultExpanded;
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-ui-tools",
3
- "version": "1.16.9",
3
+ "version": "1.17.0",
4
4
  "description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
5
5
  "homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
6
6
  "bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",