@tylertech/forge 3.12.1 → 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.
Files changed (53) hide show
  1. package/custom-elements.json +1788 -231
  2. package/dist/lib.js +12 -12
  3. package/dist/lib.js.map +4 -4
  4. package/dist/vscode.css-custom-data.json +2 -1
  5. package/dist/vscode.html-custom-data.json +85 -1
  6. package/esm/autocomplete/autocomplete-constants.d.ts +1 -0
  7. package/esm/autocomplete/autocomplete-constants.js +1 -0
  8. package/esm/autocomplete/autocomplete-core.d.ts +6 -0
  9. package/esm/autocomplete/autocomplete-core.js +49 -12
  10. package/esm/autocomplete/autocomplete.d.ts +7 -0
  11. package/esm/autocomplete/autocomplete.js +7 -0
  12. package/esm/calendar/calendar-core.d.ts +2 -0
  13. package/esm/calendar/calendar-core.js +20 -14
  14. package/esm/core/utils/index.d.ts +1 -0
  15. package/esm/core/utils/index.js +1 -0
  16. package/esm/core/utils/key-action.d.ts +102 -0
  17. package/esm/core/utils/key-action.js +109 -0
  18. package/esm/expansion-panel/expansion-panel-adapter.d.ts +9 -0
  19. package/esm/expansion-panel/expansion-panel-adapter.js +31 -3
  20. package/esm/expansion-panel/expansion-panel-constants.d.ts +2 -0
  21. package/esm/expansion-panel/expansion-panel-constants.js +2 -1
  22. package/esm/expansion-panel/expansion-panel-core.d.ts +7 -0
  23. package/esm/expansion-panel/expansion-panel-core.js +30 -0
  24. package/esm/expansion-panel/expansion-panel.d.ts +11 -3
  25. package/esm/expansion-panel/expansion-panel.js +16 -3
  26. package/esm/paginator/paginator-adapter.d.ts +1 -0
  27. package/esm/paginator/paginator-adapter.js +15 -4
  28. package/esm/split-view/split-view-panel/split-view-panel.js +1 -1
  29. package/esm/tree/index.d.ts +7 -0
  30. package/esm/tree/index.js +7 -0
  31. package/esm/tree/tree/index.d.ts +7 -0
  32. package/esm/tree/tree/index.js +11 -0
  33. package/esm/tree/tree/tree-selection-controller.d.ts +104 -0
  34. package/esm/tree/tree/tree-selection-controller.js +375 -0
  35. package/esm/tree/tree/tree.d.ts +141 -0
  36. package/esm/tree/tree/tree.js +488 -0
  37. package/esm/tree/tree-item/index.d.ts +7 -0
  38. package/esm/tree/tree-item/index.js +11 -0
  39. package/esm/tree/tree-item/tree-item.d.ts +112 -0
  40. package/esm/tree/tree-item/tree-item.js +378 -0
  41. package/esm/tree/tree-utils.d.ts +154 -0
  42. package/esm/tree/tree-utils.js +315 -0
  43. package/package.json +2 -1
  44. package/sass/core/styles/tokens/tree/tree/_tokens.scss +24 -0
  45. package/sass/core/styles/tokens/tree/tree-item/_tokens.scss +39 -0
  46. package/sass/tree/tree/_core.scss +31 -0
  47. package/sass/tree/tree/_token-utils.scss +30 -0
  48. package/sass/tree/tree/index.scss +6 -0
  49. package/sass/tree/tree/tree.scss +44 -0
  50. package/sass/tree/tree-item/_core.scss +121 -0
  51. package/sass/tree/tree-item/_token-utils.scss +30 -0
  52. package/sass/tree/tree-item/index.scss +6 -0
  53. package/sass/tree/tree-item/tree-item.scss +199 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * @license
3
+ * Copyright Tyler Technologies, Inc.
4
+ * License: Apache-2.0
5
+ */
6
+ import { getChildItems, getFirstChildItem, getNextItem, getParentItem, getPreviousItem, getTreeItemFromEvent, indeterminate, isIndeterminate } from '../tree-utils';
7
+ export class TreeSelectionController {
8
+ /**
9
+ * An array containing the values of all selected tree items.
10
+ */
11
+ get value() {
12
+ return this.items.filter(item => !item.indeterminate).map(item => item.value);
13
+ }
14
+ set value(value) {
15
+ this.items = [];
16
+ const allItems = getChildItems(this.host, true);
17
+ // If multiple selections are not allowed, select the first item with a matching value
18
+ if (this.host.mode !== 'multiple') {
19
+ const singleItem = allItems.find(item => item.value === value[0]);
20
+ if (singleItem) {
21
+ this._selectItem(singleItem, true);
22
+ this.items.push(singleItem);
23
+ }
24
+ this._clearIndeterminate(allItems);
25
+ return;
26
+ }
27
+ // Keep track of changes for more efficient updates to ancestor items
28
+ const changedItems = [];
29
+ // Select the items with matching values
30
+ allItems.forEach(item => {
31
+ const willSelect = value.includes(item.value);
32
+ if (willSelect === item.selected) {
33
+ return;
34
+ }
35
+ this._selectItem(item, willSelect);
36
+ if (item.selected) {
37
+ this.items.push(item);
38
+ }
39
+ changedItems.push(item);
40
+ });
41
+ // Update all selected item descendants
42
+ this.items.forEach(item => this._updateDescendentSelections(item));
43
+ // Update all changed item ancestors
44
+ changedItems.forEach(item => this._updateAncestorSelections(item));
45
+ }
46
+ constructor(host) {
47
+ /**
48
+ * An array containing all selected tree items.
49
+ */
50
+ this.items = [];
51
+ // A set of items that are currently being selected or deselected to distinguish between
52
+ // user-initiated and programmatic toggles and prevent user events from being handled twice
53
+ this._itemsBeingToggled = new WeakSet();
54
+ this._updateListener = (evt) => this._handleUpdateEvent(evt);
55
+ this.host = host;
56
+ host.addController(this);
57
+ }
58
+ hostConnected() {
59
+ this.host.addEventListener('forge-tree-item-update', this._updateListener);
60
+ }
61
+ hostDisconnected() {
62
+ this.host.removeEventListener('forge-tree-item-update', this._updateListener);
63
+ }
64
+ /**
65
+ * Deselects items that are not allowed for a given selection mode
66
+ */
67
+ cleanup() {
68
+ if (!this.items.length) {
69
+ return;
70
+ }
71
+ let changedItems = [];
72
+ switch (this.host.mode) {
73
+ case 'single': {
74
+ // The last selected item is the only one that should remain
75
+ if (this.items.length === 1) {
76
+ return;
77
+ }
78
+ const singleItem = this.items.splice(-1, 1);
79
+ changedItems = [...this.items];
80
+ this.items.forEach(item => this._selectItem(item, false));
81
+ this.items = singleItem;
82
+ break;
83
+ }
84
+ case 'leaf': {
85
+ // The last selected leaf item is the only one that should remain
86
+ const index = this.items.reverse().findIndex(item => item.leaf); // TODO: replace with findLastIndex()
87
+ const leafItem = index > -1 ? this.items.splice(index, 1) : null;
88
+ changedItems = [...this.items];
89
+ this.items.forEach(item => this._selectItem(item, false));
90
+ if (leafItem) {
91
+ this.items = leafItem;
92
+ }
93
+ break;
94
+ }
95
+ case 'multiple':
96
+ // No items should be deselected but descendent items should be updated
97
+ changedItems = this.items;
98
+ changedItems.forEach(item => this._updateDescendentSelections(item));
99
+ break;
100
+ case 'off':
101
+ // All items should be deselected
102
+ changedItems = [...this.items];
103
+ this.items.forEach(item => this._selectItem(item, false));
104
+ this.items = [];
105
+ break;
106
+ }
107
+ // Update all changed item ancestors
108
+ changedItems.forEach(item => this._updateAncestorSelections(item));
109
+ }
110
+ /**
111
+ * Selects or deselects a tree item.
112
+ * @param item The tree item to toggle.
113
+ * @param force If true, the item will be selected. If false, the item will be deselected.
114
+ */
115
+ toggle(item, force) {
116
+ if (item.disabled) {
117
+ return;
118
+ }
119
+ // Save a snapshot of the current state in case the event is canceled
120
+ let snapshot = [];
121
+ this._addToSnapshot(item, snapshot);
122
+ const selected = force ?? !item.selected;
123
+ this._selectItem(item, selected);
124
+ // Save the current selected items in case the event is canceled
125
+ const oldSelectedItems = this.items.slice();
126
+ // Update the selected items array and deselect items if necessary
127
+ snapshot = this._updateSelectionsFromItem(item, snapshot);
128
+ // Dispatch a select event from the item
129
+ const event = new CustomEvent('forge-tree-item-select', { bubbles: true, composed: true, detail: item.value });
130
+ item.dispatchEvent(event);
131
+ // Revert if the event was canceled
132
+ if (event.defaultPrevented) {
133
+ this._restoreSnapshot(snapshot);
134
+ this.items = oldSelectedItems;
135
+ }
136
+ }
137
+ /**
138
+ * Selects all tree items between the last selected item and the given item.
139
+ * @param item The end item to extend the selection to.
140
+ */
141
+ extend(item) {
142
+ if (item.disabled) {
143
+ return;
144
+ }
145
+ if (this.host.mode !== 'multiple') {
146
+ return;
147
+ }
148
+ const lastSelectedItem = this.items[this.items.length - 1];
149
+ if (!lastSelectedItem) {
150
+ return;
151
+ }
152
+ const positionComparison = item.compareDocumentPosition(lastSelectedItem);
153
+ // If the items are the same, do nothing
154
+ if (positionComparison === 0) {
155
+ return;
156
+ }
157
+ // eslint-disable-next-line no-bitwise
158
+ const extendForward = positionComparison & Node.DOCUMENT_POSITION_FOLLOWING;
159
+ const iteratorFn = extendForward ? getNextItem : getPreviousItem;
160
+ // Select all items between the item and the last selected item
161
+ let nextItem = iteratorFn(item);
162
+ while (nextItem) {
163
+ // Exit the loop after reaching the last selected item
164
+ if (nextItem === lastSelectedItem) {
165
+ break;
166
+ }
167
+ // Don't select open non-leaf items to avoid selecting entire branches twice
168
+ if (nextItem.leaf || !nextItem.open) {
169
+ this.toggle(nextItem, true);
170
+ }
171
+ nextItem = iteratorFn(nextItem);
172
+ }
173
+ }
174
+ /**
175
+ * Selects all tree items.
176
+ */
177
+ selectAll() {
178
+ this.items = [];
179
+ const allItems = getChildItems(this.host, true);
180
+ allItems.forEach(child => {
181
+ this._selectItem(child, true);
182
+ this.items.push(child);
183
+ });
184
+ allItems.reverse().forEach(child => {
185
+ this._updateAncestorSelections(child);
186
+ });
187
+ this.host.dispatchEvent(new CustomEvent('forge-tree-select-all', { bubbles: true, composed: true }));
188
+ }
189
+ /**
190
+ * Updates other items when an item is updated outside of the tree's interaction handlers.
191
+ * @param evt The update event emitted from a tree item.
192
+ */
193
+ _handleUpdateEvent(evt) {
194
+ const item = getTreeItemFromEvent(evt);
195
+ if (!item) {
196
+ return;
197
+ }
198
+ switch (evt.detail.reason) {
199
+ case 'deselected':
200
+ case 'selected':
201
+ if (!this._itemsBeingToggled.has(item)) {
202
+ this._updateSelectionsFromItem(item);
203
+ }
204
+ this._itemsBeingToggled.delete(item);
205
+ break;
206
+ case 'added':
207
+ case 'removed':
208
+ this._updateAncestorSelections(item);
209
+ break;
210
+ }
211
+ }
212
+ /**
213
+ * Selects or deselects the given tree item and updates the list of selected items to reflect
214
+ * the change.
215
+ * @param item The item to select or deselect.
216
+ * @param changes The original state of all changed tree items.
217
+ */
218
+ _updateSelectionsFromItem(item, changes = []) {
219
+ // Remove a deselected item from the array
220
+ if (!item.selected) {
221
+ const index = this.items.indexOf(item);
222
+ if (index !== -1) {
223
+ this.items.splice(index, 1);
224
+ }
225
+ if (this.host.mode === 'multiple') {
226
+ // Update descendants and ancestors
227
+ changes = this._updateDescendentSelections(item, changes);
228
+ changes = this._updateAncestorSelections(item, changes);
229
+ }
230
+ }
231
+ else if (this.host.mode !== 'multiple') {
232
+ // Deselect all other items if an item was selected and the mode is not multiple
233
+ this.items.forEach(selectedItem => {
234
+ if (selectedItem !== item) {
235
+ this._addToSnapshot(selectedItem, changes);
236
+ this._selectItem(selectedItem, false);
237
+ }
238
+ });
239
+ this.items = [item];
240
+ return changes;
241
+ }
242
+ else if (item.selected) {
243
+ // If the item was selected in multiple mode just add the item to the array
244
+ this.items.push(item);
245
+ }
246
+ // Update descendants and ancestors
247
+ changes = this._updateDescendentSelections(item, changes);
248
+ changes = this._updateAncestorSelections(item, changes);
249
+ return changes;
250
+ }
251
+ /**
252
+ * Sets the selected state of all children of the given item.
253
+ * @param item The item that was selected or deselected.
254
+ * @param changes The original state of all changed tree items.
255
+ * @returns The updated snapshot of all changed tree items.
256
+ */
257
+ _updateDescendentSelections(item, changes = []) {
258
+ // Exit if the item has no children
259
+ if (item.leaf) {
260
+ return changes;
261
+ }
262
+ // Recursively get all children and set their selected states
263
+ const children = getChildItems(item, true);
264
+ children.forEach(child => {
265
+ this._addToSnapshot(child, changes);
266
+ if (child.selected !== item.selected) {
267
+ this._selectItem(child, item.selected);
268
+ }
269
+ const index = this.items.indexOf(child);
270
+ if (item.selected && index === -1) {
271
+ this.items.push(child);
272
+ }
273
+ else if (!item.selected && index !== -1) {
274
+ this.items.splice(index, 1);
275
+ }
276
+ });
277
+ // Find all items with children and set their indeterminate states
278
+ const branches = children.filter(child => !child.leaf);
279
+ branches.reverse().forEach(branch => {
280
+ this._addToSnapshot(branch, changes, { indeterminate: true });
281
+ branch[indeterminate] = isIndeterminate(branch);
282
+ });
283
+ this._addToSnapshot(item, changes, { indeterminate: true });
284
+ item[indeterminate] = isIndeterminate(item);
285
+ // Return the snapshot of the affected items
286
+ return changes;
287
+ }
288
+ /**
289
+ * Sets ancestor items of the given item to selected, deselected, or indeterminate based on the
290
+ * state of the item.
291
+ * @param item The item that was selected or deselected.
292
+ * @param changes The original state of all changed tree items.
293
+ * @return The updated snapshot of all changed tree items.
294
+ */
295
+ _updateAncestorSelections(item, changes = []) {
296
+ let parentItem = getParentItem(item);
297
+ while (parentItem) {
298
+ // Save a snapshot of the parent item's current state
299
+ this._addToSnapshot(parentItem, changes);
300
+ // Set the parent item's indeterminate state
301
+ parentItem[indeterminate] = isIndeterminate(parentItem);
302
+ // If the parent item is not indetermine, set the selected state from the first child
303
+ if (!parentItem.indeterminate) {
304
+ const firstChild = getFirstChildItem(parentItem);
305
+ const willSelect = firstChild?.selected ?? false;
306
+ if (parentItem.selected !== willSelect) {
307
+ this._selectItem(parentItem, willSelect);
308
+ }
309
+ // Add or remove the parent item from the selected items array
310
+ const index = this.items.indexOf(parentItem);
311
+ if (parentItem.selected && index === -1) {
312
+ this.items.push(parentItem);
313
+ }
314
+ else if (!parentItem.selected && index !== -1) {
315
+ this.items.splice(index, 1);
316
+ }
317
+ }
318
+ // Go up the tree one level and repeat
319
+ parentItem = getParentItem(parentItem);
320
+ }
321
+ return changes;
322
+ }
323
+ /**
324
+ * Adds a tree item to a snapshot of all changed tree items.
325
+ * @param item The tree item.
326
+ * @param snapshot The snapshot of all changed tree items.
327
+ * @param options Properties of the tree item to change if it already exists in the snapshot.
328
+ */
329
+ _addToSnapshot(item, snapshot, options) {
330
+ const exisitingState = snapshot.find(state => state.el === item);
331
+ if (exisitingState) {
332
+ options = options ?? { indeterminate: true, open: true, selected: true };
333
+ exisitingState.indeterminate = options.indeterminate ? item.indeterminate : exisitingState.indeterminate;
334
+ exisitingState.open = options.open ? item.open : exisitingState.open;
335
+ exisitingState.selected = options.selected ? item.selected : exisitingState.selected;
336
+ return;
337
+ }
338
+ snapshot.push({
339
+ el: item,
340
+ indeterminate: item.indeterminate,
341
+ open: item.open,
342
+ selected: item.selected
343
+ });
344
+ }
345
+ /**
346
+ * Restores the state of all changed tree items from a snapshot.
347
+ * @param snapshot An array containing the original state of all changed tree items.
348
+ */
349
+ _restoreSnapshot(snapshot) {
350
+ snapshot.forEach(state => {
351
+ state.el.open = state.open;
352
+ state.el[indeterminate] = state.indeterminate;
353
+ this._selectItem(state.el, state.selected);
354
+ });
355
+ }
356
+ /**
357
+ * Clears the indeterminate state of all tree items.
358
+ * @param items An optional array of items to clear the indeterminate state of.
359
+ */
360
+ _clearIndeterminate(items) {
361
+ (items ?? getChildItems(this.host)).forEach(item => {
362
+ item[indeterminate] = false;
363
+ });
364
+ }
365
+ /**
366
+ * Sets a tree item's selected state and adds it to the set of items being toggled to prevent the
367
+ * event from being handled twice.
368
+ * @param item The item to select or deselect.
369
+ * @param force If true, the item will be selected. If false, the item will be deselected.
370
+ */
371
+ _selectItem(item, force) {
372
+ this._itemsBeingToggled.add(item);
373
+ item.selected = force;
374
+ }
375
+ }
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @license
3
+ * Copyright Tyler Technologies, Inc.
4
+ * License: Apache-2.0
5
+ */
6
+ import { LitElement, PropertyValues, TemplateResult } from 'lit';
7
+ export type TreeMode = 'single' | 'multiple' | 'leaf' | 'off';
8
+ export interface ITreeContext {
9
+ collapseIcon?: HTMLElement;
10
+ disabled: boolean;
11
+ expandIcon?: HTMLElement;
12
+ indentLines: boolean;
13
+ mode: TreeMode;
14
+ }
15
+ export declare const TREE_CONTEXT: {
16
+ __context__: ITreeContext;
17
+ };
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
+ export declare class TreeComponent extends LitElement {
34
+ static styles: import("lit").CSSResult;
35
+ /**
36
+ * Whether opening an item closes all other items.
37
+ * @default false
38
+ * @attribute
39
+ */
40
+ accordion: boolean;
41
+ /**
42
+ * Toggles the rendering of indent lines showing hierarchy.
43
+ * @default false
44
+ * @attribute
45
+ */
46
+ indentLines: boolean;
47
+ /**
48
+ * How selecting tree items is handled.
49
+ * @default 'single'
50
+ * @attribute
51
+ */
52
+ mode: TreeMode;
53
+ /**
54
+ * Whether focusing an item also selects it. This takes no effect when in multiple mode.
55
+ * @default false
56
+ * @attribute
57
+ */
58
+ selectionFollowsFocus: boolean;
59
+ /**
60
+ * Whether selecting items is disabled.
61
+ * @default false
62
+ * @attribute
63
+ */
64
+ disabled: boolean;
65
+ /**
66
+ * The value of all selected items.
67
+ * @default []
68
+ * @attribute
69
+ */
70
+ get value(): unknown[];
71
+ set value(value: unknown[]);
72
+ private _context;
73
+ private _internals;
74
+ private _keyActionController;
75
+ private _selectionController;
76
+ private _lastFocusedItem?;
77
+ private _expandIcon?;
78
+ private _collapseIcon?;
79
+ private _expandIconObserver?;
80
+ private _collapseIconObserver?;
81
+ constructor();
82
+ connectedCallback(): void;
83
+ disconnectedCallback(): void;
84
+ willUpdate(_changedProperties: PropertyValues<this>): void;
85
+ render(): TemplateResult;
86
+ private _updateContext;
87
+ /**
88
+ * Toggle either the selected or open state of a key item when its header is clicked.
89
+ */
90
+ private _handleClick;
91
+ /**
92
+ * Focuses the next or previous visible item.
93
+ */
94
+ private _handleArrowUpOrDown;
95
+ /**
96
+ * If the focused item is open, closes it. Otherwise, focuses the parent item.
97
+ */
98
+ private _handleArrowLeft;
99
+ /**
100
+ * If the focused item is closed, opens it. Otherwise, focuses the first child item.
101
+ */
102
+ private _handleArrowRight;
103
+ /**
104
+ * Focuses the first visible item. If the mode is multiple and the meta key is pressed, selects
105
+ * all previous items.
106
+ */
107
+ private _handleHome;
108
+ /**
109
+ * Focuses the last visible item. If the mode is multiple and the meta key is pressed, selects
110
+ * all subsequent items.
111
+ */
112
+ private _handleEnd;
113
+ /**
114
+ * Opens the focused item and all sibling items.
115
+ */
116
+ private _handleAsterisk;
117
+ /**
118
+ * If the mode is multiple, selects all items.
119
+ */
120
+ private _handleA;
121
+ /**
122
+ * Toggles the open or selected state of the focused item.
123
+ */
124
+ private _handleEnterOrSpace;
125
+ private _handleFocusIn;
126
+ private _handleFocusOut;
127
+ private _handleUpdate;
128
+ private _search;
129
+ private _toggleOpen;
130
+ private _focusItem;
131
+ private _detectSlottedIcon;
132
+ private _observeIcon;
133
+ private _updateIcon;
134
+ private _setDisabled;
135
+ private _setMode;
136
+ }
137
+ declare global {
138
+ interface HTMLElementTagNameMap {
139
+ 'forge-tree': TreeComponent;
140
+ }
141
+ }