@mozaic-ds/angular 2.0.33 → 2.0.34
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.
|
@@ -11433,7 +11433,8 @@ class TreeStateService {
|
|
|
11433
11433
|
this.toggleExpanded(node.id);
|
|
11434
11434
|
if (!wasExpanded) {
|
|
11435
11435
|
const loadFn = this.loadChildrenFn();
|
|
11436
|
-
|
|
11436
|
+
const resolved = this.findNode(node.id) ?? node;
|
|
11437
|
+
if (loadFn && resolved.children === undefined) {
|
|
11437
11438
|
this.addLoading(node.id);
|
|
11438
11439
|
return loadFn(node).pipe(take(1), tap((children) => {
|
|
11439
11440
|
this.patchChildren(node.id, children);
|
|
@@ -11495,6 +11496,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
11495
11496
|
}] });
|
|
11496
11497
|
|
|
11497
11498
|
class TreeSelectionService {
|
|
11499
|
+
stateService = inject(TreeStateService);
|
|
11498
11500
|
selectedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
|
|
11499
11501
|
selectionMode = signal('none', ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
|
|
11500
11502
|
rootNodes = signal([], ...(ngDevMode ? [{ debugName: "rootNodes" }] : /* istanbul ignore next */ []));
|
|
@@ -11515,40 +11517,62 @@ class TreeSelectionService {
|
|
|
11515
11517
|
return true;
|
|
11516
11518
|
return ancestors.some((a) => a.disabled);
|
|
11517
11519
|
}
|
|
11520
|
+
/**
|
|
11521
|
+
* A node is indeterminate when it has loaded children AND
|
|
11522
|
+
* some (but not all) of its leaf-level descendants are selected.
|
|
11523
|
+
*/
|
|
11518
11524
|
isIndeterminate(node) {
|
|
11519
|
-
|
|
11525
|
+
const resolved = this._resolveNode(node);
|
|
11526
|
+
const leaves = this._collectLeafIds(resolved);
|
|
11527
|
+
if (leaves.length === 0)
|
|
11520
11528
|
return false;
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
const enabledIds = this._collectEnabledDescendantIds(node);
|
|
11524
|
-
if (enabledIds.length === 0)
|
|
11525
|
-
return false;
|
|
11526
|
-
const selectedCount = enabledIds.filter((id) => this.selectedIds().has(id)).length;
|
|
11527
|
-
return selectedCount > 0 && selectedCount < enabledIds.length;
|
|
11529
|
+
const selectedCount = leaves.filter((id) => this.selectedIds().has(id)).length;
|
|
11530
|
+
return selectedCount > 0 && selectedCount < leaves.length;
|
|
11528
11531
|
}
|
|
11532
|
+
/**
|
|
11533
|
+
* A node is checked when:
|
|
11534
|
+
* - Leaf node (no children or children not loaded): its ID is in selectedIds
|
|
11535
|
+
* - Parent with loaded children: ALL leaf descendants are selected
|
|
11536
|
+
*/
|
|
11529
11537
|
isCheckedComputed(node) {
|
|
11530
11538
|
if (this.selectionMode() === 'radio') {
|
|
11531
11539
|
return this.selectedIds().has(node.id);
|
|
11532
11540
|
}
|
|
11533
|
-
|
|
11541
|
+
const resolved = this._resolveNode(node);
|
|
11542
|
+
const leaves = this._collectLeafIds(resolved);
|
|
11543
|
+
// Node is a leaf or has no loaded children
|
|
11544
|
+
if (leaves.length === 0) {
|
|
11534
11545
|
return this.selectedIds().has(node.id);
|
|
11535
11546
|
}
|
|
11536
|
-
|
|
11537
|
-
if (enabledIds.length === 0)
|
|
11538
|
-
return this.selectedIds().has(node.id);
|
|
11539
|
-
return enabledIds.every((id) => this.selectedIds().has(id));
|
|
11547
|
+
return leaves.every((id) => this.selectedIds().has(id));
|
|
11540
11548
|
}
|
|
11549
|
+
/**
|
|
11550
|
+
* Select a node: add all leaf descendant IDs (or the node's own ID if leaf).
|
|
11551
|
+
*/
|
|
11541
11552
|
selectNode(node) {
|
|
11542
11553
|
if (this.selectionMode() === 'radio') {
|
|
11543
11554
|
this.selectedIds.set(new Set([node.id]));
|
|
11544
11555
|
return;
|
|
11545
11556
|
}
|
|
11557
|
+
const resolved = this._resolveNode(node);
|
|
11546
11558
|
this.selectedIds.update((current) => {
|
|
11547
11559
|
const next = new Set(current);
|
|
11548
|
-
|
|
11560
|
+
const leaves = this._collectLeafIds(resolved);
|
|
11561
|
+
if (leaves.length === 0) {
|
|
11562
|
+
// Node is a leaf
|
|
11563
|
+
if (!node.disabled)
|
|
11564
|
+
next.add(node.id);
|
|
11565
|
+
}
|
|
11566
|
+
else {
|
|
11567
|
+
leaves.forEach((id) => next.add(id));
|
|
11568
|
+
}
|
|
11549
11569
|
return next;
|
|
11550
11570
|
});
|
|
11551
11571
|
}
|
|
11572
|
+
/**
|
|
11573
|
+
* Deselect a node: remove all leaf descendant IDs (or the node's own ID if leaf).
|
|
11574
|
+
* Also remove the node's own ID in case it's in the set from external sources.
|
|
11575
|
+
*/
|
|
11552
11576
|
deselectNode(node) {
|
|
11553
11577
|
if (this.selectionMode() === 'radio') {
|
|
11554
11578
|
this.selectedIds.update((current) => {
|
|
@@ -11558,9 +11582,13 @@ class TreeSelectionService {
|
|
|
11558
11582
|
});
|
|
11559
11583
|
return;
|
|
11560
11584
|
}
|
|
11585
|
+
const resolved = this._resolveNode(node);
|
|
11561
11586
|
this.selectedIds.update((current) => {
|
|
11562
11587
|
const next = new Set(current);
|
|
11563
|
-
|
|
11588
|
+
// Always remove the node itself
|
|
11589
|
+
next.delete(node.id);
|
|
11590
|
+
// Remove all known descendants (leaf + parents)
|
|
11591
|
+
this._collectAllDescendantIds(resolved).forEach((id) => next.delete(id));
|
|
11564
11592
|
return next;
|
|
11565
11593
|
});
|
|
11566
11594
|
}
|
|
@@ -11572,30 +11600,83 @@ class TreeSelectionService {
|
|
|
11572
11600
|
this.selectNode(node);
|
|
11573
11601
|
}
|
|
11574
11602
|
}
|
|
11575
|
-
|
|
11576
|
-
|
|
11603
|
+
/**
|
|
11604
|
+
* Called when children are loaded for a node.
|
|
11605
|
+
* - If parent ID was in selectedIds: replace it with children's leaf IDs (propagate down).
|
|
11606
|
+
* - If parent ID was NOT in selectedIds: remove any orphan children IDs that
|
|
11607
|
+
* may have been left from a prior bulk operation (e.g. Select All → deselect parent).
|
|
11608
|
+
*/
|
|
11609
|
+
propagateOnChildrenLoaded(parentId) {
|
|
11610
|
+
const parent = this._resolveNode({ id: parentId });
|
|
11611
|
+
if (!parent || !parent.children || parent.children.length === 0)
|
|
11612
|
+
return;
|
|
11613
|
+
const selected = this.selectedIds();
|
|
11614
|
+
const next = new Set(selected);
|
|
11615
|
+
if (selected.has(parentId)) {
|
|
11616
|
+
// Parent was selected → replace with leaf children
|
|
11617
|
+
next.delete(parentId);
|
|
11618
|
+
for (const child of parent.children) {
|
|
11619
|
+
if (!child.disabled) {
|
|
11620
|
+
const childLeaves = this._collectLeafIds(this._resolveNode(child));
|
|
11621
|
+
if (childLeaves.length === 0) {
|
|
11622
|
+
next.add(child.id);
|
|
11623
|
+
}
|
|
11624
|
+
else {
|
|
11625
|
+
childLeaves.forEach((id) => next.add(id));
|
|
11626
|
+
}
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11629
|
+
}
|
|
11630
|
+
else {
|
|
11631
|
+
// Parent was NOT selected → clean up any orphan descendant IDs
|
|
11632
|
+
this._collectAllDescendantIds(parent).forEach((id) => next.delete(id));
|
|
11633
|
+
}
|
|
11634
|
+
this.selectedIds.set(next);
|
|
11635
|
+
}
|
|
11636
|
+
/**
|
|
11637
|
+
* Collect leaf-level IDs (terminal nodes that have no loaded children).
|
|
11638
|
+
* Returns empty array if the node itself is a leaf.
|
|
11639
|
+
*/
|
|
11640
|
+
_collectLeafIds(node) {
|
|
11641
|
+
const resolved = this._resolveNode(node);
|
|
11642
|
+
if (!resolved.children || resolved.children.length === 0) {
|
|
11577
11643
|
return [];
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11644
|
+
}
|
|
11645
|
+
const ids = [];
|
|
11646
|
+
for (const child of resolved.children) {
|
|
11647
|
+
if (child.disabled)
|
|
11648
|
+
continue;
|
|
11649
|
+
const resolvedChild = this._resolveNode(child);
|
|
11650
|
+
const childLeaves = this._collectLeafIds(resolvedChild);
|
|
11651
|
+
if (childLeaves.length === 0) {
|
|
11652
|
+
// This child is a leaf
|
|
11653
|
+
ids.push(resolvedChild.id);
|
|
11654
|
+
}
|
|
11655
|
+
else {
|
|
11656
|
+
ids.push(...childLeaves);
|
|
11582
11657
|
}
|
|
11583
11658
|
}
|
|
11584
11659
|
return ids;
|
|
11585
11660
|
}
|
|
11586
|
-
|
|
11661
|
+
/**
|
|
11662
|
+
* Collect ALL descendant IDs (both parents and leaves) for thorough cleanup on deselect.
|
|
11663
|
+
*/
|
|
11664
|
+
_collectAllDescendantIds(node) {
|
|
11665
|
+
const resolved = this._resolveNode(node);
|
|
11587
11666
|
const ids = [];
|
|
11588
|
-
if (!
|
|
11667
|
+
if (!resolved.children)
|
|
11589
11668
|
return ids;
|
|
11590
|
-
for (const child of
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
}
|
|
11669
|
+
for (const child of resolved.children) {
|
|
11670
|
+
const resolvedChild = this._resolveNode(child);
|
|
11671
|
+
ids.push(resolvedChild.id);
|
|
11672
|
+
ids.push(...this._collectAllDescendantIds(resolvedChild));
|
|
11595
11673
|
}
|
|
11596
11674
|
return ids;
|
|
11597
11675
|
}
|
|
11598
11676
|
allSelectedIds = computed(() => new Set(this.selectedIds()), ...(ngDevMode ? [{ debugName: "allSelectedIds" }] : /* istanbul ignore next */ []));
|
|
11677
|
+
_resolveNode(node) {
|
|
11678
|
+
return this.stateService.findNode(node.id) ?? node;
|
|
11679
|
+
}
|
|
11599
11680
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: TreeSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
11600
11681
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: TreeSelectionService });
|
|
11601
11682
|
}
|
|
@@ -11826,7 +11907,7 @@ class MozTreeNodeComponent {
|
|
|
11826
11907
|
return null;
|
|
11827
11908
|
}
|
|
11828
11909
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: MozTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11829
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: MozTreeNodeComponent, isStandalone: true, selector: "moz-tree-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, indentSize: { classPropertyName: "indentSize", publicName: "indentSize", isSignal: true, isRequired: false, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, nodeTemplates: { classPropertyName: "nodeTemplates", publicName: "nodeTemplates", isSignal: true, isRequired: false, transformFunction: null }, loadChildren: { classPropertyName: "loadChildren", publicName: "loadChildren", isSignal: true, isRequired: false, transformFunction: null }, ancestors: { classPropertyName: "ancestors", publicName: "ancestors", isSignal: true, isRequired: false, transformFunction: null }, flat: { classPropertyName: "flat", publicName: "flat", isSignal: true, isRequired: false, transformFunction: null }, noResultText: { classPropertyName: "noResultText", publicName: "noResultText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandChange: "expandChange", selectionChange: "selectionChange" }, host: { styleAttribute: "display: block" }, ngImport: i0, template: "<li\n class=\"tree-node\"\n [class.tree-node--selected]=\"isSelected()\"\n [class.tree-node--disabled]=\"isDisabled()\"\n [class.tree-node--focused]=\"isFocused()\"\n role=\"treeitem\"\n [id]=\"'tree-node-' + node().id\"\n [attr.aria-level]=\"depth() + 1\"\n [attr.aria-expanded]=\"hasChildren() ? isExpanded() : null\"\n [attr.aria-selected]=\"selectionMode() !== 'none' ? isSelected() : null\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n [attr.data-tree-node-id]=\"node().id\"\n [tabindex]=\"isFocused() ? 0 : -1\"\n>\n <div\n class=\"tree-node__header\"\n [class.tree-node__header--expandable]=\"hasChildren()\"\n (click)=\"onHeaderClick()\"\n >\n <div class=\"tree-node__indent\" [style.width.px]=\"indentPx()\"></div>\n\n <span class=\"tree-node__chevron\" [class.tree-node__chevron--leaf]=\"!hasChildren()\">\n
|
|
11910
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: MozTreeNodeComponent, isStandalone: true, selector: "moz-tree-node", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, indentSize: { classPropertyName: "indentSize", publicName: "indentSize", isSignal: true, isRequired: false, transformFunction: null }, nodeTemplate: { classPropertyName: "nodeTemplate", publicName: "nodeTemplate", isSignal: true, isRequired: false, transformFunction: null }, nodeTemplates: { classPropertyName: "nodeTemplates", publicName: "nodeTemplates", isSignal: true, isRequired: false, transformFunction: null }, loadChildren: { classPropertyName: "loadChildren", publicName: "loadChildren", isSignal: true, isRequired: false, transformFunction: null }, ancestors: { classPropertyName: "ancestors", publicName: "ancestors", isSignal: true, isRequired: false, transformFunction: null }, flat: { classPropertyName: "flat", publicName: "flat", isSignal: true, isRequired: false, transformFunction: null }, noResultText: { classPropertyName: "noResultText", publicName: "noResultText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandChange: "expandChange", selectionChange: "selectionChange" }, host: { styleAttribute: "display: block" }, ngImport: i0, template: "<li\n class=\"tree-node\"\n [class.tree-node--selected]=\"isSelected()\"\n [class.tree-node--disabled]=\"isDisabled()\"\n [class.tree-node--focused]=\"isFocused()\"\n role=\"treeitem\"\n [id]=\"'tree-node-' + node().id\"\n [attr.aria-level]=\"depth() + 1\"\n [attr.aria-expanded]=\"hasChildren() ? isExpanded() : null\"\n [attr.aria-selected]=\"selectionMode() !== 'none' ? isSelected() : null\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n [attr.data-tree-node-id]=\"node().id\"\n [tabindex]=\"isFocused() ? 0 : -1\"\n>\n <div\n class=\"tree-node__header\"\n [class.tree-node__header--expandable]=\"hasChildren()\"\n (click)=\"onHeaderClick()\"\n >\n <div class=\"tree-node__indent\" [style.width.px]=\"indentPx()\"></div>\n\n <div class=\"tree-node__row\">\n <span class=\"tree-node__chevron\" [class.tree-node__chevron--leaf]=\"!hasChildren()\">\n @if (hasChildren()) { @if (isExpanded()) {\n <ChevronDown20 />\n } @else {\n <ChevronRight20 />\n } }\n </span>\n\n <div class=\"tree-node__content\">\n @if (resolvedTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"resolvedTemplate()!\"\n [ngTemplateOutletContext]=\"templateContext()\"\n />\n } @else {\n <span class=\"tree-node__label\">{{ node().id }}</span>\n }\n </div>\n\n @if (selectionMode() === 'checkbox') {\n <moz-checkbox\n class=\"tree-node__selection\"\n [id]=\"'tree-checkbox-' + node().id\"\n [indeterminate]=\"isIndeterminate()\"\n [disabled]=\"isDisabled()\"\n [ngModel]=\"isSelected()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @else if (selectionMode() === 'radio') {\n <moz-radio\n class=\"tree-node__selection\"\n [id]=\"'tree-radio-' + node().id\"\n [name]=\"radioName\"\n [disabled]=\"isDisabled()\"\n [ngModel]=\"isSelected()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onRadioChange($event)\"\n />\n }\n </div>\n </div>\n\n @if (isExpanded() && !flat()) {\n <ul class=\"tree-node__children\" role=\"group\">\n @if (isLoading()) {\n <li class=\"tree-node__loading\" role=\"presentation\">\n <moz-loader size=\"s\" [appearance]=\"'accent'\" />\n </li>\n } @else { @if (resolvedChildren().length === 0) {\n <li class=\"tree-node__empty\" role=\"presentation\">\n <span>{{ noResultText() }}</span>\n </li>\n } @for (child of resolvedChildren(); track child.id) {\n <moz-tree-node\n [node]=\"child\"\n [depth]=\"depth() + 1\"\n [selectionMode]=\"selectionMode()\"\n [indentSize]=\"indentSize()\"\n [nodeTemplate]=\"nodeTemplate()\"\n [nodeTemplates]=\"nodeTemplates()\"\n [loadChildren]=\"loadChildren()\"\n [ancestors]=\"ancestorsWithSelf()\"\n (expandChange)=\"expandChange.emit($event)\"\n (selectionChange)=\"selectionChange.emit($event)\"\n />\n } }\n </ul>\n }\n</li>\n", styles: [".tree-node{list-style:none;margin:0;padding:0;outline:none;display:flex;flex-direction:column;gap:4px}.tree-node--selected>.tree-node__header>.tree-node__row{background:var(--color-background-accent);border-radius:var(--border-radius-m, 8px)}.tree-node--disabled{pointer-events:none;color:var(--color-text-disabled)}.tree-node--disabled>.tree-node__header{opacity:.5}.tree-node--focused>.tree-node__header>.tree-node__row,.tree-node:focus-visible>.tree-node__header>.tree-node__row{outline:2px solid var(--color-background-accent-inverse);outline-offset:-2px;border-radius:var(--border-radius-m, 8px)}.tree-node__header{display:flex;align-items:center;width:100%;min-height:57px;-webkit-user-select:none;user-select:none}.tree-node__header--expandable{cursor:pointer}.tree-node__row{display:flex;align-items:center;height:57px;flex:1;min-width:0;border-radius:var(--border-radius-m, 8px);transition:background .15s ease}.tree-node__header:hover>.tree-node__row{background:var(--color-background-secondary)}.tree-node--selected>.tree-node__header:hover>.tree-node__row{background:var(--color-background-accent)}.tree-node__indent{flex-shrink:0}.tree-node__chevron{display:flex;align-items:center;justify-content:center;flex-shrink:0;padding-left:16px;color:var(--color-text-primary)}.tree-node__chevron--leaf{width:0;padding-left:0;overflow:hidden}.tree-node__content{flex:1;min-width:0;display:flex;align-items:center;padding:4px 8px}.tree-node__selection{flex-shrink:0;margin-left:auto;margin-right:8px}.tree-node__label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-node__children{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:4px}.tree-node__loading{display:flex;align-items:center;justify-content:center;padding:8px 16px}.tree-node__empty{display:flex;align-items:center;padding:8px 16px;font-size:var(--font-size-s, 14px);color:var(--color-text-secondary)}\n"], dependencies: [{ kind: "component", type: MozTreeNodeComponent, selector: "moz-tree-node", inputs: ["node", "depth", "selectionMode", "indentSize", "nodeTemplate", "nodeTemplates", "loadChildren", "ancestors", "flat", "noResultText"], outputs: ["expandChange", "selectionChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MozCheckboxComponent, selector: "moz-checkbox", inputs: ["id", "name", "label", "indeterminate", "isInvalid", "disabled", "indented"] }, { kind: "component", type: MozRadioComponent, selector: "moz-radio", inputs: ["id", "name", "label", "isInvalid", "disabled"] }, { kind: "component", type: MozLoaderComponent, selector: "moz-loader", inputs: ["appearance", "size", "text"] }, { kind: "component", type: ChevronDown20, selector: "ChevronDown20", inputs: ["hostClass"] }, { kind: "component", type: ChevronRight20, selector: "ChevronRight20", inputs: ["hostClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11830
11911
|
}
|
|
11831
11912
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: MozTreeNodeComponent, decorators: [{
|
|
11832
11913
|
type: Component,
|
|
@@ -11838,7 +11919,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
11838
11919
|
MozLoaderComponent,
|
|
11839
11920
|
ChevronDown20,
|
|
11840
11921
|
ChevronRight20,
|
|
11841
|
-
], template: "<li\n class=\"tree-node\"\n [class.tree-node--selected]=\"isSelected()\"\n [class.tree-node--disabled]=\"isDisabled()\"\n [class.tree-node--focused]=\"isFocused()\"\n role=\"treeitem\"\n [id]=\"'tree-node-' + node().id\"\n [attr.aria-level]=\"depth() + 1\"\n [attr.aria-expanded]=\"hasChildren() ? isExpanded() : null\"\n [attr.aria-selected]=\"selectionMode() !== 'none' ? isSelected() : null\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n [attr.data-tree-node-id]=\"node().id\"\n [tabindex]=\"isFocused() ? 0 : -1\"\n>\n <div\n class=\"tree-node__header\"\n [class.tree-node__header--expandable]=\"hasChildren()\"\n (click)=\"onHeaderClick()\"\n >\n <div class=\"tree-node__indent\" [style.width.px]=\"indentPx()\"></div>\n\n <span class=\"tree-node__chevron\" [class.tree-node__chevron--leaf]=\"!hasChildren()\">\n
|
|
11922
|
+
], template: "<li\n class=\"tree-node\"\n [class.tree-node--selected]=\"isSelected()\"\n [class.tree-node--disabled]=\"isDisabled()\"\n [class.tree-node--focused]=\"isFocused()\"\n role=\"treeitem\"\n [id]=\"'tree-node-' + node().id\"\n [attr.aria-level]=\"depth() + 1\"\n [attr.aria-expanded]=\"hasChildren() ? isExpanded() : null\"\n [attr.aria-selected]=\"selectionMode() !== 'none' ? isSelected() : null\"\n [attr.aria-disabled]=\"isDisabled() || null\"\n [attr.data-tree-node-id]=\"node().id\"\n [tabindex]=\"isFocused() ? 0 : -1\"\n>\n <div\n class=\"tree-node__header\"\n [class.tree-node__header--expandable]=\"hasChildren()\"\n (click)=\"onHeaderClick()\"\n >\n <div class=\"tree-node__indent\" [style.width.px]=\"indentPx()\"></div>\n\n <div class=\"tree-node__row\">\n <span class=\"tree-node__chevron\" [class.tree-node__chevron--leaf]=\"!hasChildren()\">\n @if (hasChildren()) { @if (isExpanded()) {\n <ChevronDown20 />\n } @else {\n <ChevronRight20 />\n } }\n </span>\n\n <div class=\"tree-node__content\">\n @if (resolvedTemplate()) {\n <ng-container\n [ngTemplateOutlet]=\"resolvedTemplate()!\"\n [ngTemplateOutletContext]=\"templateContext()\"\n />\n } @else {\n <span class=\"tree-node__label\">{{ node().id }}</span>\n }\n </div>\n\n @if (selectionMode() === 'checkbox') {\n <moz-checkbox\n class=\"tree-node__selection\"\n [id]=\"'tree-checkbox-' + node().id\"\n [indeterminate]=\"isIndeterminate()\"\n [disabled]=\"isDisabled()\"\n [ngModel]=\"isSelected()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onCheckboxChange($event)\"\n />\n } @else if (selectionMode() === 'radio') {\n <moz-radio\n class=\"tree-node__selection\"\n [id]=\"'tree-radio-' + node().id\"\n [name]=\"radioName\"\n [disabled]=\"isDisabled()\"\n [ngModel]=\"isSelected()\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"onRadioChange($event)\"\n />\n }\n </div>\n </div>\n\n @if (isExpanded() && !flat()) {\n <ul class=\"tree-node__children\" role=\"group\">\n @if (isLoading()) {\n <li class=\"tree-node__loading\" role=\"presentation\">\n <moz-loader size=\"s\" [appearance]=\"'accent'\" />\n </li>\n } @else { @if (resolvedChildren().length === 0) {\n <li class=\"tree-node__empty\" role=\"presentation\">\n <span>{{ noResultText() }}</span>\n </li>\n } @for (child of resolvedChildren(); track child.id) {\n <moz-tree-node\n [node]=\"child\"\n [depth]=\"depth() + 1\"\n [selectionMode]=\"selectionMode()\"\n [indentSize]=\"indentSize()\"\n [nodeTemplate]=\"nodeTemplate()\"\n [nodeTemplates]=\"nodeTemplates()\"\n [loadChildren]=\"loadChildren()\"\n [ancestors]=\"ancestorsWithSelf()\"\n (expandChange)=\"expandChange.emit($event)\"\n (selectionChange)=\"selectionChange.emit($event)\"\n />\n } }\n </ul>\n }\n</li>\n", styles: [".tree-node{list-style:none;margin:0;padding:0;outline:none;display:flex;flex-direction:column;gap:4px}.tree-node--selected>.tree-node__header>.tree-node__row{background:var(--color-background-accent);border-radius:var(--border-radius-m, 8px)}.tree-node--disabled{pointer-events:none;color:var(--color-text-disabled)}.tree-node--disabled>.tree-node__header{opacity:.5}.tree-node--focused>.tree-node__header>.tree-node__row,.tree-node:focus-visible>.tree-node__header>.tree-node__row{outline:2px solid var(--color-background-accent-inverse);outline-offset:-2px;border-radius:var(--border-radius-m, 8px)}.tree-node__header{display:flex;align-items:center;width:100%;min-height:57px;-webkit-user-select:none;user-select:none}.tree-node__header--expandable{cursor:pointer}.tree-node__row{display:flex;align-items:center;height:57px;flex:1;min-width:0;border-radius:var(--border-radius-m, 8px);transition:background .15s ease}.tree-node__header:hover>.tree-node__row{background:var(--color-background-secondary)}.tree-node--selected>.tree-node__header:hover>.tree-node__row{background:var(--color-background-accent)}.tree-node__indent{flex-shrink:0}.tree-node__chevron{display:flex;align-items:center;justify-content:center;flex-shrink:0;padding-left:16px;color:var(--color-text-primary)}.tree-node__chevron--leaf{width:0;padding-left:0;overflow:hidden}.tree-node__content{flex:1;min-width:0;display:flex;align-items:center;padding:4px 8px}.tree-node__selection{flex-shrink:0;margin-left:auto;margin-right:8px}.tree-node__label{font-size:var(--font-size-m, 16px);color:var(--color-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tree-node__children{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:4px}.tree-node__loading{display:flex;align-items:center;justify-content:center;padding:8px 16px}.tree-node__empty{display:flex;align-items:center;padding:8px 16px;font-size:var(--font-size-s, 14px);color:var(--color-text-secondary)}\n"] }]
|
|
11842
11923
|
}], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], depth: [{ type: i0.Input, args: [{ isSignal: true, alias: "depth", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], indentSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "indentSize", required: false }] }], nodeTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeTemplate", required: false }] }], nodeTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeTemplates", required: false }] }], loadChildren: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadChildren", required: false }] }], ancestors: [{ type: i0.Input, args: [{ isSignal: true, alias: "ancestors", required: false }] }], flat: [{ type: i0.Input, args: [{ isSignal: true, alias: "flat", required: false }] }], noResultText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultText", required: false }] }], expandChange: [{ type: i0.Output, args: ["expandChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
|
|
11843
11924
|
|
|
11844
11925
|
class MozTreeNodeTemplateDirective {
|
|
@@ -11907,9 +11988,44 @@ class MozTreeComponent {
|
|
|
11907
11988
|
effect(() => {
|
|
11908
11989
|
this.stateService.loadChildrenFn.set(this.loadChildren());
|
|
11909
11990
|
});
|
|
11991
|
+
// When children load for a node whose ID is in selectedIds,
|
|
11992
|
+
// replace the parent ID with its children's leaf IDs.
|
|
11993
|
+
let prevChildMap = new Map();
|
|
11994
|
+
effect(() => {
|
|
11995
|
+
const nodes = this.stateService.internalNodes();
|
|
11996
|
+
if (this.selectionService.selectionMode() !== 'checkbox')
|
|
11997
|
+
return;
|
|
11998
|
+
const newChildMap = new Map();
|
|
11999
|
+
const newlyLoadedParentIds = [];
|
|
12000
|
+
const visit = (list) => {
|
|
12001
|
+
for (const n of list) {
|
|
12002
|
+
const hadChildren = prevChildMap.get(n.id) ?? false;
|
|
12003
|
+
const hasChildrenNow = !!(n.children && n.children.length > 0);
|
|
12004
|
+
newChildMap.set(n.id, hasChildrenNow);
|
|
12005
|
+
if (!hadChildren && hasChildrenNow) {
|
|
12006
|
+
newlyLoadedParentIds.push(n.id);
|
|
12007
|
+
}
|
|
12008
|
+
if (n.children)
|
|
12009
|
+
visit(n.children);
|
|
12010
|
+
}
|
|
12011
|
+
};
|
|
12012
|
+
visit(nodes);
|
|
12013
|
+
prevChildMap = newChildMap;
|
|
12014
|
+
for (const parentId of newlyLoadedParentIds) {
|
|
12015
|
+
this.selectionService.propagateOnChildrenLoaded(parentId);
|
|
12016
|
+
}
|
|
12017
|
+
if (newlyLoadedParentIds.length > 0) {
|
|
12018
|
+
this.selectionChange.emit(this.selectionService.allSelectedIds());
|
|
12019
|
+
}
|
|
12020
|
+
});
|
|
11910
12021
|
}
|
|
11911
12022
|
onTreeKeydown(event) {
|
|
12023
|
+
const prevSelection = this.selectionService.allSelectedIds();
|
|
11912
12024
|
this.keyboardService.handleKeydown(event);
|
|
12025
|
+
const newSelection = this.selectionService.allSelectedIds();
|
|
12026
|
+
if (prevSelection !== newSelection) {
|
|
12027
|
+
this.selectionChange.emit(newSelection);
|
|
12028
|
+
}
|
|
11913
12029
|
}
|
|
11914
12030
|
onTreeFocus() {
|
|
11915
12031
|
this.keyboardService.initFocus();
|