@webx-ui/core 0.9.0 → 0.10.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 +21 -3
- package/dist/components/Table/Table.d.ts +3 -2
- package/dist/components/Table/types.d.ts +57 -0
- package/dist/components/Transfer/Transfer.d.ts +57 -0
- package/dist/components/Transfer/index.d.ts +2 -0
- package/dist/components/Transfer/types.d.ts +40 -0
- package/dist/components/Tree/Tree.d.ts +79 -0
- package/dist/components/Tree/index.d.ts +2 -0
- package/dist/components/Tree/types.d.ts +99 -0
- package/dist/components/TreeSelect/TreeSelect.d.ts +47 -0
- package/dist/components/TreeSelect/index.d.ts +2 -0
- package/dist/components/TreeSelect/types.d.ts +76 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/composables/useTreeNodes.d.ts +96 -0
- package/dist/index.js +6157 -5057
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,10 +46,28 @@ const title = ref('')
|
|
|
46
46
|
`@webx-ui/core/style.css` already includes the token variables, so importing
|
|
47
47
|
`@webx-ui/tokens/tokens.css` separately is optional.
|
|
48
48
|
|
|
49
|
-
##
|
|
49
|
+
## What is in it
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
91 components — the form controls, the layout, the overlays, the data ones (`WxTable` takes
|
|
52
|
+
Laravel's `paginate()` payload as it comes), and the ones an admin panel needs that Element Plus
|
|
53
|
+
has no name for: `WxListDetail`, `WxKanban`, `WxEntityCard`, `WxSelectionArea`,
|
|
54
|
+
`WxSortableList`. The [roadmap](https://webx-ui.github.io/webx-ui/guide/roadmap.html) lists every
|
|
55
|
+
one of them and what is still open.
|
|
56
|
+
|
|
57
|
+
Not everything is a component:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { toast, confirm, openModal, createModal } from '@webx-ui/core'
|
|
61
|
+
|
|
62
|
+
toast.success('Saved')
|
|
63
|
+
if (await confirm('Delete this product?')) await api.delete(product)
|
|
64
|
+
|
|
65
|
+
/* Any component, mounted from code and awaited for its answer. */
|
|
66
|
+
const product = await createModal<Product>(ProductBrowser, { resolveOn: 'select' })()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
One directive ships with the library, `v-wx-select`, which hands an item to the selection area
|
|
70
|
+
around it. `app.use(WebxUI)` registers it along with the components.
|
|
53
71
|
|
|
54
72
|
## Conventions
|
|
55
73
|
|
|
@@ -12,8 +12,9 @@ declare const _default: <T extends TableRow = TableRow>(__VLS_props: NonNullable
|
|
|
12
12
|
readonly "onSort-change"?: ((sort: TableSort | null) => any) | undefined;
|
|
13
13
|
readonly "onSelection-change"?: ((keys: RowKey[], rows: T[]) => any) | undefined;
|
|
14
14
|
readonly "onExpand-change"?: ((keys: RowKey[], rows: T[]) => any) | undefined;
|
|
15
|
+
readonly "onNode-drop"?: ((event: import('./types').TableNodeDropEvent<T>) => any) | undefined;
|
|
15
16
|
readonly "onState-change"?: ((state: TableState) => any) | undefined;
|
|
16
|
-
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onSearch" | "onUpdate:page" | "onUpdate:perPage" | "onUpdate:selected" | "onUpdate:expanded" | "onUpdate:sort" | "onUpdate:search" | "onRow-click" | "onSort-change" | "onSelection-change" | "onExpand-change" | "onState-change"> & ({
|
|
17
|
+
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onSearch" | "onUpdate:page" | "onUpdate:perPage" | "onUpdate:selected" | "onUpdate:expanded" | "onUpdate:sort" | "onUpdate:search" | "onRow-click" | "onSort-change" | "onSelection-change" | "onExpand-change" | "onNode-drop" | "onState-change"> & ({
|
|
17
18
|
selected?: RowKey[];
|
|
18
19
|
expanded?: RowKey[];
|
|
19
20
|
sort?: TableSort | null;
|
|
@@ -70,7 +71,7 @@ declare const _default: <T extends TableRow = TableRow>(__VLS_props: NonNullable
|
|
|
70
71
|
index: number;
|
|
71
72
|
}) => unknown;
|
|
72
73
|
};
|
|
73
|
-
emit: (((evt: "search", term: string) => void) & ((evt: "row-click", row: T, index: number, event: MouseEvent) => void) & ((evt: "sort-change", sort: TableSort | null) => void) & ((evt: "selection-change", keys: RowKey[], rows: T[]) => void) & ((evt: "expand-change", keys: RowKey[], rows: T[]) => void) & ((evt: "state-change", state: TableState) => void)) & (((evt: "update:page", value: number) => void) & ((evt: "update:perPage", value: number) => void) & ((evt: "update:selected", value: RowKey[]) => void) & ((evt: "update:expanded", value: RowKey[]) => void) & ((evt: "update:sort", value: TableSort | null) => void) & ((evt: "update:search", value: string) => void));
|
|
74
|
+
emit: (((evt: "search", term: string) => void) & ((evt: "row-click", row: T, index: number, event: MouseEvent) => void) & ((evt: "sort-change", sort: TableSort | null) => void) & ((evt: "selection-change", keys: RowKey[], rows: T[]) => void) & ((evt: "expand-change", keys: RowKey[], rows: T[]) => void) & ((evt: "node-drop", event: import('./types').TableNodeDropEvent<T>) => void) & ((evt: "state-change", state: TableState) => void)) & (((evt: "update:page", value: number) => void) & ((evt: "update:perPage", value: number) => void) & ((evt: "update:selected", value: RowKey[]) => void) & ((evt: "update:expanded", value: RowKey[]) => void) & ((evt: "update:sort", value: TableSort | null) => void) & ((evt: "update:search", value: string) => void));
|
|
74
75
|
}>) => import('vue').VNode & {
|
|
75
76
|
__ctx?: Awaited<typeof __VLS_setup>;
|
|
76
77
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ControlSize } from '../../composables/useFormField';
|
|
2
|
+
import { TreeDropZone } from '../../composables/useTreeNodes';
|
|
3
|
+
export type { TreeDropZone };
|
|
2
4
|
/**
|
|
3
5
|
* A page as Laravel's `->paginate()` serialises it. Taken as it arrives, with the
|
|
4
6
|
* snake_case keys intact — renaming them in every controller is work the table can
|
|
@@ -63,6 +65,57 @@ export interface TableSummaryRow {
|
|
|
63
65
|
strong?: boolean;
|
|
64
66
|
class?: string;
|
|
65
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Turns the rows into a tree: the first column keeps the disclosure and the
|
|
70
|
+
* indentation, every other column is still a column.
|
|
71
|
+
*
|
|
72
|
+
* A tree is a structure, and the two things a table does to a flat list destroy it —
|
|
73
|
+
* so in this mode `sortable` columns are drawn without their control and `pagination`
|
|
74
|
+
* is off. Ordering a tree is what dragging is for, and a page of it would cut branches
|
|
75
|
+
* in half.
|
|
76
|
+
*/
|
|
77
|
+
export interface TableTreeOptions<T = TableRow> {
|
|
78
|
+
/** Field holding the children of a row. */
|
|
79
|
+
childrenKey?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Field that says a row has children before any have been fetched — Laravel's
|
|
82
|
+
* `withCount('children')` under its own name, or a boolean of your own. Without it a
|
|
83
|
+
* lazy table cannot tell a leaf from a branch nobody has opened yet.
|
|
84
|
+
*/
|
|
85
|
+
hasChildrenKey?: string;
|
|
86
|
+
/** Children arrive when a row is opened. Requires `load`. */
|
|
87
|
+
lazy?: boolean;
|
|
88
|
+
/** Fetches the children of one row. */
|
|
89
|
+
load?: (row: T) => T[] | Promise<T[]>;
|
|
90
|
+
/** Open every branch that is already loaded, once, on the first render. */
|
|
91
|
+
defaultExpandAll?: boolean;
|
|
92
|
+
/** How far one level sits from the next, in pixels. */
|
|
93
|
+
indent?: number;
|
|
94
|
+
/** Rows can be picked up and dropped before, after or inside another. */
|
|
95
|
+
draggable?: boolean;
|
|
96
|
+
/** Rows this returns `false` for cannot be picked up. */
|
|
97
|
+
allowDrag?: (row: T) => boolean;
|
|
98
|
+
/** Vetoes a landing spot before anything moves. */
|
|
99
|
+
allowDrop?: (drag: T, drop: T, zone: TreeDropZone) => boolean;
|
|
100
|
+
/**
|
|
101
|
+
* How long a row dragged over a closed branch waits before that branch opens.
|
|
102
|
+
* Dropping into a branch you cannot see the inside of is a guess; this turns the
|
|
103
|
+
* guess into a look. Zero switches it off.
|
|
104
|
+
*/
|
|
105
|
+
springDelay?: number;
|
|
106
|
+
}
|
|
107
|
+
/** What a finished move says: the row, where it went, and how it got there. */
|
|
108
|
+
export interface TableNodeDropEvent<T = TableRow> {
|
|
109
|
+
row: T;
|
|
110
|
+
/** The row it was dropped on. */
|
|
111
|
+
target: T;
|
|
112
|
+
zone: TreeDropZone;
|
|
113
|
+
/** The row it now hangs from — `null` at the top level. */
|
|
114
|
+
parent: T | null;
|
|
115
|
+
/** Its position among its new siblings. */
|
|
116
|
+
index: number;
|
|
117
|
+
via: 'pointer' | 'keyboard';
|
|
118
|
+
}
|
|
66
119
|
/** Everything the table asks the backend for, in one object. */
|
|
67
120
|
export interface TableState {
|
|
68
121
|
page: number;
|
|
@@ -124,6 +177,8 @@ export interface TableProps<T = TableRow> {
|
|
|
124
177
|
maxHeight?: string | number;
|
|
125
178
|
/** Extra class per row, for status colouring and the like. */
|
|
126
179
|
rowClass?: (row: T, index: number) => string | undefined;
|
|
180
|
+
/** Draws the rows as a tree. See {@link TableTreeOptions}. */
|
|
181
|
+
tree?: TableTreeOptions<T>;
|
|
127
182
|
/** Fixes the column widths instead of letting the content decide. */
|
|
128
183
|
layout?: 'auto' | 'fixed';
|
|
129
184
|
ariaLabel?: string;
|
|
@@ -133,6 +188,8 @@ export interface TableEmits<T = TableRow> {
|
|
|
133
188
|
'sort-change': [sort: TableSort | null];
|
|
134
189
|
'selection-change': [keys: RowKey[], rows: T[]];
|
|
135
190
|
'expand-change': [keys: RowKey[], rows: T[]];
|
|
191
|
+
/** A row was moved in tree mode. The rows have already been rearranged. */
|
|
192
|
+
'node-drop': [event: TableNodeDropEvent<T>];
|
|
136
193
|
search: [term: string];
|
|
137
194
|
/**
|
|
138
195
|
* Everything the backend needs, together. Fires once on mount — with whatever
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { TransferItem, TransferProps, TransferSide, TransferValue } from './types';
|
|
2
|
+
type __VLS_Props = TransferProps;
|
|
3
|
+
type __VLS_PublicProps = {
|
|
4
|
+
modelValue?: TransferValue[];
|
|
5
|
+
} & __VLS_Props;
|
|
6
|
+
declare function __VLS_template(): {
|
|
7
|
+
attrs: Partial<{}>;
|
|
8
|
+
slots: Readonly<{
|
|
9
|
+
/** One row, on either side. */
|
|
10
|
+
item?: (props: {
|
|
11
|
+
item: TransferItem;
|
|
12
|
+
side: TransferSide;
|
|
13
|
+
}) => unknown;
|
|
14
|
+
/** Shown in a panel with nothing in it. */
|
|
15
|
+
empty?: (props: {
|
|
16
|
+
side: TransferSide;
|
|
17
|
+
}) => unknown;
|
|
18
|
+
}> & {
|
|
19
|
+
/** One row, on either side. */
|
|
20
|
+
item?: (props: {
|
|
21
|
+
item: TransferItem;
|
|
22
|
+
side: TransferSide;
|
|
23
|
+
}) => unknown;
|
|
24
|
+
/** Shown in a panel with nothing in it. */
|
|
25
|
+
empty?: (props: {
|
|
26
|
+
side: TransferSide;
|
|
27
|
+
}) => unknown;
|
|
28
|
+
};
|
|
29
|
+
refs: {};
|
|
30
|
+
rootEl: HTMLDivElement;
|
|
31
|
+
};
|
|
32
|
+
type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
|
|
33
|
+
declare const __VLS_component: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
34
|
+
change: (change: import('./types').TransferChange) => any;
|
|
35
|
+
"update:modelValue": (value: TransferValue[]) => any;
|
|
36
|
+
}, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
37
|
+
onChange?: ((change: import('./types').TransferChange) => any) | undefined;
|
|
38
|
+
"onUpdate:modelValue"?: ((value: TransferValue[]) => any) | undefined;
|
|
39
|
+
}>, {
|
|
40
|
+
size: import('../..').ControlSize;
|
|
41
|
+
disabled: boolean;
|
|
42
|
+
emptyText: string;
|
|
43
|
+
height: number | string;
|
|
44
|
+
items: TransferItem[];
|
|
45
|
+
searchable: boolean;
|
|
46
|
+
searchPlaceholder: string;
|
|
47
|
+
titles: [string, string];
|
|
48
|
+
toRightLabel: string;
|
|
49
|
+
toLeftLabel: string;
|
|
50
|
+
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
|
|
51
|
+
declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
|
|
52
|
+
export default _default;
|
|
53
|
+
type __VLS_WithTemplateSlots<T, S> = T & {
|
|
54
|
+
new (): {
|
|
55
|
+
$slots: S;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ControlSize } from '../../composables/useFormField';
|
|
2
|
+
export type TransferValue = string | number;
|
|
3
|
+
export type TransferSide = 'left' | 'right';
|
|
4
|
+
export interface TransferItem {
|
|
5
|
+
value: TransferValue;
|
|
6
|
+
/** What is read on the row. Falls back to the value. */
|
|
7
|
+
label?: string;
|
|
8
|
+
/** A second line under the label. */
|
|
9
|
+
description?: string;
|
|
10
|
+
/** Cannot be moved, from either side. */
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface TransferProps {
|
|
14
|
+
/** Everything there is to choose from, both panels together. */
|
|
15
|
+
items?: TransferItem[];
|
|
16
|
+
/** Headings of the two panels. */
|
|
17
|
+
titles?: [string, string];
|
|
18
|
+
/** A search field over each panel. */
|
|
19
|
+
searchable?: boolean;
|
|
20
|
+
searchPlaceholder?: string;
|
|
21
|
+
/** Height of a list: a number in pixels, or any CSS length. */
|
|
22
|
+
height?: number | string;
|
|
23
|
+
/** Shown in a panel with nothing in it. */
|
|
24
|
+
emptyText?: string;
|
|
25
|
+
size?: ControlSize;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
/** Accessible names for the two buttons. */
|
|
28
|
+
toRightLabel?: string;
|
|
29
|
+
toLeftLabel?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface TransferChange {
|
|
32
|
+
/** What moved, in the order it was shown. */
|
|
33
|
+
values: TransferValue[];
|
|
34
|
+
/** Which panel it moved to. */
|
|
35
|
+
to: TransferSide;
|
|
36
|
+
}
|
|
37
|
+
export interface TransferEmits {
|
|
38
|
+
/** Something moved. The model has already been rewritten. */
|
|
39
|
+
change: [change: TransferChange];
|
|
40
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TreeKey } from '../../composables/useTreeNodes';
|
|
2
|
+
import { TreeNode, TreeProps } from './types';
|
|
3
|
+
declare const _default: <T extends TreeNode>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
4
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
5
|
+
readonly "onUpdate:modelValue"?: ((value: T[]) => any) | undefined;
|
|
6
|
+
readonly onCheck?: ((keys: TreeKey[], detail: {
|
|
7
|
+
node: T;
|
|
8
|
+
checked: boolean;
|
|
9
|
+
}) => any) | undefined;
|
|
10
|
+
readonly onSelect?: ((node: T | null, key: TreeKey | null) => any) | undefined;
|
|
11
|
+
readonly onCollapse?: ((node: T) => any) | undefined;
|
|
12
|
+
readonly onDrop?: ((event: import('./types').TreeDropEvent<T>) => any) | undefined;
|
|
13
|
+
readonly onExpand?: ((node: T) => any) | undefined;
|
|
14
|
+
readonly "onUpdate:selected"?: ((value: TreeKey | null) => any) | undefined;
|
|
15
|
+
readonly "onUpdate:expanded"?: ((value: TreeKey[]) => any) | undefined;
|
|
16
|
+
readonly "onUpdate:checked"?: ((value: TreeKey[]) => any) | undefined;
|
|
17
|
+
readonly "onNode-click"?: ((node: T, event: MouseEvent) => any) | undefined;
|
|
18
|
+
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onUpdate:modelValue" | "onDrop" | "onSelect" | "onCollapse" | "onExpand" | "onUpdate:selected" | "onUpdate:expanded" | "onCheck" | "onUpdate:checked" | "onNode-click"> & ({
|
|
19
|
+
modelValue?: T[];
|
|
20
|
+
expanded?: TreeKey[];
|
|
21
|
+
selected?: TreeKey | null;
|
|
22
|
+
checked?: TreeKey[];
|
|
23
|
+
} & TreeProps<T>) & Partial<{}>> & import('vue').PublicProps;
|
|
24
|
+
expose(exposed: import('vue').ShallowUnwrapRef<{
|
|
25
|
+
/** Opens every branch on the way to a node. */
|
|
26
|
+
reveal: (key: TreeKey) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Opens a named path one level at a time, waiting for each lazy level before asking
|
|
29
|
+
* for the next — the way to reach a node the tree has not fetched yet.
|
|
30
|
+
*/
|
|
31
|
+
openPath: (keys: TreeKey[]) => Promise<boolean>;
|
|
32
|
+
expandAll: () => void;
|
|
33
|
+
collapseAll: () => void;
|
|
34
|
+
/** The node itself and everything above it — a breadcrumb of the tree. */
|
|
35
|
+
path: (key: TreeKey) => T[];
|
|
36
|
+
}>): void;
|
|
37
|
+
attrs: any;
|
|
38
|
+
slots: Readonly<{
|
|
39
|
+
/** One node's label. Without it the tree reads `labelKey` and highlights the filter. */
|
|
40
|
+
default?: (props: {
|
|
41
|
+
node: T;
|
|
42
|
+
depth: number;
|
|
43
|
+
expanded: boolean;
|
|
44
|
+
selected: boolean;
|
|
45
|
+
}) => unknown;
|
|
46
|
+
/** The end of a row, where the buttons that act on it go. */
|
|
47
|
+
actions?: (props: {
|
|
48
|
+
node: T;
|
|
49
|
+
depth: number;
|
|
50
|
+
}) => unknown;
|
|
51
|
+
/** Shown in place of an empty tree. */
|
|
52
|
+
empty?: () => unknown;
|
|
53
|
+
}> & {
|
|
54
|
+
/** One node's label. Without it the tree reads `labelKey` and highlights the filter. */
|
|
55
|
+
default?: (props: {
|
|
56
|
+
node: T;
|
|
57
|
+
depth: number;
|
|
58
|
+
expanded: boolean;
|
|
59
|
+
selected: boolean;
|
|
60
|
+
}) => unknown;
|
|
61
|
+
/** The end of a row, where the buttons that act on it go. */
|
|
62
|
+
actions?: (props: {
|
|
63
|
+
node: T;
|
|
64
|
+
depth: number;
|
|
65
|
+
}) => unknown;
|
|
66
|
+
/** Shown in place of an empty tree. */
|
|
67
|
+
empty?: () => unknown;
|
|
68
|
+
};
|
|
69
|
+
emit: (((evt: "check", keys: TreeKey[], detail: {
|
|
70
|
+
node: T;
|
|
71
|
+
checked: boolean;
|
|
72
|
+
}) => void) & ((evt: "select", node: T | null, key: TreeKey | null) => void) & ((evt: "collapse", node: T) => void) & ((evt: "drop", event: import('./types').TreeDropEvent<T>) => void) & ((evt: "expand", node: T) => void) & ((evt: "node-click", node: T, event: MouseEvent) => void)) & (((evt: "update:modelValue", value: T[]) => void) & ((evt: "update:selected", value: TreeKey | null) => void) & ((evt: "update:expanded", value: TreeKey[]) => void) & ((evt: "update:checked", value: TreeKey[]) => void));
|
|
73
|
+
}>) => import('vue').VNode & {
|
|
74
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
75
|
+
};
|
|
76
|
+
export default _default;
|
|
77
|
+
type __VLS_PrettifyLocal<T> = {
|
|
78
|
+
[K in keyof T]: T[K];
|
|
79
|
+
} & {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TreeDropZone, TreeKey } from '../../composables/useTreeNodes';
|
|
2
|
+
export type { TreeDropZone, TreeKey };
|
|
3
|
+
export type TreeSize = 'sm' | 'md';
|
|
4
|
+
/**
|
|
5
|
+
* A node as the tree hopes to find it. Every field is optional and every name is a prop
|
|
6
|
+
* away from being something else, so a record straight out of a Laravel controller —
|
|
7
|
+
* `{ id, title, children }` — is a tree without a `map` over it first.
|
|
8
|
+
*/
|
|
9
|
+
export interface TreeNode {
|
|
10
|
+
id?: TreeKey;
|
|
11
|
+
key?: TreeKey;
|
|
12
|
+
label?: string;
|
|
13
|
+
children?: TreeNode[];
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Says this node has nothing under it. Only needed with `lazy`, where an empty
|
|
17
|
+
* `children` cannot tell "not loaded yet" from "nothing there".
|
|
18
|
+
*/
|
|
19
|
+
leaf?: boolean;
|
|
20
|
+
[field: string]: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** What a finished move says: the node, where it went, and how it got there. */
|
|
23
|
+
export interface TreeDropEvent<T = TreeNode> {
|
|
24
|
+
node: T;
|
|
25
|
+
/** The node it was dropped on. */
|
|
26
|
+
target: T;
|
|
27
|
+
zone: TreeDropZone;
|
|
28
|
+
/** The node it now hangs from — `null` at the top level. */
|
|
29
|
+
parent: T | null;
|
|
30
|
+
/** Its position among its new siblings. */
|
|
31
|
+
index: number;
|
|
32
|
+
via: 'pointer' | 'keyboard';
|
|
33
|
+
}
|
|
34
|
+
export interface TreeProps<T = TreeNode> {
|
|
35
|
+
/** Field holding the identity of a node. */
|
|
36
|
+
nodeKey?: string;
|
|
37
|
+
/** Field holding the text of a node. */
|
|
38
|
+
labelKey?: string;
|
|
39
|
+
/** Field holding the array of children. */
|
|
40
|
+
childrenKey?: string;
|
|
41
|
+
/** Field that, when true, makes a node unselectable and unmovable. */
|
|
42
|
+
disabledKey?: string;
|
|
43
|
+
/** Field that says a node has no children to fetch. Only read when `lazy`. */
|
|
44
|
+
leafKey?: string;
|
|
45
|
+
/** Expand every branch once, on the first render. */
|
|
46
|
+
defaultExpandAll?: boolean;
|
|
47
|
+
/** Opening a branch closes the others at its level. */
|
|
48
|
+
accordion?: boolean;
|
|
49
|
+
/** Clicking a label opens the branch as well as selecting it. */
|
|
50
|
+
expandOnClick?: boolean;
|
|
51
|
+
/** Adds a checkbox to every node. */
|
|
52
|
+
checkable?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Checking a branch leaves its children alone. Off — the default — a branch follows
|
|
55
|
+
* its children and its children follow it, which is what a permission tree wants.
|
|
56
|
+
*/
|
|
57
|
+
checkStrictly?: boolean;
|
|
58
|
+
/** Rows can be picked up and dropped before, after or inside another. */
|
|
59
|
+
draggable?: boolean;
|
|
60
|
+
/** Nodes this returns `false` for cannot be picked up. */
|
|
61
|
+
allowDrag?: (node: T) => boolean;
|
|
62
|
+
/** Vetoes a landing spot — say, to keep a page out of the root. */
|
|
63
|
+
allowDrop?: (drag: T, drop: T, zone: TreeDropZone) => boolean;
|
|
64
|
+
/** Children are fetched when a branch first opens. Requires `load`. */
|
|
65
|
+
lazy?: boolean;
|
|
66
|
+
/** Fetches the children of one node. */
|
|
67
|
+
load?: (node: T) => T[] | Promise<T[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Shows only the nodes whose label contains this, together with the branches that
|
|
70
|
+
* lead to them — which are opened for as long as the term stands.
|
|
71
|
+
*/
|
|
72
|
+
filter?: string;
|
|
73
|
+
/** Draws the guide lines that connect a child to its parent. */
|
|
74
|
+
showLines?: boolean;
|
|
75
|
+
/** How far one level sits from the next, in pixels. */
|
|
76
|
+
indent?: number;
|
|
77
|
+
size?: TreeSize;
|
|
78
|
+
/** Shown when there is nothing to draw. */
|
|
79
|
+
emptyText?: string;
|
|
80
|
+
/** What the drag handle is called, before the name of the node it holds. */
|
|
81
|
+
dragLabel?: string;
|
|
82
|
+
/** Accessible name for the tree. */
|
|
83
|
+
ariaLabel?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface TreeEmits<T = TreeNode> {
|
|
86
|
+
/** A node was clicked — before anything was decided about selection. */
|
|
87
|
+
'node-click': [node: T, event: MouseEvent];
|
|
88
|
+
/** The selected node changed. */
|
|
89
|
+
select: [node: T | null, key: TreeKey | null];
|
|
90
|
+
/** A checkbox changed. The keys are every checked node, branches included. */
|
|
91
|
+
check: [keys: TreeKey[], detail: {
|
|
92
|
+
node: T;
|
|
93
|
+
checked: boolean;
|
|
94
|
+
}];
|
|
95
|
+
expand: [node: T];
|
|
96
|
+
collapse: [node: T];
|
|
97
|
+
/** A node was moved. The tree has already been rearranged. */
|
|
98
|
+
drop: [event: TreeDropEvent<T>];
|
|
99
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { TreeNode } from '../Tree/types';
|
|
2
|
+
import { TreeSelectProps, TreeSelectValue } from './types';
|
|
3
|
+
declare const _default: <T extends TreeNode>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_expose?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
|
|
4
|
+
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
5
|
+
readonly onChange?: ((value: TreeSelectValue, nodes: T[]) => any) | undefined;
|
|
6
|
+
readonly "onUpdate:modelValue"?: ((value: TreeSelectValue) => any) | undefined;
|
|
7
|
+
readonly onClose?: (() => any) | undefined;
|
|
8
|
+
readonly onOpen?: (() => any) | undefined;
|
|
9
|
+
readonly onClear?: (() => any) | undefined;
|
|
10
|
+
} & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onChange" | "onUpdate:modelValue" | "onOpen" | "onClose" | "onClear"> & ({
|
|
11
|
+
modelValue?: TreeSelectValue;
|
|
12
|
+
} & TreeSelectProps<T>) & Partial<{}>> & import('vue').PublicProps;
|
|
13
|
+
expose(exposed: import('vue').ShallowUnwrapRef<{}>): void;
|
|
14
|
+
attrs: any;
|
|
15
|
+
slots: Readonly<{
|
|
16
|
+
/** One node in the panel. */
|
|
17
|
+
node?: (props: {
|
|
18
|
+
node: T;
|
|
19
|
+
depth: number;
|
|
20
|
+
expanded: boolean;
|
|
21
|
+
selected: boolean;
|
|
22
|
+
}) => unknown;
|
|
23
|
+
/** What the field shows instead of the labels. */
|
|
24
|
+
value?: (props: {
|
|
25
|
+
nodes: T[];
|
|
26
|
+
}) => unknown;
|
|
27
|
+
}> & {
|
|
28
|
+
/** One node in the panel. */
|
|
29
|
+
node?: (props: {
|
|
30
|
+
node: T;
|
|
31
|
+
depth: number;
|
|
32
|
+
expanded: boolean;
|
|
33
|
+
selected: boolean;
|
|
34
|
+
}) => unknown;
|
|
35
|
+
/** What the field shows instead of the labels. */
|
|
36
|
+
value?: (props: {
|
|
37
|
+
nodes: T[];
|
|
38
|
+
}) => unknown;
|
|
39
|
+
};
|
|
40
|
+
emit: (((evt: "change", value: TreeSelectValue, nodes: T[]) => void) & ((evt: "close") => void) & ((evt: "open") => void) & ((evt: "clear") => void)) & ((evt: "update:modelValue", value: TreeSelectValue) => void);
|
|
41
|
+
}>) => import('vue').VNode & {
|
|
42
|
+
__ctx?: Awaited<typeof __VLS_setup>;
|
|
43
|
+
};
|
|
44
|
+
export default _default;
|
|
45
|
+
type __VLS_PrettifyLocal<T> = {
|
|
46
|
+
[K in keyof T]: T[K];
|
|
47
|
+
} & {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ControlSize, ControlStatus } from '../../composables/useFormField';
|
|
2
|
+
import { TreeKey, TreeNode } from '../Tree/types';
|
|
3
|
+
export type { TreeKey, TreeNode };
|
|
4
|
+
/** One key, several of them with `multiple`, or nothing chosen. */
|
|
5
|
+
export type TreeSelectValue = TreeKey | TreeKey[] | null;
|
|
6
|
+
export interface TreeSelectProps<T = TreeNode> {
|
|
7
|
+
/** The tree to choose from — the same shape `WxTree` takes. */
|
|
8
|
+
nodes?: T[];
|
|
9
|
+
/** Adds checkboxes and turns the model into an array. */
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* With `multiple`, a tick stays where it was made instead of running down into the
|
|
13
|
+
* children and up into the parent. The model then holds exactly what was ticked,
|
|
14
|
+
* which is usually what a form wants to send.
|
|
15
|
+
*/
|
|
16
|
+
checkStrictly?: boolean;
|
|
17
|
+
/** Field holding the identity of a node. */
|
|
18
|
+
nodeKey?: string;
|
|
19
|
+
/** Field holding the text of a node. */
|
|
20
|
+
labelKey?: string;
|
|
21
|
+
/** Field holding the array of children. */
|
|
22
|
+
childrenKey?: string;
|
|
23
|
+
/** Field that, when true, makes a node unchoosable. */
|
|
24
|
+
disabledKey?: string;
|
|
25
|
+
/** Field that says a node has no children to fetch. Only read when `lazy`. */
|
|
26
|
+
leafKey?: string;
|
|
27
|
+
/** Open every branch when the panel is first built. */
|
|
28
|
+
defaultExpandAll?: boolean;
|
|
29
|
+
/** Children are fetched when a branch first opens. Requires `load`. */
|
|
30
|
+
lazy?: boolean;
|
|
31
|
+
/** Fetches the children of one node. */
|
|
32
|
+
load?: (node: T) => T[] | Promise<T[]>;
|
|
33
|
+
/**
|
|
34
|
+
* The path to what is chosen — the ancestors and the node itself — for a tree that
|
|
35
|
+
* has not fetched it yet. A lazy field has no other way to name the value it holds,
|
|
36
|
+
* or to know which branches to open in order to show it. One path, or an array of
|
|
37
|
+
* them with `multiple`.
|
|
38
|
+
*
|
|
39
|
+
* A backend that knows the record knows its ancestors, so this is the same query the
|
|
40
|
+
* form already makes.
|
|
41
|
+
*/
|
|
42
|
+
selectedPath?: T[] | T[][];
|
|
43
|
+
/** Adds the search field above the tree. */
|
|
44
|
+
filterable?: boolean;
|
|
45
|
+
filterPlaceholder?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Shows the whole path in the field — `Engine / Pistons` — rather than the label
|
|
48
|
+
* alone. Worth it wherever the same name appears under two parents.
|
|
49
|
+
*/
|
|
50
|
+
showPath?: boolean;
|
|
51
|
+
/** What goes between the steps of a path. */
|
|
52
|
+
separator?: string;
|
|
53
|
+
/** How tall the tree inside the panel may get before it scrolls. */
|
|
54
|
+
panelHeight?: number | string;
|
|
55
|
+
/** Shows a button that empties the selection. */
|
|
56
|
+
clearable?: boolean;
|
|
57
|
+
placeholder?: string;
|
|
58
|
+
/** Shown in the panel when there is nothing to choose from. */
|
|
59
|
+
emptyText?: string;
|
|
60
|
+
/** Render the panel in a portal so it escapes `overflow: hidden`. */
|
|
61
|
+
teleport?: boolean;
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
size?: ControlSize;
|
|
64
|
+
status?: ControlStatus;
|
|
65
|
+
id?: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
/** Accessible label used when there is no visible `<label>`. */
|
|
68
|
+
ariaLabel?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface TreeSelectEmits<T = TreeNode> {
|
|
71
|
+
/** The selection changed. The nodes come with it, since an id alone rarely is it. */
|
|
72
|
+
change: [value: TreeSelectValue, nodes: T[]];
|
|
73
|
+
clear: [];
|
|
74
|
+
open: [];
|
|
75
|
+
close: [];
|
|
76
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { MaybeRefOrGetter, Ref } from 'vue';
|
|
2
|
+
export type TreeKey = string | number;
|
|
3
|
+
/** Where a dragged node lands relative to the one it was dropped on. */
|
|
4
|
+
export type TreeDropZone = 'before' | 'after' | 'inside';
|
|
5
|
+
/**
|
|
6
|
+
* How to read one node. Everything else here works through these four, which is what
|
|
7
|
+
* lets the same machinery drive a tree of its own and a table whose rows happen to
|
|
8
|
+
* nest: neither has to agree with the other on what a node looks like.
|
|
9
|
+
*/
|
|
10
|
+
export interface TreeAccessors<T> {
|
|
11
|
+
key: (node: T, path: number[]) => TreeKey;
|
|
12
|
+
children: (node: T) => T[] | undefined;
|
|
13
|
+
/** Called when a branch that had no `children` array needs one. */
|
|
14
|
+
setChildren: (node: T, children: T[]) => void;
|
|
15
|
+
label?: (node: T) => string;
|
|
16
|
+
disabled?: (node: T) => boolean;
|
|
17
|
+
/** Says a node has nothing under it — only needed with `lazy`, where an empty
|
|
18
|
+
* `children` cannot tell "not loaded yet" from "nothing there". */
|
|
19
|
+
leaf?: (node: T) => boolean;
|
|
20
|
+
}
|
|
21
|
+
/** One node and everything about where it sits, kept for the whole tree. */
|
|
22
|
+
export interface TreeEntry<T> {
|
|
23
|
+
node: T;
|
|
24
|
+
key: TreeKey;
|
|
25
|
+
depth: number;
|
|
26
|
+
/** The array this node currently lives in — moving is a splice on it. */
|
|
27
|
+
siblings: T[];
|
|
28
|
+
index: number;
|
|
29
|
+
parent: T | null;
|
|
30
|
+
parentKey: TreeKey | null;
|
|
31
|
+
children: T[];
|
|
32
|
+
}
|
|
33
|
+
/** A node as the screen shows it: visible, in order, at a depth. */
|
|
34
|
+
export interface TreeRow<T> extends TreeEntry<T> {
|
|
35
|
+
expandable: boolean;
|
|
36
|
+
expanded: boolean;
|
|
37
|
+
loading: boolean;
|
|
38
|
+
disabled: boolean;
|
|
39
|
+
/** Matched the filter itself, as opposed to merely leading to a match. */
|
|
40
|
+
matched: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface UseTreeNodesOptions<T> {
|
|
43
|
+
/**
|
|
44
|
+
* The tree. A getter or a plain array is enough for a tree that is only read; a
|
|
45
|
+
* `ref` is what a tree that can be rearranged wants, since a move splices the array
|
|
46
|
+
* the caller is holding.
|
|
47
|
+
*/
|
|
48
|
+
nodes: MaybeRefOrGetter<T[]>;
|
|
49
|
+
accessors: TreeAccessors<T>;
|
|
50
|
+
/** Keys of the open branches. Owned by the caller, so `v-model:expanded` works. */
|
|
51
|
+
expanded: Ref<TreeKey[]>;
|
|
52
|
+
/** Only branches matching this, and the branches leading to them, are shown. */
|
|
53
|
+
filter?: MaybeRefOrGetter<string | undefined>;
|
|
54
|
+
/** Opening a branch closes the others at its level. */
|
|
55
|
+
accordion?: MaybeRefOrGetter<boolean>;
|
|
56
|
+
/** Children arrive when a branch opens. Requires `load`. */
|
|
57
|
+
lazy?: MaybeRefOrGetter<boolean>;
|
|
58
|
+
load?: (node: T) => T[] | Promise<T[]>;
|
|
59
|
+
/** Vetoes a move before it happens. */
|
|
60
|
+
allowDrop?: (drag: T, drop: T, zone: TreeDropZone) => boolean;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The parts of a tree that have nothing to do with how it looks: what is where, what is
|
|
64
|
+
* open, what a filter leaves standing, and what a move does to the arrays underneath.
|
|
65
|
+
*/
|
|
66
|
+
export declare function useTreeNodes<T>(options: UseTreeNodesOptions<T>): {
|
|
67
|
+
rows: import('vue').ComputedRef<TreeRow<T>[]>;
|
|
68
|
+
entries: import('vue').ComputedRef<Map<TreeKey, TreeEntry<T>>>;
|
|
69
|
+
entry: (key: TreeKey) => TreeEntry<T> | undefined;
|
|
70
|
+
keyOf: (node: T, path?: number[]) => TreeKey;
|
|
71
|
+
filtered: import('vue').ComputedRef<{
|
|
72
|
+
keep: Set<TreeKey>;
|
|
73
|
+
matched: Set<TreeKey>;
|
|
74
|
+
} | null>;
|
|
75
|
+
isExpanded: (key: TreeKey) => boolean;
|
|
76
|
+
expand: (key: TreeKey) => Promise<void>;
|
|
77
|
+
collapse: (key: TreeKey) => void;
|
|
78
|
+
toggle: (key: TreeKey) => void | Promise<void>;
|
|
79
|
+
expandAll: () => void;
|
|
80
|
+
collapseAll: () => void;
|
|
81
|
+
reveal: (key: TreeKey) => void;
|
|
82
|
+
openPath: (keys: TreeKey[]) => Promise<boolean>;
|
|
83
|
+
ancestors: (key: TreeKey) => TreeEntry<T>[];
|
|
84
|
+
path: (key: TreeKey) => TreeEntry<T>[];
|
|
85
|
+
descendants: (key: TreeKey) => TreeEntry<T>[];
|
|
86
|
+
isAncestorOf: (key: TreeKey, other: TreeKey) => boolean;
|
|
87
|
+
canDrop: (dragKey: TreeKey, dropKey: TreeKey, zone: TreeDropZone) => boolean;
|
|
88
|
+
move: (dragKey: TreeKey, dropKey: TreeKey, zone: TreeDropZone) => {
|
|
89
|
+
node: T;
|
|
90
|
+
parent: T | null;
|
|
91
|
+
siblings: T[];
|
|
92
|
+
index: number;
|
|
93
|
+
} | null;
|
|
94
|
+
loading: Ref<Set<TreeKey> & Omit<Set<TreeKey>, keyof Set<any>>, Set<TreeKey> | (Set<TreeKey> & Omit<Set<TreeKey>, keyof Set<any>>)>;
|
|
95
|
+
};
|
|
96
|
+
export type UseTreeNodes<T> = ReturnType<typeof useTreeNodes<T>>;
|