@agorapulse/ui-components 20.3.26-beta.1 → 20.3.27-beta.1

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.
@@ -1036,6 +1036,9 @@ class NavSelectorBuilder {
1036
1036
  }
1037
1037
  const displayTokenInvalid = children.some(({ displayTokenInvalid }) => displayTokenInvalid);
1038
1038
  const selectedChildrenCount = children.filter(({ selected }) => selected).length;
1039
+ // A group is selected if all children are either selected or not selectable
1040
+ // (disabled children must still be selected to count)
1041
+ const selected = children.every(c => c.selected || !c.selectable);
1039
1042
  return {
1040
1043
  uid: node.uid,
1041
1044
  alias: node.alias,
@@ -1054,9 +1057,9 @@ class NavSelectorBuilder {
1054
1057
  displayCounter: node.folded && children.some(child => isInternalNavSelectorEntryALeaf(child) && child.counter !== 0),
1055
1058
  counter: children.reduce((acc, { counter }) => acc + counter, 0),
1056
1059
  displayTokenInvalid,
1057
- selected: false,
1060
+ selected,
1058
1061
  selectable: multipleModeEnabled && children.some(child => child.selectable && !child.disabled),
1059
- undeterminedSelection: selectedChildrenCount !== children.length && selectedChildrenCount > 0,
1062
+ undeterminedSelection: !selected && selectedChildrenCount > 0,
1060
1063
  folded: node.folded,
1061
1064
  type: node.type,
1062
1065
  };
@@ -1156,11 +1159,10 @@ class NavSelectorBuilder {
1156
1159
  }
1157
1160
  else if (isInternalNavSelectorEntryAGroup(entry)) {
1158
1161
  const children = this.setInitialSelection(entry.children, selectedEntryUids);
1159
- // A group is selected if all its selectable non-disabled children are selected
1160
- const selectableChildren = children.filter(child => child.selectable && !child.disabled);
1161
- const selectedChildren = children.filter(child => child.selected);
1162
- const selected = selectableChildren.length > 0 && selectableChildren.every(child => child.selected);
1163
- const undeterminedSelection = !selected && selectedChildren.length > 0;
1162
+ // A group is selected if all children are either selected or not selectable
1163
+ // (disabled children must still be selected to count)
1164
+ const selected = children.every(c => c.selected || !c.selectable);
1165
+ const undeterminedSelection = !selected && children.some(c => c.selected);
1164
1166
  return {
1165
1167
  ...entry,
1166
1168
  children,
@@ -1367,14 +1369,14 @@ class NavSelectorMerger {
1367
1369
  newLeaf.viewMoreDisplayed = oldLeaf.viewMoreDisplayed;
1368
1370
  newLeaf.folded = oldLeaf.folded;
1369
1371
  newLeaf.displayCounter = newLeaf.folded && newLeaf.counterDisplayable;
1370
- newLeaf.selected = oldLeaf.selected;
1372
+ // Don't copy selected state - it's set by the builder based on initialSelectedEntryUids
1371
1373
  newLeaf.accessibility.tabIndex = oldLeaf.accessibility.tabIndex;
1372
1374
  const oldDetailByUId = associateBy(oldLeaf.details, 'uid');
1373
1375
  newLeaf.details.forEach(newDetail => {
1374
1376
  if (!oldDetailByUId[newDetail.uid] || newDetail.errorReason) {
1375
1377
  return;
1376
1378
  }
1377
- newDetail.selected = oldDetailByUId[newDetail.uid].selected;
1379
+ // Don't copy selected state - it's set by the builder based on initialSelectedEntryUids
1378
1380
  newDetail.accessibility.tabIndex = oldDetailByUId[newDetail.uid].accessibility.tabIndex;
1379
1381
  });
1380
1382
  }
@@ -1574,14 +1576,16 @@ class NavSelectorMultiSelect {
1574
1576
  }
1575
1577
  // Recalculate selected and undeterminedSelection based on children
1576
1578
  // because disabled items may have retained their selected state.
1577
- // A group is selected if all children are either: selected, not selectable, or disabled
1578
- selected = children.every(child => child.selected || (isInternalNavSelectorEntryALeaf(child) && (!child.selectable || child.disabled)));
1579
+ // A group is selected if all children are either selected or not selectable
1580
+ // (disabled children must still be selected to count)
1581
+ selected = children.every(child => child.selected || (isInternalNavSelectorEntryALeaf(child) && !child.selectable));
1579
1582
  undeterminedSelection = !selected && children.some(child => child.selected);
1580
1583
  }
1581
1584
  else {
1582
1585
  children = entry.children.map(child => this.toggleSelectLeaf(child, entryUid));
1583
- // A group is selected if all children are either: selected, not selectable, or disabled
1584
- selected = children.every(child => child.selected || (isInternalNavSelectorEntryALeaf(child) && (!child.selectable || child.disabled)));
1586
+ // A group is selected if all children are either selected or not selectable
1587
+ // (disabled children must still be selected to count)
1588
+ selected = children.every(child => child.selected || (isInternalNavSelectorEntryALeaf(child) && !child.selectable));
1585
1589
  undeterminedSelection = !selected && children.some(child => child.selected);
1586
1590
  }
1587
1591
  return {
@@ -1619,7 +1623,8 @@ class NavSelectorMultiSelect {
1619
1623
  if (isInternalNavSelectorEntryALeaf(child)) {
1620
1624
  return {
1621
1625
  ...child,
1622
- selected: false,
1626
+ // Disabled or non-selectable leafs retain their current selected state
1627
+ selected: child.disabled || !child.selectable ? child.selected : false,
1623
1628
  };
1624
1629
  }
1625
1630
  else if (isInternalNavSelectorEntryACategory(child)) {
@@ -1628,11 +1633,18 @@ class NavSelectorMultiSelect {
1628
1633
  children: this.unselectChildren(child.children),
1629
1634
  };
1630
1635
  }
1631
- else if (isInternalNavSelectorEntryANode(child)) {
1636
+ else if (isInternalNavSelectorEntryAGroup(child)) {
1637
+ const children = this.unselectChildren(child.children);
1638
+ // Recalculate group state based on children
1639
+ // A group is selected if all children are either selected or not selectable
1640
+ // (disabled children must still be selected to count)
1641
+ const selected = children.every(c => c.selected || (isInternalNavSelectorEntryALeaf(c) && !c.selectable));
1642
+ const undeterminedSelection = !selected && children.some(c => c.selected);
1632
1643
  return {
1633
1644
  ...child,
1634
- children: this.unselectChildren(child.children),
1635
- selected: false,
1645
+ children,
1646
+ selected,
1647
+ undeterminedSelection,
1636
1648
  };
1637
1649
  }
1638
1650
  return child;
@@ -1643,7 +1655,8 @@ class NavSelectorMultiSelect {
1643
1655
  if (isInternalNavSelectorEntryALeaf(child)) {
1644
1656
  return {
1645
1657
  ...child,
1646
- selected: child.selectable,
1658
+ // Disabled or non-selectable leafs retain their current selected state
1659
+ selected: child.disabled || !child.selectable ? child.selected : child.selectable,
1647
1660
  };
1648
1661
  }
1649
1662
  else if (isInternalNavSelectorEntryACategory(child)) {
@@ -1653,10 +1666,17 @@ class NavSelectorMultiSelect {
1653
1666
  };
1654
1667
  }
1655
1668
  else if (isInternalNavSelectorEntryAGroup(child)) {
1669
+ const children = this.selectChildren(child.children);
1670
+ // Recalculate group state based on children
1671
+ // A group is selected if all children are either selected or not selectable
1672
+ // (disabled children must still be selected to count)
1673
+ const selected = children.every(c => c.selected || (isInternalNavSelectorEntryALeaf(c) && !c.selectable));
1674
+ const undeterminedSelection = !selected && children.some(c => c.selected);
1656
1675
  return {
1657
1676
  ...child,
1658
- children: this.selectChildren(child.children),
1659
- selected: true,
1677
+ children,
1678
+ selected,
1679
+ undeterminedSelection,
1660
1680
  };
1661
1681
  }
1662
1682
  return child;
@@ -1786,6 +1806,27 @@ class NavSelectorState {
1786
1806
  search = signal('', ...(ngDevMode ? [{ debugName: "search" }] : []));
1787
1807
  noResults = computed(() => this._entries().every(({ hidden }) => hidden), ...(ngDevMode ? [{ debugName: "noResults" }] : []));
1788
1808
  texts = this._texts.asReadonly();
1809
+ isAllSelected = computed(() => {
1810
+ if (!this.multipleModeEnabled()) {
1811
+ return false;
1812
+ }
1813
+ const entries = this._entries();
1814
+ return this.areAllSelectableItemsSelected(entries);
1815
+ }, ...(ngDevMode ? [{ debugName: "isAllSelected" }] : []));
1816
+ isSelectAllIndeterminate = computed(() => {
1817
+ if (!this.multipleModeEnabled()) {
1818
+ return false;
1819
+ }
1820
+ const entries = this._entries();
1821
+ const allSelected = this.areAllSelectableItemsSelected(entries);
1822
+ if (allSelected) {
1823
+ return false;
1824
+ }
1825
+ // Check if ANY item is selected (including disabled/mandatory items)
1826
+ // This matches the behavior of group checkboxes
1827
+ const selectedUids = this.collectSelectedUids(entries);
1828
+ return selectedUids.length > 0;
1829
+ }, ...(ngDevMode ? [{ debugName: "isSelectAllIndeterminate" }] : []));
1789
1830
  expanded = signal(true, ...(ngDevMode ? [{ debugName: "expanded" }] : []));
1790
1831
  previousSelectedUids = [];
1791
1832
  selectedUidsChangeCallback = null;
@@ -1998,6 +2039,35 @@ class NavSelectorState {
1998
2039
  resetFocus() {
1999
2040
  this._entries.update(entries => NavSelectorAccessibility.resetFocus(entries));
2000
2041
  }
2042
+ toggleSelectAll() {
2043
+ if (!this.multipleModeEnabled()) {
2044
+ return;
2045
+ }
2046
+ this._entries.update(entries => {
2047
+ const allSelected = this.areAllSelectableItemsSelected(entries);
2048
+ return allSelected ? NavSelectorMultiSelect.unselectAll(entries) : NavSelectorMultiSelect.selectAll(entries);
2049
+ });
2050
+ }
2051
+ areAllSelectableItemsSelected(entries) {
2052
+ const selectableNonDisabledUids = this.collectSelectableNonDisabledUids(entries);
2053
+ const selectedUids = this.collectSelectedUids(entries);
2054
+ if (selectableNonDisabledUids.length === 0) {
2055
+ return false;
2056
+ }
2057
+ return selectableNonDisabledUids.every(uid => selectedUids.includes(uid));
2058
+ }
2059
+ collectSelectableNonDisabledUids(entries) {
2060
+ return entries.flatMap(entry => {
2061
+ if (isInternalNavSelectorEntryALeaf(entry)) {
2062
+ // Only include selectable and non-disabled items (user-interactable)
2063
+ return entry.selectable && !entry.disabled ? [entry.uid] : [];
2064
+ }
2065
+ else if (isInternalNavSelectorEntryANode(entry)) {
2066
+ return this.collectSelectableNonDisabledUids(entry.children);
2067
+ }
2068
+ return [];
2069
+ });
2070
+ }
2001
2071
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NavSelectorState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2002
2072
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: NavSelectorState });
2003
2073
  }
@@ -2937,9 +3007,6 @@ class NavSelectorComponent {
2937
3007
  expanded = this.navSelectorState.expanded.asReadonly();
2938
3008
  entries = computed(() => this.navSelectorState.entries(), ...(ngDevMode ? [{ debugName: "entries", equal: isEqual }] : [{ equal: isEqual }]));
2939
3009
  selectableEntryUids = computed(() => computeSelectableUids(this.entries()), ...(ngDevMode ? [{ debugName: "selectableEntryUids", equal: isEqual }] : [{ equal: isEqual }]));
2940
- existingSelectableUids = computed(() => this.selectedEntryUids().filter(uid => this.selectableEntryUids().includes(uid)), ...(ngDevMode ? [{ debugName: "existingSelectableUids", equal: isEqual }] : [{
2941
- equal: isEqual,
2942
- }]));
2943
3010
  visibleEntryUids = computed(() => computeVisibleUids(this.entries()), ...(ngDevMode ? [{ debugName: "visibleEntryUids", equal: isEqual }] : [{ equal: isEqual }]));
2944
3011
  visibleAccountsChange = outputFromObservable(toObservable(this.visibleEntryUids).pipe(map(visibleAccountUids => ({ visibleAccountUids }))));
2945
3012
  constructor() {
@@ -2963,16 +3030,17 @@ class NavSelectorComponent {
2963
3030
  untracked(() => this.onExpansionStateChange.emit({ expanded }));
2964
3031
  });
2965
3032
  effect(() => {
2966
- // When AS inputs changes, rebuild AS with current initial selected uids
3033
+ // When inputs change, rebuild entries with current selected uids
2967
3034
  const multipleModeEnabled = this.multipleModeEnabled();
2968
- const newEntries = this.navSelectorEntries(); // FIXME: Check if it's needed to add a guard on this value
3035
+ const newEntries = this.navSelectorEntries();
3036
+ const selectedUids = this.selectedEntryUids();
2969
3037
  untracked(() => {
2970
3038
  this.navSelectorState.updateMultiModeEnabled(multipleModeEnabled);
2971
3039
  if (!this.entries().length) {
2972
- this.navSelectorState.initEntries(newEntries, this.selectedEntryUids(), this.detailsDisplayedLimit());
3040
+ this.navSelectorState.initEntries(newEntries, selectedUids, this.detailsDisplayedLimit());
2973
3041
  }
2974
3042
  else {
2975
- this.navSelectorState.updateEntries(newEntries, this.selectedEntryUids(), this.detailsDisplayedLimit());
3043
+ this.navSelectorState.updateEntries(newEntries, selectedUids, this.detailsDisplayedLimit());
2976
3044
  }
2977
3045
  });
2978
3046
  });
@@ -2980,16 +3048,9 @@ class NavSelectorComponent {
2980
3048
  const detailsDisplayedLimit = this.detailsDisplayedLimit();
2981
3049
  untracked(() => this.navSelectorState.updateDetailsDisplayedLimit(detailsDisplayedLimit));
2982
3050
  });
2983
- effect(() => {
2984
- const isInit = !!this.entries().length;
2985
- const existingSelectableUids = this.existingSelectableUids();
2986
- untracked(() => {
2987
- if (isInit) {
2988
- // Trigger selection change when initial selectedEntryUids model changes
2989
- this.navSelectorState.onSelectionChange(existingSelectableUids);
2990
- }
2991
- });
2992
- });
3051
+ // Note: No longer need separate effect for selectedEntryUids changes
3052
+ // The effect above (line 166) now watches selectedEntryUids and calls updateEntries
3053
+ // which properly handles disabled items through the builder
2993
3054
  effect(() => {
2994
3055
  const translatedTexts = this.translatedTexts();
2995
3056
  untracked(() => this.navSelectorState.updateTexts(translatedTexts));