@ngneers/controls 0.0.1-next.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/fesm2022/ngneers-controls-accordion.mjs +234 -0
- package/fesm2022/ngneers-controls-accordion.mjs.map +1 -0
- package/fesm2022/ngneers-controls-api-ng.mjs +959 -0
- package/fesm2022/ngneers-controls-api-ng.mjs.map +1 -0
- package/fesm2022/ngneers-controls-api-resize.mjs +712 -0
- package/fesm2022/ngneers-controls-api-resize.mjs.map +1 -0
- package/fesm2022/ngneers-controls-api.mjs +347 -0
- package/fesm2022/ngneers-controls-api.mjs.map +1 -0
- package/fesm2022/ngneers-controls-avatar.mjs +77 -0
- package/fesm2022/ngneers-controls-avatar.mjs.map +1 -0
- package/fesm2022/ngneers-controls-base.mjs +620 -0
- package/fesm2022/ngneers-controls-base.mjs.map +1 -0
- package/fesm2022/ngneers-controls-breadcrumb.mjs +100 -0
- package/fesm2022/ngneers-controls-breadcrumb.mjs.map +1 -0
- package/fesm2022/ngneers-controls-button-group.mjs +110 -0
- package/fesm2022/ngneers-controls-button-group.mjs.map +1 -0
- package/fesm2022/ngneers-controls-button.mjs +83 -0
- package/fesm2022/ngneers-controls-button.mjs.map +1 -0
- package/fesm2022/ngneers-controls-calendar-time-CJT5AFlA.mjs +74 -0
- package/fesm2022/ngneers-controls-calendar-time-CJT5AFlA.mjs.map +1 -0
- package/fesm2022/ngneers-controls-calendar.mjs +801 -0
- package/fesm2022/ngneers-controls-calendar.mjs.map +1 -0
- package/fesm2022/ngneers-controls-checkbox.mjs +63 -0
- package/fesm2022/ngneers-controls-checkbox.mjs.map +1 -0
- package/fesm2022/ngneers-controls-chip.mjs +55 -0
- package/fesm2022/ngneers-controls-chip.mjs.map +1 -0
- package/fesm2022/ngneers-controls-default-icons.mjs +82 -0
- package/fesm2022/ngneers-controls-default-icons.mjs.map +1 -0
- package/fesm2022/ngneers-controls-defer.mjs +72 -0
- package/fesm2022/ngneers-controls-defer.mjs.map +1 -0
- package/fesm2022/ngneers-controls-dialog.mjs +407 -0
- package/fesm2022/ngneers-controls-dialog.mjs.map +1 -0
- package/fesm2022/ngneers-controls-directives.mjs +498 -0
- package/fesm2022/ngneers-controls-directives.mjs.map +1 -0
- package/fesm2022/ngneers-controls-drawer.mjs +271 -0
- package/fesm2022/ngneers-controls-drawer.mjs.map +1 -0
- package/fesm2022/ngneers-controls-edit-inplace.mjs +127 -0
- package/fesm2022/ngneers-controls-edit-inplace.mjs.map +1 -0
- package/fesm2022/ngneers-controls-errors.mjs +329 -0
- package/fesm2022/ngneers-controls-errors.mjs.map +1 -0
- package/fesm2022/ngneers-controls-filter.mjs +708 -0
- package/fesm2022/ngneers-controls-filter.mjs.map +1 -0
- package/fesm2022/ngneers-controls-hint.mjs +153 -0
- package/fesm2022/ngneers-controls-hint.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n-translations-de.mjs +170 -0
- package/fesm2022/ngneers-controls-i18n-translations-de.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n-translations-en.mjs +170 -0
- package/fesm2022/ngneers-controls-i18n-translations-en.mjs.map +1 -0
- package/fesm2022/ngneers-controls-i18n.mjs +57 -0
- package/fesm2022/ngneers-controls-i18n.mjs.map +1 -0
- package/fesm2022/ngneers-controls-icon.mjs +195 -0
- package/fesm2022/ngneers-controls-icon.mjs.map +1 -0
- package/fesm2022/ngneers-controls-inplace.mjs +150 -0
- package/fesm2022/ngneers-controls-inplace.mjs.map +1 -0
- package/fesm2022/ngneers-controls-input-field.mjs +165 -0
- package/fesm2022/ngneers-controls-input-field.mjs.map +1 -0
- package/fesm2022/ngneers-controls-input.mjs +72 -0
- package/fesm2022/ngneers-controls-input.mjs.map +1 -0
- package/fesm2022/ngneers-controls-item-view.mjs +473 -0
- package/fesm2022/ngneers-controls-item-view.mjs.map +1 -0
- package/fesm2022/ngneers-controls-list-box.mjs +281 -0
- package/fesm2022/ngneers-controls-list-box.mjs.map +1 -0
- package/fesm2022/ngneers-controls-mask-input.mjs +888 -0
- package/fesm2022/ngneers-controls-mask-input.mjs.map +1 -0
- package/fesm2022/ngneers-controls-menu.mjs +371 -0
- package/fesm2022/ngneers-controls-menu.mjs.map +1 -0
- package/fesm2022/ngneers-controls-message.mjs +30 -0
- package/fesm2022/ngneers-controls-message.mjs.map +1 -0
- package/fesm2022/ngneers-controls-number-input.mjs +366 -0
- package/fesm2022/ngneers-controls-number-input.mjs.map +1 -0
- package/fesm2022/ngneers-controls-paginator.mjs +129 -0
- package/fesm2022/ngneers-controls-paginator.mjs.map +1 -0
- package/fesm2022/ngneers-controls-popover.mjs +215 -0
- package/fesm2022/ngneers-controls-popover.mjs.map +1 -0
- package/fesm2022/ngneers-controls-progress.mjs +98 -0
- package/fesm2022/ngneers-controls-progress.mjs.map +1 -0
- package/fesm2022/ngneers-controls-radio.mjs +152 -0
- package/fesm2022/ngneers-controls-radio.mjs.map +1 -0
- package/fesm2022/ngneers-controls-roving-focus.mjs +323 -0
- package/fesm2022/ngneers-controls-roving-focus.mjs.map +1 -0
- package/fesm2022/ngneers-controls-scroll-shadow.mjs +65 -0
- package/fesm2022/ngneers-controls-scroll-shadow.mjs.map +1 -0
- package/fesm2022/ngneers-controls-scroller.mjs +308 -0
- package/fesm2022/ngneers-controls-scroller.mjs.map +1 -0
- package/fesm2022/ngneers-controls-select-button.mjs +58 -0
- package/fesm2022/ngneers-controls-select-button.mjs.map +1 -0
- package/fesm2022/ngneers-controls-select.mjs +417 -0
- package/fesm2022/ngneers-controls-select.mjs.map +1 -0
- package/fesm2022/ngneers-controls-slider.mjs +167 -0
- package/fesm2022/ngneers-controls-slider.mjs.map +1 -0
- package/fesm2022/ngneers-controls-snackbar.mjs +414 -0
- package/fesm2022/ngneers-controls-snackbar.mjs.map +1 -0
- package/fesm2022/ngneers-controls-spin-buttons.mjs +126 -0
- package/fesm2022/ngneers-controls-spin-buttons.mjs.map +1 -0
- package/fesm2022/ngneers-controls-spinner.mjs +184 -0
- package/fesm2022/ngneers-controls-spinner.mjs.map +1 -0
- package/fesm2022/ngneers-controls-splitter.mjs +604 -0
- package/fesm2022/ngneers-controls-splitter.mjs.map +1 -0
- package/fesm2022/ngneers-controls-state.mjs +185 -0
- package/fesm2022/ngneers-controls-state.mjs.map +1 -0
- package/fesm2022/ngneers-controls-switch.mjs +39 -0
- package/fesm2022/ngneers-controls-switch.mjs.map +1 -0
- package/fesm2022/ngneers-controls-table.mjs +2634 -0
- package/fesm2022/ngneers-controls-table.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tabs.mjs +336 -0
- package/fesm2022/ngneers-controls-tabs.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tag.mjs +30 -0
- package/fesm2022/ngneers-controls-tag.mjs.map +1 -0
- package/fesm2022/ngneers-controls-toast.mjs +350 -0
- package/fesm2022/ngneers-controls-toast.mjs.map +1 -0
- package/fesm2022/ngneers-controls-toggle-button.mjs +70 -0
- package/fesm2022/ngneers-controls-toggle-button.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tooltip.mjs +506 -0
- package/fesm2022/ngneers-controls-tooltip.mjs.map +1 -0
- package/fesm2022/ngneers-controls-tree.mjs +599 -0
- package/fesm2022/ngneers-controls-tree.mjs.map +1 -0
- package/fesm2022/ngneers-controls-upload.mjs +377 -0
- package/fesm2022/ngneers-controls-upload.mjs.map +1 -0
- package/fesm2022/ngneers-controls-utils-ng.mjs +411 -0
- package/fesm2022/ngneers-controls-utils-ng.mjs.map +1 -0
- package/fesm2022/ngneers-controls-utils.mjs +261 -0
- package/fesm2022/ngneers-controls-utils.mjs.map +1 -0
- package/fesm2022/ngneers-controls.mjs +6 -0
- package/fesm2022/ngneers-controls.mjs.map +1 -0
- package/package.json +302 -0
- package/types/ngneers-controls-accordion.d.ts +139 -0
- package/types/ngneers-controls-api-ng.d.ts +364 -0
- package/types/ngneers-controls-api-resize.d.ts +318 -0
- package/types/ngneers-controls-api.d.ts +145 -0
- package/types/ngneers-controls-avatar.d.ts +54 -0
- package/types/ngneers-controls-base.d.ts +277 -0
- package/types/ngneers-controls-breadcrumb.d.ts +272 -0
- package/types/ngneers-controls-button-group.d.ts +46 -0
- package/types/ngneers-controls-button.d.ts +48 -0
- package/types/ngneers-controls-calendar.d.ts +466 -0
- package/types/ngneers-controls-checkbox.d.ts +40 -0
- package/types/ngneers-controls-chip.d.ts +202 -0
- package/types/ngneers-controls-default-icons.d.ts +5 -0
- package/types/ngneers-controls-defer.d.ts +48 -0
- package/types/ngneers-controls-dialog.d.ts +399 -0
- package/types/ngneers-controls-directives.d.ts +171 -0
- package/types/ngneers-controls-drawer.d.ts +308 -0
- package/types/ngneers-controls-edit-inplace.d.ts +263 -0
- package/types/ngneers-controls-errors.d.ts +155 -0
- package/types/ngneers-controls-filter.d.ts +609 -0
- package/types/ngneers-controls-hint.d.ts +88 -0
- package/types/ngneers-controls-i18n-translations-de.d.ts +164 -0
- package/types/ngneers-controls-i18n-translations-en.d.ts +164 -0
- package/types/ngneers-controls-i18n.d.ts +186 -0
- package/types/ngneers-controls-icon.d.ts +68 -0
- package/types/ngneers-controls-inplace.d.ts +98 -0
- package/types/ngneers-controls-input-field.d.ts +248 -0
- package/types/ngneers-controls-input.d.ts +30 -0
- package/types/ngneers-controls-item-view.d.ts +215 -0
- package/types/ngneers-controls-list-box.d.ts +334 -0
- package/types/ngneers-controls-mask-input.d.ts +434 -0
- package/types/ngneers-controls-menu.d.ts +192 -0
- package/types/ngneers-controls-message.d.ts +21 -0
- package/types/ngneers-controls-number-input.d.ts +146 -0
- package/types/ngneers-controls-paginator.d.ts +255 -0
- package/types/ngneers-controls-popover.d.ts +116 -0
- package/types/ngneers-controls-progress.d.ts +49 -0
- package/types/ngneers-controls-radio.d.ts +91 -0
- package/types/ngneers-controls-roving-focus.d.ts +107 -0
- package/types/ngneers-controls-scroll-shadow.d.ts +25 -0
- package/types/ngneers-controls-scroller.d.ts +151 -0
- package/types/ngneers-controls-select-button.d.ts +51 -0
- package/types/ngneers-controls-select.d.ts +409 -0
- package/types/ngneers-controls-slider.d.ts +57 -0
- package/types/ngneers-controls-snackbar.d.ts +330 -0
- package/types/ngneers-controls-spin-buttons.d.ts +69 -0
- package/types/ngneers-controls-spinner.d.ts +117 -0
- package/types/ngneers-controls-splitter.d.ts +465 -0
- package/types/ngneers-controls-state.d.ts +102 -0
- package/types/ngneers-controls-switch.d.ts +18 -0
- package/types/ngneers-controls-table.d.ts +3127 -0
- package/types/ngneers-controls-tabs.d.ts +118 -0
- package/types/ngneers-controls-tag.d.ts +21 -0
- package/types/ngneers-controls-toast.d.ts +298 -0
- package/types/ngneers-controls-toggle-button.d.ts +48 -0
- package/types/ngneers-controls-tooltip.d.ts +189 -0
- package/types/ngneers-controls-tree.d.ts +409 -0
- package/types/ngneers-controls-upload.d.ts +347 -0
- package/types/ngneers-controls-utils-ng.d.ts +129 -0
- package/types/ngneers-controls-utils.d.ts +130 -0
- package/types/ngneers-controls.d.ts +2 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { viewChild, contentChild, input, computed, Directive, inject, booleanAttribute, model, output, signal, effect, untracked, runInInjectionContext, Component } from '@angular/core';
|
|
4
|
+
import { treeControlTemplate } from '@ngneers/controls-themes/templates/tree';
|
|
5
|
+
import { filterOptions } from '@ngneers/controls/api';
|
|
6
|
+
import { templateTypesFn, NgnStorage, NgnTemplate } from '@ngneers/controls/api/ng';
|
|
7
|
+
import { ValueControlBase, NgnPt, provideSelf } from '@ngneers/controls/base';
|
|
8
|
+
import { NgnCheckbox } from '@ngneers/controls/checkbox';
|
|
9
|
+
import { I18n } from '@ngneers/controls/i18n';
|
|
10
|
+
import { NgnScroller, NgnScrollerItem } from '@ngneers/controls/scroller';
|
|
11
|
+
import { createConditionalSpinner, NgnSpinner } from '@ngneers/controls/spinner';
|
|
12
|
+
import { maybeCallback, Logger } from '@ngneers/controls/utils';
|
|
13
|
+
import { asyncComputed } from '@ngneers/controls/utils-ng';
|
|
14
|
+
|
|
15
|
+
class TreeTemplates extends ValueControlBase {
|
|
16
|
+
// Item template
|
|
17
|
+
_defaultItemTemplate = viewChild.required('defaultItemTemplate');
|
|
18
|
+
_userItemTemplate = contentChild('item', /* @ts-ignore */
|
|
19
|
+
...(ngDevMode ? [{ debugName: "_userItemTemplate" }] : /* istanbul ignore next */ []));
|
|
20
|
+
/**
|
|
21
|
+
* Set a custom template for a tree node.
|
|
22
|
+
* Can also be set via an `<ng-template #item>` element. A per-node
|
|
23
|
+
* `item.template` takes precedence over this global template.
|
|
24
|
+
*/
|
|
25
|
+
templateItem = input(null, /* @ts-ignore */
|
|
26
|
+
...(ngDevMode ? [{ debugName: "templateItem" }] : /* istanbul ignore next */ []));
|
|
27
|
+
itemTemplate = computed(() => this._userItemTemplate() ?? this.templateItem() ?? this._defaultItemTemplate(), /* @ts-ignore */
|
|
28
|
+
...(ngDevMode ? [{ debugName: "itemTemplate" }] : /* istanbul ignore next */ []));
|
|
29
|
+
// Empty template
|
|
30
|
+
_defaultEmptyTemplate = viewChild.required('defaultEmptyTemplate');
|
|
31
|
+
_userEmptyTemplate = contentChild('empty', /* @ts-ignore */
|
|
32
|
+
...(ngDevMode ? [{ debugName: "_userEmptyTemplate" }] : /* istanbul ignore next */ []));
|
|
33
|
+
/**
|
|
34
|
+
* Set a custom template for the empty state.
|
|
35
|
+
* Can also be set via an `<ng-template #empty>` element.
|
|
36
|
+
*/
|
|
37
|
+
templateEmpty = input(null, /* @ts-ignore */
|
|
38
|
+
...(ngDevMode ? [{ debugName: "templateEmpty" }] : /* istanbul ignore next */ []));
|
|
39
|
+
emptyTemplate = computed(() => this._userEmptyTemplate() ?? this.templateEmpty() ?? this._defaultEmptyTemplate(), /* @ts-ignore */
|
|
40
|
+
...(ngDevMode ? [{ debugName: "emptyTemplate" }] : /* istanbul ignore next */ []));
|
|
41
|
+
templateTypes = templateTypesFn();
|
|
42
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: TreeTemplates, deps: null, target: i0.ɵɵFactoryTarget.Directive });
|
|
43
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.5", type: TreeTemplates, isStandalone: true, inputs: { templateItem: { classPropertyName: "templateItem", publicName: "templateItem", isSignal: true, isRequired: false, transformFunction: null }, templateEmpty: { classPropertyName: "templateEmpty", publicName: "templateEmpty", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "_userItemTemplate", first: true, predicate: ["item"], descendants: true, isSignal: true }, { propertyName: "_userEmptyTemplate", first: true, predicate: ["empty"], descendants: true, isSignal: true }], viewQueries: [{ propertyName: "_defaultItemTemplate", first: true, predicate: ["defaultItemTemplate"], descendants: true, isSignal: true }, { propertyName: "_defaultEmptyTemplate", first: true, predicate: ["defaultEmptyTemplate"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0 });
|
|
44
|
+
}
|
|
45
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: TreeTemplates, decorators: [{
|
|
46
|
+
type: Directive
|
|
47
|
+
}], propDecorators: { _defaultItemTemplate: [{ type: i0.ViewChild, args: ['defaultItemTemplate', { isSignal: true }] }], _userItemTemplate: [{ type: i0.ContentChild, args: ['item', { isSignal: true }] }], templateItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateItem", required: false }] }], _defaultEmptyTemplate: [{ type: i0.ViewChild, args: ['defaultEmptyTemplate', { isSignal: true }] }], _userEmptyTemplate: [{ type: i0.ContentChild, args: ['empty', { isSignal: true }] }], templateEmpty: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateEmpty", required: false }] }] } });
|
|
48
|
+
|
|
49
|
+
/** Walk the tree, emitting only expanded + visible nodes as a flat list. */
|
|
50
|
+
function flattenTree(items, expanded, level = 0, parentDisabled = false) {
|
|
51
|
+
const result = [];
|
|
52
|
+
const setSize = items.length;
|
|
53
|
+
items.forEach((item, index) => {
|
|
54
|
+
// A lazy branch shows a toggle before its children are loaded.
|
|
55
|
+
const hasChildren = (!!item.items && item.items.length > 0) || !!item.lazy;
|
|
56
|
+
const isExpanded = hasChildren && expanded.has(item.value);
|
|
57
|
+
const effectiveDisabled = parentDisabled || !!item.disabled;
|
|
58
|
+
result.push({
|
|
59
|
+
item,
|
|
60
|
+
level,
|
|
61
|
+
setSize,
|
|
62
|
+
posInSet: index + 1,
|
|
63
|
+
hasChildren,
|
|
64
|
+
expanded: isExpanded,
|
|
65
|
+
parentDisabled,
|
|
66
|
+
});
|
|
67
|
+
if (isExpanded && item.items) {
|
|
68
|
+
result.push(...flattenTree(item.items, expanded, level + 1, effectiveDisabled));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Inject lazily-loaded children (keyed by node value) into the tree, returning
|
|
75
|
+
* a new forest where resolved lazy nodes carry their loaded `items` and are no
|
|
76
|
+
* longer marked `lazy`. Returns the input unchanged when nothing is loaded.
|
|
77
|
+
*/
|
|
78
|
+
function applyLoadedChildren(items, loaded) {
|
|
79
|
+
if (loaded.size === 0) {
|
|
80
|
+
return items;
|
|
81
|
+
}
|
|
82
|
+
return items.map(item => {
|
|
83
|
+
const cached = loaded.get(item.value);
|
|
84
|
+
if (cached !== undefined) {
|
|
85
|
+
return { ...item, items: [...applyLoadedChildren(cached, loaded)], lazy: false };
|
|
86
|
+
}
|
|
87
|
+
if (item.items && item.items.length > 0) {
|
|
88
|
+
return { ...item, items: [...applyLoadedChildren(item.items, loaded)] };
|
|
89
|
+
}
|
|
90
|
+
return item;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** All selectable, enabled leaf values under (and including) an item. */
|
|
94
|
+
function collectLeafValues(item, parentDisabled = false) {
|
|
95
|
+
const disabled = parentDisabled || !!item.disabled;
|
|
96
|
+
if (item.items && item.items.length > 0) {
|
|
97
|
+
return item.items.flatMap(child => collectLeafValues(child, disabled));
|
|
98
|
+
}
|
|
99
|
+
if (disabled || item.selectable === false) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return [item.value];
|
|
103
|
+
}
|
|
104
|
+
/** All branch values in the forest (used to auto-expand while filtering). */
|
|
105
|
+
function collectBranchValues(items) {
|
|
106
|
+
return items.flatMap(item => item.items && item.items.length > 0 ? [item.value, ...collectBranchValues(item.items)] : []);
|
|
107
|
+
}
|
|
108
|
+
/** Add/remove all of a node's enabled leaf values against the current value set. */
|
|
109
|
+
function cascadeCheck(node, shouldCheck, currentValue) {
|
|
110
|
+
const leaves = collectLeafValues(node);
|
|
111
|
+
const set = new Set(currentValue);
|
|
112
|
+
for (const value of leaves) {
|
|
113
|
+
if (shouldCheck) {
|
|
114
|
+
set.add(value);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
set.delete(value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return [...set];
|
|
121
|
+
}
|
|
122
|
+
/** Derive a node's tri-state from which of its leaves are in the value set. */
|
|
123
|
+
function computeNodeState(item, valueSet) {
|
|
124
|
+
const leaves = collectLeafValues(item);
|
|
125
|
+
if (leaves.length === 0) {
|
|
126
|
+
return 'unchecked';
|
|
127
|
+
}
|
|
128
|
+
let checked = 0;
|
|
129
|
+
for (const value of leaves) {
|
|
130
|
+
if (valueSet.has(value)) {
|
|
131
|
+
checked++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (checked === 0)
|
|
135
|
+
return 'unchecked';
|
|
136
|
+
if (checked === leaves.length)
|
|
137
|
+
return 'checked';
|
|
138
|
+
return 'indeterminate';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @category control
|
|
143
|
+
*/
|
|
144
|
+
class NgnTree extends TreeTemplates {
|
|
145
|
+
i18n = inject(I18n).translations;
|
|
146
|
+
theme = this.injectThemeTemplate(treeControlTemplate, {
|
|
147
|
+
root: true,
|
|
148
|
+
invalid: () => this.invalid(),
|
|
149
|
+
empty: () => !this.flatNodes().length,
|
|
150
|
+
});
|
|
151
|
+
_scroller = viewChild.required(NgnScroller);
|
|
152
|
+
/** The tree items to render, as a nested list of {@link NgnTreeItem} objects. */
|
|
153
|
+
items = input.required(/* @ts-ignore */
|
|
154
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/**
|
|
156
|
+
* Whether nodes can be selected/checked.
|
|
157
|
+
* @default true
|
|
158
|
+
*/
|
|
159
|
+
selectable = input(true, { ...(ngDevMode ? { debugName: "selectable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
160
|
+
/**
|
|
161
|
+
* Whether the tree is focusable and participates in keyboard navigation.
|
|
162
|
+
* @default true
|
|
163
|
+
*/
|
|
164
|
+
focussable = input(true, { ...(ngDevMode ? { debugName: "focussable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
165
|
+
/**
|
|
166
|
+
* Whether the tree is virtualized. When enabled, provide {@link itemHeight}.
|
|
167
|
+
* @default false
|
|
168
|
+
*/
|
|
169
|
+
virtual = input(false, { ...(ngDevMode ? { debugName: "virtual" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
170
|
+
/**
|
|
171
|
+
* When {@link virtual} is enabled, this defines the height of each node in the list.
|
|
172
|
+
*/
|
|
173
|
+
itemHeight = input(/* @ts-ignore */
|
|
174
|
+
...(ngDevMode ? [undefined, { debugName: "itemHeight" }] : /* istanbul ignore next */ []));
|
|
175
|
+
/**
|
|
176
|
+
* Enable this to allow selecting multiple nodes. The value then becomes an array of node values.
|
|
177
|
+
* @default false
|
|
178
|
+
*/
|
|
179
|
+
multiple = input(/* @ts-ignore */
|
|
180
|
+
...(ngDevMode ? [undefined, { debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
181
|
+
/**
|
|
182
|
+
* Whether to render checkboxes. Defaults to `multiple()`.
|
|
183
|
+
* @default multiple()
|
|
184
|
+
*/
|
|
185
|
+
checkbox = input(/* @ts-ignore */
|
|
186
|
+
...(ngDevMode ? [undefined, { debugName: "checkbox" }] : /* istanbul ignore next */ []));
|
|
187
|
+
/**
|
|
188
|
+
* Boolean to enable filtering, or a `FilterConfig` to customize it.
|
|
189
|
+
* @default false
|
|
190
|
+
*/
|
|
191
|
+
filter = input(false, /* @ts-ignore */
|
|
192
|
+
...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
193
|
+
/** Manually set the filter text. */
|
|
194
|
+
filterText = input(null, /* @ts-ignore */
|
|
195
|
+
...(ngDevMode ? [{ debugName: "filterText" }] : /* istanbul ignore next */ []));
|
|
196
|
+
/** Values of expanded branch nodes (two-way). */
|
|
197
|
+
expandedValues = model([], /* @ts-ignore */
|
|
198
|
+
...(ngDevMode ? [{ debugName: "expandedValues" }] : /* istanbul ignore next */ []));
|
|
199
|
+
/**
|
|
200
|
+
* Persist expansion + selection state across reloads. Provide a key and,
|
|
201
|
+
* optionally, an `NgnStorageKind` (defaults to `'localstorage'`).
|
|
202
|
+
*/
|
|
203
|
+
storage = input(null, /* @ts-ignore */
|
|
204
|
+
...(ngDevMode ? [{ debugName: "storage" }] : /* istanbul ignore next */ []));
|
|
205
|
+
_storage;
|
|
206
|
+
/**
|
|
207
|
+
* Loads the children of a `lazy` node the first time it is expanded.
|
|
208
|
+
* The returned items are cached internally and merged into the tree.
|
|
209
|
+
*/
|
|
210
|
+
loadChildren = input(/* @ts-ignore */
|
|
211
|
+
...(ngDevMode ? [undefined, { debugName: "loadChildren" }] : /* istanbul ignore next */ []));
|
|
212
|
+
/** Emitted with the node's value whenever a node is clicked/selected. */
|
|
213
|
+
itemClicked = output();
|
|
214
|
+
/** Emitted when a branch is expanded (fires before any lazy load resolves). */
|
|
215
|
+
nodeExpand = output();
|
|
216
|
+
maybeCallback = maybeCallback;
|
|
217
|
+
/** Lazily-loaded children, keyed by node value. */
|
|
218
|
+
_loadedChildren = signal(new Map(), /* @ts-ignore */
|
|
219
|
+
...(ngDevMode ? [{ debugName: "_loadedChildren" }] : /* istanbul ignore next */ []));
|
|
220
|
+
/** Node values currently awaiting their lazy children. */
|
|
221
|
+
_loadingNodes = signal(new Set(), /* @ts-ignore */
|
|
222
|
+
...(ngDevMode ? [{ debugName: "_loadingNodes" }] : /* istanbul ignore next */ []));
|
|
223
|
+
filteredItems = asyncComputed(async () => {
|
|
224
|
+
const filter = !!this.filter();
|
|
225
|
+
const appliedFilterOptions = this._appliedFilterOptions();
|
|
226
|
+
const filterText = this.filterText();
|
|
227
|
+
if (!filter || !filterText) {
|
|
228
|
+
return this.items();
|
|
229
|
+
}
|
|
230
|
+
return await filterOptions(this.items(), filterText, appliedFilterOptions);
|
|
231
|
+
}, []);
|
|
232
|
+
_appliedFilterOptions = computed(() => {
|
|
233
|
+
const filter = this.filter();
|
|
234
|
+
const providedFilterArgs = typeof filter === 'boolean' ? {} : filter;
|
|
235
|
+
const options = {
|
|
236
|
+
filterFieldsCallback: item => item.label,
|
|
237
|
+
fieldItems: 'items',
|
|
238
|
+
splitWords: true,
|
|
239
|
+
caseSensitive: false,
|
|
240
|
+
filterFn: 'contains',
|
|
241
|
+
...providedFilterArgs,
|
|
242
|
+
};
|
|
243
|
+
return options;
|
|
244
|
+
}, /* @ts-ignore */
|
|
245
|
+
...(ngDevMode ? [{ debugName: "_appliedFilterOptions" }] : /* istanbul ignore next */ []));
|
|
246
|
+
filterIsExecuting = this.filteredItems.isRunning;
|
|
247
|
+
/** While filtering, auto-expand every branch that survived the filter. */
|
|
248
|
+
_autoExpanded = computed(() => this.filterText() ? collectBranchValues(this.filteredItems()) : [], /* @ts-ignore */
|
|
249
|
+
...(ngDevMode ? [{ debugName: "_autoExpanded" }] : /* istanbul ignore next */ []));
|
|
250
|
+
_expandedSet = computed(() => new Set([...this.expandedValues(), ...this._autoExpanded()]), /* @ts-ignore */
|
|
251
|
+
...(ngDevMode ? [{ debugName: "_expandedSet" }] : /* istanbul ignore next */ []));
|
|
252
|
+
/** Filtered items with any lazily-loaded children merged in. */
|
|
253
|
+
_effectiveItems = computed(() => applyLoadedChildren(this.filteredItems(), this._loadedChildren()), /* @ts-ignore */
|
|
254
|
+
...(ngDevMode ? [{ debugName: "_effectiveItems" }] : /* istanbul ignore next */ []));
|
|
255
|
+
flatNodes = computed(() => flattenTree(this._effectiveItems(), this._expandedSet()), /* @ts-ignore */
|
|
256
|
+
...(ngDevMode ? [{ debugName: "flatNodes" }] : /* istanbul ignore next */ []));
|
|
257
|
+
valueArray = computed(() => {
|
|
258
|
+
const v = this.value();
|
|
259
|
+
return (Array.isArray(v) ? v : v != null ? [v] : []);
|
|
260
|
+
}, /* @ts-ignore */
|
|
261
|
+
...(ngDevMode ? [{ debugName: "valueArray" }] : /* istanbul ignore next */ []));
|
|
262
|
+
_valueSet = computed(() => new Set(this.valueArray()), /* @ts-ignore */
|
|
263
|
+
...(ngDevMode ? [{ debugName: "_valueSet" }] : /* istanbul ignore next */ []));
|
|
264
|
+
currentHighlightedValue = signal(null, /* @ts-ignore */
|
|
265
|
+
...(ngDevMode ? [{ debugName: "currentHighlightedValue" }] : /* istanbul ignore next */ []));
|
|
266
|
+
showCheckboxes = computed(() => this.checkbox() ?? this.multiple() ?? false, /* @ts-ignore */
|
|
267
|
+
...(ngDevMode ? [{ debugName: "showCheckboxes" }] : /* istanbul ignore next */ []));
|
|
268
|
+
constructor() {
|
|
269
|
+
super();
|
|
270
|
+
createConditionalSpinner(this.filterIsExecuting);
|
|
271
|
+
// Fallback init for when the `storage` config arrives after ngOnInit (e.g.
|
|
272
|
+
// an async binding). Idempotent with the ngOnInit path. Registered before
|
|
273
|
+
// the persistence effect so the store is restored before any write; the
|
|
274
|
+
// persistence effect no-ops until `_storage` exists anyway.
|
|
275
|
+
effect(() => {
|
|
276
|
+
const cfg = this.storage();
|
|
277
|
+
if (cfg) {
|
|
278
|
+
untracked(() => this._initStorage(cfg));
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
// Persist expansion + selection whenever they change.
|
|
282
|
+
effect(() => {
|
|
283
|
+
const expanded = this.expandedValues();
|
|
284
|
+
const value = this.value();
|
|
285
|
+
untracked(() => {
|
|
286
|
+
if (!this._storage) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (this._persistsState('expanded')) {
|
|
290
|
+
this._storage.set('expanded', expanded);
|
|
291
|
+
}
|
|
292
|
+
if (this._persistsState('value')) {
|
|
293
|
+
this._storage.set('value', value ?? null);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
// Drop cached lazy children when the source items change identity.
|
|
298
|
+
effect(() => {
|
|
299
|
+
this.items();
|
|
300
|
+
untracked(() => {
|
|
301
|
+
if (this._loadedChildren().size || this._loadingNodes().size) {
|
|
302
|
+
this._loadedChildren.set(new Map());
|
|
303
|
+
this._loadingNodes.set(new Set());
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
effect(() => {
|
|
308
|
+
const highlighted = this.currentHighlightedValue();
|
|
309
|
+
if (highlighted == null) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const index = this.flatNodes().findIndex(node => node.item.value === highlighted);
|
|
313
|
+
if (index >= 0) {
|
|
314
|
+
this._scroller().scrollToIndex(index);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Create the store and restore persisted state before the first view check
|
|
320
|
+
* (so it also feeds the SSR render for the `cookie` kind without triggering
|
|
321
|
+
* ExpressionChangedAfterItHasBeenChecked).
|
|
322
|
+
*
|
|
323
|
+
* This is the common path: the `storage` config is normally bound before
|
|
324
|
+
* init, so the restore happens pre-render. When the config only becomes
|
|
325
|
+
* available after init (e.g. an async binding, or a dynamic host that applies
|
|
326
|
+
* inputs after creating the component), the constructor effect below picks it
|
|
327
|
+
* up instead — `_initStorage` is idempotent, so at most one of them runs.
|
|
328
|
+
*/
|
|
329
|
+
ngOnInit() {
|
|
330
|
+
const cfg = this.storage();
|
|
331
|
+
if (cfg) {
|
|
332
|
+
this._initStorage(cfg);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/** Create the store and restore persisted state. Runs at most once. */
|
|
336
|
+
_initStorage(cfg) {
|
|
337
|
+
if (this._storage) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// NgnStorage reads platform/document/request tokens, so it needs an
|
|
341
|
+
// injection context.
|
|
342
|
+
this._storage = runInInjectionContext(this.injector, () => new NgnStorage(cfg.key, cfg.kind ?? 'localstorage', {
|
|
343
|
+
expanded: this.expandedValues(),
|
|
344
|
+
value: this.value() ?? null,
|
|
345
|
+
}));
|
|
346
|
+
if (this._persistsState('expanded')) {
|
|
347
|
+
const savedExpanded = this._storage.get('expanded');
|
|
348
|
+
if (Array.isArray(savedExpanded)) {
|
|
349
|
+
this.expandedValues.set(savedExpanded);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (this._persistsState('value')) {
|
|
353
|
+
const savedValue = this._storage.get('value');
|
|
354
|
+
if (savedValue !== undefined) {
|
|
355
|
+
this.value.set(savedValue);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
/** Whether the given state should be persisted (all states when unset). */
|
|
360
|
+
_persistsState(state) {
|
|
361
|
+
const states = this.storage()?.states;
|
|
362
|
+
return !states || states.includes(state);
|
|
363
|
+
}
|
|
364
|
+
/** Derived tri-state for a node's checkbox / aria-checked. */
|
|
365
|
+
nodeState(item) {
|
|
366
|
+
return computeNodeState(item, this._valueSet());
|
|
367
|
+
}
|
|
368
|
+
/** Whether a node can be selected/checked right now. */
|
|
369
|
+
isSelectable(node) {
|
|
370
|
+
const disabled = node.parentDisabled || !!node.item.disabled;
|
|
371
|
+
return this.selectable() && node.item.selectable !== false && !disabled;
|
|
372
|
+
}
|
|
373
|
+
/** Whether a lazy node is currently loading its children. */
|
|
374
|
+
isLoading(item) {
|
|
375
|
+
return this._loadingNodes().has(item.value);
|
|
376
|
+
}
|
|
377
|
+
async toggleExpand(item) {
|
|
378
|
+
const value = item.value;
|
|
379
|
+
const willExpand = !this.expandedValues().includes(value);
|
|
380
|
+
this.expandedValues.update(values => values.includes(value) ? values.filter(v => v !== value) : [...values, value]);
|
|
381
|
+
if (!willExpand) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
this.nodeExpand.emit(item);
|
|
385
|
+
const loader = this.loadChildren();
|
|
386
|
+
if (!item.lazy ||
|
|
387
|
+
!loader ||
|
|
388
|
+
this._loadedChildren().has(value) ||
|
|
389
|
+
this._loadingNodes().has(value)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
this._loadingNodes.update(set => new Set(set).add(value));
|
|
393
|
+
let children;
|
|
394
|
+
try {
|
|
395
|
+
children = await loader(item);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
Logger.warn(`ngn-tree: failed to load children for node "${String(value)}"`, error);
|
|
399
|
+
}
|
|
400
|
+
// Ignore stale results: if items() changed mid-load, the loading state was
|
|
401
|
+
// cleared and this result should be dropped.
|
|
402
|
+
if (children && this._loadingNodes().has(value)) {
|
|
403
|
+
this._loadedChildren.update(map => new Map(map).set(value, children));
|
|
404
|
+
}
|
|
405
|
+
this._loadingNodes.update(set => {
|
|
406
|
+
const next = new Set(set);
|
|
407
|
+
next.delete(value);
|
|
408
|
+
return next;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
onRowClick(node) {
|
|
412
|
+
if (!this.isSelectable(node)) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.onSelect(node.item);
|
|
416
|
+
}
|
|
417
|
+
onSelect(item) {
|
|
418
|
+
const value = item.value;
|
|
419
|
+
if (this.multiple()) {
|
|
420
|
+
if (this.showCheckboxes()) {
|
|
421
|
+
// Cascade tri-state checking; value stays a leaf-only array.
|
|
422
|
+
const shouldCheck = this.nodeState(item) !== 'checked';
|
|
423
|
+
this.value.set(cascadeCheck(item, shouldCheck, this.valueArray()));
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
const current = this.value() ?? [];
|
|
427
|
+
this.value.set((current.includes(value)
|
|
428
|
+
? current.filter(v => v !== value)
|
|
429
|
+
: [...current, value]));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// Single-select always stores a single value (never an array), even when
|
|
434
|
+
// checkboxes are shown.
|
|
435
|
+
this.value.set(value);
|
|
436
|
+
}
|
|
437
|
+
this.itemClicked.emit(value);
|
|
438
|
+
this.touched.set(true);
|
|
439
|
+
}
|
|
440
|
+
scrollToIndex(index) {
|
|
441
|
+
this._scroller().scrollToIndex(index);
|
|
442
|
+
}
|
|
443
|
+
onKeyDown(event) {
|
|
444
|
+
const nodes = this.flatNodes();
|
|
445
|
+
if (!nodes.length) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const enabled = nodes.filter(n => !(n.parentDisabled || n.item.disabled));
|
|
449
|
+
const key = event.key;
|
|
450
|
+
if (key === 'ArrowDown' || key === 'ArrowUp') {
|
|
451
|
+
event.preventDefault();
|
|
452
|
+
event.stopPropagation();
|
|
453
|
+
this._moveHighlight(enabled, key === 'ArrowDown' ? 1 : -1);
|
|
454
|
+
}
|
|
455
|
+
else if (key === 'Home' || key === 'End') {
|
|
456
|
+
event.preventDefault();
|
|
457
|
+
event.stopPropagation();
|
|
458
|
+
const target = key === 'Home' ? enabled[0] : enabled[enabled.length - 1];
|
|
459
|
+
this.currentHighlightedValue.set((target?.item.value ?? null));
|
|
460
|
+
}
|
|
461
|
+
else if (key === 'ArrowRight') {
|
|
462
|
+
event.preventDefault();
|
|
463
|
+
event.stopPropagation();
|
|
464
|
+
this._expandOrEnter(nodes);
|
|
465
|
+
}
|
|
466
|
+
else if (key === 'ArrowLeft') {
|
|
467
|
+
event.preventDefault();
|
|
468
|
+
event.stopPropagation();
|
|
469
|
+
this._collapseOrLeave(nodes);
|
|
470
|
+
}
|
|
471
|
+
else if (key === 'Enter' || key === ' ') {
|
|
472
|
+
const node = this._currentNode(nodes);
|
|
473
|
+
if (node && this.isSelectable(node)) {
|
|
474
|
+
event.preventDefault();
|
|
475
|
+
event.stopImmediatePropagation();
|
|
476
|
+
this.onSelect(node.item);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
else if (key.length === 1 && /\S/.test(key)) {
|
|
480
|
+
this._typeahead(enabled, key);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
_currentNode(nodes) {
|
|
484
|
+
const current = this.currentHighlightedValue();
|
|
485
|
+
return nodes.find(n => n.item.value === current);
|
|
486
|
+
}
|
|
487
|
+
_moveHighlight(enabled, dir) {
|
|
488
|
+
if (!enabled.length) {
|
|
489
|
+
this.currentHighlightedValue.set(null);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const current = this.currentHighlightedValue();
|
|
493
|
+
const idx = enabled.findIndex(n => n.item.value === current);
|
|
494
|
+
let next;
|
|
495
|
+
if (idx === -1) {
|
|
496
|
+
next = dir === 1 ? 0 : enabled.length - 1;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
next = (idx + dir + enabled.length) % enabled.length;
|
|
500
|
+
}
|
|
501
|
+
const target = enabled[next];
|
|
502
|
+
if (target) {
|
|
503
|
+
this.currentHighlightedValue.set(target.item.value);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
_expandOrEnter(nodes) {
|
|
507
|
+
const node = this._currentNode(nodes);
|
|
508
|
+
if (!node) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (node.hasChildren && !node.expanded) {
|
|
512
|
+
void this.toggleExpand(node.item);
|
|
513
|
+
}
|
|
514
|
+
else if (node.hasChildren && node.expanded) {
|
|
515
|
+
const idx = nodes.indexOf(node);
|
|
516
|
+
for (let i = idx + 1; i < nodes.length; i++) {
|
|
517
|
+
const candidate = nodes[i];
|
|
518
|
+
if (!candidate || candidate.level <= node.level) {
|
|
519
|
+
break; // left this node's subtree
|
|
520
|
+
}
|
|
521
|
+
if (!(candidate.parentDisabled || candidate.item.disabled)) {
|
|
522
|
+
this.currentHighlightedValue.set(candidate.item.value);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
_collapseOrLeave(nodes) {
|
|
529
|
+
const node = this._currentNode(nodes);
|
|
530
|
+
if (!node) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (node.hasChildren && node.expanded) {
|
|
534
|
+
void this.toggleExpand(node.item);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// move to parent: previous node with a smaller level
|
|
538
|
+
const idx = nodes.indexOf(node);
|
|
539
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
540
|
+
const ancestor = nodes[i];
|
|
541
|
+
if (!ancestor) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (ancestor.level < node.level) {
|
|
545
|
+
if (!(ancestor.parentDisabled || ancestor.item.disabled)) {
|
|
546
|
+
this.currentHighlightedValue.set(ancestor.item.value);
|
|
547
|
+
}
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
_typeahead(enabled, char) {
|
|
553
|
+
const lower = char.toLowerCase();
|
|
554
|
+
const current = this.currentHighlightedValue();
|
|
555
|
+
const start = enabled.findIndex(n => n.item.value === current);
|
|
556
|
+
for (let offset = 1; offset <= enabled.length; offset++) {
|
|
557
|
+
const node = enabled[(start + offset + enabled.length) % enabled.length];
|
|
558
|
+
if (!node) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
const label = maybeCallback(node.item.label).toLowerCase();
|
|
562
|
+
if (label.startsWith(lower)) {
|
|
563
|
+
this.currentHighlightedValue.set(node.item.value);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
569
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: NgnTree, isStandalone: true, selector: "ngn-tree", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, focussable: { classPropertyName: "focussable", publicName: "focussable", isSignal: true, isRequired: false, transformFunction: null }, virtual: { classPropertyName: "virtual", publicName: "virtual", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, checkbox: { classPropertyName: "checkbox", publicName: "checkbox", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, filterText: { classPropertyName: "filterText", publicName: "filterText", isSignal: true, isRequired: false, transformFunction: null }, expandedValues: { classPropertyName: "expandedValues", publicName: "expandedValues", isSignal: true, isRequired: false, transformFunction: null }, storage: { classPropertyName: "storage", publicName: "storage", isSignal: true, isRequired: false, transformFunction: null }, loadChildren: { classPropertyName: "loadChildren", publicName: "loadChildren", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedValues: "expandedValuesChange", itemClicked: "itemClicked", nodeExpand: "nodeExpand" }, host: { attributes: { "role": "tree" }, listeners: { "keydown": "onKeyDown($event)", "focusout": "currentHighlightedValue.set(null)" }, properties: { "attr.tabIndex": "focussable() ? 0 : null", "attr.aria-activedescendant": "currentHighlightedValue() != null ? inputId() + \"_node_\" + currentHighlightedValue() : null", "attr.aria-multiselectable": "!!multiple()", "attr.aria-label": "label()", "attr.aria-labelledby": "labelledBy()", "id": "inputId()" } }, providers: [provideSelf(NgnTree)], viewQueries: [{ propertyName: "_scroller", first: true, predicate: NgnScroller, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ngn-scroller\n [ptInt]=\"this\"\n [ptDep]=\"'scroller'\"\n [virtual]=\"virtual()\"\n [itemHeight]=\"itemHeight()\"\n [items]=\"flatNodes()\"\n [templateItem]=\"usedItemTemplate\"\n #scroller\n/>\n\n@if (!flatNodes().length) {\n <div role=\"status\">\n <ng-container\n *ngTemplateOutlet=\"emptyTemplate() || defaultEmptyTemplate; context: {}\"\n ></ng-container>\n </div>\n}\n\n<ng-template #usedItemTemplate [ngnTemplate]=\"scroller.templateTypes.item\" let-node>\n @let item = node.item;\n @let disabled = node.parentDisabled || item.disabled;\n @let state = nodeState(item);\n @let selectableNode = isSelectable(node);\n <!--\n Virtual scrolling renders a flat list, so treeitems are not DOM-nested in\n role=\"group\" containers. Per WAI-ARIA, aria-level + aria-setsize +\n aria-posinset are the sanctioned substitute that convey the hierarchy to\n assistive tech. (The `group` CSS class below is visual styling only.)\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n [ngnScrollerItem]=\"node\"\n [id]=\"inputId() + '_node_' + item.value\"\n role=\"treeitem\"\n [attr.aria-level]=\"node.level + 1\"\n [attr.aria-setsize]=\"node.setSize\"\n [attr.aria-posinset]=\"node.posInSet\"\n [attr.aria-expanded]=\"node.hasChildren ? node.expanded : null\"\n [attr.aria-selected]=\"\n !showCheckboxes() && selectableNode ? valueArray().includes(item.value) : null\n \"\n [attr.aria-checked]=\"\n showCheckboxes() && selectableNode\n ? state === 'indeterminate'\n ? 'mixed'\n : state === 'checked'\n ? 'true'\n : 'false'\n : null\n \"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [style.--ngn-tree-level]=\"node.level\"\n [ptInt]=\"this\"\n [ptClass]=\"{\n item: !item.items,\n group: !!item.items,\n 'item-selectable': selectableNode,\n 'item-selected': valueArray().includes(item.value),\n 'item-highlighted': item.value === currentHighlightedValue(),\n 'item-disabled': !!disabled,\n 'item-expanded': node.expanded,\n }\"\n (click)=\"onRowClick(node)\"\n >\n @if (node.hasChildren) {\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [ptInt]=\"this\"\n [ptClass]=\"'toggle'\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"toggleExpand(item); $event.stopPropagation()\"\n >\n <span [ptInt]=\"this\" [ptClass]=\"'toggle-icon'\">\n @if (isLoading(item)) {\n <ngn-spinner [size]=\"14\" />\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'toggle-arrow'\"></span>\n }\n </span>\n </button>\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'toggle-placeholder'\" aria-hidden=\"true\"></span>\n }\n @if (showCheckboxes() && selectableNode) {\n <ngn-checkbox\n [ptInt]=\"this\"\n [ptDep]=\"'item-checkbox'\"\n [allowIndeterminate]=\"true\"\n [value]=\"state === 'indeterminate' ? null : state === 'checked'\"\n aria-hidden=\"true\"\n inert=\"true\"\n />\n } @else if (showCheckboxes() && selectable() && item.selectable !== false) {\n <!-- Disabled node: show the checkbox for consistency, but disabled. -->\n <ngn-checkbox\n [ptInt]=\"this\"\n [ptDep]=\"'item-checkbox'\"\n [allowIndeterminate]=\"true\"\n [value]=\"state === 'indeterminate' ? null : state === 'checked'\"\n [disabled]=\"true\"\n aria-hidden=\"true\"\n inert=\"true\"\n />\n } @else if (showCheckboxes()) {\n <span [ptInt]=\"this\" [ptClass]=\"'item-checkbox-placeholder'\" aria-hidden=\"true\"></span>\n }\n @if (item.template) {\n <ng-container *ngTemplateOutlet=\"item.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n itemTemplate();\n context: {\n $implicit: item,\n level: node.level,\n expanded: node.expanded,\n hasChildren: node.hasChildren,\n }\n \"\n ></ng-container>\n }\n </div>\n</ng-template>\n\n<ng-template #defaultItemTemplate [ngnTemplate]=\"templateTypes.item\" let-item>\n <span [attr.data-testid]=\"item?.testId\" [ptInt]=\"this\" [ptClass]=\"'default-item'\">\n @let label = item?.label;\n @if (label) {\n <span>{{ maybeCallback(label) }}</span>\n } @else {\n <span>​</span>\n }\n </span>\n</ng-template>\n\n<ng-template #defaultEmptyTemplate>\n <span [ptInt]=\"this\" [ptClass]=\"'empty'\" [attr.data-testid]=\"'empty'\">{{\n i18n['tree_noItemsFound']()\n }}</span>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgnScroller, selector: "ngn-scroller, [ngn-scroller]", inputs: ["items", "fieldId", "virtual", "itemHeight", "virtualPadding", "focusable", "fieldSticky"] }, { kind: "directive", type: NgnScrollerItem, selector: "[ngnScrollerItem]", inputs: ["ngnScrollerItem"] }, { kind: "component", type: NgnCheckbox, selector: "ngn-checkbox", inputs: ["iconChecked", "iconUnchecked", "iconIndeterminate", "allowIndeterminate"] }, { kind: "component", type: NgnSpinner, selector: "ngn-spinner", inputs: ["size", "thickness", "centered"] }, { kind: "directive", type: NgnTemplate, selector: "ng-template[ngnTemplate]", inputs: ["ngnTemplate"] }, { kind: "directive", type: NgnPt, selector: "[ptInt]", inputs: ["ptInt", "ptClass", "ptDep"] }] });
|
|
570
|
+
}
|
|
571
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: NgnTree, decorators: [{
|
|
572
|
+
type: Component,
|
|
573
|
+
args: [{ selector: 'ngn-tree', imports: [
|
|
574
|
+
NgTemplateOutlet,
|
|
575
|
+
NgnScroller,
|
|
576
|
+
NgnScrollerItem,
|
|
577
|
+
NgnCheckbox,
|
|
578
|
+
NgnSpinner,
|
|
579
|
+
NgnTemplate,
|
|
580
|
+
NgnPt,
|
|
581
|
+
], providers: [provideSelf(NgnTree)], host: {
|
|
582
|
+
'[attr.tabIndex]': 'focussable() ? 0 : null',
|
|
583
|
+
'(keydown)': 'onKeyDown($event)',
|
|
584
|
+
'(focusout)': 'currentHighlightedValue.set(null)',
|
|
585
|
+
role: 'tree',
|
|
586
|
+
'[attr.aria-activedescendant]': 'currentHighlightedValue() != null ? inputId() + "_node_" + currentHighlightedValue() : null',
|
|
587
|
+
'[attr.aria-multiselectable]': '!!multiple()',
|
|
588
|
+
'[attr.aria-label]': 'label()',
|
|
589
|
+
'[attr.aria-labelledby]': 'labelledBy()',
|
|
590
|
+
'[id]': 'inputId()',
|
|
591
|
+
}, template: "<ngn-scroller\n [ptInt]=\"this\"\n [ptDep]=\"'scroller'\"\n [virtual]=\"virtual()\"\n [itemHeight]=\"itemHeight()\"\n [items]=\"flatNodes()\"\n [templateItem]=\"usedItemTemplate\"\n #scroller\n/>\n\n@if (!flatNodes().length) {\n <div role=\"status\">\n <ng-container\n *ngTemplateOutlet=\"emptyTemplate() || defaultEmptyTemplate; context: {}\"\n ></ng-container>\n </div>\n}\n\n<ng-template #usedItemTemplate [ngnTemplate]=\"scroller.templateTypes.item\" let-node>\n @let item = node.item;\n @let disabled = node.parentDisabled || item.disabled;\n @let state = nodeState(item);\n @let selectableNode = isSelectable(node);\n <!--\n Virtual scrolling renders a flat list, so treeitems are not DOM-nested in\n role=\"group\" containers. Per WAI-ARIA, aria-level + aria-setsize +\n aria-posinset are the sanctioned substitute that convey the hierarchy to\n assistive tech. (The `group` CSS class below is visual styling only.)\n -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events, @angular-eslint/template/interactive-supports-focus -->\n <div\n [ngnScrollerItem]=\"node\"\n [id]=\"inputId() + '_node_' + item.value\"\n role=\"treeitem\"\n [attr.aria-level]=\"node.level + 1\"\n [attr.aria-setsize]=\"node.setSize\"\n [attr.aria-posinset]=\"node.posInSet\"\n [attr.aria-expanded]=\"node.hasChildren ? node.expanded : null\"\n [attr.aria-selected]=\"\n !showCheckboxes() && selectableNode ? valueArray().includes(item.value) : null\n \"\n [attr.aria-checked]=\"\n showCheckboxes() && selectableNode\n ? state === 'indeterminate'\n ? 'mixed'\n : state === 'checked'\n ? 'true'\n : 'false'\n : null\n \"\n [attr.aria-disabled]=\"disabled ? 'true' : null\"\n [style.--ngn-tree-level]=\"node.level\"\n [ptInt]=\"this\"\n [ptClass]=\"{\n item: !item.items,\n group: !!item.items,\n 'item-selectable': selectableNode,\n 'item-selected': valueArray().includes(item.value),\n 'item-highlighted': item.value === currentHighlightedValue(),\n 'item-disabled': !!disabled,\n 'item-expanded': node.expanded,\n }\"\n (click)=\"onRowClick(node)\"\n >\n @if (node.hasChildren) {\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n [ptInt]=\"this\"\n [ptClass]=\"'toggle'\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"toggleExpand(item); $event.stopPropagation()\"\n >\n <span [ptInt]=\"this\" [ptClass]=\"'toggle-icon'\">\n @if (isLoading(item)) {\n <ngn-spinner [size]=\"14\" />\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'toggle-arrow'\"></span>\n }\n </span>\n </button>\n } @else {\n <span [ptInt]=\"this\" [ptClass]=\"'toggle-placeholder'\" aria-hidden=\"true\"></span>\n }\n @if (showCheckboxes() && selectableNode) {\n <ngn-checkbox\n [ptInt]=\"this\"\n [ptDep]=\"'item-checkbox'\"\n [allowIndeterminate]=\"true\"\n [value]=\"state === 'indeterminate' ? null : state === 'checked'\"\n aria-hidden=\"true\"\n inert=\"true\"\n />\n } @else if (showCheckboxes() && selectable() && item.selectable !== false) {\n <!-- Disabled node: show the checkbox for consistency, but disabled. -->\n <ngn-checkbox\n [ptInt]=\"this\"\n [ptDep]=\"'item-checkbox'\"\n [allowIndeterminate]=\"true\"\n [value]=\"state === 'indeterminate' ? null : state === 'checked'\"\n [disabled]=\"true\"\n aria-hidden=\"true\"\n inert=\"true\"\n />\n } @else if (showCheckboxes()) {\n <span [ptInt]=\"this\" [ptClass]=\"'item-checkbox-placeholder'\" aria-hidden=\"true\"></span>\n }\n @if (item.template) {\n <ng-container *ngTemplateOutlet=\"item.template; context: { $implicit: item }\"></ng-container>\n } @else {\n <ng-container\n *ngTemplateOutlet=\"\n itemTemplate();\n context: {\n $implicit: item,\n level: node.level,\n expanded: node.expanded,\n hasChildren: node.hasChildren,\n }\n \"\n ></ng-container>\n }\n </div>\n</ng-template>\n\n<ng-template #defaultItemTemplate [ngnTemplate]=\"templateTypes.item\" let-item>\n <span [attr.data-testid]=\"item?.testId\" [ptInt]=\"this\" [ptClass]=\"'default-item'\">\n @let label = item?.label;\n @if (label) {\n <span>{{ maybeCallback(label) }}</span>\n } @else {\n <span>​</span>\n }\n </span>\n</ng-template>\n\n<ng-template #defaultEmptyTemplate>\n <span [ptInt]=\"this\" [ptClass]=\"'empty'\" [attr.data-testid]=\"'empty'\">{{\n i18n['tree_noItemsFound']()\n }}</span>\n</ng-template>\n" }]
|
|
592
|
+
}], ctorParameters: () => [], propDecorators: { _scroller: [{ type: i0.ViewChild, args: [i0.forwardRef(() => NgnScroller), { isSignal: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], focussable: [{ type: i0.Input, args: [{ isSignal: true, alias: "focussable", required: false }] }], virtual: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtual", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], checkbox: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkbox", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], filterText: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterText", required: false }] }], expandedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandedValues", required: false }] }, { type: i0.Output, args: ["expandedValuesChange"] }], storage: [{ type: i0.Input, args: [{ isSignal: true, alias: "storage", required: false }] }], loadChildren: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadChildren", required: false }] }], itemClicked: [{ type: i0.Output, args: ["itemClicked"] }], nodeExpand: [{ type: i0.Output, args: ["nodeExpand"] }] } });
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Generated bundle index. Do not edit.
|
|
596
|
+
*/
|
|
597
|
+
|
|
598
|
+
export { NgnTree, TreeTemplates, applyLoadedChildren, cascadeCheck, collectBranchValues, collectLeafValues, computeNodeState, flattenTree };
|
|
599
|
+
//# sourceMappingURL=ngneers-controls-tree.mjs.map
|