@tylertech/forge 3.12.0 → 3.13.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/custom-elements.json +1792 -231
- package/dist/lib.js +12 -12
- package/dist/lib.js.map +4 -4
- package/dist/vscode.css-custom-data.json +2 -1
- package/dist/vscode.html-custom-data.json +85 -1
- package/esm/autocomplete/autocomplete-constants.d.ts +1 -0
- package/esm/autocomplete/autocomplete-constants.js +1 -0
- package/esm/autocomplete/autocomplete-core.d.ts +6 -0
- package/esm/autocomplete/autocomplete-core.js +49 -12
- package/esm/autocomplete/autocomplete.d.ts +7 -0
- package/esm/autocomplete/autocomplete.js +7 -0
- package/esm/calendar/calendar-core.d.ts +2 -0
- package/esm/calendar/calendar-core.js +20 -14
- package/esm/core/testing/test-harness.d.ts +11 -0
- package/esm/core/testing/test-harness.js +14 -0
- package/esm/core/testing/utils.d.ts +7 -0
- package/esm/core/testing/utils.js +14 -0
- package/esm/core/utils/index.d.ts +1 -0
- package/esm/core/utils/index.js +1 -0
- package/esm/core/utils/key-action.d.ts +102 -0
- package/esm/core/utils/key-action.js +109 -0
- package/esm/expansion-panel/expansion-panel-adapter.d.ts +9 -0
- package/esm/expansion-panel/expansion-panel-adapter.js +31 -3
- package/esm/expansion-panel/expansion-panel-constants.d.ts +2 -0
- package/esm/expansion-panel/expansion-panel-constants.js +2 -1
- package/esm/expansion-panel/expansion-panel-core.d.ts +7 -0
- package/esm/expansion-panel/expansion-panel-core.js +30 -0
- package/esm/expansion-panel/expansion-panel.d.ts +11 -3
- package/esm/expansion-panel/expansion-panel.js +16 -3
- package/esm/list-dropdown/list-dropdown-core.js +1 -1
- package/esm/list-dropdown/list-dropdown-utils.js +1 -1
- package/esm/list-dropdown/list-dropdown.js +1 -1
- package/esm/paginator/paginator-adapter.d.ts +1 -0
- package/esm/paginator/paginator-adapter.js +15 -4
- package/esm/split-view/split-view-panel/split-view-panel.js +1 -1
- package/esm/tree/index.d.ts +7 -0
- package/esm/tree/index.js +7 -0
- package/esm/tree/tree/index.d.ts +7 -0
- package/esm/tree/tree/index.js +11 -0
- package/esm/tree/tree/tree-selection-controller.d.ts +104 -0
- package/esm/tree/tree/tree-selection-controller.js +375 -0
- package/esm/tree/tree/tree.d.ts +141 -0
- package/esm/tree/tree/tree.js +488 -0
- package/esm/tree/tree-item/index.d.ts +7 -0
- package/esm/tree/tree-item/index.js +11 -0
- package/esm/tree/tree-item/tree-item.d.ts +112 -0
- package/esm/tree/tree-item/tree-item.js +378 -0
- package/esm/tree/tree-utils.d.ts +154 -0
- package/esm/tree/tree-utils.js +315 -0
- package/package.json +2 -1
- package/sass/core/styles/tokens/tree/tree/_tokens.scss +24 -0
- package/sass/core/styles/tokens/tree/tree-item/_tokens.scss +39 -0
- package/sass/tree/tree/_core.scss +31 -0
- package/sass/tree/tree/_token-utils.scss +30 -0
- package/sass/tree/tree/index.scss +6 -0
- package/sass/tree/tree/tree.scss +44 -0
- package/sass/tree/tree-item/_core.scss +121 -0
- package/sass/tree/tree-item/_token-utils.scss +30 -0
- package/sass/tree/tree-item/index.scss +6 -0
- package/sass/tree/tree-item/tree-item.scss +199 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Tyler Technologies, Inc.
|
|
4
|
+
* License: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { __decorate } from "tslib";
|
|
7
|
+
import { createContext, provide } from '@lit/context';
|
|
8
|
+
import { html, LitElement, unsafeCSS } from 'lit';
|
|
9
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
10
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
11
|
+
import { setDefaultAria } from '../../core/utils/a11y-utils';
|
|
12
|
+
import { toggleState } from '../../core/utils/utils';
|
|
13
|
+
import { KeyActionController } from '../../core/utils/key-action';
|
|
14
|
+
import { closeDescendants, eventPathIncludesTreeItemExpandIcon, eventPathIncludesTreeItemHeader, getFirstChildItem, getLastChildItem, getNextItem, getParentItem, getPreviousItem, getSiblingItems, getTreeItemFromEvent, getTreeItemsInEventPath, getTreeItemTarget, isTreeItem, searchItems } from '../tree-utils';
|
|
15
|
+
import { TreeSelectionController } from './tree-selection-controller';
|
|
16
|
+
const styles = ':host{display:block}.forge-tree{--_tree-background:var(--forge-tree-background, var(--forge-theme-primary-container, #d1d5ed));--_tree-color:var(--forge-tree-color, var(--forge-theme-on-primary-container, #222c62));--_tree-transition-duration:var(--forge-tree-transition-duration, var(--forge-animation-duration-short4, 200ms));--_tree-transition-timing:var(--forge-tree-transition-timing, var(--forge-animation-easing-standard, cubic-bezier(0.2, 0, 0, 1)));--_tree-shape:var(--forge-tree-shape, calc(var(--forge-shape-medium, 4px) * var(--forge-shape-factor, 1)));--_tree-padding:var(--forge-tree-padding, var(--forge-spacing-medium, 16px));--_tree-item-gap:var(--forge-tree-item-gap, var(--forge-spacing-xxxsmall, 2px))}.forge-tree{--_tree-item-check-display:var(--forge-tree-item-check-display, none);--_tree-item-indent-line-display:var(--forge-tree-item-indent-line-display, none);display:flex;flex-direction:column;gap:var(--_tree-item-gap)}.forge-tree.indent-lines{--_tree-item-indent-line-display:var(--forge-tree-item-indent-line-display, block)}.forge-tree.multiple{--_tree-item-check-display:var(--forge-tree-item-check-display, block);--forge-tree-item-selected-background:transparent}.forge-tree .icons{display:none}';
|
|
17
|
+
export const TREE_CONTEXT = createContext('forge-tree');
|
|
18
|
+
/**
|
|
19
|
+
* @tag forge-tree
|
|
20
|
+
*
|
|
21
|
+
* @summary Trees are interactive lists that allow users to navigate through hierarchical data.
|
|
22
|
+
*
|
|
23
|
+
* @dependency forge-tree-item
|
|
24
|
+
*
|
|
25
|
+
* @event {CustomEvent<void>} forge-tree-select-all - Dispatched when the user selects all items.
|
|
26
|
+
*
|
|
27
|
+
* @csspart root - The root tree element.
|
|
28
|
+
*
|
|
29
|
+
* @slot - The default slot for tree items.
|
|
30
|
+
* @slot expand-icon - A custom expand icon to show when an item is closed.
|
|
31
|
+
* @slot collapse-icon - A custom collapse icon to show when an item is open.
|
|
32
|
+
*/
|
|
33
|
+
let TreeComponent = class TreeComponent extends LitElement {
|
|
34
|
+
/**
|
|
35
|
+
* The value of all selected items.
|
|
36
|
+
* @default []
|
|
37
|
+
* @attribute
|
|
38
|
+
*/
|
|
39
|
+
get value() {
|
|
40
|
+
return this._selectionController.value;
|
|
41
|
+
}
|
|
42
|
+
set value(value) {
|
|
43
|
+
this._selectionController.value = value;
|
|
44
|
+
}
|
|
45
|
+
constructor() {
|
|
46
|
+
super();
|
|
47
|
+
/**
|
|
48
|
+
* Whether opening an item closes all other items.
|
|
49
|
+
* @default false
|
|
50
|
+
* @attribute
|
|
51
|
+
*/
|
|
52
|
+
this.accordion = false;
|
|
53
|
+
/**
|
|
54
|
+
* Toggles the rendering of indent lines showing hierarchy.
|
|
55
|
+
* @default false
|
|
56
|
+
* @attribute
|
|
57
|
+
*/
|
|
58
|
+
this.indentLines = false;
|
|
59
|
+
/**
|
|
60
|
+
* How selecting tree items is handled.
|
|
61
|
+
* @default 'single'
|
|
62
|
+
* @attribute
|
|
63
|
+
*/
|
|
64
|
+
this.mode = 'single';
|
|
65
|
+
/**
|
|
66
|
+
* Whether focusing an item also selects it. This takes no effect when in multiple mode.
|
|
67
|
+
* @default false
|
|
68
|
+
* @attribute
|
|
69
|
+
*/
|
|
70
|
+
this.selectionFollowsFocus = false;
|
|
71
|
+
/**
|
|
72
|
+
* Whether selecting items is disabled.
|
|
73
|
+
* @default false
|
|
74
|
+
* @attribute
|
|
75
|
+
*/
|
|
76
|
+
this.disabled = false;
|
|
77
|
+
this._keyActionController = new KeyActionController(this, {
|
|
78
|
+
actions: [
|
|
79
|
+
{ key: ['ArrowUp', 'ArrowDown'], handler: this._handleArrowUpOrDown.bind(this), allowRepeat: true },
|
|
80
|
+
{ key: 'ArrowLeft', handler: this._handleArrowLeft.bind(this), allowRepeat: true },
|
|
81
|
+
{ key: 'ArrowRight', handler: this._handleArrowRight.bind(this), allowRepeat: true },
|
|
82
|
+
{ key: 'Home', handler: this._handleHome.bind(this) },
|
|
83
|
+
{ key: 'End', handler: this._handleEnd.bind(this) },
|
|
84
|
+
{ key: '*', handler: this._handleAsterisk.bind(this) },
|
|
85
|
+
{
|
|
86
|
+
key: [
|
|
87
|
+
{ key: 'a', modifier: 'ctrl' },
|
|
88
|
+
{ key: 'a', modifier: 'meta' }
|
|
89
|
+
],
|
|
90
|
+
handler: this._handleA.bind(this)
|
|
91
|
+
},
|
|
92
|
+
{ key: ['Enter', ' '], handler: this._handleEnterOrSpace.bind(this) }
|
|
93
|
+
],
|
|
94
|
+
searchHandler: this._search.bind(this)
|
|
95
|
+
});
|
|
96
|
+
this._selectionController = new TreeSelectionController(this);
|
|
97
|
+
this._internals = this.attachInternals();
|
|
98
|
+
this._updateContext();
|
|
99
|
+
}
|
|
100
|
+
connectedCallback() {
|
|
101
|
+
super.connectedCallback();
|
|
102
|
+
this.tabIndex = 0;
|
|
103
|
+
setDefaultAria(this, this._internals, { role: 'tree' });
|
|
104
|
+
// Listen for focus events on the host element
|
|
105
|
+
this.addEventListener('focusin', this._handleFocusIn.bind(this));
|
|
106
|
+
this.addEventListener('focusout', this._handleFocusOut.bind(this));
|
|
107
|
+
// Listen for item update events on the host element
|
|
108
|
+
this.addEventListener('forge-tree-item-update', this._handleUpdate.bind(this));
|
|
109
|
+
}
|
|
110
|
+
disconnectedCallback() {
|
|
111
|
+
super.disconnectedCallback();
|
|
112
|
+
this._expandIconObserver?.disconnect();
|
|
113
|
+
this._collapseIconObserver?.disconnect();
|
|
114
|
+
}
|
|
115
|
+
willUpdate(_changedProperties) {
|
|
116
|
+
if (_changedProperties.has('accordion')) {
|
|
117
|
+
if (this.accordion) {
|
|
118
|
+
closeDescendants(this);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (_changedProperties.has('disabled')) {
|
|
122
|
+
this._setDisabled();
|
|
123
|
+
}
|
|
124
|
+
if (_changedProperties.has('disabled') || _changedProperties.has('indentLines') || _changedProperties.has('mode')) {
|
|
125
|
+
this._updateContext();
|
|
126
|
+
}
|
|
127
|
+
if (_changedProperties.has('mode')) {
|
|
128
|
+
this._setDisabled();
|
|
129
|
+
this._setMode();
|
|
130
|
+
this._selectionController.cleanup();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
render() {
|
|
134
|
+
return html `
|
|
135
|
+
<div
|
|
136
|
+
part="root"
|
|
137
|
+
class=${classMap({ 'forge-tree': true, 'indent-lines': this.indentLines, multiple: this.mode === 'multiple' })}
|
|
138
|
+
@click=${this._handleClick}>
|
|
139
|
+
<slot></slot>
|
|
140
|
+
<div class="icons" @slotchange="${this._detectSlottedIcon}">
|
|
141
|
+
<slot name="expand-icon"></slot>
|
|
142
|
+
<slot name="collapse-icon"></slot>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
_updateContext() {
|
|
148
|
+
this._context = {
|
|
149
|
+
collapseIcon: this._collapseIcon,
|
|
150
|
+
disabled: this.disabled,
|
|
151
|
+
expandIcon: this._expandIcon,
|
|
152
|
+
indentLines: this.indentLines,
|
|
153
|
+
mode: this.mode
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Toggle either the selected or open state of a key item when its header is clicked.
|
|
158
|
+
*/
|
|
159
|
+
_handleClick(evt) {
|
|
160
|
+
// Ensure an item's header was clicked
|
|
161
|
+
if (!eventPathIncludesTreeItemHeader(evt)) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Get the item
|
|
165
|
+
const item = getTreeItemTarget(evt);
|
|
166
|
+
if (!item) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// If the item is a leaf node toggle the selected state
|
|
170
|
+
if (item.leaf) {
|
|
171
|
+
// Do nothing if selection is off
|
|
172
|
+
if (this.mode === 'off') {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (evt.shiftKey) {
|
|
176
|
+
this._selectionController.extend(item);
|
|
177
|
+
}
|
|
178
|
+
this._selectionController.toggle(item);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// If in leaf or off mode a click anywhere toggles the open state
|
|
182
|
+
if (this.mode === 'leaf' || this.mode === 'off') {
|
|
183
|
+
this._toggleOpen(item, evt.altKey && item.open);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Otherwise only a click on the expand icon toggles the open state
|
|
187
|
+
if (!item.openDisabled && eventPathIncludesTreeItemExpandIcon(evt)) {
|
|
188
|
+
this._toggleOpen(item, evt.altKey && item.open);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (evt.shiftKey) {
|
|
192
|
+
this._selectionController.extend(item);
|
|
193
|
+
}
|
|
194
|
+
this._selectionController.toggle(item);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Focuses the next or previous visible item.
|
|
198
|
+
*/
|
|
199
|
+
_handleArrowUpOrDown(evt) {
|
|
200
|
+
const target = getTreeItemFromEvent(evt);
|
|
201
|
+
if (!target) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const destinationItem = evt.key === 'ArrowDown' ? getNextItem(target) : getPreviousItem(target);
|
|
205
|
+
if (destinationItem) {
|
|
206
|
+
if (evt.shiftKey && this.mode === 'multiple') {
|
|
207
|
+
this._selectionController.toggle(destinationItem, true);
|
|
208
|
+
}
|
|
209
|
+
this._focusItem(destinationItem);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* If the focused item is open, closes it. Otherwise, focuses the parent item.
|
|
214
|
+
*/
|
|
215
|
+
_handleArrowLeft(evt) {
|
|
216
|
+
const target = getTreeItemFromEvent(evt);
|
|
217
|
+
if (!target) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (!target.open) {
|
|
221
|
+
const parent = getParentItem(target);
|
|
222
|
+
if (parent) {
|
|
223
|
+
this._focusItem(parent);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
this._toggleOpen(target, false, false);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* If the focused item is closed, opens it. Otherwise, focuses the first child item.
|
|
230
|
+
*/
|
|
231
|
+
_handleArrowRight(evt) {
|
|
232
|
+
const target = getTreeItemFromEvent(evt);
|
|
233
|
+
if (!target || target.leaf) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (target.open) {
|
|
237
|
+
const firstChild = getFirstChildItem(target);
|
|
238
|
+
if (firstChild) {
|
|
239
|
+
this._focusItem(firstChild);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
this._toggleOpen(target, false, true);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Focuses the first visible item. If the mode is multiple and the meta key is pressed, selects
|
|
246
|
+
* all previous items.
|
|
247
|
+
*/
|
|
248
|
+
_handleHome(evt) {
|
|
249
|
+
const target = getTreeItemFromEvent(evt);
|
|
250
|
+
if (!target) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Select all previous items if the shift and meta keys are pressed
|
|
254
|
+
if (evt.shiftKey && (evt.metaKey || evt.ctrlKey) && this.mode === 'multiple') {
|
|
255
|
+
// TODO: can this just use the selection controller's extend method?
|
|
256
|
+
let previousItem = getPreviousItem(target);
|
|
257
|
+
while (previousItem) {
|
|
258
|
+
this._selectionController.toggle(previousItem, true);
|
|
259
|
+
previousItem = getPreviousItem(previousItem);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const firstItem = getFirstChildItem(this);
|
|
263
|
+
if (firstItem) {
|
|
264
|
+
this._focusItem(firstItem);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Focuses the last visible item. If the mode is multiple and the meta key is pressed, selects
|
|
269
|
+
* all subsequent items.
|
|
270
|
+
*/
|
|
271
|
+
_handleEnd(evt) {
|
|
272
|
+
const target = getTreeItemFromEvent(evt);
|
|
273
|
+
if (!target) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Select all subsequent items if the shift and meta keys are pressed
|
|
277
|
+
if (evt.shiftKey && (evt.metaKey || evt.ctrlKey) && this.mode === 'multiple') {
|
|
278
|
+
// TODO: can this just use the selection controller's extend method?
|
|
279
|
+
let nextItem = getNextItem(target);
|
|
280
|
+
while (nextItem) {
|
|
281
|
+
this._selectionController.toggle(nextItem, true);
|
|
282
|
+
nextItem = getNextItem(nextItem);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
let lastItem = getLastChildItem(this);
|
|
286
|
+
while (lastItem && lastItem.open) {
|
|
287
|
+
lastItem = getLastChildItem(lastItem);
|
|
288
|
+
}
|
|
289
|
+
if (lastItem) {
|
|
290
|
+
this._focusItem(lastItem);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Opens the focused item and all sibling items.
|
|
295
|
+
*/
|
|
296
|
+
_handleAsterisk(evt) {
|
|
297
|
+
const target = getTreeItemFromEvent(evt);
|
|
298
|
+
if (!target) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
getSiblingItems(target, true).forEach(item => this._toggleOpen(item, false, true));
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* If the mode is multiple, selects all items.
|
|
305
|
+
*/
|
|
306
|
+
_handleA() {
|
|
307
|
+
if (this.mode !== 'multiple') {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
this._selectionController.selectAll();
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Toggles the open or selected state of the focused item.
|
|
314
|
+
*/
|
|
315
|
+
_handleEnterOrSpace(evt) {
|
|
316
|
+
const target = getTreeItemFromEvent(evt);
|
|
317
|
+
if (!target) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (target.leaf) {
|
|
321
|
+
if (this.mode === 'off') {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (evt.shiftKey) {
|
|
325
|
+
this._selectionController.extend(target);
|
|
326
|
+
}
|
|
327
|
+
this._selectionController.toggle(target);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (evt.key === 'Enter') {
|
|
331
|
+
this._toggleOpen(target, evt.altKey && target.open);
|
|
332
|
+
}
|
|
333
|
+
else if (this.mode !== 'off') {
|
|
334
|
+
this._selectionController.toggle(target);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
_handleFocusIn(evt) {
|
|
338
|
+
// Focus either the last focused item or first item when the tree receives focus
|
|
339
|
+
if (evt.target === this) {
|
|
340
|
+
const itemToFocus = this._lastFocusedItem ?? getFirstChildItem(this);
|
|
341
|
+
if (!itemToFocus) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this._focusItem(itemToFocus);
|
|
345
|
+
}
|
|
346
|
+
// If a tree item was focused, set it as the last focused item and add it to the tab order
|
|
347
|
+
const target = evt.target;
|
|
348
|
+
if (target && isTreeItem(target)) {
|
|
349
|
+
if (this._lastFocusedItem) {
|
|
350
|
+
this._lastFocusedItem.tabIndex = -1;
|
|
351
|
+
}
|
|
352
|
+
target.tabIndex = 0;
|
|
353
|
+
this._lastFocusedItem = target;
|
|
354
|
+
// Remove this from the tab order while focus is inside
|
|
355
|
+
this.tabIndex = -1;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
_handleFocusOut(evt) {
|
|
359
|
+
// If focus moved outside the tree, reset the tree's tab index
|
|
360
|
+
const relatedTarget = evt.relatedTarget;
|
|
361
|
+
if (!relatedTarget || !this.contains(relatedTarget)) {
|
|
362
|
+
this.tabIndex = 0;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
_handleUpdate(evt) {
|
|
366
|
+
evt.stopPropagation();
|
|
367
|
+
if (evt.detail.reason === 'opened') {
|
|
368
|
+
// Do nothing if accordion isn't enabled
|
|
369
|
+
if (!this.accordion) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
// Do nothing if the target is missing or closed
|
|
373
|
+
const target = getTreeItemFromEvent(evt);
|
|
374
|
+
if (!target || !target.open) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// If accordion is enabled, close all the items outside the target's path
|
|
378
|
+
const items = getTreeItemsInEventPath(evt);
|
|
379
|
+
closeDescendants(this);
|
|
380
|
+
items.forEach(item => (item.open = true));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
_search(searchString, evt) {
|
|
384
|
+
const target = getTreeItemFromEvent(evt);
|
|
385
|
+
if (!target) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const item = searchItems(target, searchString);
|
|
389
|
+
if (item) {
|
|
390
|
+
this._focusItem(item);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
_toggleOpen(item, flatten = false, force) {
|
|
394
|
+
if (item.openDisabled) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
item.open = force ?? !item.open;
|
|
398
|
+
if (!item.open && flatten) {
|
|
399
|
+
closeDescendants(item);
|
|
400
|
+
}
|
|
401
|
+
// Dispatch an open event from the item
|
|
402
|
+
const event = new CustomEvent(`forge-tree-item-${item.open ? 'open' : 'close'}`, { bubbles: true, composed: true });
|
|
403
|
+
item.dispatchEvent(event);
|
|
404
|
+
if (event.defaultPrevented) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
_focusItem(item) {
|
|
409
|
+
item.focus();
|
|
410
|
+
if (this.mode !== 'multiple' && this.selectionFollowsFocus) {
|
|
411
|
+
this._selectionController.toggle(item, true);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
_detectSlottedIcon(evt) {
|
|
415
|
+
// Get the slot and clone the first assigned element
|
|
416
|
+
const slot = evt.target;
|
|
417
|
+
const assignedElement = slot.assignedElements()[0];
|
|
418
|
+
// If the assigned element is removed, remove the icon and update the context
|
|
419
|
+
if (!assignedElement) {
|
|
420
|
+
if (slot.name === 'expand-icon') {
|
|
421
|
+
this._expandIcon = undefined;
|
|
422
|
+
this._expandIconObserver?.disconnect();
|
|
423
|
+
}
|
|
424
|
+
else if (slot.name === 'collapse-icon') {
|
|
425
|
+
this._collapseIcon = undefined;
|
|
426
|
+
this._collapseIconObserver?.disconnect();
|
|
427
|
+
}
|
|
428
|
+
this._updateContext();
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (slot.name === 'expand-icon') {
|
|
432
|
+
this._observeIcon(assignedElement, 'expand');
|
|
433
|
+
}
|
|
434
|
+
else if (slot.name === 'collapse-icon') {
|
|
435
|
+
this._observeIcon(assignedElement, 'collapse');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
_observeIcon(assignedElement, type) {
|
|
439
|
+
const key = `_${type}IconObserver`;
|
|
440
|
+
this[key] ?? (this[key] = new MutationObserver(() => this._updateIcon(assignedElement, type)));
|
|
441
|
+
this[key].observe(assignedElement, { attributes: true, subtree: true, childList: true, characterData: true });
|
|
442
|
+
this._updateIcon(assignedElement, type);
|
|
443
|
+
}
|
|
444
|
+
_updateIcon(assignedElement, type) {
|
|
445
|
+
// Clone the element and remove any ids
|
|
446
|
+
const clone = assignedElement?.cloneNode(true);
|
|
447
|
+
[clone, ...clone.querySelectorAll('[id]')].forEach(el => el.removeAttribute('id'));
|
|
448
|
+
// Set the slot, save the clone to the appropriate property, and update the context
|
|
449
|
+
clone.slot = `context-${type}-icon`;
|
|
450
|
+
this[`_${type}Icon`] = clone;
|
|
451
|
+
this._updateContext();
|
|
452
|
+
}
|
|
453
|
+
_setDisabled() {
|
|
454
|
+
setDefaultAria(this, this._internals, { ariaDisabled: this.disabled ? 'true' : 'false' });
|
|
455
|
+
toggleState(this._internals, 'disabled', this.disabled);
|
|
456
|
+
}
|
|
457
|
+
_setMode() {
|
|
458
|
+
setDefaultAria(this, this._internals, {
|
|
459
|
+
ariaMultiSelectable: this.mode === 'multiple' ? 'true' : 'false' // || this.mode === 'multiple-discrete'
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
TreeComponent.styles = unsafeCSS(styles);
|
|
464
|
+
__decorate([
|
|
465
|
+
property({ type: Boolean })
|
|
466
|
+
], TreeComponent.prototype, "accordion", void 0);
|
|
467
|
+
__decorate([
|
|
468
|
+
property({ type: Boolean, attribute: 'indent-lines' })
|
|
469
|
+
], TreeComponent.prototype, "indentLines", void 0);
|
|
470
|
+
__decorate([
|
|
471
|
+
property({ type: String })
|
|
472
|
+
], TreeComponent.prototype, "mode", void 0);
|
|
473
|
+
__decorate([
|
|
474
|
+
property({ type: Boolean, attribute: 'selection-follows-focus' })
|
|
475
|
+
], TreeComponent.prototype, "selectionFollowsFocus", void 0);
|
|
476
|
+
__decorate([
|
|
477
|
+
property({ type: Boolean })
|
|
478
|
+
], TreeComponent.prototype, "disabled", void 0);
|
|
479
|
+
__decorate([
|
|
480
|
+
property({ type: Array })
|
|
481
|
+
], TreeComponent.prototype, "value", null);
|
|
482
|
+
__decorate([
|
|
483
|
+
provide({ context: TREE_CONTEXT })
|
|
484
|
+
], TreeComponent.prototype, "_context", void 0);
|
|
485
|
+
TreeComponent = __decorate([
|
|
486
|
+
customElement('forge-tree')
|
|
487
|
+
], TreeComponent);
|
|
488
|
+
export { TreeComponent };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Tyler Technologies, Inc.
|
|
4
|
+
* License: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { tryDefine } from '@tylertech/forge-core';
|
|
7
|
+
import { TreeItemComponent } from './tree-item';
|
|
8
|
+
export * from './tree-item';
|
|
9
|
+
export function defineTreeItemComponent() {
|
|
10
|
+
tryDefine('forge-tree-item', TreeItemComponent);
|
|
11
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Tyler Technologies, Inc.
|
|
4
|
+
* License: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { LitElement, PropertyValues, TemplateResult } from 'lit';
|
|
7
|
+
import { indeterminate } from '../tree-utils';
|
|
8
|
+
import '../../icon';
|
|
9
|
+
import '../../open-icon';
|
|
10
|
+
export type TreeItemCheckboxIcon = 'check_box_outline_blank' | 'check_box' | 'indeterminate_check_box';
|
|
11
|
+
export type TreeItemUpdateReason = 'added' | 'deselected' | 'opened' | 'removed' | 'selected';
|
|
12
|
+
/**
|
|
13
|
+
* @tag forge-tree-item
|
|
14
|
+
*
|
|
15
|
+
* @dependency forge-circular-progress
|
|
16
|
+
* @dependency forge-icon
|
|
17
|
+
* @dependency forge-open-icon
|
|
18
|
+
*
|
|
19
|
+
* @event {CustomEvent<unknown>} forge-tree-item-select - Dispatched when the user selects a tree item.
|
|
20
|
+
* @event {CustomEvent<void>} forge-tree-item-open - Dispatched when the user opens a tree item.
|
|
21
|
+
* @event {CustomEvent<void>} forge-tree-item-close - Dispatched when the user closes a tree item.
|
|
22
|
+
*/
|
|
23
|
+
export declare class TreeItemComponent extends LitElement {
|
|
24
|
+
static styles: import("lit").CSSResult;
|
|
25
|
+
/**
|
|
26
|
+
* The value of the tree item.
|
|
27
|
+
* @default undefined
|
|
28
|
+
* @attribute
|
|
29
|
+
*/
|
|
30
|
+
value: unknown;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the tree item is selected.
|
|
33
|
+
* @default false
|
|
34
|
+
* @attribute
|
|
35
|
+
*/
|
|
36
|
+
selected: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the tree item is expanded.
|
|
39
|
+
* @default false
|
|
40
|
+
* @attribute
|
|
41
|
+
*/
|
|
42
|
+
open: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the tree item supports lazy loading.
|
|
45
|
+
* @default false
|
|
46
|
+
* @attribute
|
|
47
|
+
*/
|
|
48
|
+
lazy: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the tree item is disabled.
|
|
51
|
+
* @default false
|
|
52
|
+
* @attribute
|
|
53
|
+
*/
|
|
54
|
+
disabled: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Whether opening the tree item is disabled.
|
|
57
|
+
* @default false
|
|
58
|
+
* @attribute
|
|
59
|
+
*/
|
|
60
|
+
openDisabled: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the selected value is indeterminate. This is a symbol property to avoid being set from
|
|
63
|
+
* outside the library.
|
|
64
|
+
* @ignore
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
[indeterminate]: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The depth of the tree item within the tree's hierarchy.
|
|
70
|
+
*/
|
|
71
|
+
get level(): number;
|
|
72
|
+
/**
|
|
73
|
+
* Whether the tree item has no child items.
|
|
74
|
+
*/
|
|
75
|
+
get leaf(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Whether the selected value is indeterminate.
|
|
78
|
+
*/
|
|
79
|
+
get indeterminate(): boolean;
|
|
80
|
+
private _treeContext?;
|
|
81
|
+
private _level;
|
|
82
|
+
private _leaf;
|
|
83
|
+
private _loading;
|
|
84
|
+
private _hasSlottedExpandIcon;
|
|
85
|
+
private _hasSlottedCollapseIcon;
|
|
86
|
+
private _checkboxIcon;
|
|
87
|
+
private _children;
|
|
88
|
+
private _internals;
|
|
89
|
+
private _hasBeenSelected;
|
|
90
|
+
constructor();
|
|
91
|
+
connectedCallback(): void;
|
|
92
|
+
disconnectedCallback(): void;
|
|
93
|
+
willUpdate(changedProperties: PropertyValues<this>): void;
|
|
94
|
+
render(): TemplateResult;
|
|
95
|
+
private _expandIconTemplate;
|
|
96
|
+
private _slotInParent;
|
|
97
|
+
private _detectChildren;
|
|
98
|
+
private _detectSlottedExpandOrCollapseIcon;
|
|
99
|
+
private _setIconFromContext;
|
|
100
|
+
private _setDisabled;
|
|
101
|
+
private _setLoading;
|
|
102
|
+
private _setMode;
|
|
103
|
+
private _setOpen;
|
|
104
|
+
private _setOpenDisabled;
|
|
105
|
+
private _setSelected;
|
|
106
|
+
private _dispatchUpdate;
|
|
107
|
+
}
|
|
108
|
+
declare global {
|
|
109
|
+
interface HTMLElementTagNameMap {
|
|
110
|
+
'forge-tree-item': TreeItemComponent;
|
|
111
|
+
}
|
|
112
|
+
}
|