@geotab/zenith 3.16.0-nightly.20261005.f8d767e → 3.17.0-beta.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/README.md CHANGED
@@ -51,6 +51,34 @@ Zenith has English and 32 non-English locale dictionaries, including Arabic (`ar
51
51
 
52
52
  ## Change log
53
53
 
54
+ ### 3.17.0
55
+
56
+ - [MYG-138984](https://geotab.atlassian.net/browse/MYG-138984): Add Primary, Destructive, and TertiaryDestructive trigger variants to `Dropdown` / `FilterButton`
57
+ - [MYG-138985](https://geotab.atlassian.net/browse/MYG-138985): Add optional separators below single- and multi-select `Dropdown` items
58
+ - [MYG-138983](https://geotab.atlassian.net/browse/MYG-138983): Add destructive styling to single-select `Dropdown` items
59
+ - [MYG-105567](https://geotab.atlassian.net/browse/MYG-105567): Add `sortSelectedToTop` to float selected `Dropdown` options to the top when opened
60
+ - [MYG-147075](https://geotab.atlassian.net/browse/MYG-147075): Show full single-select `Dropdown` option labels on hover
61
+ - [MYG-147879](https://geotab.atlassian.net/browse/MYG-147879): Add loaded/total counts and an end-of-results message for desktop infinite-scroll tables; improve row-loading announcements
62
+ - [MYG-130553](https://geotab.atlassian.net/browse/MYG-130553): Add progressive loading of nested `Table` rows with a +N more control, plus the `useShowMore` hook and `DEFAULT_SHOW_MORE_CAP` export
63
+ - [MYG-148443](https://geotab.atlassian.net/browse/MYG-148443): Add seven sequential and seven diverging data-visualization color tokens
64
+ - [MYG-129023](https://geotab.atlassian.net/browse/MYG-129023): Support destructive menu items and open-in-new-page indicators, including collapsed `Header` actions
65
+ - [MYG-142469](https://geotab.atlassian.net/browse/MYG-142469): Export `PillBox`, `DEFAULT_MAX_PILLS`, `IPillData`, and `IPillBox` from the package entry point
66
+ - [MYG-139327](https://geotab.atlassian.net/browse/MYG-139327): Add mouse-drag and Shift-click range selection to `Table` and selectable `DataGrid` rows, including expanded nested rows
67
+ - [MYG-142234](https://geotab.atlassian.net/browse/MYG-142234): Add opt-in cross-page Select All exclusions when individual rows or the current-page header are unchecked
68
+ - [MYG-149449](https://geotab.atlassian.net/browse/MYG-149449): Preserve cross-page Select All and existing exclusions during drag and Shift-click range selection; correct exclusion counts, childless nested-row selection, and global selection commands; exclude loading and show-more placeholders from fully loaded selection counts
69
+ - [MYG-148015](https://geotab.atlassian.net/browse/MYG-148015): Make cross-page exclusions opt-in through `selectable.allowExclusions` while preserving the existing selection contract by default; correct selection feedback during reloads and empty-selection bulk actions
70
+ - [MYG-147924](https://geotab.atlassian.net/browse/MYG-147924): Preserve `Table` selection during append loading and refreshes
71
+ - [MYG-143552](https://geotab.atlassian.net/browse/MYG-143552): Keep `Waiting` overlays covering scrollable content, improve spinner positioning, and block keyboard interaction with sibling content while the overlay is visible
72
+ - [MYG-148751](https://geotab.atlassian.net/browse/MYG-148751): Remove special bold styling from the first `Table` / `DataGrid` column header
73
+ - [MYG-113389](https://geotab.atlassian.net/browse/MYG-113389): Reduce form label/control gaps and correct `Dropdown` form-field styling and width
74
+ - [MYG-148954](https://geotab.atlassian.net/browse/MYG-148954): Close `PillExpandable` popovers on user scrolling while ignoring browser scroll-anchoring adjustments
75
+ - [MYG-149705](https://geotab.atlassian.net/browse/MYG-149705): Fix RTL tab keyboard navigation, focus scrolling, and tab/pill/chip/accordion styling
76
+ - [MYG-145500](https://geotab.atlassian.net/browse/MYG-145500): Allow `Tabs` to associate tabs with explicitly named panels through `ariaControls`, including panels outside the default tab layout
77
+ - [MYG-148568](https://geotab.atlassian.net/browse/MYG-148568): Forward ARIA attributes to the appropriate input elements in `DropdownRaw` and `TextInputRaw`
78
+ - [MYG-150227](https://geotab.atlassian.net/browse/MYG-150227): Make `FormLayout` positioning deterministic for fractional and invalid positions
79
+ - [MYG-145929](https://geotab.atlassian.net/browse/MYG-145929): Keep disabled `Button` and related menu/header buttons keyboard-focusable while preventing activation
80
+ - [MYG-148696](https://geotab.atlassian.net/browse/MYG-148696): Improve `PillExpandable` popover focus management and accessible trigger/content relationships
81
+
54
82
  ### 3.16.0
55
83
 
56
84
  - Add `Tooltip.InlineTrigger` subcomponent for inline tooltip triggers
@@ -12,6 +12,8 @@ export interface IBulkActionsProps extends IZenComponentProps {
12
12
  * even when `selected` is 0 (i.e., cross-page "select all" is active).
13
13
  */
14
14
  allSelected?: boolean;
15
+ /** Show a selection label without a count when the selected total is unknown. */
16
+ selectionCountUnknown?: boolean;
15
17
  onClearSelection: () => void;
16
18
  onSelectAll?: () => void;
17
19
  gridType?: DeviceType;
@@ -23,7 +25,7 @@ export interface IBulkActions extends IBulkActionsProps {
23
25
  ActionButton: (props: IBulkActionButton) => ReactNode;
24
26
  }
25
27
  export declare const BulkActions: {
26
- ({ selected, allSelected, className, onClearSelection, onSelectAll, gridType, children, turnOffSelectAll }: IBulkActionsProps): import("react/jsx-runtime").JSX.Element;
28
+ ({ selected, allSelected, selectionCountUnknown, className, onClearSelection, onSelectAll, gridType, children, turnOffSelectAll }: IBulkActionsProps): import("react/jsx-runtime").JSX.Element;
27
29
  ActionLink: ({ title, icon, link, disabled, type, ref }: IBulkActionLink) => import("react/jsx-runtime").JSX.Element;
28
30
  ActionButton: ({ title, icon, clickHandler, onClick, disabled, type, ref }: IBulkActionButton) => import("react/jsx-runtime").JSX.Element;
29
31
  translations: string[];
@@ -8,6 +8,39 @@ Object.defineProperty(exports, "__esModule", {
8
8
  });
9
9
  exports.BulkActions = void 0;
10
10
  const jsx_runtime_1 = require("react/jsx-runtime");
11
+ injectString("cs", "Selected", "Vybr\xE1no");
12
+ injectString("da-DK", "Selected", "Valgt");
13
+ injectString("de", "Selected", "Ausgew\xE4hlt");
14
+ injectString("en", "Selected", "Selected");
15
+ injectString("es", "Selected", "Seleccionado");
16
+ injectString("fi-FI", "Selected", "Valittu");
17
+ injectString("fr", "Selected", "S\xE9lectionn\xE9");
18
+ injectString("fr-FR", "Selected", "S\xE9lectionn\xE9");
19
+ injectString("hu-HU", "Selected", "Kiv\xE1lasztott");
20
+ injectString("id", "Selected", "Dipilih");
21
+ injectString("it", "Selected", "Selezionato");
22
+ injectString("ja", "Selected", "\u9078\u629E\u8ECA\u4E21");
23
+ injectString("ko-KR", "Selected", "\uC120\uD0DD\uB428");
24
+ injectString("ms", "Selected", "Dipilih");
25
+ injectString("nb-NO", "Selected", "Valgt");
26
+ injectString("nl", "Selected", "Geselecteerd");
27
+ injectString("pl", "Selected", "Wybrano");
28
+ injectString("pt-BR", "Selected", "Selecionado");
29
+ injectString("pt-PT", "Selected", "Selecionado");
30
+ injectString("sk-SK", "Selected", "Vybran\xE9");
31
+ injectString("sv", "Selected", "Valda");
32
+ injectString("th", "Selected", "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E41\u0E25\u0E49\u0E27");
33
+ injectString("tr", "Selected", "Se\xE7ildi");
34
+ injectString("zh-Hans", "Selected", "\u9009\u4E2D\u7684");
35
+ injectString("zh-TW", "Selected", "\u5DF2\u9078\u53D6");
36
+ injectString("ro-RO", "Selected", "Selec\u021Bie");
37
+ injectString("ar-001", "Selected", "\u0627\u0644\u0645\u062D\u062F\u062F");
38
+ injectString("lt-LT", "Selected", "Pasirinkta");
39
+ injectString("lv-LV", "Selected", "Atlas\u012Bts");
40
+ injectString("ka-GE", "Selected", "\u10D0\u10E0\u10E9\u10D4\u10E3\u10DA\u10D8");
41
+ injectString("el-GR", "Selected", "\u0395\u03C0\u03B9\u03BB\u03B5\u03B3\u03BC\u03AD\u03BD\u03B1");
42
+ injectString("et-EE", "Selected", "Valitud");
43
+ injectString("bg-BG", "Selected", "\u0418\u0437\u0431\u0440\u0430\u043D\u0438");
11
44
  injectString("cs", "{1} selected", "{1} vybran\xFD/vybran\xE9");
12
45
  injectString("da-DK", "{1} selected", "{1} valgt");
13
46
  injectString("de", "{1} selected", "{1} ausgew\xE4hlt");
@@ -189,6 +222,7 @@ const button_1 = require("../../../../button/button");
189
222
  const BulkActions = ({
190
223
  selected,
191
224
  allSelected,
225
+ selectionCountUnknown,
192
226
  className,
193
227
  onClearSelection,
194
228
  onSelectAll,
@@ -222,7 +256,8 @@ const BulkActions = ({
222
256
  widthDelta
223
257
  });
224
258
  const isMobile = gridType === deviceType_1.DeviceType.Mobile;
225
- const text = translate("{1} selected").replace("{1}", allSelected ? translate("All") : selected.toFixed());
259
+ const hasSelection = selected > 0 || allSelected || selectionCountUnknown;
260
+ const text = selectionCountUnknown ? translate("Selected") : translate("{1} selected").replace("{1}", allSelected ? translate("All") : selected.toFixed());
226
261
  return (0, jsx_runtime_1.jsxs)("div", {
227
262
  className: (0, classNames_1.classNames)(["zen-bulk-actions", className || "", isMobile ? "zen-bulk-actions--condensed" : ""]),
228
263
  ref: bulkActionsElement,
@@ -248,7 +283,7 @@ const BulkActions = ({
248
283
  className: "zen-bulk-actions__action-button-holder",
249
284
  ref: buttonHolder,
250
285
  children: [react_1.Children.map(visibleElements, element => (0, react_1.cloneElement)(element, {
251
- disabled: selected > 0 || allSelected ? element.props.disabled : true
286
+ disabled: hasSelection ? element.props.disabled : true
252
287
  })), nonFittingElements.length > 0 ? (0, jsx_runtime_1.jsx)(menu_1.Menu, {
253
288
  title: translate("More"),
254
289
  trigger: (0, jsx_runtime_1.jsx)(textIconButton_1.TextIconButton, {
@@ -269,7 +304,7 @@ const BulkActions = ({
269
304
  id: props.id || "",
270
305
  name: props.title,
271
306
  icon: props.icon,
272
- disabled: props.disabled,
307
+ disabled: hasSelection ? props.disabled : true,
273
308
  onClick: clickHandler,
274
309
  link: link
275
310
  }, props.id);
@@ -285,4 +320,4 @@ const BulkActions = ({
285
320
  exports.BulkActions = BulkActions;
286
321
  exports.BulkActions.ActionLink = bulkActionLink_1.BulkActionLink;
287
322
  exports.BulkActions.ActionButton = bulkActionButton_1.BulkActionButton;
288
- exports.BulkActions.translations = ["Clear selection", "More options", "{1} selected", "Selecting..."];
323
+ exports.BulkActions.translations = ["Clear selection", "More options", "{1} selected", "Selecting...", "Selected"];
@@ -1,6 +1,7 @@
1
1
  import { ReactElement, ReactNode, RefObject } from "react";
2
- export declare const useTableChildren: (children: ReactNode, isMobile: boolean, selected: number, allSelected: boolean, clearSelection: () => void, activeId?: string, gridRef?: RefObject<HTMLElement | null>, onCloseDetailPanel?: () => void, isInfinite?: boolean, loadedCount?: number) => {
3
- bulkActions: ReactElement | undefined;
2
+ export declare const useTableChildren: (children: ReactNode, isMobile: boolean, selected: number, allSelected: boolean, clearSelection: () => void, activeId?: string, gridRef?: RefObject<HTMLElement | null>, onCloseDetailPanel?: () => void, isInfinite?: boolean, loadedCount?: number, excludedCount?: number, canUsePaginationTotal?: boolean, hasMoreRows?: boolean) => {
3
+ bulkActions: import("react/jsx-runtime").JSX.Element | undefined;
4
+ hasSelection: boolean;
4
5
  pagination: ReactElement | undefined;
5
6
  paginationTotal: number | undefined;
6
7
  activePage: undefined;
@@ -31,8 +31,8 @@ const useTableChildren = (children, isMobile, selected, allSelected, clearSelect
31
31
  // Desktop infinite scroll (Table's `onScrollEnd`): when true, a `Table.Pagination` child is
32
32
  // rendered as the chevron-less "loaded" variant (see `pagination.tsx`) instead of the normal
33
33
  // paged controls, driven by `loadedCount` (the number of rows currently rendered).
34
- isInfinite = false, loadedCount = 0) => (0, react_1.useMemo)(() => {
35
- let bulkActions = undefined;
34
+ isInfinite = false, loadedCount = 0, excludedCount = 0, canUsePaginationTotal = true, hasMoreRows = false) => (0, react_1.useMemo)(() => {
35
+ let bulkActionButtons;
36
36
  let tablePagination = undefined;
37
37
  let activePage = undefined;
38
38
  // A plain `let` reassigned only inside the `Children.forEach` callback below would be seen
@@ -52,11 +52,11 @@ isInfinite = false, loadedCount = 0) => (0, react_1.useMemo)(() => {
52
52
  return;
53
53
  }
54
54
  if (isChildElement(child, tableBulkActions_1.TableBulkActions.displayName)) {
55
- const bulkActionButtons = child.props.children || [];
56
- bulkActions = ((0, jsx_runtime_1.jsx)(bulkActions_1.BulkActions, { gridType: isMobile ? deviceType_1.DeviceType.Mobile : deviceType_1.DeviceType.Desktop, selected: selected, allSelected: allSelected, onClearSelection: clearSelection, children: bulkActionButtons }));
55
+ bulkActionButtons = child.props.children || [];
57
56
  return;
58
57
  }
59
58
  if (isChildElement(child, tablePagination_1.TablePagination.displayName)) {
59
+ paginationMeta.present = true;
60
60
  const total = child.props.total || 0;
61
61
  tablePagination = isInfinite
62
62
  ? (0, react_1.createElement)(pagination_1.Pagination, {
@@ -78,7 +78,7 @@ isInfinite = false, loadedCount = 0) => (0, react_1.useMemo)(() => {
78
78
  gridRef: gridRef,
79
79
  disabled: child.props.disabled
80
80
  });
81
- paginationMeta.total = total;
81
+ paginationMeta.total = child.props.total;
82
82
  activePage = child.props.activePage;
83
83
  return;
84
84
  }
@@ -116,8 +116,18 @@ isInfinite = false, loadedCount = 0) => (0, react_1.useMemo)(() => {
116
116
  }
117
117
  other.push(child);
118
118
  });
119
+ const allRowsLoaded = (!paginationMeta.present && !hasMoreRows) || (paginationMeta.total !== undefined && loadedCount >= paginationMeta.total);
120
+ const hasExclusions = allSelected && excludedCount > 0;
121
+ const selectedCount = allSelected && !allRowsLoaded
122
+ ? canUsePaginationTotal && paginationMeta.total !== undefined
123
+ ? Math.max(0, paginationMeta.total - excludedCount)
124
+ : undefined
125
+ : selected;
126
+ const hasSelection = selectedCount === undefined || selectedCount > 0;
127
+ const bulkActions = bulkActionButtons !== undefined ? ((0, jsx_runtime_1.jsx)(bulkActions_1.BulkActions, { gridType: isMobile ? deviceType_1.DeviceType.Mobile : deviceType_1.DeviceType.Desktop, selected: selectedCount !== null && selectedCount !== void 0 ? selectedCount : selected, allSelected: allSelected && !hasExclusions && hasSelection, selectionCountUnknown: hasExclusions && selectedCount === undefined, onClearSelection: clearSelection, children: bulkActionButtons })) : undefined;
119
128
  return {
120
- bulkActions: bulkActions,
129
+ bulkActions,
130
+ hasSelection,
121
131
  pagination: tablePagination,
122
132
  paginationTotal: paginationMeta.total,
123
133
  activePage: activePage,
@@ -127,5 +137,19 @@ isInfinite = false, loadedCount = 0) => (0, react_1.useMemo)(() => {
127
137
  detailPanel: detailPanel,
128
138
  other: other
129
139
  };
130
- }, [children, clearSelection, isMobile, selected, gridRef, allSelected, activeId, onCloseDetailPanel, isInfinite, loadedCount]);
140
+ }, [
141
+ children,
142
+ clearSelection,
143
+ isMobile,
144
+ selected,
145
+ gridRef,
146
+ allSelected,
147
+ activeId,
148
+ onCloseDetailPanel,
149
+ isInfinite,
150
+ loadedCount,
151
+ excludedCount,
152
+ canUsePaginationTotal,
153
+ hasMoreRows
154
+ ]);
131
155
  exports.useTableChildren = useTableChildren;
@@ -10,14 +10,9 @@ export interface ISelectionValue {
10
10
  */
11
11
  all: boolean;
12
12
  /**
13
- * The meaning of this list depends on `all`:
14
- * - when `all` is `false`, it is the ids explicitly *included* in the selection.
15
- * - when `all` is `true`, it is the ids explicitly *excluded* from the otherwise
16
- * fully-selected set (e.g. rows unchecked after a "select all" across paginated data).
17
- *
18
- * This lets `all: true` represent "all except these ids" without needing to know the
19
- * full id universe (MYG-142234) - unchecking a row while `all` is true only ever adds
20
- * to this exclusion list; it must never be rebuilt from the currently rendered page.
13
+ * Included IDs by default. With `ISelectableRowsOptions.allowExclusions: true`,
14
+ * these are excluded IDs while `all` is true, and included IDs while `all` is false.
15
+ * Consumers opting in must resolve the effective selection before running bulk actions.
21
16
  */
22
17
  selected: string[];
23
18
  }
@@ -8,6 +8,8 @@ export type TSelectionStrategy = "main" | "nested";
8
8
  export interface ISelectableRowsOptions<T extends IRowEntity<N>, N extends IEntityWithId> {
9
9
  selection: ISelectionValue | undefined;
10
10
  onSelect: (selection: ISelectionValue) => void;
11
+ /** Preserve cross-page Select All using excluded IDs. Consumers must resolve exclusions before running bulk actions. Defaults to false. */
12
+ allowExclusions?: boolean;
11
13
  checkboxDisabled?: (entity: T | N, isNested?: boolean) => boolean;
12
14
  checkboxVisible?: (entity: T | N, isNested?: boolean) => boolean;
13
15
  checkboxInHeader?: boolean;
@@ -32,6 +34,7 @@ export declare const getEmptySelection: () => {
32
34
  all: boolean;
33
35
  selected: string[];
34
36
  };
37
+ export declare const isAllSelection: (all: boolean | undefined, selectedCount: number | undefined, allowExclusions?: boolean) => boolean;
35
38
  export declare const SelectableWrapperName = "Selectable";
36
39
  export declare const useSelectableRows: <T extends IRowEntity<N>, N extends IEntityWithId>(columns: IListColumn<T, N>[], entities: T[], isMobile: boolean, selectMode: boolean, setSelectMode: (value: boolean) => void, selectable?: ISelectableRowsOptions<T, N>, isInfinite?: boolean, gridRef?: RefObject<HTMLElement | null>, expandedRows?: string[]) => {
37
40
  selectableColumns: IListColumn<T, N>[];
@@ -6,8 +6,9 @@ const {
6
6
  Object.defineProperty(exports, "__esModule", {
7
7
  value: true
8
8
  });
9
- exports.TRANSLATIONS = exports.useSelectableRows = exports.SelectableWrapperName = exports.getEmptySelection = void 0;
9
+ exports.TRANSLATIONS = exports.useSelectableRows = exports.SelectableWrapperName = exports.isAllSelection = exports.getEmptySelection = void 0;
10
10
  const jsx_runtime_1 = require("react/jsx-runtime");
11
+ /* eslint-disable complexity */
11
12
  const react_1 = require("react");
12
13
  const interfaces_1 = require("./interfaces");
13
14
  const checkboxCell_1 = require("../../dataGrid/columns/checkboxColumn/checkboxCell");
@@ -92,9 +93,12 @@ const getEmptySelection = () => ({
92
93
  selected: []
93
94
  });
94
95
  exports.getEmptySelection = getEmptySelection;
96
+ const isAllSelection = (all, selectedCount, allowExclusions) => !!all && (!!allowExclusions || selectedCount === 0);
97
+ exports.isAllSelection = isAllSelection;
95
98
  exports.SelectableWrapperName = "Selectable";
96
99
  // Infinite scroll has no pages, so the header checkbox always means "select all" (never current page).
97
100
  const resolveCheckboxEmitsCurrentPage = (isInfinite, checkboxSelectsCurrentPage, turnOffSelectAll) => isInfinite ? false : checkboxSelectsCurrentPage !== null && checkboxSelectsCurrentPage !== void 0 ? checkboxSelectsCurrentPage : turnOffSelectAll;
101
+ const getSelectionMenuState = (allSelected, selections, pageState) => allSelected ? selections.size === 0 ? interfaces_1.SelectionState.All : interfaces_1.SelectionState.Partial : pageState;
98
102
  const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMode, selectable, isInfinite = false, gridRef, expandedRows) => {
99
103
  var _a;
100
104
  const {
@@ -138,20 +142,25 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
138
142
  }, [selectable === null || selectable === void 0 ? void 0 : selectable.checkboxPlaceholder, selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
139
143
  const isAllSelected = (0, react_1.useMemo)(() => {
140
144
  var _a;
141
- return ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false;
142
- }, [(_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all]);
145
+ return (0, exports.isAllSelection)((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, selections.size, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions);
146
+ }, [(_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, selections.size]);
143
147
  const selectableState = (0, react_1.useMemo)(() => (0, calculateSelectionState_1.calculateSelectionState)(entities, selections, isAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy), [checkboxDisabled, checkboxVisible, entities, isAllSelected, selections, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
148
+ const selectionMenuState = getSelectionMenuState(isAllSelected, selections, selectableState);
144
149
  const firstColumnId = (0, react_1.useMemo)(() => columns.length > 0 ? columns[0].id : null, [columns]);
145
150
  (0, react_1.useEffect)(() => {
146
151
  if (selectable && selectable.selection) {
147
152
  const newSelections = new Set(selectable.selection.selected);
148
- const newState = (0, calculateSelectionState_1.calculateSelectionState)(entities, newSelections, selectable.selection.all || false, checkboxVisible, checkboxDisabled, selectable.strategy);
153
+ const newState = (0, calculateSelectionState_1.calculateSelectionState)(entities, newSelections, isAllSelected, checkboxVisible, checkboxDisabled, selectable.strategy);
149
154
  setState(newState);
150
155
  }
151
- }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
156
+ }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, selectable === null || selectable === void 0 ? void 0 : selectable.strategy, isAllSelected]);
152
157
  const getNewSelection = (0, react_1.useCallback)((id, checked) => {
153
- var _a;
154
- if ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) {
158
+ if (isAllSelected && !(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions)) {
159
+ const included = (0, getCurrentPageSelection_1.getCurrentPageSelection)(entities, wrappedSelectable);
160
+ (0, checkRow_1.checkRow)(included, wrappedSelectable, entities, id, checked);
161
+ return included;
162
+ }
163
+ if (isAllSelected) {
155
164
  // MYG-142234: `selections` holds the ids excluded from the "all" selection.
156
165
  // Toggling a row here must only ever add/remove that single id from the
157
166
  // exclusion set - it must never be rebuilt from `entities` (the current page),
@@ -162,10 +171,9 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
162
171
  }
163
172
  (0, checkRow_1.checkRow)(selections, wrappedSelectable, entities, id, checked);
164
173
  return selections;
165
- }, [entities, selectable, wrappedSelectable, selections]);
174
+ }, [entities, selectable, wrappedSelectable, selections, isAllSelected]);
166
175
  const onSelectListener = (0, react_1.useCallback)((id, checked) => {
167
- var _a;
168
- const wasAllSelected = ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false;
176
+ const wasAllSelected = !!(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && isAllSelected;
169
177
  const newSelection = getNewSelection(id, checked);
170
178
  const newState = (0, calculateSelectionState_1.calculateSelectionState)(entities, newSelection, wasAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
171
179
  // MYG-142234: once selection spans "all" pages, unchecking a row must keep `all: true`
@@ -180,26 +188,26 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
180
188
  setState(newState);
181
189
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect(newValue);
182
190
  setSelectMode(newValue.all || newValue.selected.length > 0);
183
- }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, setState, getNewSelection]);
191
+ }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, setState, getNewSelection, isAllSelected]);
184
192
  const onBatchSelect = (0, react_1.useCallback)((rangeIds, select, base) => {
185
- var _a;
186
193
  const result = new Set(base);
187
194
  const limit = selectable === null || selectable === void 0 ? void 0 : selectable.selectionLimit;
195
+ const wasAllSelected = !!(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && isAllSelected;
188
196
  for (const id of rangeIds) {
189
- if (select && limit !== undefined && result.size >= limit) {
197
+ if (!wasAllSelected && select && limit !== undefined && result.size >= limit) {
190
198
  break;
191
199
  }
192
- (0, checkRow_1.checkRow)(result, wrappedSelectable, entities, id, select);
200
+ (0, checkRow_1.checkRow)(result, wrappedSelectable, entities, id, wasAllSelected ? !select : select);
193
201
  }
194
- const newState = (0, calculateSelectionState_1.calculateSelectionState)(entities, result, ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
202
+ const newState = (0, calculateSelectionState_1.calculateSelectionState)(entities, result, wasAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
195
203
  const newValue = {
196
204
  selected: [...result],
197
- all: newState === interfaces_1.SelectionState.All
205
+ all: wasAllSelected || newState === interfaces_1.SelectionState.All
198
206
  };
199
207
  setState(newState);
200
208
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect(newValue);
201
209
  setSelectMode(newValue.all || newValue.selected.length > 0);
202
- }, [entities, selectable, wrappedSelectable, checkboxVisible, checkboxDisabled, setSelectMode]);
210
+ }, [entities, selectable, wrappedSelectable, checkboxVisible, checkboxDisabled, setSelectMode, isAllSelected]);
203
211
  // Deliberately independent of the current selection, so the drag anchor survives the
204
212
  // selection changes a drag itself produces.
205
213
  const {
@@ -210,7 +218,7 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
210
218
  const entity = entityById.get(row.id);
211
219
  return !!entity && checkboxVisible(entity, row.isNested) && !checkboxDisabled(entity, row.isNested);
212
220
  }, [entityById, checkboxVisible, checkboxDisabled]);
213
- const getBaseSelection = (0, react_1.useCallback)(() => state === interfaces_1.SelectionState.All ? (0, getCurrentPageSelection_1.getCurrentPageSelection)(entities, wrappedSelectable) : new Set(selections), [state, entities, wrappedSelectable, selections]);
221
+ const getBaseSelection = (0, react_1.useCallback)(() => isAllSelected && !(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) ? (0, getCurrentPageSelection_1.getCurrentPageSelection)(entities, wrappedSelectable) : new Set(selections), [isAllSelected, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, entities, wrappedSelectable, selections]);
214
222
  const {
215
223
  onCheckboxMouseDown,
216
224
  suppressNextChangeRef
@@ -242,14 +250,12 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
242
250
  }
243
251
  const newSelections = (0, getCurrentPageSelection_1.getCurrentPageSelection)(entities, wrappedSelectable);
244
252
  setState(newState);
245
- // MYG-142234: previous `selected` ids mean something different depending on `all` -
246
- // included ids when `all` is false, excluded ids when `all` is true. Switching to an
247
- // explicit "select current page" here always leaves `all: false`, so a previous
248
- // exclusion list must not be reinterpreted as an inclusion list.
249
- const previousSelections = ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) ? [] : (_c = (_b = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _b === void 0 ? void 0 : _b.selected) !== null && _c !== void 0 ? _c : [];
250
- const mergedSelections = new Set([...previousSelections, ...newSelections]);
251
- const newValue = {
252
- selected: [...mergedSelections],
253
+ const previousSelections = (_b = (_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.selected) !== null && _b !== void 0 ? _b : [];
254
+ const newValue = (selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && ((_c = selectable.selection) === null || _c === void 0 ? void 0 : _c.all) ? {
255
+ selected: previousSelections.filter(id => !newSelections.has(id)),
256
+ all: true
257
+ } : {
258
+ selected: [...new Set([...previousSelections, ...newSelections])],
253
259
  all: false
254
260
  };
255
261
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect(newValue);
@@ -328,7 +334,9 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
328
334
  const newState = checkboxEmitsCurrentPage ? interfaces_1.SelectionState.CurrentPage : interfaces_1.SelectionState.All;
329
335
  setState(newState);
330
336
  onChangeSelectionState(newState);
331
- } else if ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all) {
337
+ } else if (isInfinite) {
338
+ onChangeSelectionState(interfaces_1.SelectionState.None);
339
+ } else if (selectable.allowExclusions && ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all)) {
332
340
  // MYG-142234: in exclusion mode, `selected` holds excluded ids. Unchecking
333
341
  // the header must add the current page's ids to that exclusion set and
334
342
  // keep `all: true` - it must never be treated as an inclusion list, or
@@ -356,7 +364,7 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
356
364
  setSelectMode(remaining.length > 0);
357
365
  }
358
366
  },
359
- calculateSelectionState: () => selectableState,
367
+ calculateSelectionState: () => selectionMenuState,
360
368
  checkboxInHeader: checkboxInHeaderValue,
361
369
  header: selectableHeader,
362
370
  onSelect: newState => {
@@ -369,7 +377,7 @@ const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMod
369
377
  return cell;
370
378
  }
371
379
  };
372
- }, [checkboxDisabled, checkboxTitle, checkboxVisible, headerTitle, onChangeSelectionState, onCheckboxChange, onCheckboxMouseDown, hasSelectable, checkboxEmitsCurrentPage, checkboxInHeaderValue, selectableHeader, selectableTurnOffSelectAll, isInfinite, selectable, wrappedSelectable, selectableState, state, isMobile, checkboxPlaceholder, isEntityChecked, entities, setSelectMode]);
380
+ }, [checkboxDisabled, checkboxTitle, checkboxVisible, headerTitle, onChangeSelectionState, onCheckboxChange, onCheckboxMouseDown, hasSelectable, checkboxEmitsCurrentPage, checkboxInHeaderValue, selectableHeader, selectableTurnOffSelectAll, isInfinite, selectable, wrappedSelectable, selectionMenuState, state, isMobile, checkboxPlaceholder, isEntityChecked, entities, setSelectMode]);
373
381
  const selectableFeedWrapper = (0, react_1.useCallback)((primaryData, secondaryData, entity, isNested) => {
374
382
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
375
383
  if (!entity) {
@@ -12,10 +12,7 @@ const calculateSelectionState = (entities, selections, all, checkboxVisible, che
12
12
  // otherwise-fully-selected set (see ISelectionValue), rather than included ids.
13
13
  const isExcluded = (entity) => {
14
14
  const children = strategy === "nested" ? (0, showMoreRow_1.getChildrenForSelection)(entity) : undefined;
15
- if (children) {
16
- if (children.length === 0) {
17
- return false;
18
- }
15
+ if (children && children.length > 0) {
19
16
  return children.every(child => {
20
17
  if (!checkboxVisible(child, true)) {
21
18
  return true;
@@ -30,7 +27,7 @@ const calculateSelectionState = (entities, selections, all, checkboxVisible, che
30
27
  };
31
28
  const isPartiallyExcluded = (entity) => {
32
29
  const children = strategy === "nested" ? (0, showMoreRow_1.getChildrenForSelection)(entity) : undefined;
33
- if (children) {
30
+ if (children && children.length > 0) {
34
31
  return children.some(child => selections.has(child.id));
35
32
  }
36
33
  return selections.has(entity.id);
@@ -53,7 +50,7 @@ const calculateSelectionState = (entities, selections, all, checkboxVisible, che
53
50
  }
54
51
  if (strategy === "nested") {
55
52
  const children = (0, showMoreRow_1.getChildrenForSelection)(entity);
56
- if (children) {
53
+ if (children && children.length > 0) {
57
54
  return children.every(child => {
58
55
  if (!checkboxVisible(child, true)) {
59
56
  return true;
@@ -64,7 +61,7 @@ const calculateSelectionState = (entities, selections, all, checkboxVisible, che
64
61
  return selections.has(child.id);
65
62
  });
66
63
  }
67
- return false;
64
+ return selections.has(entity.id);
68
65
  }
69
66
  return selections.has(entity.id);
70
67
  })
@@ -5,12 +5,15 @@ const showMoreRow_1 = require("../../../commonHelpers/showMoreRow");
5
5
  const getCurrentPageSelection = (entities, selectable) => {
6
6
  const newSelections = new Set();
7
7
  entities.forEach(entity => {
8
- const isVisible = (selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible) ? selectable.checkboxVisible(entity) : true;
8
+ const isVisible = !entity.isLoading && !(0, showMoreRow_1.isShowMoreRow)(entity) && ((selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible) ? selectable.checkboxVisible(entity) : true);
9
9
  const isEnabled = !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) || !selectable.checkboxDisabled(entity);
10
- if ((selectable === null || selectable === void 0 ? void 0 : selectable.strategy) === "nested") {
11
- ((0, showMoreRow_1.getChildrenForSelection)(entity) || []).forEach(child => {
12
- const isChildVisible = selectable.checkboxVisible ? selectable.checkboxVisible(child, true) : true;
13
- const isChildEnabled = !selectable.checkboxDisabled || !selectable.checkboxDisabled(child, true);
10
+ const children = (selectable === null || selectable === void 0 ? void 0 : selectable.strategy) === "nested" ? (0, showMoreRow_1.getChildrenForSelection)(entity) : undefined;
11
+ if (children && children.length > 0) {
12
+ children.forEach(child => {
13
+ const isChildVisible = !child.isLoading &&
14
+ !(0, showMoreRow_1.isShowMoreRow)(child) &&
15
+ ((selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible) ? selectable.checkboxVisible(child, true) : true);
16
+ const isChildEnabled = !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) || !selectable.checkboxDisabled(child, true);
14
17
  if (isChildVisible && isChildEnabled) {
15
18
  newSelections.add(child.id);
16
19
  }
@@ -1,6 +1,6 @@
1
1
  import { IZenComponentProps, IZenGridItem } from "../commonHelpers/zenComponent";
2
2
  import { ReactNode } from "react";
3
- import { ISelectableRowsOptions } from "./selectable/useSelectableRows";
3
+ import { getEmptySelection, ISelectableRowsOptions } from "./selectable/useSelectableRows";
4
4
  import { ISortableOptions } from "./sortable/useSortableColumns";
5
5
  import { IFlexibleColumnsOptions } from "./flexible/useFlexibleColumns";
6
6
  import { IColumnComponent, IListColumnWrapper } from "../dataGrid/listColumn";
@@ -11,7 +11,6 @@ import { ITablePagination, TablePagination } from "./children/tablePagination";
11
11
  import { ITableEmpty, TableEmpty } from "./children/tableEmpty";
12
12
  import { ITableFooter, TableFooter } from "./children/tableFooter";
13
13
  import "./table.less";
14
- import { getEmptySelection } from "./selectable/useSelectableRows";
15
14
  import { ColumnSortDirection } from "./sortable/interfaces";
16
15
  import { getSortableValue } from "./sortable/useSortableColumns";
17
16
  import { ActionsColumn } from "../dataGrid/columns/actionsColumn/actionsColumn";
@@ -13,22 +13,6 @@ const dataFeed_1 = require("../dataFeed/dataFeed");
13
13
  const dataGrid_1 = require("../dataGrid/dataGrid");
14
14
  const lazyLoadingEnd_1 = require("../dataGrid/lazyLoadingEnd/lazyLoadingEnd");
15
15
  const useSelectableRows_1 = require("./selectable/useSelectableRows");
16
- const useSortableColumns_1 = require("./sortable/useSortableColumns");
17
- const useFlexibleColumns_1 = require("./flexible/useFlexibleColumns");
18
- const useActions_1 = require("./actions/useActions");
19
- const useLoading_1 = require("./loading/useLoading");
20
- const emptySearchList_1 = require("../dataGrid/emptySearchList/emptySearchList");
21
- const entitiesListActions_1 = require("../dataGrid/entitiesListActions/entitiesListActions");
22
- const dataFeedSelectPanel_1 = require("../dataFeed/dataFeedSelectPanel/dataFeedSelectPanel");
23
- const columnsListButton_1 = require("../dataGrid/entitiesListActions/actions/columnsListButton");
24
- const useTableChildren_1 = require("./children/useTableChildren");
25
- const deviceType_1 = require("../commonHelpers/hooks/deviceType");
26
- const useMobile_1 = require("../commonHelpers/hooks/useMobile");
27
- const tableFullscreen_1 = require("./children/tableFullscreen");
28
- const tableBulkActions_1 = require("./children/tableBulkActions");
29
- const tablePagination_1 = require("./children/tablePagination");
30
- const tableEmpty_1 = require("./children/tableEmpty");
31
- const tableFooter_1 = require("./children/tableFooter");
32
16
  injectString("da-DK", "Loading more rows", "Indl\xE6ser flere r\xE6kker");
33
17
  injectString("en", "Loading more rows", "Loading more rows");
34
18
  injectString("fi-FI", "Loading more rows", "Ladataan lis\xE4\xE4 rivej\xE4");
@@ -146,13 +130,29 @@ injectString("ka-GE", "Bulk select", "\u10DB\u10D0\u10E1\u10D8\u10E3\u10E0\u10D8
146
130
  injectString("el-GR", "Bulk select", "\u039C\u03B1\u03B6\u03B9\u03BA\u03AE \u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AE");
147
131
  injectString("et-EE", "Bulk select", "Massvalimine");
148
132
  injectString("bg-BG", "Bulk select", "\u041C\u0430\u0441\u043E\u0432 \u0438\u0437\u0431\u043E\u0440");
149
- const useSelectableRows_2 = require("./selectable/useSelectableRows");
150
133
  Object.defineProperty(exports, "getEmptySelection", {
151
134
  enumerable: true,
152
135
  get: function () {
153
- return useSelectableRows_2.getEmptySelection;
136
+ return useSelectableRows_1.getEmptySelection;
154
137
  }
155
138
  });
139
+ const useSortableColumns_1 = require("./sortable/useSortableColumns");
140
+ const useFlexibleColumns_1 = require("./flexible/useFlexibleColumns");
141
+ const useActions_1 = require("./actions/useActions");
142
+ const useLoading_1 = require("./loading/useLoading");
143
+ const emptySearchList_1 = require("../dataGrid/emptySearchList/emptySearchList");
144
+ const entitiesListActions_1 = require("../dataGrid/entitiesListActions/entitiesListActions");
145
+ const dataFeedSelectPanel_1 = require("../dataFeed/dataFeedSelectPanel/dataFeedSelectPanel");
146
+ const columnsListButton_1 = require("../dataGrid/entitiesListActions/actions/columnsListButton");
147
+ const useTableChildren_1 = require("./children/useTableChildren");
148
+ const deviceType_1 = require("../commonHelpers/hooks/deviceType");
149
+ const useMobile_1 = require("../commonHelpers/hooks/useMobile");
150
+ const getCurrentPageSelection_1 = require("./selectable/utils/getCurrentPageSelection");
151
+ const tableFullscreen_1 = require("./children/tableFullscreen");
152
+ const tableBulkActions_1 = require("./children/tableBulkActions");
153
+ const tablePagination_1 = require("./children/tablePagination");
154
+ const tableEmpty_1 = require("./children/tableEmpty");
155
+ const tableFooter_1 = require("./children/tableFooter");
156
156
  const interfaces_1 = require("./sortable/interfaces");
157
157
  Object.defineProperty(exports, "ColumnSortDirection", {
158
158
  enumerable: true,
@@ -235,7 +235,7 @@ const TableInner = ({
235
235
  active,
236
236
  onClick
237
237
  }) => {
238
- var _a, _b, _c;
238
+ var _a, _b, _c, _d, _e;
239
239
  const isMobile = (0, useMobile_1.useMobile)();
240
240
  const {
241
241
  translate
@@ -325,13 +325,19 @@ const TableInner = ({
325
325
  } = (0, useFeedExpandable_1.useFeedExpandable)(isMobile, feedExpandable);
326
326
  // Keep bulk-action state available during initial/replacement loading for backward compatibility.
327
327
  // The row controls themselves still use selectableForRender and remain hidden until real rows can render.
328
- const selectionQty = (0, react_1.useMemo)(() => {
328
+ const allSelected = (0, react_1.useMemo)(() => {
329
329
  var _a, _b;
330
+ return (0, useSelectableRows_1.isAllSelection)((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, (_b = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _b === void 0 ? void 0 : _b.selected.length, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions);
331
+ }, [(_c = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _c === void 0 ? void 0 : _c.all, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, (_d = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _d === void 0 ? void 0 : _d.selected.length]);
332
+ const selectionQty = (0, react_1.useMemo)(() => {
333
+ var _a;
330
334
  if (!selectable) {
331
335
  return 0;
332
336
  }
333
- return ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all) ? loadingEntities.length : ((_b = selectable.selection) === null || _b === void 0 ? void 0 : _b.selected.length) || 0;
334
- }, [selectable, loadingEntities.length]);
337
+ const ids = new Set((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.selected);
338
+ const base = entities.length > 0 ? entities : loadingEntities;
339
+ return allSelected ? [...(0, getCurrentPageSelection_1.getCurrentPageSelection)(base, selectable)].filter(id => !selectable.allowExclusions || !ids.has(id)).length : ids.size;
340
+ }, [selectable, entities, loadingEntities, allSelected]);
335
341
  const clearSelection = (0, react_1.useCallback)(() => {
336
342
  setSelectMode(false);
337
343
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect({
@@ -348,6 +354,7 @@ const TableInner = ({
348
354
  }, [onClickRow]);
349
355
  const {
350
356
  bulkActions,
357
+ hasSelection,
351
358
  pagination,
352
359
  paginationTotal,
353
360
  activePage,
@@ -356,7 +363,7 @@ const TableInner = ({
356
363
  detailPanel,
357
364
  footer,
358
365
  other
359
- } = (0, useTableChildren_1.useTableChildren)(children, isMobile, selectionQty, ((_c = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _c === void 0 ? void 0 : _c.all) || false, clearSelection, activeId, gridRef, onCloseDetailPanel, isInfinite, entities.length);
366
+ } = (0, useTableChildren_1.useTableChildren)(children, isMobile, selectionQty, allSelected, clearSelection, activeId, gridRef, onCloseDetailPanel, isInfinite, entities.length, (selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && ((_e = selectable.selection) === null || _e === void 0 ? void 0 : _e.all) ? new Set(selectable.selection.selected).size : 0, (selectable === null || selectable === void 0 ? void 0 : selectable.strategy) !== "nested" && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible), !!feedEndCallback || !!isLoading);
360
367
  const hasDetailPanel = !!detailPanel;
361
368
  const shouldEnableRowClick = hasDetailPanel || !!onClick;
362
369
  const activeIdForRow = shouldEnableRowClick ? activeId : undefined;
@@ -391,7 +398,7 @@ const TableInner = ({
391
398
  children: [hasToolbar ? (0, jsx_runtime_1.jsxs)(entitiesListActions_1.EntitiesListActions, {
392
399
  gridType: isMobile ? deviceType_1.DeviceType.Mobile : deviceType_1.DeviceType.Desktop,
393
400
  children: [(0, jsx_runtime_1.jsxs)(entitiesListActions_1.EntitiesListActions.Actions, {
394
- children: [!isMobile && bulkActions && selectionQty > 0 ? bulkActions : null, feedSortControl]
401
+ children: [!isMobile && hasSelection ? bulkActions : null, feedSortControl]
395
402
  }), showPagination || other.length > 0 ? (0, jsx_runtime_1.jsxs)(entitiesListActions_1.EntitiesListActions.Pagination, {
396
403
  children: [(0, jsx_runtime_1.jsx)(entitiesListActions_1.EntitiesListActions.CustomElements, {
397
404
  children: other
@@ -12,6 +12,8 @@ export interface IBulkActionsProps extends IZenComponentProps {
12
12
  * even when `selected` is 0 (i.e., cross-page "select all" is active).
13
13
  */
14
14
  allSelected?: boolean;
15
+ /** Show a selection label without a count when the selected total is unknown. */
16
+ selectionCountUnknown?: boolean;
15
17
  onClearSelection: () => void;
16
18
  onSelectAll?: () => void;
17
19
  gridType?: DeviceType;
@@ -23,7 +25,7 @@ export interface IBulkActions extends IBulkActionsProps {
23
25
  ActionButton: (props: IBulkActionButton) => ReactNode;
24
26
  }
25
27
  export declare const BulkActions: {
26
- ({ selected, allSelected, className, onClearSelection, onSelectAll, gridType, children, turnOffSelectAll }: IBulkActionsProps): import("react/jsx-runtime").JSX.Element;
28
+ ({ selected, allSelected, selectionCountUnknown, className, onClearSelection, onSelectAll, gridType, children, turnOffSelectAll }: IBulkActionsProps): import("react/jsx-runtime").JSX.Element;
27
29
  ActionLink: ({ title, icon, link, disabled, type, ref }: IBulkActionLink) => import("react/jsx-runtime").JSX.Element;
28
30
  ActionButton: ({ title, icon, clickHandler, onClick, disabled, type, ref }: IBulkActionButton) => import("react/jsx-runtime").JSX.Element;
29
31
  translations: string[];
@@ -13,6 +13,39 @@ import { useResizeObserver } from "../../../../commonHelpers/hooks/useResizeObse
13
13
  import { Menu } from "../../../../menu/menu";
14
14
  import { IconDotVertical } from "../../../../icons/iconDotVertical";
15
15
  import { Button } from "../../../../button/button";
16
+ injectString("cs", "Selected", "Vybr\xE1no");
17
+ injectString("da-DK", "Selected", "Valgt");
18
+ injectString("de", "Selected", "Ausgew\xE4hlt");
19
+ injectString("en", "Selected", "Selected");
20
+ injectString("es", "Selected", "Seleccionado");
21
+ injectString("fi-FI", "Selected", "Valittu");
22
+ injectString("fr", "Selected", "S\xE9lectionn\xE9");
23
+ injectString("fr-FR", "Selected", "S\xE9lectionn\xE9");
24
+ injectString("hu-HU", "Selected", "Kiv\xE1lasztott");
25
+ injectString("id", "Selected", "Dipilih");
26
+ injectString("it", "Selected", "Selezionato");
27
+ injectString("ja", "Selected", "\u9078\u629E\u8ECA\u4E21");
28
+ injectString("ko-KR", "Selected", "\uC120\uD0DD\uB428");
29
+ injectString("ms", "Selected", "Dipilih");
30
+ injectString("nb-NO", "Selected", "Valgt");
31
+ injectString("nl", "Selected", "Geselecteerd");
32
+ injectString("pl", "Selected", "Wybrano");
33
+ injectString("pt-BR", "Selected", "Selecionado");
34
+ injectString("pt-PT", "Selected", "Selecionado");
35
+ injectString("sk-SK", "Selected", "Vybran\xE9");
36
+ injectString("sv", "Selected", "Valda");
37
+ injectString("th", "Selected", "\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E41\u0E25\u0E49\u0E27");
38
+ injectString("tr", "Selected", "Se\xE7ildi");
39
+ injectString("zh-Hans", "Selected", "\u9009\u4E2D\u7684");
40
+ injectString("zh-TW", "Selected", "\u5DF2\u9078\u53D6");
41
+ injectString("ro-RO", "Selected", "Selec\u021Bie");
42
+ injectString("ar-001", "Selected", "\u0627\u0644\u0645\u062D\u062F\u062F");
43
+ injectString("lt-LT", "Selected", "Pasirinkta");
44
+ injectString("lv-LV", "Selected", "Atlas\u012Bts");
45
+ injectString("ka-GE", "Selected", "\u10D0\u10E0\u10E9\u10D4\u10E3\u10DA\u10D8");
46
+ injectString("el-GR", "Selected", "\u0395\u03C0\u03B9\u03BB\u03B5\u03B3\u03BC\u03AD\u03BD\u03B1");
47
+ injectString("et-EE", "Selected", "Valitud");
48
+ injectString("bg-BG", "Selected", "\u0418\u0437\u0431\u0440\u0430\u043D\u0438");
16
49
  injectString("cs", "{1} selected", "{1} vybran\xFD/vybran\xE9");
17
50
  injectString("da-DK", "{1} selected", "{1} valgt");
18
51
  injectString("de", "{1} selected", "{1} ausgew\xE4hlt");
@@ -181,6 +214,7 @@ injectString("bg-BG", "More", "\u041F\u043E\u0432\u0435\u0447\u0435");
181
214
  export const BulkActions = ({
182
215
  selected,
183
216
  allSelected,
217
+ selectionCountUnknown,
184
218
  className,
185
219
  onClearSelection,
186
220
  onSelectAll,
@@ -214,7 +248,8 @@ export const BulkActions = ({
214
248
  widthDelta
215
249
  });
216
250
  const isMobile = gridType === DeviceType.Mobile;
217
- const text = translate("{1} selected").replace("{1}", allSelected ? translate("All") : selected.toFixed());
251
+ const hasSelection = selected > 0 || allSelected || selectionCountUnknown;
252
+ const text = selectionCountUnknown ? translate("Selected") : translate("{1} selected").replace("{1}", allSelected ? translate("All") : selected.toFixed());
218
253
  return _jsxs("div", {
219
254
  className: classNames(["zen-bulk-actions", className || "", isMobile ? "zen-bulk-actions--condensed" : ""]),
220
255
  ref: bulkActionsElement,
@@ -240,7 +275,7 @@ export const BulkActions = ({
240
275
  className: "zen-bulk-actions__action-button-holder",
241
276
  ref: buttonHolder,
242
277
  children: [Children.map(visibleElements, element => cloneElement(element, {
243
- disabled: selected > 0 || allSelected ? element.props.disabled : true
278
+ disabled: hasSelection ? element.props.disabled : true
244
279
  })), nonFittingElements.length > 0 ? _jsx(Menu, {
245
280
  title: translate("More"),
246
281
  trigger: _jsx(TextIconButton, {
@@ -261,7 +296,7 @@ export const BulkActions = ({
261
296
  id: props.id || "",
262
297
  name: props.title,
263
298
  icon: props.icon,
264
- disabled: props.disabled,
299
+ disabled: hasSelection ? props.disabled : true,
265
300
  onClick: clickHandler,
266
301
  link: link
267
302
  }, props.id);
@@ -276,4 +311,4 @@ export const BulkActions = ({
276
311
  };
277
312
  BulkActions.ActionLink = BulkActionLink;
278
313
  BulkActions.ActionButton = BulkActionButton;
279
- BulkActions.translations = ["Clear selection", "More options", "{1} selected", "Selecting..."];
314
+ BulkActions.translations = ["Clear selection", "More options", "{1} selected", "Selecting...", "Selected"];
@@ -1,6 +1,7 @@
1
1
  import { ReactElement, ReactNode, RefObject } from "react";
2
- export declare const useTableChildren: (children: ReactNode, isMobile: boolean, selected: number, allSelected: boolean, clearSelection: () => void, activeId?: string, gridRef?: RefObject<HTMLElement | null>, onCloseDetailPanel?: () => void, isInfinite?: boolean, loadedCount?: number) => {
3
- bulkActions: ReactElement | undefined;
2
+ export declare const useTableChildren: (children: ReactNode, isMobile: boolean, selected: number, allSelected: boolean, clearSelection: () => void, activeId?: string, gridRef?: RefObject<HTMLElement | null>, onCloseDetailPanel?: () => void, isInfinite?: boolean, loadedCount?: number, excludedCount?: number, canUsePaginationTotal?: boolean, hasMoreRows?: boolean) => {
3
+ bulkActions: import("react/jsx-runtime").JSX.Element | undefined;
4
+ hasSelection: boolean;
4
5
  pagination: ReactElement | undefined;
5
6
  paginationTotal: number | undefined;
6
7
  activePage: undefined;
@@ -28,8 +28,8 @@ export const useTableChildren = (children, isMobile, selected, allSelected, clea
28
28
  // Desktop infinite scroll (Table's `onScrollEnd`): when true, a `Table.Pagination` child is
29
29
  // rendered as the chevron-less "loaded" variant (see `pagination.tsx`) instead of the normal
30
30
  // paged controls, driven by `loadedCount` (the number of rows currently rendered).
31
- isInfinite = false, loadedCount = 0) => useMemo(() => {
32
- let bulkActions = undefined;
31
+ isInfinite = false, loadedCount = 0, excludedCount = 0, canUsePaginationTotal = true, hasMoreRows = false) => useMemo(() => {
32
+ let bulkActionButtons;
33
33
  let tablePagination = undefined;
34
34
  let activePage = undefined;
35
35
  // A plain `let` reassigned only inside the `Children.forEach` callback below would be seen
@@ -49,11 +49,11 @@ isInfinite = false, loadedCount = 0) => useMemo(() => {
49
49
  return;
50
50
  }
51
51
  if (isChildElement(child, TableBulkActions.displayName)) {
52
- const bulkActionButtons = child.props.children || [];
53
- bulkActions = (_jsx(BulkActions, { gridType: isMobile ? DeviceType.Mobile : DeviceType.Desktop, selected: selected, allSelected: allSelected, onClearSelection: clearSelection, children: bulkActionButtons }));
52
+ bulkActionButtons = child.props.children || [];
54
53
  return;
55
54
  }
56
55
  if (isChildElement(child, TablePagination.displayName)) {
56
+ paginationMeta.present = true;
57
57
  const total = child.props.total || 0;
58
58
  tablePagination = isInfinite
59
59
  ? createElement(Pagination, {
@@ -75,7 +75,7 @@ isInfinite = false, loadedCount = 0) => useMemo(() => {
75
75
  gridRef: gridRef,
76
76
  disabled: child.props.disabled
77
77
  });
78
- paginationMeta.total = total;
78
+ paginationMeta.total = child.props.total;
79
79
  activePage = child.props.activePage;
80
80
  return;
81
81
  }
@@ -113,8 +113,18 @@ isInfinite = false, loadedCount = 0) => useMemo(() => {
113
113
  }
114
114
  other.push(child);
115
115
  });
116
+ const allRowsLoaded = (!paginationMeta.present && !hasMoreRows) || (paginationMeta.total !== undefined && loadedCount >= paginationMeta.total);
117
+ const hasExclusions = allSelected && excludedCount > 0;
118
+ const selectedCount = allSelected && !allRowsLoaded
119
+ ? canUsePaginationTotal && paginationMeta.total !== undefined
120
+ ? Math.max(0, paginationMeta.total - excludedCount)
121
+ : undefined
122
+ : selected;
123
+ const hasSelection = selectedCount === undefined || selectedCount > 0;
124
+ const bulkActions = bulkActionButtons !== undefined ? (_jsx(BulkActions, { gridType: isMobile ? DeviceType.Mobile : DeviceType.Desktop, selected: selectedCount !== null && selectedCount !== void 0 ? selectedCount : selected, allSelected: allSelected && !hasExclusions && hasSelection, selectionCountUnknown: hasExclusions && selectedCount === undefined, onClearSelection: clearSelection, children: bulkActionButtons })) : undefined;
116
125
  return {
117
- bulkActions: bulkActions,
126
+ bulkActions,
127
+ hasSelection,
118
128
  pagination: tablePagination,
119
129
  paginationTotal: paginationMeta.total,
120
130
  activePage: activePage,
@@ -124,4 +134,18 @@ isInfinite = false, loadedCount = 0) => useMemo(() => {
124
134
  detailPanel: detailPanel,
125
135
  other: other
126
136
  };
127
- }, [children, clearSelection, isMobile, selected, gridRef, allSelected, activeId, onCloseDetailPanel, isInfinite, loadedCount]);
137
+ }, [
138
+ children,
139
+ clearSelection,
140
+ isMobile,
141
+ selected,
142
+ gridRef,
143
+ allSelected,
144
+ activeId,
145
+ onCloseDetailPanel,
146
+ isInfinite,
147
+ loadedCount,
148
+ excludedCount,
149
+ canUsePaginationTotal,
150
+ hasMoreRows
151
+ ]);
@@ -10,14 +10,9 @@ export interface ISelectionValue {
10
10
  */
11
11
  all: boolean;
12
12
  /**
13
- * The meaning of this list depends on `all`:
14
- * - when `all` is `false`, it is the ids explicitly *included* in the selection.
15
- * - when `all` is `true`, it is the ids explicitly *excluded* from the otherwise
16
- * fully-selected set (e.g. rows unchecked after a "select all" across paginated data).
17
- *
18
- * This lets `all: true` represent "all except these ids" without needing to know the
19
- * full id universe (MYG-142234) - unchecking a row while `all` is true only ever adds
20
- * to this exclusion list; it must never be rebuilt from the currently rendered page.
13
+ * Included IDs by default. With `ISelectableRowsOptions.allowExclusions: true`,
14
+ * these are excluded IDs while `all` is true, and included IDs while `all` is false.
15
+ * Consumers opting in must resolve the effective selection before running bulk actions.
21
16
  */
22
17
  selected: string[];
23
18
  }
@@ -8,6 +8,8 @@ export type TSelectionStrategy = "main" | "nested";
8
8
  export interface ISelectableRowsOptions<T extends IRowEntity<N>, N extends IEntityWithId> {
9
9
  selection: ISelectionValue | undefined;
10
10
  onSelect: (selection: ISelectionValue) => void;
11
+ /** Preserve cross-page Select All using excluded IDs. Consumers must resolve exclusions before running bulk actions. Defaults to false. */
12
+ allowExclusions?: boolean;
11
13
  checkboxDisabled?: (entity: T | N, isNested?: boolean) => boolean;
12
14
  checkboxVisible?: (entity: T | N, isNested?: boolean) => boolean;
13
15
  checkboxInHeader?: boolean;
@@ -32,6 +34,7 @@ export declare const getEmptySelection: () => {
32
34
  all: boolean;
33
35
  selected: string[];
34
36
  };
37
+ export declare const isAllSelection: (all: boolean | undefined, selectedCount: number | undefined, allowExclusions?: boolean) => boolean;
35
38
  export declare const SelectableWrapperName = "Selectable";
36
39
  export declare const useSelectableRows: <T extends IRowEntity<N>, N extends IEntityWithId>(columns: IListColumn<T, N>[], entities: T[], isMobile: boolean, selectMode: boolean, setSelectMode: (value: boolean) => void, selectable?: ISelectableRowsOptions<T, N>, isInfinite?: boolean, gridRef?: RefObject<HTMLElement | null>, expandedRows?: string[]) => {
37
40
  selectableColumns: IListColumn<T, N>[];
@@ -1,5 +1,6 @@
1
1
  import { injectString } from "../../utils/localization/translationsDictionary";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
+ /* eslint-disable complexity */
3
4
  import { useCallback, useEffect, useMemo, useState } from "react";
4
5
  import { SelectionState } from "./interfaces";
5
6
  import { CheckboxCell } from "../../dataGrid/columns/checkboxColumn/checkboxCell";
@@ -83,9 +84,11 @@ export const getEmptySelection = () => ({
83
84
  all: false,
84
85
  selected: []
85
86
  });
87
+ export const isAllSelection = (all, selectedCount, allowExclusions) => !!all && (!!allowExclusions || selectedCount === 0);
86
88
  export const SelectableWrapperName = "Selectable";
87
89
  // Infinite scroll has no pages, so the header checkbox always means "select all" (never current page).
88
90
  const resolveCheckboxEmitsCurrentPage = (isInfinite, checkboxSelectsCurrentPage, turnOffSelectAll) => isInfinite ? false : checkboxSelectsCurrentPage !== null && checkboxSelectsCurrentPage !== void 0 ? checkboxSelectsCurrentPage : turnOffSelectAll;
91
+ const getSelectionMenuState = (allSelected, selections, pageState) => allSelected ? selections.size === 0 ? SelectionState.All : SelectionState.Partial : pageState;
89
92
  export const useSelectableRows = (columns, entities, isMobile, selectMode, setSelectMode, selectable, isInfinite = false, gridRef, expandedRows) => {
90
93
  var _a;
91
94
  const {
@@ -129,20 +132,25 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
129
132
  }, [selectable === null || selectable === void 0 ? void 0 : selectable.checkboxPlaceholder, selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
130
133
  const isAllSelected = useMemo(() => {
131
134
  var _a;
132
- return ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false;
133
- }, [(_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all]);
135
+ return isAllSelection((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, selections.size, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions);
136
+ }, [(_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, selections.size]);
134
137
  const selectableState = useMemo(() => calculateSelectionState(entities, selections, isAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy), [checkboxDisabled, checkboxVisible, entities, isAllSelected, selections, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
138
+ const selectionMenuState = getSelectionMenuState(isAllSelected, selections, selectableState);
135
139
  const firstColumnId = useMemo(() => columns.length > 0 ? columns[0].id : null, [columns]);
136
140
  useEffect(() => {
137
141
  if (selectable && selectable.selection) {
138
142
  const newSelections = new Set(selectable.selection.selected);
139
- const newState = calculateSelectionState(entities, newSelections, selectable.selection.all || false, checkboxVisible, checkboxDisabled, selectable.strategy);
143
+ const newState = calculateSelectionState(entities, newSelections, isAllSelected, checkboxVisible, checkboxDisabled, selectable.strategy);
140
144
  setState(newState);
141
145
  }
142
- }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, selectable === null || selectable === void 0 ? void 0 : selectable.strategy]);
146
+ }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, selectable === null || selectable === void 0 ? void 0 : selectable.strategy, isAllSelected]);
143
147
  const getNewSelection = useCallback((id, checked) => {
144
- var _a;
145
- if ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) {
148
+ if (isAllSelected && !(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions)) {
149
+ const included = getCurrentPageSelection(entities, wrappedSelectable);
150
+ checkRow(included, wrappedSelectable, entities, id, checked);
151
+ return included;
152
+ }
153
+ if (isAllSelected) {
146
154
  // MYG-142234: `selections` holds the ids excluded from the "all" selection.
147
155
  // Toggling a row here must only ever add/remove that single id from the
148
156
  // exclusion set - it must never be rebuilt from `entities` (the current page),
@@ -153,10 +161,9 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
153
161
  }
154
162
  checkRow(selections, wrappedSelectable, entities, id, checked);
155
163
  return selections;
156
- }, [entities, selectable, wrappedSelectable, selections]);
164
+ }, [entities, selectable, wrappedSelectable, selections, isAllSelected]);
157
165
  const onSelectListener = useCallback((id, checked) => {
158
- var _a;
159
- const wasAllSelected = ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false;
166
+ const wasAllSelected = !!(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && isAllSelected;
160
167
  const newSelection = getNewSelection(id, checked);
161
168
  const newState = calculateSelectionState(entities, newSelection, wasAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
162
169
  // MYG-142234: once selection spans "all" pages, unchecking a row must keep `all: true`
@@ -171,26 +178,26 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
171
178
  setState(newState);
172
179
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect(newValue);
173
180
  setSelectMode(newValue.all || newValue.selected.length > 0);
174
- }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, setState, getNewSelection]);
181
+ }, [checkboxDisabled, checkboxVisible, entities, selectable, setSelectMode, setState, getNewSelection, isAllSelected]);
175
182
  const onBatchSelect = useCallback((rangeIds, select, base) => {
176
- var _a;
177
183
  const result = new Set(base);
178
184
  const limit = selectable === null || selectable === void 0 ? void 0 : selectable.selectionLimit;
185
+ const wasAllSelected = !!(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && isAllSelected;
179
186
  for (const id of rangeIds) {
180
- if (select && limit !== undefined && result.size >= limit) {
187
+ if (!wasAllSelected && select && limit !== undefined && result.size >= limit) {
181
188
  break;
182
189
  }
183
- checkRow(result, wrappedSelectable, entities, id, select);
190
+ checkRow(result, wrappedSelectable, entities, id, wasAllSelected ? !select : select);
184
191
  }
185
- const newState = calculateSelectionState(entities, result, ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) || false, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
192
+ const newState = calculateSelectionState(entities, result, wasAllSelected, checkboxVisible, checkboxDisabled, selectable === null || selectable === void 0 ? void 0 : selectable.strategy);
186
193
  const newValue = {
187
194
  selected: [...result],
188
- all: newState === SelectionState.All
195
+ all: wasAllSelected || newState === SelectionState.All
189
196
  };
190
197
  setState(newState);
191
198
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect(newValue);
192
199
  setSelectMode(newValue.all || newValue.selected.length > 0);
193
- }, [entities, selectable, wrappedSelectable, checkboxVisible, checkboxDisabled, setSelectMode]);
200
+ }, [entities, selectable, wrappedSelectable, checkboxVisible, checkboxDisabled, setSelectMode, isAllSelected]);
194
201
  // Deliberately independent of the current selection, so the drag anchor survives the
195
202
  // selection changes a drag itself produces.
196
203
  const {
@@ -201,7 +208,7 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
201
208
  const entity = entityById.get(row.id);
202
209
  return !!entity && checkboxVisible(entity, row.isNested) && !checkboxDisabled(entity, row.isNested);
203
210
  }, [entityById, checkboxVisible, checkboxDisabled]);
204
- const getBaseSelection = useCallback(() => state === SelectionState.All ? getCurrentPageSelection(entities, wrappedSelectable) : new Set(selections), [state, entities, wrappedSelectable, selections]);
211
+ const getBaseSelection = useCallback(() => isAllSelected && !(selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) ? getCurrentPageSelection(entities, wrappedSelectable) : new Set(selections), [isAllSelected, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, entities, wrappedSelectable, selections]);
205
212
  const {
206
213
  onCheckboxMouseDown,
207
214
  suppressNextChangeRef
@@ -233,14 +240,12 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
233
240
  }
234
241
  const newSelections = getCurrentPageSelection(entities, wrappedSelectable);
235
242
  setState(newState);
236
- // MYG-142234: previous `selected` ids mean something different depending on `all` -
237
- // included ids when `all` is false, excluded ids when `all` is true. Switching to an
238
- // explicit "select current page" here always leaves `all: false`, so a previous
239
- // exclusion list must not be reinterpreted as an inclusion list.
240
- const previousSelections = ((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all) ? [] : (_c = (_b = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _b === void 0 ? void 0 : _b.selected) !== null && _c !== void 0 ? _c : [];
241
- const mergedSelections = new Set([...previousSelections, ...newSelections]);
242
- const newValue = {
243
- selected: [...mergedSelections],
243
+ const previousSelections = (_b = (_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.selected) !== null && _b !== void 0 ? _b : [];
244
+ const newValue = (selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && ((_c = selectable.selection) === null || _c === void 0 ? void 0 : _c.all) ? {
245
+ selected: previousSelections.filter(id => !newSelections.has(id)),
246
+ all: true
247
+ } : {
248
+ selected: [...new Set([...previousSelections, ...newSelections])],
244
249
  all: false
245
250
  };
246
251
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect(newValue);
@@ -319,7 +324,9 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
319
324
  const newState = checkboxEmitsCurrentPage ? SelectionState.CurrentPage : SelectionState.All;
320
325
  setState(newState);
321
326
  onChangeSelectionState(newState);
322
- } else if ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all) {
327
+ } else if (isInfinite) {
328
+ onChangeSelectionState(SelectionState.None);
329
+ } else if (selectable.allowExclusions && ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all)) {
323
330
  // MYG-142234: in exclusion mode, `selected` holds excluded ids. Unchecking
324
331
  // the header must add the current page's ids to that exclusion set and
325
332
  // keep `all: true` - it must never be treated as an inclusion list, or
@@ -347,7 +354,7 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
347
354
  setSelectMode(remaining.length > 0);
348
355
  }
349
356
  },
350
- calculateSelectionState: () => selectableState,
357
+ calculateSelectionState: () => selectionMenuState,
351
358
  checkboxInHeader: checkboxInHeaderValue,
352
359
  header: selectableHeader,
353
360
  onSelect: newState => {
@@ -360,7 +367,7 @@ export const useSelectableRows = (columns, entities, isMobile, selectMode, setSe
360
367
  return cell;
361
368
  }
362
369
  };
363
- }, [checkboxDisabled, checkboxTitle, checkboxVisible, headerTitle, onChangeSelectionState, onCheckboxChange, onCheckboxMouseDown, hasSelectable, checkboxEmitsCurrentPage, checkboxInHeaderValue, selectableHeader, selectableTurnOffSelectAll, isInfinite, selectable, wrappedSelectable, selectableState, state, isMobile, checkboxPlaceholder, isEntityChecked, entities, setSelectMode]);
370
+ }, [checkboxDisabled, checkboxTitle, checkboxVisible, headerTitle, onChangeSelectionState, onCheckboxChange, onCheckboxMouseDown, hasSelectable, checkboxEmitsCurrentPage, checkboxInHeaderValue, selectableHeader, selectableTurnOffSelectAll, isInfinite, selectable, wrappedSelectable, selectionMenuState, state, isMobile, checkboxPlaceholder, isEntityChecked, entities, setSelectMode]);
364
371
  const selectableFeedWrapper = useCallback((primaryData, secondaryData, entity, isNested) => {
365
372
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
366
373
  if (!entity) {
@@ -9,10 +9,7 @@ export const calculateSelectionState = (entities, selections, all, checkboxVisib
9
9
  // otherwise-fully-selected set (see ISelectionValue), rather than included ids.
10
10
  const isExcluded = (entity) => {
11
11
  const children = strategy === "nested" ? getChildrenForSelection(entity) : undefined;
12
- if (children) {
13
- if (children.length === 0) {
14
- return false;
15
- }
12
+ if (children && children.length > 0) {
16
13
  return children.every(child => {
17
14
  if (!checkboxVisible(child, true)) {
18
15
  return true;
@@ -27,7 +24,7 @@ export const calculateSelectionState = (entities, selections, all, checkboxVisib
27
24
  };
28
25
  const isPartiallyExcluded = (entity) => {
29
26
  const children = strategy === "nested" ? getChildrenForSelection(entity) : undefined;
30
- if (children) {
27
+ if (children && children.length > 0) {
31
28
  return children.some(child => selections.has(child.id));
32
29
  }
33
30
  return selections.has(entity.id);
@@ -50,7 +47,7 @@ export const calculateSelectionState = (entities, selections, all, checkboxVisib
50
47
  }
51
48
  if (strategy === "nested") {
52
49
  const children = getChildrenForSelection(entity);
53
- if (children) {
50
+ if (children && children.length > 0) {
54
51
  return children.every(child => {
55
52
  if (!checkboxVisible(child, true)) {
56
53
  return true;
@@ -61,7 +58,7 @@ export const calculateSelectionState = (entities, selections, all, checkboxVisib
61
58
  return selections.has(child.id);
62
59
  });
63
60
  }
64
- return false;
61
+ return selections.has(entity.id);
65
62
  }
66
63
  return selections.has(entity.id);
67
64
  })
@@ -1,13 +1,16 @@
1
- import { getChildrenForSelection } from "../../../commonHelpers/showMoreRow";
1
+ import { getChildrenForSelection, isShowMoreRow } from "../../../commonHelpers/showMoreRow";
2
2
  export const getCurrentPageSelection = (entities, selectable) => {
3
3
  const newSelections = new Set();
4
4
  entities.forEach(entity => {
5
- const isVisible = (selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible) ? selectable.checkboxVisible(entity) : true;
5
+ const isVisible = !entity.isLoading && !isShowMoreRow(entity) && ((selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible) ? selectable.checkboxVisible(entity) : true);
6
6
  const isEnabled = !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) || !selectable.checkboxDisabled(entity);
7
- if ((selectable === null || selectable === void 0 ? void 0 : selectable.strategy) === "nested") {
8
- (getChildrenForSelection(entity) || []).forEach(child => {
9
- const isChildVisible = selectable.checkboxVisible ? selectable.checkboxVisible(child, true) : true;
10
- const isChildEnabled = !selectable.checkboxDisabled || !selectable.checkboxDisabled(child, true);
7
+ const children = (selectable === null || selectable === void 0 ? void 0 : selectable.strategy) === "nested" ? getChildrenForSelection(entity) : undefined;
8
+ if (children && children.length > 0) {
9
+ children.forEach(child => {
10
+ const isChildVisible = !child.isLoading &&
11
+ !isShowMoreRow(child) &&
12
+ ((selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible) ? selectable.checkboxVisible(child, true) : true);
13
+ const isChildEnabled = !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) || !selectable.checkboxDisabled(child, true);
11
14
  if (isChildVisible && isChildEnabled) {
12
15
  newSelections.add(child.id);
13
16
  }
@@ -1,6 +1,6 @@
1
1
  import { IZenComponentProps, IZenGridItem } from "../commonHelpers/zenComponent";
2
2
  import { ReactNode } from "react";
3
- import { ISelectableRowsOptions } from "./selectable/useSelectableRows";
3
+ import { getEmptySelection, ISelectableRowsOptions } from "./selectable/useSelectableRows";
4
4
  import { ISortableOptions } from "./sortable/useSortableColumns";
5
5
  import { IFlexibleColumnsOptions } from "./flexible/useFlexibleColumns";
6
6
  import { IColumnComponent, IListColumnWrapper } from "../dataGrid/listColumn";
@@ -11,7 +11,6 @@ import { ITablePagination, TablePagination } from "./children/tablePagination";
11
11
  import { ITableEmpty, TableEmpty } from "./children/tableEmpty";
12
12
  import { ITableFooter, TableFooter } from "./children/tableFooter";
13
13
  import "./table.less";
14
- import { getEmptySelection } from "./selectable/useSelectableRows";
15
14
  import { ColumnSortDirection } from "./sortable/interfaces";
16
15
  import { getSortableValue } from "./sortable/useSortableColumns";
17
16
  import { ActionsColumn } from "../dataGrid/columns/actionsColumn/actionsColumn";
@@ -4,7 +4,7 @@ import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
4
4
  import { DataFeed } from "../dataFeed/dataFeed";
5
5
  import { DataGrid as DataGridInternal, EndOfList as DataGridEndOfList } from "../dataGrid/dataGrid";
6
6
  import { LazyLoadingEnd } from "../dataGrid/lazyLoadingEnd/lazyLoadingEnd";
7
- import { useSelectableRows } from "./selectable/useSelectableRows";
7
+ import { getEmptySelection, isAllSelection, useSelectableRows } from "./selectable/useSelectableRows";
8
8
  import { useSortableColumns } from "./sortable/useSortableColumns";
9
9
  import { useFlexibleColumns } from "./flexible/useFlexibleColumns";
10
10
  import { useActions } from "./actions/useActions";
@@ -16,12 +16,12 @@ import { ColumnsListButton } from "../dataGrid/entitiesListActions/actions/colum
16
16
  import { useTableChildren } from "./children/useTableChildren";
17
17
  import { DeviceType } from "../commonHelpers/hooks/deviceType";
18
18
  import { useMobile } from "../commonHelpers/hooks/useMobile";
19
+ import { getCurrentPageSelection } from "./selectable/utils/getCurrentPageSelection";
19
20
  import { TableFullscreen } from "./children/tableFullscreen";
20
21
  import { TableBulkActions } from "./children/tableBulkActions";
21
22
  import { TablePagination } from "./children/tablePagination";
22
23
  import { TableEmpty } from "./children/tableEmpty";
23
24
  import { TableFooter } from "./children/tableFooter";
24
- import { getEmptySelection } from "./selectable/useSelectableRows";
25
25
  import { ColumnSortDirection } from "./sortable/interfaces";
26
26
  import { getSortableValue } from "./sortable/useSortableColumns";
27
27
  import { ActionsColumn } from "../dataGrid/columns/actionsColumn/actionsColumn";
@@ -185,7 +185,7 @@ const TableInner = ({
185
185
  active,
186
186
  onClick
187
187
  }) => {
188
- var _a, _b, _c;
188
+ var _a, _b, _c, _d, _e;
189
189
  const isMobile = useMobile();
190
190
  const {
191
191
  translate
@@ -275,13 +275,19 @@ const TableInner = ({
275
275
  } = useFeedExpandable(isMobile, feedExpandable);
276
276
  // Keep bulk-action state available during initial/replacement loading for backward compatibility.
277
277
  // The row controls themselves still use selectableForRender and remain hidden until real rows can render.
278
- const selectionQty = useMemo(() => {
278
+ const allSelected = useMemo(() => {
279
279
  var _a, _b;
280
+ return isAllSelection((_a = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _a === void 0 ? void 0 : _a.all, (_b = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _b === void 0 ? void 0 : _b.selected.length, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions);
281
+ }, [(_c = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _c === void 0 ? void 0 : _c.all, selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions, (_d = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _d === void 0 ? void 0 : _d.selected.length]);
282
+ const selectionQty = useMemo(() => {
283
+ var _a;
280
284
  if (!selectable) {
281
285
  return 0;
282
286
  }
283
- return ((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.all) ? loadingEntities.length : ((_b = selectable.selection) === null || _b === void 0 ? void 0 : _b.selected.length) || 0;
284
- }, [selectable, loadingEntities.length]);
287
+ const ids = new Set((_a = selectable.selection) === null || _a === void 0 ? void 0 : _a.selected);
288
+ const base = entities.length > 0 ? entities : loadingEntities;
289
+ return allSelected ? [...getCurrentPageSelection(base, selectable)].filter(id => !selectable.allowExclusions || !ids.has(id)).length : ids.size;
290
+ }, [selectable, entities, loadingEntities, allSelected]);
285
291
  const clearSelection = useCallback(() => {
286
292
  setSelectMode(false);
287
293
  selectable === null || selectable === void 0 ? void 0 : selectable.onSelect({
@@ -298,6 +304,7 @@ const TableInner = ({
298
304
  }, [onClickRow]);
299
305
  const {
300
306
  bulkActions,
307
+ hasSelection,
301
308
  pagination,
302
309
  paginationTotal,
303
310
  activePage,
@@ -306,7 +313,7 @@ const TableInner = ({
306
313
  detailPanel,
307
314
  footer,
308
315
  other
309
- } = useTableChildren(children, isMobile, selectionQty, ((_c = selectable === null || selectable === void 0 ? void 0 : selectable.selection) === null || _c === void 0 ? void 0 : _c.all) || false, clearSelection, activeId, gridRef, onCloseDetailPanel, isInfinite, entities.length);
316
+ } = useTableChildren(children, isMobile, selectionQty, allSelected, clearSelection, activeId, gridRef, onCloseDetailPanel, isInfinite, entities.length, (selectable === null || selectable === void 0 ? void 0 : selectable.allowExclusions) && ((_e = selectable.selection) === null || _e === void 0 ? void 0 : _e.all) ? new Set(selectable.selection.selected).size : 0, (selectable === null || selectable === void 0 ? void 0 : selectable.strategy) !== "nested" && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxDisabled) && !(selectable === null || selectable === void 0 ? void 0 : selectable.checkboxVisible), !!feedEndCallback || !!isLoading);
310
317
  const hasDetailPanel = !!detailPanel;
311
318
  const shouldEnableRowClick = hasDetailPanel || !!onClick;
312
319
  const activeIdForRow = shouldEnableRowClick ? activeId : undefined;
@@ -341,7 +348,7 @@ const TableInner = ({
341
348
  children: [hasToolbar ? _jsxs(EntitiesListActions, {
342
349
  gridType: isMobile ? DeviceType.Mobile : DeviceType.Desktop,
343
350
  children: [_jsxs(EntitiesListActions.Actions, {
344
- children: [!isMobile && bulkActions && selectionQty > 0 ? bulkActions : null, feedSortControl]
351
+ children: [!isMobile && hasSelection ? bulkActions : null, feedSortControl]
345
352
  }), showPagination || other.length > 0 ? _jsxs(EntitiesListActions.Pagination, {
346
353
  children: [_jsx(EntitiesListActions.CustomElements, {
347
354
  children: other
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@geotab/zenith",
3
- "version": "3.16.0-nightly.20261005.f8d767e",
3
+ "version": "3.17.0-beta.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
4
7
  "description": "Zenith components library on React",
5
8
  "main": "dist/index.js",
6
9
  "types": "esm/index.d.ts",
@@ -156,8 +159,5 @@
156
159
  "minimatch@<9": {
157
160
  "brace-expansion": "^1.1.18"
158
161
  }
159
- },
160
- "publishConfig": {
161
- "access": "public"
162
162
  }
163
163
  }