@mozaic-ds/angular 2.0.32 → 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.
|
@@ -7,7 +7,7 @@ import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/fo
|
|
|
7
7
|
import { WarningCircle32, Uploading32, CheckCircle32, CrossCircleFilled20, Refresh32, Refresh20, Eye20, Upload24, Cross24, ChevronLeft24, ChevronRight24, ChevronLeft20, ChevronRight20, CrossCircleFilled24, More24, Less24, InfoCircle32, CrossCircle32, Cross20, CrossCircle24, ImageAlt32, ChevronDown24, CheckCircleFilled32, WarningCircleFilled32, CrossCircleFilled32, InfoCircleFilled32, SidebarExpand24, ChevronDown20, InfoCircleFilled24, WarningCircleFilled24, CheckCircleFilled24, ArrowBottomRight24, ArrowTopRight24, StarHalf32, StarFilled32, Star32, StarHalf24, StarFilled24, Star24, StarHalf20, StarFilled20, Star20, Check20, Check24, ArrowBack24, ArrowNext24, HelpCircle24, Menu24, Notification24, Search24, PauseCircle24, PlayCircle24, ChevronUp20, Settings20, Drag20, ListAdd20, ViewGridX420, Filter20, FullscreenEnter20, FullscreenExit20, Download20, CheckCircle24 } from '@mozaic-ds/icons-angular';
|
|
8
8
|
import { Overlay, OverlayConfig, OverlayPositionBuilder, CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
9
9
|
import { CdkPortalOutlet, ComponentPortal } from '@angular/cdk/portal';
|
|
10
|
-
import { Subject, take } from 'rxjs';
|
|
10
|
+
import { Subject, take, tap, of, firstValueFrom } from 'rxjs';
|
|
11
11
|
import parsePhoneNumberFromString, { getCountries, getExampleNumber, isValidPhoneNumber, getCountryCallingCode } from 'libphonenumber-js';
|
|
12
12
|
import examples from 'libphonenumber-js/mobile/examples';
|
|
13
13
|
import * as i1$1 from '@angular/cdk/scrolling';
|
|
@@ -11392,6 +11392,7 @@ class TreeStateService {
|
|
|
11392
11392
|
expandedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedIds" }] : /* istanbul ignore next */ []));
|
|
11393
11393
|
loadingIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "loadingIds" }] : /* istanbul ignore next */ []));
|
|
11394
11394
|
internalNodes = signal([], ...(ngDevMode ? [{ debugName: "internalNodes" }] : /* istanbul ignore next */ []));
|
|
11395
|
+
loadChildrenFn = signal(null, ...(ngDevMode ? [{ debugName: "loadChildrenFn" }] : /* istanbul ignore next */ []));
|
|
11395
11396
|
flatVisibleNodes = computed(() => {
|
|
11396
11397
|
const result = [];
|
|
11397
11398
|
this._flatten(this.internalNodes(), result, 0, null);
|
|
@@ -11427,6 +11428,22 @@ class TreeStateService {
|
|
|
11427
11428
|
return next;
|
|
11428
11429
|
});
|
|
11429
11430
|
}
|
|
11431
|
+
expandAndLoad(node) {
|
|
11432
|
+
const wasExpanded = this.expandedIds().has(node.id);
|
|
11433
|
+
this.toggleExpanded(node.id);
|
|
11434
|
+
if (!wasExpanded) {
|
|
11435
|
+
const loadFn = this.loadChildrenFn();
|
|
11436
|
+
const resolved = this.findNode(node.id) ?? node;
|
|
11437
|
+
if (loadFn && resolved.children === undefined) {
|
|
11438
|
+
this.addLoading(node.id);
|
|
11439
|
+
return loadFn(node).pipe(take(1), tap((children) => {
|
|
11440
|
+
this.patchChildren(node.id, children);
|
|
11441
|
+
this.removeLoading(node.id);
|
|
11442
|
+
}), tap(() => void 0));
|
|
11443
|
+
}
|
|
11444
|
+
}
|
|
11445
|
+
return of(undefined);
|
|
11446
|
+
}
|
|
11430
11447
|
addLoading(id) {
|
|
11431
11448
|
this.loadingIds.update((s) => new Set([...s, id]));
|
|
11432
11449
|
}
|
|
@@ -11451,6 +11468,21 @@ class TreeStateService {
|
|
|
11451
11468
|
return n;
|
|
11452
11469
|
});
|
|
11453
11470
|
}
|
|
11471
|
+
findNode(nodeId) {
|
|
11472
|
+
return this._findNodeRecursive(this.internalNodes(), nodeId);
|
|
11473
|
+
}
|
|
11474
|
+
_findNodeRecursive(nodes, id) {
|
|
11475
|
+
for (const node of nodes) {
|
|
11476
|
+
if (node.id === id)
|
|
11477
|
+
return node;
|
|
11478
|
+
if (node.children) {
|
|
11479
|
+
const found = this._findNodeRecursive(node.children, id);
|
|
11480
|
+
if (found)
|
|
11481
|
+
return found;
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11484
|
+
return null;
|
|
11485
|
+
}
|
|
11454
11486
|
findParentId(nodeId) {
|
|
11455
11487
|
const flat = this.flatVisibleNodes();
|
|
11456
11488
|
const entry = flat.find((f) => f.node.id === nodeId);
|
|
@@ -11464,6 +11496,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
11464
11496
|
}] });
|
|
11465
11497
|
|
|
11466
11498
|
class TreeSelectionService {
|
|
11499
|
+
stateService = inject(TreeStateService);
|
|
11467
11500
|
selectedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
|
|
11468
11501
|
selectionMode = signal('none', ...(ngDevMode ? [{ debugName: "selectionMode" }] : /* istanbul ignore next */ []));
|
|
11469
11502
|
rootNodes = signal([], ...(ngDevMode ? [{ debugName: "rootNodes" }] : /* istanbul ignore next */ []));
|
|
@@ -11484,40 +11517,62 @@ class TreeSelectionService {
|
|
|
11484
11517
|
return true;
|
|
11485
11518
|
return ancestors.some((a) => a.disabled);
|
|
11486
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
|
+
*/
|
|
11487
11524
|
isIndeterminate(node) {
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
if (
|
|
11525
|
+
const resolved = this._resolveNode(node);
|
|
11526
|
+
const leaves = this._collectLeafIds(resolved);
|
|
11527
|
+
if (leaves.length === 0)
|
|
11491
11528
|
return false;
|
|
11492
|
-
const
|
|
11493
|
-
|
|
11494
|
-
return false;
|
|
11495
|
-
const selectedCount = enabledIds.filter((id) => this.selectedIds().has(id)).length;
|
|
11496
|
-
return selectedCount > 0 && selectedCount < enabledIds.length;
|
|
11529
|
+
const selectedCount = leaves.filter((id) => this.selectedIds().has(id)).length;
|
|
11530
|
+
return selectedCount > 0 && selectedCount < leaves.length;
|
|
11497
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
|
+
*/
|
|
11498
11537
|
isCheckedComputed(node) {
|
|
11499
11538
|
if (this.selectionMode() === 'radio') {
|
|
11500
11539
|
return this.selectedIds().has(node.id);
|
|
11501
11540
|
}
|
|
11502
|
-
|
|
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) {
|
|
11503
11545
|
return this.selectedIds().has(node.id);
|
|
11504
11546
|
}
|
|
11505
|
-
|
|
11506
|
-
if (enabledIds.length === 0)
|
|
11507
|
-
return this.selectedIds().has(node.id);
|
|
11508
|
-
return enabledIds.every((id) => this.selectedIds().has(id));
|
|
11547
|
+
return leaves.every((id) => this.selectedIds().has(id));
|
|
11509
11548
|
}
|
|
11549
|
+
/**
|
|
11550
|
+
* Select a node: add all leaf descendant IDs (or the node's own ID if leaf).
|
|
11551
|
+
*/
|
|
11510
11552
|
selectNode(node) {
|
|
11511
11553
|
if (this.selectionMode() === 'radio') {
|
|
11512
11554
|
this.selectedIds.set(new Set([node.id]));
|
|
11513
11555
|
return;
|
|
11514
11556
|
}
|
|
11557
|
+
const resolved = this._resolveNode(node);
|
|
11515
11558
|
this.selectedIds.update((current) => {
|
|
11516
11559
|
const next = new Set(current);
|
|
11517
|
-
|
|
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
|
+
}
|
|
11518
11569
|
return next;
|
|
11519
11570
|
});
|
|
11520
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
|
+
*/
|
|
11521
11576
|
deselectNode(node) {
|
|
11522
11577
|
if (this.selectionMode() === 'radio') {
|
|
11523
11578
|
this.selectedIds.update((current) => {
|
|
@@ -11527,9 +11582,13 @@ class TreeSelectionService {
|
|
|
11527
11582
|
});
|
|
11528
11583
|
return;
|
|
11529
11584
|
}
|
|
11585
|
+
const resolved = this._resolveNode(node);
|
|
11530
11586
|
this.selectedIds.update((current) => {
|
|
11531
11587
|
const next = new Set(current);
|
|
11532
|
-
|
|
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));
|
|
11533
11592
|
return next;
|
|
11534
11593
|
});
|
|
11535
11594
|
}
|
|
@@ -11541,30 +11600,83 @@ class TreeSelectionService {
|
|
|
11541
11600
|
this.selectNode(node);
|
|
11542
11601
|
}
|
|
11543
11602
|
}
|
|
11544
|
-
|
|
11545
|
-
|
|
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) {
|
|
11546
11643
|
return [];
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
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);
|
|
11551
11657
|
}
|
|
11552
11658
|
}
|
|
11553
11659
|
return ids;
|
|
11554
11660
|
}
|
|
11555
|
-
|
|
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);
|
|
11556
11666
|
const ids = [];
|
|
11557
|
-
if (!
|
|
11667
|
+
if (!resolved.children)
|
|
11558
11668
|
return ids;
|
|
11559
|
-
for (const child of
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
}
|
|
11669
|
+
for (const child of resolved.children) {
|
|
11670
|
+
const resolvedChild = this._resolveNode(child);
|
|
11671
|
+
ids.push(resolvedChild.id);
|
|
11672
|
+
ids.push(...this._collectAllDescendantIds(resolvedChild));
|
|
11564
11673
|
}
|
|
11565
11674
|
return ids;
|
|
11566
11675
|
}
|
|
11567
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
|
+
}
|
|
11568
11680
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: TreeSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
11569
11681
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: TreeSelectionService });
|
|
11570
11682
|
}
|
|
@@ -11614,7 +11726,7 @@ class TreeKeyboardService {
|
|
|
11614
11726
|
break;
|
|
11615
11727
|
const isExpanded = this.state.expandedIds().has(current.node.id);
|
|
11616
11728
|
if (this._hasChildren(current.node) && !isExpanded && !current.node.disabled) {
|
|
11617
|
-
this.state.
|
|
11729
|
+
this.state.expandAndLoad(current.node).subscribe();
|
|
11618
11730
|
}
|
|
11619
11731
|
else if (isExpanded) {
|
|
11620
11732
|
const firstChild = flat[currentIndex + 1];
|
|
@@ -11642,7 +11754,7 @@ class TreeKeyboardService {
|
|
|
11642
11754
|
if (!current)
|
|
11643
11755
|
break;
|
|
11644
11756
|
if (this._hasChildren(current.node) && !current.node.disabled) {
|
|
11645
|
-
this.state.
|
|
11757
|
+
this.state.expandAndLoad(current.node).subscribe();
|
|
11646
11758
|
}
|
|
11647
11759
|
break;
|
|
11648
11760
|
}
|
|
@@ -11690,7 +11802,7 @@ class TreeKeyboardService {
|
|
|
11690
11802
|
const siblings = flat.filter((f) => f.depth === current.depth && f.parentId === current.parentId);
|
|
11691
11803
|
for (const sibling of siblings) {
|
|
11692
11804
|
if (this._hasChildren(sibling.node) && !this.state.expandedIds().has(sibling.node.id)) {
|
|
11693
|
-
this.state.
|
|
11805
|
+
this.state.expandAndLoad(sibling.node).subscribe();
|
|
11694
11806
|
}
|
|
11695
11807
|
}
|
|
11696
11808
|
}
|
|
@@ -11760,24 +11872,8 @@ class MozTreeNodeComponent {
|
|
|
11760
11872
|
}
|
|
11761
11873
|
}
|
|
11762
11874
|
onToggleExpand() {
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
this.stateService.toggleExpanded(node.id);
|
|
11766
|
-
this.expandChange.emit(node.id);
|
|
11767
|
-
return;
|
|
11768
|
-
}
|
|
11769
|
-
this.stateService.toggleExpanded(node.id);
|
|
11770
|
-
this.expandChange.emit(node.id);
|
|
11771
|
-
const loadFn = this.loadChildren();
|
|
11772
|
-
if (loadFn && node.children === undefined) {
|
|
11773
|
-
this.stateService.addLoading(node.id);
|
|
11774
|
-
loadFn(node)
|
|
11775
|
-
.pipe(take(1))
|
|
11776
|
-
.subscribe((children) => {
|
|
11777
|
-
this.stateService.patchChildren(node.id, children);
|
|
11778
|
-
this.stateService.removeLoading(node.id);
|
|
11779
|
-
});
|
|
11780
|
-
}
|
|
11875
|
+
this.stateService.expandAndLoad(this.node()).subscribe();
|
|
11876
|
+
this.expandChange.emit(this.node().id);
|
|
11781
11877
|
}
|
|
11782
11878
|
onCheckboxChange(event) {
|
|
11783
11879
|
if (this.isDisabled())
|
|
@@ -11811,7 +11907,7 @@ class MozTreeNodeComponent {
|
|
|
11811
11907
|
return null;
|
|
11812
11908
|
}
|
|
11813
11909
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: MozTreeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11814
|
-
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 });
|
|
11815
11911
|
}
|
|
11816
11912
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: MozTreeNodeComponent, decorators: [{
|
|
11817
11913
|
type: Component,
|
|
@@ -11823,7 +11919,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
11823
11919
|
MozLoaderComponent,
|
|
11824
11920
|
ChevronDown20,
|
|
11825
11921
|
ChevronRight20,
|
|
11826
|
-
], 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"] }]
|
|
11827
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"] }] } });
|
|
11828
11924
|
|
|
11829
11925
|
class MozTreeNodeTemplateDirective {
|
|
@@ -11889,9 +11985,47 @@ class MozTreeComponent {
|
|
|
11889
11985
|
effect(() => {
|
|
11890
11986
|
this.selectionService.setRootNodes(this.nodes());
|
|
11891
11987
|
});
|
|
11988
|
+
effect(() => {
|
|
11989
|
+
this.stateService.loadChildrenFn.set(this.loadChildren());
|
|
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
|
+
});
|
|
11892
12021
|
}
|
|
11893
12022
|
onTreeKeydown(event) {
|
|
12023
|
+
const prevSelection = this.selectionService.allSelectedIds();
|
|
11894
12024
|
this.keyboardService.handleKeydown(event);
|
|
12025
|
+
const newSelection = this.selectionService.allSelectedIds();
|
|
12026
|
+
if (prevSelection !== newSelection) {
|
|
12027
|
+
this.selectionChange.emit(newSelection);
|
|
12028
|
+
}
|
|
11895
12029
|
}
|
|
11896
12030
|
onTreeFocus() {
|
|
11897
12031
|
this.keyboardService.initFocus();
|
|
@@ -11902,6 +12036,41 @@ class MozTreeComponent {
|
|
|
11902
12036
|
onSelectionChange(ids) {
|
|
11903
12037
|
this.selectionChange.emit(ids);
|
|
11904
12038
|
}
|
|
12039
|
+
async expandPath(path) {
|
|
12040
|
+
const loadFn = this.stateService.loadChildrenFn();
|
|
12041
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
12042
|
+
const nodeId = path[i];
|
|
12043
|
+
const node = this.stateService.findNode(nodeId);
|
|
12044
|
+
if (!node)
|
|
12045
|
+
break;
|
|
12046
|
+
// Expand first so the children area is visible
|
|
12047
|
+
if (!this.stateService.expandedIds().has(nodeId)) {
|
|
12048
|
+
this.stateService.toggleExpanded(nodeId);
|
|
12049
|
+
this.expandedIdsChange.emit(new Set(this.stateService.expandedIds()));
|
|
12050
|
+
// Yield to let Angular render the expansion
|
|
12051
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
12052
|
+
}
|
|
12053
|
+
// Load children if needed
|
|
12054
|
+
if (loadFn && node.children === undefined) {
|
|
12055
|
+
this.stateService.addLoading(nodeId);
|
|
12056
|
+
this.expandedIdsChange.emit(new Set(this.stateService.expandedIds()));
|
|
12057
|
+
// Yield to let Angular render the loader
|
|
12058
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
12059
|
+
const children = await firstValueFrom(loadFn(node).pipe(take(1)));
|
|
12060
|
+
this.stateService.patchChildren(nodeId, children);
|
|
12061
|
+
this.stateService.removeLoading(nodeId);
|
|
12062
|
+
// Yield to let Angular render the new children
|
|
12063
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
12064
|
+
}
|
|
12065
|
+
}
|
|
12066
|
+
this.expandedIdsChange.emit(new Set(this.stateService.expandedIds()));
|
|
12067
|
+
}
|
|
12068
|
+
scrollToNode(nodeId) {
|
|
12069
|
+
setTimeout(() => {
|
|
12070
|
+
const el = document.querySelector(`[data-tree-node-id="${nodeId}"]`);
|
|
12071
|
+
el?.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
12072
|
+
});
|
|
12073
|
+
}
|
|
11905
12074
|
expandAll() {
|
|
11906
12075
|
const flat = this.stateService.flatVisibleNodes();
|
|
11907
12076
|
const allIds = new Set(flat.map((f) => f.node.id));
|