@fluid-topics/ft-tree-selector 1.1.103
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 +30 -0
- package/build/ft-tree-selector.d.ts +48 -0
- package/build/ft-tree-selector.js +279 -0
- package/build/ft-tree-selector.light.js +1116 -0
- package/build/ft-tree-selector.min.js +1344 -0
- package/build/ft-tree-selector.properties.d.ts +3 -0
- package/build/ft-tree-selector.properties.js +1 -0
- package/build/ft-tree-selector.styles.d.ts +11 -0
- package/build/ft-tree-selector.styles.js +138 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +6 -0
- package/build/model/FtTree.d.ts +19 -0
- package/build/model/FtTree.js +1 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
A hierarchical selector for tree
|
|
2
|
+
## Install
|
|
3
|
+
|
|
4
|
+
```shell
|
|
5
|
+
npm install @fluid-topics/ft-tree-selector
|
|
6
|
+
yarn add @fluid-topics/ft-tree-selector
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {html} from "lit"
|
|
13
|
+
import "@fluid-topics/ft-tree-selector"
|
|
14
|
+
|
|
15
|
+
function render() {
|
|
16
|
+
return html` <ft-tree-selector .data=${
|
|
17
|
+
rootNodes = [{
|
|
18
|
+
value: "parent",
|
|
19
|
+
label: "label parent",
|
|
20
|
+
children: [
|
|
21
|
+
{value: "1", label: "label 1", children: [], selected: true},
|
|
22
|
+
{value: "2", label: "label 2", children: [], selected: true}
|
|
23
|
+
],
|
|
24
|
+
selected: false,
|
|
25
|
+
indeterminate: true
|
|
26
|
+
}
|
|
27
|
+
]}></ft-tree-selector> `
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PropertyValues } from "lit";
|
|
2
|
+
import { ElementDefinitionsMap, FtLitElement } from "@fluid-topics/ft-wc-utils";
|
|
3
|
+
import { FtTree, FtTreeChangedDetail, FtTreeExpandedChangeDetail, FtTreeNode } from "./model/FtTree";
|
|
4
|
+
import { FtButton } from "@fluid-topics/ft-button";
|
|
5
|
+
export declare class TreeNodeChangeEvent extends CustomEvent<FtTreeChangedDetail> {
|
|
6
|
+
constructor(values: FtTreeChangedDetail);
|
|
7
|
+
}
|
|
8
|
+
export declare class TreeNodeExpandedChangeEvent extends CustomEvent<FtTreeExpandedChangeDetail> {
|
|
9
|
+
constructor(values: FtTreeExpandedChangeDetail);
|
|
10
|
+
}
|
|
11
|
+
export declare class TreeNodeAllNodeCollapsedEvent extends CustomEvent<void> {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
export declare class TreeNodeClearAll extends CustomEvent<void> {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
export declare class FtTreeSelector extends FtLitElement {
|
|
18
|
+
static elementDefinitions: ElementDefinitionsMap;
|
|
19
|
+
static styles: import("lit").CSSResult;
|
|
20
|
+
data: FtTree;
|
|
21
|
+
userOpenedNodes: Set<string>;
|
|
22
|
+
userClosedNodes: Set<string>;
|
|
23
|
+
currentLevelExpandButtons?: FtButton[];
|
|
24
|
+
currentExpandButtonFocusedIndex: number;
|
|
25
|
+
isTreeSelectorCollapsed?: boolean;
|
|
26
|
+
label: string;
|
|
27
|
+
expandLabel: string;
|
|
28
|
+
collapseLabel: string;
|
|
29
|
+
expandParametrizedLabel: string;
|
|
30
|
+
collapseParametrizedLabel: string;
|
|
31
|
+
expandAllLabel: string;
|
|
32
|
+
collapseAllLabel: string;
|
|
33
|
+
clearLabel: string;
|
|
34
|
+
protected update(changedProperties: PropertyValues): void;
|
|
35
|
+
protected render(): import("lit").TemplateResult<1>;
|
|
36
|
+
protected renderNode(node: FtTreeNode): unknown;
|
|
37
|
+
private renderOpenCloseButton;
|
|
38
|
+
private userChangeNodeExpandStatus;
|
|
39
|
+
private sendAllNodeCollapseEventIfNeeded;
|
|
40
|
+
private onKeydown;
|
|
41
|
+
private focusPreviousButton;
|
|
42
|
+
private focusNextButton;
|
|
43
|
+
private isNodeExpanded;
|
|
44
|
+
expandAll(): void;
|
|
45
|
+
private getAllClosedNodes;
|
|
46
|
+
collapseAll(): void;
|
|
47
|
+
getAriaControlId(node: FtTreeNode): string;
|
|
48
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html, nothing } from "lit";
|
|
8
|
+
import { FtLitElement, hasChanged, ParametrizedLabelResolver, } from "@fluid-topics/ft-wc-utils";
|
|
9
|
+
import { styles } from "./ft-tree-selector.styles";
|
|
10
|
+
import { FtCheckbox } from "@fluid-topics/ft-checkbox";
|
|
11
|
+
import { FtTypography } from "@fluid-topics/ft-typography";
|
|
12
|
+
import { repeat } from "lit/directives/repeat.js";
|
|
13
|
+
import { property, queryAll, state } from "lit/decorators.js";
|
|
14
|
+
import { FtButton } from "@fluid-topics/ft-button";
|
|
15
|
+
export class TreeNodeChangeEvent extends CustomEvent {
|
|
16
|
+
constructor(values) {
|
|
17
|
+
super("treenode-change", { detail: values, bubbles: true, composed: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class TreeNodeExpandedChangeEvent extends CustomEvent {
|
|
21
|
+
constructor(values) {
|
|
22
|
+
super("treenode-expanded-change", { detail: values, bubbles: true, composed: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class TreeNodeAllNodeCollapsedEvent extends CustomEvent {
|
|
26
|
+
constructor() {
|
|
27
|
+
super("treenode-all-node-collapsed", { bubbles: true, composed: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class TreeNodeClearAll extends CustomEvent {
|
|
31
|
+
constructor() {
|
|
32
|
+
super("treenode-clear-all", { bubbles: true, composed: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class FtTreeSelector extends FtLitElement {
|
|
36
|
+
constructor() {
|
|
37
|
+
super(...arguments);
|
|
38
|
+
this.userOpenedNodes = new Set();
|
|
39
|
+
this.userClosedNodes = new Set();
|
|
40
|
+
this.currentExpandButtonFocusedIndex = 0;
|
|
41
|
+
this.label = "";
|
|
42
|
+
this.expandLabel = "Expand";
|
|
43
|
+
this.collapseLabel = "Collapse";
|
|
44
|
+
this.expandParametrizedLabel = "Expand {0}";
|
|
45
|
+
this.collapseParametrizedLabel = "Collapse {0}";
|
|
46
|
+
this.expandAllLabel = "Expand all";
|
|
47
|
+
this.collapseAllLabel = "Collapse all";
|
|
48
|
+
this.clearLabel = "Clear";
|
|
49
|
+
}
|
|
50
|
+
update(changedProperties) {
|
|
51
|
+
super.update(changedProperties);
|
|
52
|
+
if (this.data && this.isTreeSelectorCollapsed == undefined) {
|
|
53
|
+
this.isTreeSelectorCollapsed = this.data.rootNodes.every(entry => !this.isNodeExpanded(entry));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
render() {
|
|
57
|
+
var _a;
|
|
58
|
+
return html `
|
|
59
|
+
<div class="ft-tree-selector">
|
|
60
|
+
<div part="header">
|
|
61
|
+
<ft-typography variant="overline" part="label">
|
|
62
|
+
${this.label}
|
|
63
|
+
</ft-typography>
|
|
64
|
+
<ft-button dense
|
|
65
|
+
tooltipposition="bottom"
|
|
66
|
+
part="expand-button"
|
|
67
|
+
icon=${this.isTreeSelectorCollapsed ? "ICON_EXPAND" : "ICON_COLLAPSE"}
|
|
68
|
+
label="${this.isTreeSelectorCollapsed
|
|
69
|
+
? this.expandAllLabel
|
|
70
|
+
: this.collapseAllLabel}"
|
|
71
|
+
aria-expanded="${this.isTreeSelectorCollapsed}"
|
|
72
|
+
@click="${this.isTreeSelectorCollapsed ? () => {
|
|
73
|
+
this.expandAll();
|
|
74
|
+
this.isTreeSelectorCollapsed = false;
|
|
75
|
+
} : () => {
|
|
76
|
+
this.collapseAll();
|
|
77
|
+
this.isTreeSelectorCollapsed = true;
|
|
78
|
+
}}">
|
|
79
|
+
</ft-button>
|
|
80
|
+
|
|
81
|
+
${((_a = this.data) === null || _a === void 0 ? void 0 : _a.rootNodes.some(node => node.selected || node.indeterminate)) ?
|
|
82
|
+
html `
|
|
83
|
+
<ft-button icon="close" dense @click=${() => this.dispatchEvent(new TreeNodeClearAll())} part="clear-button">
|
|
84
|
+
${this.clearLabel}
|
|
85
|
+
</ft-button>
|
|
86
|
+
` : nothing}
|
|
87
|
+
</div>
|
|
88
|
+
${repeat(this.data.rootNodes, (node) => node.value, (node) => {
|
|
89
|
+
return this.renderNode(node);
|
|
90
|
+
})}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
renderNode(node) {
|
|
96
|
+
const isNodeExpanded = this.isNodeExpanded(node);
|
|
97
|
+
return html `
|
|
98
|
+
<div part="level" class="${node.children.length != 0 ? "expandable" : "leaf"}"
|
|
99
|
+
data-value="${node.value}">
|
|
100
|
+
<div part="actions">
|
|
101
|
+
${node.children.length != 0 ? this.renderOpenCloseButton(node) : nothing}
|
|
102
|
+
<ft-checkbox part="checkbox" name="${node.label}" ?checked="${node.selected}"
|
|
103
|
+
?indeterminate="${node.indeterminate}"
|
|
104
|
+
@change=${(e) => this.dispatchEvent(new TreeNodeChangeEvent({
|
|
105
|
+
value: node.value,
|
|
106
|
+
selected: e.detail
|
|
107
|
+
}))}>
|
|
108
|
+
${node.label}
|
|
109
|
+
</ft-checkbox>
|
|
110
|
+
</div>
|
|
111
|
+
${node.children.length != 0 && this.isNodeExpanded(node)
|
|
112
|
+
? html `
|
|
113
|
+
<div part="children"
|
|
114
|
+
class="${isNodeExpanded ? "expanded" : "collapsed"}"
|
|
115
|
+
id=${this.getAriaControlId(node)}>
|
|
116
|
+
${isNodeExpanded
|
|
117
|
+
? html `${repeat(node.children, (c) => c.value, (c) => this.renderNode(c))}`
|
|
118
|
+
: nothing}
|
|
119
|
+
</div>`
|
|
120
|
+
: nothing}
|
|
121
|
+
</div>
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
renderOpenCloseButton(node) {
|
|
125
|
+
let isNodeExpanded = this.isNodeExpanded(node);
|
|
126
|
+
return html `
|
|
127
|
+
<ft-button dense part="expand" tooltipposition="bottom"
|
|
128
|
+
icon=${isNodeExpanded ? "TRIANGLE_BOTTOM" : "TRIANGLE_RIGHT"}
|
|
129
|
+
label="${isNodeExpanded ? this.collapseLabel : this.expandLabel}"
|
|
130
|
+
aria-label="${ParametrizedLabelResolver.replaceParameters(isNodeExpanded
|
|
131
|
+
? this.collapseParametrizedLabel
|
|
132
|
+
: this.expandParametrizedLabel, node.label)}"
|
|
133
|
+
aria-expanded="${isNodeExpanded}"
|
|
134
|
+
aria-control=${this.getAriaControlId(node)}
|
|
135
|
+
@keydown="${(e) => this.onKeydown(e, node)}"
|
|
136
|
+
@click="${() => this.userChangeNodeExpandStatus(!isNodeExpanded, node.value)}">
|
|
137
|
+
</ft-button>
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
userChangeNodeExpandStatus(expanded, currentNodeValue) {
|
|
141
|
+
if (expanded) {
|
|
142
|
+
if (this.userClosedNodes.has(currentNodeValue)) {
|
|
143
|
+
this.userClosedNodes.delete(currentNodeValue);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
this.userOpenedNodes.add(currentNodeValue);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
if (this.userOpenedNodes.has(currentNodeValue)) {
|
|
151
|
+
this.userOpenedNodes.delete(currentNodeValue);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.userClosedNodes.add(currentNodeValue);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
this.dispatchEvent(new TreeNodeExpandedChangeEvent({
|
|
158
|
+
value: currentNodeValue,
|
|
159
|
+
expanded: expanded
|
|
160
|
+
}));
|
|
161
|
+
this.sendAllNodeCollapseEventIfNeeded();
|
|
162
|
+
this.requestUpdate();
|
|
163
|
+
}
|
|
164
|
+
sendAllNodeCollapseEventIfNeeded() {
|
|
165
|
+
if (this.data.rootNodes.every(node => !this.isNodeExpanded(node))) {
|
|
166
|
+
this.dispatchEvent(new TreeNodeAllNodeCollapsedEvent());
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
onKeydown(event, node) {
|
|
170
|
+
let needToStopPropagation = false;
|
|
171
|
+
switch (event.key) {
|
|
172
|
+
case "ArrowDown":
|
|
173
|
+
this.focusNextButton(event.target);
|
|
174
|
+
needToStopPropagation = true;
|
|
175
|
+
break;
|
|
176
|
+
case "ArrowUp":
|
|
177
|
+
this.focusPreviousButton(event.target);
|
|
178
|
+
needToStopPropagation = true;
|
|
179
|
+
break;
|
|
180
|
+
case "ArrowRight":
|
|
181
|
+
if (!this.isNodeExpanded(node)) {
|
|
182
|
+
this.userChangeNodeExpandStatus(true, node.value);
|
|
183
|
+
}
|
|
184
|
+
needToStopPropagation = true;
|
|
185
|
+
break;
|
|
186
|
+
case "ArrowLeft":
|
|
187
|
+
if (this.isNodeExpanded(node)) {
|
|
188
|
+
this.userChangeNodeExpandStatus(false, node.value);
|
|
189
|
+
}
|
|
190
|
+
needToStopPropagation = true;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (needToStopPropagation) {
|
|
194
|
+
event.stopPropagation();
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
focusPreviousButton(currentButton) {
|
|
199
|
+
let currentIndex = this.currentLevelExpandButtons.indexOf(currentButton);
|
|
200
|
+
this.currentLevelExpandButtons[currentIndex == 0 ? this.currentLevelExpandButtons.length - 1 : currentIndex - 1].focus();
|
|
201
|
+
}
|
|
202
|
+
focusNextButton(currentButton) {
|
|
203
|
+
let ftButtons = [...this.currentLevelExpandButtons];
|
|
204
|
+
let currentIndex = ftButtons.indexOf(currentButton);
|
|
205
|
+
ftButtons[currentIndex == ftButtons.length - 1 ? 0 : currentIndex + 1].focus();
|
|
206
|
+
}
|
|
207
|
+
isNodeExpanded(node) {
|
|
208
|
+
return !this.userClosedNodes.has(node.value) && (node.expanded || this.userOpenedNodes.has(node.value));
|
|
209
|
+
}
|
|
210
|
+
expandAll() {
|
|
211
|
+
this.userClosedNodes = new Set();
|
|
212
|
+
this.userOpenedNodes = new Set(this.data.rootNodes.flatMap((n) => this.getAllClosedNodes(n)));
|
|
213
|
+
}
|
|
214
|
+
getAllClosedNodes(node) {
|
|
215
|
+
if (node.children.length !== 0) {
|
|
216
|
+
return [...(!node.expanded ? [node.value] : []), ...node.children.flatMap((c) => this.getAllClosedNodes(c))];
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
collapseAll() {
|
|
223
|
+
this.userOpenedNodes = new Set();
|
|
224
|
+
this.userClosedNodes = new Set(this.data.rootNodes.filter((n) => n.expanded).map(n => n.value));
|
|
225
|
+
this.dispatchEvent(new TreeNodeAllNodeCollapsedEvent());
|
|
226
|
+
}
|
|
227
|
+
getAriaControlId(node) {
|
|
228
|
+
return node.value.replace(" ", "").replace("|", "-") + "-children";
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
FtTreeSelector.elementDefinitions = {
|
|
232
|
+
"ft-checkbox": FtCheckbox,
|
|
233
|
+
"ft-typography": FtTypography,
|
|
234
|
+
"ft-button": FtButton
|
|
235
|
+
};
|
|
236
|
+
FtTreeSelector.styles = styles;
|
|
237
|
+
__decorate([
|
|
238
|
+
property()
|
|
239
|
+
], FtTreeSelector.prototype, "data", void 0);
|
|
240
|
+
__decorate([
|
|
241
|
+
state({ hasChanged })
|
|
242
|
+
], FtTreeSelector.prototype, "userOpenedNodes", void 0);
|
|
243
|
+
__decorate([
|
|
244
|
+
state({ hasChanged })
|
|
245
|
+
], FtTreeSelector.prototype, "userClosedNodes", void 0);
|
|
246
|
+
__decorate([
|
|
247
|
+
queryAll("[part='expand']")
|
|
248
|
+
], FtTreeSelector.prototype, "currentLevelExpandButtons", void 0);
|
|
249
|
+
__decorate([
|
|
250
|
+
state()
|
|
251
|
+
], FtTreeSelector.prototype, "currentExpandButtonFocusedIndex", void 0);
|
|
252
|
+
__decorate([
|
|
253
|
+
state()
|
|
254
|
+
], FtTreeSelector.prototype, "isTreeSelectorCollapsed", void 0);
|
|
255
|
+
__decorate([
|
|
256
|
+
property()
|
|
257
|
+
], FtTreeSelector.prototype, "label", void 0);
|
|
258
|
+
__decorate([
|
|
259
|
+
property()
|
|
260
|
+
], FtTreeSelector.prototype, "expandLabel", void 0);
|
|
261
|
+
__decorate([
|
|
262
|
+
property()
|
|
263
|
+
], FtTreeSelector.prototype, "collapseLabel", void 0);
|
|
264
|
+
__decorate([
|
|
265
|
+
property()
|
|
266
|
+
], FtTreeSelector.prototype, "expandParametrizedLabel", void 0);
|
|
267
|
+
__decorate([
|
|
268
|
+
property()
|
|
269
|
+
], FtTreeSelector.prototype, "collapseParametrizedLabel", void 0);
|
|
270
|
+
__decorate([
|
|
271
|
+
property()
|
|
272
|
+
], FtTreeSelector.prototype, "expandAllLabel", void 0);
|
|
273
|
+
__decorate([
|
|
274
|
+
property()
|
|
275
|
+
], FtTreeSelector.prototype, "collapseAllLabel", void 0);
|
|
276
|
+
__decorate([
|
|
277
|
+
property()
|
|
278
|
+
], FtTreeSelector.prototype, "clearLabel", void 0);
|
|
279
|
+
export { FtTreeSelector };
|