@linzjs/step-ag-grid 31.2.5 → 32.1.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.
Files changed (69) hide show
  1. package/dist/index.css +5 -1
  2. package/dist/src/components/GridCell.d.ts +1 -1
  3. package/dist/src/components/GridLoadableCell.d.ts +1 -1
  4. package/dist/src/components/GridNoRowsOverlay.d.ts +1 -1
  5. package/dist/src/components/GridPopoverHook.d.ts +1 -1
  6. package/dist/src/components/gridFilter/GridFilterDownloadCsvButton.d.ts +1 -1
  7. package/dist/src/components/gridFilter/GridFilterQuick.d.ts +1 -1
  8. package/dist/src/components/gridFilter/GridFilters.d.ts +1 -1
  9. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  10. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +1 -1
  11. package/dist/src/components/gridForm/GridFormInlineTextInput.d.ts +1 -1
  12. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +2 -2
  13. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +1 -1
  14. package/dist/src/components/gridForm/GridFormTextInput.d.ts +1 -1
  15. package/dist/src/components/gridHeader/GridHeaderSelect.d.ts +1 -1
  16. package/dist/src/components/gridHook/useGridContextMenu.d.ts +1 -1
  17. package/dist/src/components/gridHook/useGridCopy.d.ts +1 -1
  18. package/dist/src/components/gridHook/useGridRangeSelection.d.ts +1 -1
  19. package/dist/src/components/gridRender/GridRenderPopoutMenuCell.d.ts +1 -1
  20. package/dist/src/components/types.d.ts +2 -0
  21. package/dist/src/contexts/GridPopoverContextProvider.d.ts +1 -1
  22. package/dist/src/contexts/GridUpdatingContextProvider.d.ts +1 -1
  23. package/dist/src/lui/FormError.d.ts +1 -1
  24. package/dist/src/lui/TextAreaInput.d.ts +1 -1
  25. package/dist/src/react-menu3/components/ControlledMenu.d.ts +1 -1
  26. package/dist/src/react-menu3/components/FocusableItem.d.ts +1 -1
  27. package/dist/src/react-menu3/components/Menu.d.ts +1 -1
  28. package/dist/src/react-menu3/components/MenuGroup.d.ts +1 -1
  29. package/dist/src/react-menu3/components/MenuHeader.d.ts +1 -1
  30. package/dist/src/react-menu3/components/MenuList.d.ts +1 -1
  31. package/dist/src/react-menu3/components/MenuRadioGroup.d.ts +1 -1
  32. package/dist/src/react-menu3/components/SubMenu.d.ts +1 -1
  33. package/dist/src/utils/useSharedInterval.d.ts +5 -0
  34. package/dist/step-ag-grid.cjs +259 -105
  35. package/dist/step-ag-grid.cjs.map +1 -1
  36. package/dist/step-ag-grid.esm.js +260 -106
  37. package/dist/step-ag-grid.esm.js.map +1 -1
  38. package/package.json +29 -30
  39. package/src/components/Grid.tsx +6 -3
  40. package/src/components/gridForm/GridFormPopoverMenu.tsx +6 -2
  41. package/src/components/gridHook/useGridRangeSelection.ts +1 -1
  42. package/src/components/gridPopoverEdit/GridEditBoolean.tsx +64 -26
  43. package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +3 -0
  44. package/src/components/types.ts +2 -0
  45. package/src/contexts/GridContextProvider.tsx +18 -5
  46. package/src/react-app-env.d.ts +4 -1
  47. package/src/stories/grid/GridFilterColumnsMultiSelect.stories.tsx +1 -1
  48. package/src/stories/grid/GridNonEditableRow.stories.tsx +2 -2
  49. package/src/stories/grid/GridPopoutEditBoolean.stories.tsx +48 -9
  50. package/src/stories/grid/GridReadOnly.stories.tsx +69 -0
  51. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +1 -1
  52. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +1 -1
  53. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +1 -1
  54. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectGridInteraction.stories.tsx +1 -1
  55. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +1 -1
  56. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +48 -1
  57. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +1 -1
  58. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +1 -1
  59. package/src/stories/grid/gridFormStatic/GridFormDropDown.stories.tsx +1 -1
  60. package/src/stories/grid/gridFormStatic/GridFormEditBearing.stories.tsx +1 -1
  61. package/src/stories/grid/gridFormStatic/GridFormEditBearingCorrection.stories.tsx +1 -1
  62. package/src/stories/grid/gridFormStatic/GridFormMessage.stories.tsx +1 -1
  63. package/src/stories/grid/gridFormStatic/GridFormMultiSelect.stories.tsx +1 -1
  64. package/src/stories/grid/gridFormStatic/GridFormPopoverMenu.stories.tsx +1 -1
  65. package/src/stories/grid/gridFormStatic/GridFormTextArea.stories.tsx +1 -1
  66. package/src/stories/grid/gridFormStatic/GridFormTextInput.stories.tsx +1 -1
  67. package/src/styles/GridLoadableCell.scss +5 -1
  68. package/src/utils/useSharedInterval.ts +57 -0
  69. package/src/utils/util.ts +4 -2
@@ -2,7 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { LuiMiniSpinner, LuiIcon, useShowLUIMessage, LuiStatusSpinner, LuiButtonGroup, LuiButton, LuiCheckboxInput } from '@linzjs/lui';
3
3
  import React13, { forwardRef, useContext, useRef, useState, useImperativeHandle, useLayoutEffect, useCallback, useMemo, createContext, memo, useEffect, createElement, Suspense, Component as Component$1, useReducer, cloneElement, Fragment as Fragment$1, useId } from 'react';
4
4
  import ReactDOM, { createPortal, unstable_batchedUpdates, flushSync } from 'react-dom';
5
- import { negate, isEmpty, findIndex, defer, debounce as debounce$1, compact, xorBy, last, difference, delay, omit, sortBy, partition, pick, groupBy, fromPairs, toPairs, isEqual, pull, filter, sumBy, remove, flatten, castArray } from 'lodash-es';
5
+ import { negate, isEmpty, findIndex, defer, debounce as debounce$1, compact, xorBy, last, difference, omit, delay, sortBy, partition, pick, groupBy, fromPairs, toPairs, isEqual, pull, filter, sumBy, remove, flatten, castArray } from 'lodash-es';
6
6
  import { createRoot } from 'react-dom/client';
7
7
 
8
8
  /**
@@ -58454,8 +58454,10 @@ function requireLib () {
58454
58454
  }
58455
58455
 
58456
58456
  var libExports = requireLib();
58457
- var natsort = /*@__PURE__*/getDefaultExportFromCjs(libExports);
58457
+ var natsortModule = /*@__PURE__*/getDefaultExportFromCjs(libExports);
58458
58458
 
58459
+ // natsort is a CJS module that sets exports.default, so interop wraps it in an extra layer
58460
+ const natsort = natsortModule.default ?? natsortModule;
58459
58461
  const isNotEmpty = negate(isEmpty);
58460
58462
  const wait = (timeoutMs) => new Promise((resolve) => {
58461
58463
  setTimeout(resolve, timeoutMs);
@@ -58497,7 +58499,7 @@ const fnOrVar = (fn, param) => (typeof fn === 'function' ? fn(param) : fn);
58497
58499
  const sanitiseFileName = (filename) => {
58498
58500
  const valid = filename
58499
58501
  .trim()
58500
- .replaceAll(/(\/|\\)+/g, '-')
58502
+ .replaceAll(/([\/\\])+/g, '-')
58501
58503
  .replaceAll(/\s+/g, '_')
58502
58504
  .replaceAll(/[^\w\-āēīōūĀĒĪŌŪ.]/g, '');
58503
58505
  const parts = valid.split('.');
@@ -59020,14 +59022,6 @@ const useMenuChange = (onMenuChange, isOpen) => {
59020
59022
  }, [onMenuChange, isOpen]);
59021
59023
  };
59022
59024
 
59023
- //#region src/utils.ts
59024
- const PRE_ENTER = 0;
59025
- const ENTERING = 1;
59026
- const ENTERED = 2;
59027
- const PRE_EXIT = 3;
59028
- const EXITING = 4;
59029
- const EXITED = 5;
59030
- const UNMOUNTED = 6;
59031
59025
  const STATUS = [
59032
59026
  "preEnter",
59033
59027
  "entering",
@@ -59038,67 +59032,76 @@ const STATUS = [
59038
59032
  "unmounted"
59039
59033
  ];
59040
59034
  const getState = (status) => ({
59041
- _s: status,
59035
+ $: status,
59042
59036
  status: STATUS[status],
59043
- isEnter: status < PRE_EXIT,
59044
- isMounted: status !== UNMOUNTED,
59045
- isResolved: status === ENTERED || status > EXITING
59037
+ isEnter: status < 3,
59038
+ isMounted: status !== 6,
59039
+ isResolved: status === 2 || status > 4
59046
59040
  });
59047
- const startOrEnd = (unmounted) => unmounted ? UNMOUNTED : EXITED;
59041
+ const startOrEnd = (unmounted) => unmounted ? 6 : 5;
59048
59042
  const getEndStatus = (status, unmountOnExit) => {
59049
59043
  switch (status) {
59050
- case ENTERING:
59051
- case PRE_ENTER: return ENTERED;
59052
- case EXITING:
59053
- case PRE_EXIT: return startOrEnd(unmountOnExit);
59044
+ case 1:
59045
+ case 0: return 2;
59046
+ case 4:
59047
+ case 3: return startOrEnd(unmountOnExit);
59054
59048
  }
59055
59049
  };
59056
59050
  const getTimeout = (timeout) => typeof timeout === "object" ? [timeout.enter, timeout.exit] : [timeout, timeout];
59057
- const _setTimeout = (...args) => setTimeout(...args);
59058
- const nextTick = (transitState, status) => _setTimeout(() => {
59059
- isNaN(document.body.offsetTop) || transitState(status + 1);
59060
- }, 0);
59051
+ const nextTick = (callback, config) => {
59052
+ config.r = requestAnimationFrame(() => {
59053
+ config.r = requestAnimationFrame(callback);
59054
+ });
59055
+ };
59061
59056
 
59062
59057
  //#region src/useTransitionState.ts
59063
- const updateState = (status, setState, latestState, timeoutId, onChange) => {
59064
- clearTimeout(timeoutId.current);
59058
+ const updateState = (status, setState, ref, onChange) => {
59059
+ clearTimeout(ref.t);
59060
+ cancelAnimationFrame(ref.r);
59065
59061
  const state = getState(status);
59066
59062
  setState(state);
59067
- latestState.current = state;
59068
- onChange && onChange({ current: state });
59063
+ ref.s = state;
59064
+ onChange?.({ current: state });
59069
59065
  };
59070
59066
  const useTransitionState = ({ enter = true, exit = true, preEnter, preExit, timeout, initialEntered, mountOnEnter, unmountOnExit, onStateChange: onChange } = {}) => {
59071
- const [state, setState] = useState(() => getState(initialEntered ? ENTERED : startOrEnd(mountOnEnter)));
59072
- const latestState = useRef(state);
59073
- const timeoutId = useRef(0);
59067
+ const [state, setState] = useState(() => getState(initialEntered ? 2 : startOrEnd(mountOnEnter)));
59068
+ const [ref] = useState({
59069
+ s: state,
59070
+ r: 0
59071
+ });
59074
59072
  const [enterTimeout, exitTimeout] = getTimeout(timeout);
59075
59073
  const endTransition = useCallback(() => {
59076
- const status = getEndStatus(latestState.current._s, unmountOnExit);
59077
- status && updateState(status, setState, latestState, timeoutId, onChange);
59078
- }, [onChange, unmountOnExit]);
59074
+ const status = getEndStatus(ref.s.$, unmountOnExit);
59075
+ if (status) updateState(status, setState, ref, onChange);
59076
+ }, [
59077
+ onChange,
59078
+ unmountOnExit,
59079
+ ref
59080
+ ]);
59079
59081
  return [
59080
59082
  state,
59081
59083
  useCallback((toEnter) => {
59082
59084
  const transitState = (status) => {
59083
- updateState(status, setState, latestState, timeoutId, onChange);
59085
+ updateState(status, setState, ref, onChange);
59084
59086
  switch (status) {
59085
- case ENTERING:
59086
- if (enterTimeout >= 0) timeoutId.current = _setTimeout(endTransition, enterTimeout);
59087
+ case 1:
59088
+ if (enterTimeout >= 0) ref.t = setTimeout(endTransition, enterTimeout);
59087
59089
  break;
59088
- case EXITING:
59089
- if (exitTimeout >= 0) timeoutId.current = _setTimeout(endTransition, exitTimeout);
59090
+ case 4:
59091
+ if (exitTimeout >= 0) ref.t = setTimeout(endTransition, exitTimeout);
59090
59092
  break;
59091
- case PRE_ENTER:
59092
- case PRE_EXIT:
59093
- timeoutId.current = nextTick(transitState, status);
59093
+ case 0:
59094
+ case 3:
59095
+ nextTick(() => transitState(status + 1), ref);
59094
59096
  break;
59095
59097
  }
59096
59098
  };
59097
- const enterStage = latestState.current.isEnter;
59099
+ const enterStage = ref.s.isEnter;
59098
59100
  if (typeof toEnter !== "boolean") toEnter = !enterStage;
59099
- if (toEnter) !enterStage && transitState(enter ? preEnter ? PRE_ENTER : ENTERING : ENTERED);
59100
- else enterStage && transitState(exit ? preExit ? PRE_EXIT : EXITING : startOrEnd(unmountOnExit));
59101
+ if (toEnter) !enterStage && transitState(enter ? preEnter ? 0 : 1 : 2);
59102
+ else enterStage && transitState(exit ? preExit ? 3 : 4 : startOrEnd(unmountOnExit));
59101
59103
  }, [
59104
+ ref,
59102
59105
  endTransition,
59103
59106
  onChange,
59104
59107
  enter,
@@ -61420,9 +61423,11 @@ maxInitialWidth,
61420
61423
  }
61421
61424
  else {
61422
61425
  const colDefEditable = colDef.editable;
61423
- const editable = combineEditables(params.loading !== true && params.readOnly !== true, params.defaultColDef?.editable, colDefEditable);
61426
+ const ignoreGridReadOnly = colDef.ignoreGridReadOnly === true;
61427
+ const editable = combineEditables(params.loading !== true, ignoreGridReadOnly ? true : params.readOnly !== true, params.defaultColDef?.editable, colDefEditable);
61424
61428
  return {
61425
- ...colDef,
61429
+ // ag-grid warns on unrecognised colDef properties, so it can't be left on the returned colDef
61430
+ ...omit(colDef, 'ignoreGridReadOnly'),
61426
61431
  editable,
61427
61432
  cellClassRules: {
61428
61433
  ...colDef.cellClassRules,
@@ -62693,20 +62698,25 @@ const regexpCache = new Map();
62693
62698
  const sanitizeArray = (input, inputName) => {
62694
62699
  if (!Array.isArray(input)) {
62695
62700
  switch (typeof input) {
62696
- case 'string':
62701
+ case 'string': {
62697
62702
  input = [input];
62698
62703
  break;
62699
- case 'undefined':
62704
+ }
62705
+
62706
+ case 'undefined': {
62700
62707
  input = [];
62701
62708
  break;
62702
- default:
62709
+ }
62710
+
62711
+ default: {
62703
62712
  throw new TypeError(`Expected '${inputName}' to be a string or an array, but got a type of '${typeof input}'`);
62713
+ }
62704
62714
  }
62705
62715
  }
62706
62716
 
62707
62717
  return input.filter(string => {
62708
62718
  if (typeof string !== 'string') {
62709
- if (typeof string === 'undefined') {
62719
+ if (string === undefined) {
62710
62720
  return false;
62711
62721
  }
62712
62722
 
@@ -62723,7 +62733,8 @@ const makeRegexp = (pattern, options) => {
62723
62733
  ...options,
62724
62734
  };
62725
62735
 
62726
- const cacheKey = pattern + JSON.stringify(options);
62736
+ const flags = 's' + (options.caseSensitive ? '' : 'i'); // Always dotAll, optionally case-insensitive
62737
+ const cacheKey = pattern + '|' + flags;
62727
62738
 
62728
62739
  if (regexpCache.has(cacheKey)) {
62729
62740
  return regexpCache.get(cacheKey);
@@ -62735,9 +62746,19 @@ const makeRegexp = (pattern, options) => {
62735
62746
  pattern = pattern.slice(1);
62736
62747
  }
62737
62748
 
62738
- pattern = escapeStringRegexp(pattern).replace(/\\\*/g, '[\\s\\S]*');
62749
+ // Handle escapes: first preserve escaped chars, then convert * to wildcards
62750
+ pattern = pattern
62751
+ .replaceAll(String.raw`\*`, '__ESCAPED_STAR__') // \* -> placeholder
62752
+ .replaceAll('\\\\', '__ESCAPED_BACKSLASH__') // \\ -> placeholder
62753
+ .replaceAll(/\\(.)/g, '$1'); // Other escapes like \<space> -> <space>
62754
+
62755
+ pattern = escapeStringRegexp(pattern).replaceAll(String.raw`\*`, '.*'); // * -> .*
62756
+
62757
+ pattern = pattern
62758
+ .replaceAll('__ESCAPED_STAR__', String.raw`\*`) // Restore escaped *
62759
+ .replaceAll('__ESCAPED_BACKSLASH__', '\\\\'); // Restore escaped \
62739
62760
 
62740
- const regexp = new RegExp(`^${pattern}$`, options.caseSensitive ? '' : 'i');
62761
+ const regexp = new RegExp(`^${pattern}$`, flags);
62741
62762
  regexp.negated = negated;
62742
62763
  regexpCache.set(cacheKey, regexp);
62743
62764
 
@@ -62754,39 +62775,75 @@ const baseMatcher = (inputs, patterns, options, firstMatchOnly) => {
62754
62775
 
62755
62776
  patterns = patterns.map(pattern => makeRegexp(pattern, options));
62756
62777
 
62778
+ // Partition patterns for faster processing
62779
+ const negatedPatterns = patterns.filter(pattern => pattern.negated);
62780
+ const positivePatterns = patterns.filter(pattern => !pattern.negated);
62781
+
62757
62782
  const {allPatterns} = {};
62758
62783
  const result = [];
62759
62784
 
62760
- for (const input of inputs) {
62761
- // String is included only if it matches at least one non-negated pattern supplied.
62762
- // Note: the `allPatterns` option requires every non-negated pattern to be matched once.
62763
- // Matching a negated pattern excludes the string.
62764
- let matches;
62765
- const didFit = [...patterns].fill(false);
62785
+ // Special handling for multiple negations with allPatterns and isMatch
62786
+ if (allPatterns && firstMatchOnly && negatedPatterns.length > 1 && positivePatterns.length === 0) {
62787
+ // Multiple negations only: ALL inputs must satisfy constraints (none should match any negation)
62788
+ for (const input of inputs) {
62789
+ for (const pattern of negatedPatterns) {
62790
+ if (pattern.test(input)) {
62791
+ return []; // Any input matching a negation means no match
62792
+ }
62793
+ }
62794
+ }
62766
62795
 
62767
- for (const [index, pattern] of patterns.entries()) {
62768
- if (pattern.test(input)) {
62769
- didFit[index] = true;
62770
- matches = !pattern.negated;
62796
+ return inputs.slice(0, 1); // All inputs passed negation constraints
62797
+ }
62771
62798
 
62772
- if (!matches) {
62773
- break;
62774
- }
62799
+ for (const input of inputs) {
62800
+ // Check negated patterns first (immediate exclusion)
62801
+ let excludedByNegation = false;
62802
+ for (const pattern of negatedPatterns) {
62803
+ if (pattern.test(input)) {
62804
+ excludedByNegation = true;
62805
+ break;
62775
62806
  }
62776
62807
  }
62777
62808
 
62778
- if (
62779
- !(
62780
- matches === false
62781
- || (matches === undefined && patterns.some(pattern => !pattern.negated))
62782
- || (allPatterns && didFit.some((yes, index) => !yes && !patterns[index].negated))
62783
- )
62784
- ) {
62809
+ if (excludedByNegation) {
62810
+ continue; // Skip this input
62811
+ }
62812
+
62813
+ // Check positive patterns
62814
+ if (positivePatterns.length === 0) {
62815
+ // No positive patterns - include if no negations matched (already checked above)
62785
62816
  result.push(input);
62817
+ } else if (allPatterns) {
62818
+ // AND logic: include if ALL positive patterns match
62819
+ const matchedPositive = Array.from({length: positivePatterns.length}, () => false);
62820
+ for (const [index, pattern] of positivePatterns.entries()) {
62821
+ if (pattern.test(input)) {
62822
+ matchedPositive[index] = true;
62823
+ }
62824
+ }
62786
62825
 
62787
- {
62788
- break;
62826
+ // All positive patterns must match
62827
+ if (matchedPositive.every(Boolean)) {
62828
+ result.push(input);
62789
62829
  }
62830
+ } else {
62831
+ // OR logic: include if any positive pattern matches
62832
+ let matchedAny = false;
62833
+ for (const pattern of positivePatterns) {
62834
+ if (pattern.test(input)) {
62835
+ matchedAny = true;
62836
+ break; // Short-circuit on first match
62837
+ }
62838
+ }
62839
+
62840
+ if (matchedAny) {
62841
+ result.push(input);
62842
+ }
62843
+ }
62844
+
62845
+ if (result.length > 0) {
62846
+ break;
62790
62847
  }
62791
62848
  }
62792
62849
 
@@ -62794,7 +62851,7 @@ const baseMatcher = (inputs, patterns, options, firstMatchOnly) => {
62794
62851
  };
62795
62852
 
62796
62853
  function isMatch(inputs, patterns, options) {
62797
- return baseMatcher(inputs, patterns, options).length > 0;
62854
+ return baseMatcher(inputs, patterns, options, true).length > 0;
62798
62855
  }
62799
62856
 
62800
62857
  /**
@@ -63744,7 +63801,11 @@ const GridFormPopoverMenu = (props) => {
63744
63801
  invalid: () => subComponentSelected && !subComponentIsValid.current,
63745
63802
  save,
63746
63803
  });
63747
- return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: !options, className: 'GridFormPopupMenu', children: jsx(Fragment, { children: isEmpty(options) ? (jsx(MenuItem, { className: 'GridPopoverMenu-noOptions', disabled: true, children: "No actions" }, `GridPopoverMenu-empty`)) : (options?.map((item, index) => item.label === '__isMenuSeparator__' ? (jsx(MenuDivider, {}, `$$divider_${index}`)) : (!item.hidden && (jsxs(Fragment$1, { children: [item.subMenu ? (jsx(SubMenu, { disabled: !!item.disabled, label: item.label, title: item.disabled && typeof item.disabled !== 'boolean' ? item.disabled : '', children: jsx(item.subMenu, {}) })) : (jsx(MenuItem, { onClick: (e) => void onMenuItemClick(e, item), disabled: !!item.disabled, title: item.disabled && typeof item.disabled !== 'boolean' ? item.disabled : '', children: item.label })), item.subComponent && subComponentSelected === item && (jsx(FocusableItem, { className: 'LuiDeprecatedForms', children: () => (jsx(GridSubComponentContext.Provider, { value: {
63804
+ // isEmpty(options) alone isn't enough: options can be a non-empty array where every item is
63805
+ // hidden (e.g. all actions unavailable in a given mode), which must also show "No actions"
63806
+ // rather than rendering an empty menu body.
63807
+ const hasVisibleOption = options?.some((item) => !item.hidden && item.label !== __isMenuSeparator__);
63808
+ return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: !options, className: 'GridFormPopupMenu', children: jsx(Fragment, { children: !hasVisibleOption ? (jsx(MenuItem, { className: 'GridPopoverMenu-noOptions', disabled: true, children: "No actions" }, `GridPopoverMenu-empty`)) : (options?.map((item, index) => item.label === '__isMenuSeparator__' ? (jsx(MenuDivider, {}, `$$divider_${index}`)) : (!item.hidden && (jsxs(Fragment$1, { children: [item.subMenu ? (jsx(SubMenu, { disabled: !!item.disabled, label: item.label, title: item.disabled && typeof item.disabled !== 'boolean' ? item.disabled : '', children: jsx(item.subMenu, {}) })) : (jsx(MenuItem, { onClick: (e) => void onMenuItemClick(e, item), disabled: !!item.disabled, title: item.disabled && typeof item.disabled !== 'boolean' ? item.disabled : '', children: item.label })), item.subComponent && subComponentSelected === item && (jsx(FocusableItem, { className: 'LuiDeprecatedForms', children: () => (jsx(GridSubComponentContext.Provider, { value: {
63748
63809
  context: {},
63749
63810
  data,
63750
63811
  value: subSelectedValue,
@@ -63891,9 +63952,57 @@ const GridButton = (colDef, editor) => {
63891
63952
  });
63892
63953
  };
63893
63954
 
63955
+ const isNode = typeof process !== 'undefined' && process.env.NODE_ENV === 'test';
63956
+ let sharedIntervalId = null;
63957
+ const callbacks = new Set();
63958
+ const startSharedInterval = () => {
63959
+ if (isNode || sharedIntervalId !== null) {
63960
+ return;
63961
+ }
63962
+ sharedIntervalId = setInterval(() => {
63963
+ callbacks.forEach((cb) => {
63964
+ try {
63965
+ cb();
63966
+ }
63967
+ catch (e) {
63968
+ console.error(e);
63969
+ }
63970
+ });
63971
+ }, 100);
63972
+ };
63973
+ const stopSharedInterval = () => {
63974
+ if (sharedIntervalId === null) {
63975
+ return;
63976
+ }
63977
+ clearInterval(sharedIntervalId);
63978
+ sharedIntervalId = null;
63979
+ };
63980
+ /**
63981
+ * Like useInterval, but all callers share a single 100ms interval.
63982
+ * Each callback is isolated in a try/catch so a throwing callback won't prevent others from running.
63983
+ */
63984
+ const useSharedInterval = (callback) => {
63985
+ const savedCallback = useRef(callback);
63986
+ useIsomorphicLayoutEffect$1(() => {
63987
+ savedCallback.current = callback;
63988
+ }, [callback]);
63989
+ useEffect(() => {
63990
+ const cb = () => savedCallback.current();
63991
+ callbacks.add(cb);
63992
+ startSharedInterval();
63993
+ return () => {
63994
+ callbacks.delete(cb);
63995
+ if (callbacks.size === 0) {
63996
+ stopSharedInterval();
63997
+ }
63998
+ };
63999
+ }, []);
64000
+ };
64001
+
63894
64002
  const BooleanCellRenderer = (props) => {
63895
- const { onValueChange, value, api, node, column, colDef, data } = props;
64003
+ const { onValueChange, value, api, node, column, colDef, data, eGridCell } = props;
63896
64004
  const inputRef = useRef(null);
64005
+ const { updatingCells, redrawRows, resetFocusedCellAfterCellEditing, prePopupOps } = useGridContext();
63897
64006
  useEffect(() => {
63898
64007
  const checkFocus = (event) => {
63899
64008
  if (event.rowIndex === node.rowIndex && event.column === column) {
@@ -63901,34 +64010,62 @@ const BooleanCellRenderer = (props) => {
63901
64010
  }
63902
64011
  };
63903
64012
  api.addEventListener('cellFocused', checkFocus);
63904
- return () => {
63905
- api.removeEventListener('cellFocused', checkFocus);
63906
- };
64013
+ return () => void api.removeEventListener('cellFocused', checkFocus);
63907
64014
  }, [api, column, node.rowIndex]);
63908
64015
  const isDisabled = !fnOrVar(colDef?.editable, props);
63909
- return (jsx("div", { className: clsx('ag-wrapper ag-input-wrapper ag-checkbox-input-wrapper', {
64016
+ const toggleCheckbox = useCallback(() => {
64017
+ if (!onValueChange) {
64018
+ return;
64019
+ }
64020
+ const params = props?.colDef?.cellEditorParams;
64021
+ if (!params) {
64022
+ return;
64023
+ }
64024
+ const nodes = [];
64025
+ api.forEachNode((n) => {
64026
+ if (n.data.id === data.id) {
64027
+ nodes.push(n);
64028
+ }
64029
+ });
64030
+ prePopupOps();
64031
+ const checked = !value;
64032
+ onValueChange(checked);
64033
+ const field = props.colDef?.field ?? props.colDef?.colId ?? '';
64034
+ const selectedRows = nodes.map((node) => node.data);
64035
+ void updatingCells({ selectedRows, field }, async () => {
64036
+ redrawRows(nodes);
64037
+ return params.onClick({ selectedRows, selectedRowIds: selectedRows.map((r) => r.id), checked });
64038
+ }).finally(() => {
64039
+ resetFocusedCellAfterCellEditing();
64040
+ });
64041
+ }, [
64042
+ api,
64043
+ data.id,
64044
+ onValueChange,
64045
+ prePopupOps,
64046
+ props.colDef?.cellEditorParams,
64047
+ props.colDef?.colId,
64048
+ props.colDef?.field,
64049
+ redrawRows,
64050
+ resetFocusedCellAfterCellEditing,
64051
+ updatingCells,
64052
+ value,
64053
+ ]);
64054
+ useSharedInterval(() => {
64055
+ if (isDisabled) {
64056
+ return;
64057
+ }
64058
+ const cell = eGridCell?.querySelector('.ag-cell-focus .grid-edit-boolean input.ag-checkbox-input:not(:disabled)');
64059
+ if (cell && cell.ownerDocument.activeElement !== cell) {
64060
+ cell.focus();
64061
+ }
64062
+ });
64063
+ return (jsx("div", { className: clsx('grid-edit-boolean ag-wrapper ag-input-wrapper ag-checkbox-input-wrapper', {
63910
64064
  'ag-checked': props.value,
63911
64065
  'ag-disabled': isDisabled,
63912
64066
  }), children: jsx("input", { type: "checkbox", className: "ag-input-field-input ag-checkbox-input", disabled: isDisabled, ref: inputRef, checked: value, onChange: () => { }, onClick: (e) => {
63913
64067
  e.stopPropagation();
63914
- // cell has to be in edit mode
63915
- // if in non-edit mode clickInputWhenContainingCellClicked will click to put it in edit mode
63916
- if (!onValueChange)
63917
- return;
63918
- const params = props?.colDef?.cellEditorParams;
63919
- if (!params)
63920
- return;
63921
- // The data cannot be relied upon if grid changed whilst editing, data will be stale
63922
- // So I get the data from the node itself which will be up to date.
63923
- const selectedRows = [];
63924
- api.forEachNode((n) => {
63925
- if (n.data.id === data.id) {
63926
- selectedRows.push(n.data);
63927
- }
63928
- });
63929
- const checked = !value;
63930
- onValueChange(checked);
63931
- void params.onClick({ selectedRows, selectedRowIds: selectedRows.map((r) => r.id), checked });
64068
+ toggleCheckbox();
63932
64069
  } }) }));
63933
64070
  };
63934
64071
  const GridEditBoolean = (colDef, editorProps) => {
@@ -63942,7 +64079,7 @@ const GridEditBoolean = (colDef, editorProps) => {
63942
64079
  singleClickEdit: true,
63943
64080
  resizable: false,
63944
64081
  editable: true,
63945
- cellClass: 'GridCellAlignCenter',
64082
+ cellClass: 'GridCellAlignCenter GridCellEditableWithNoPopup',
63946
64083
  headerClass: 'GridHeaderAlignCenter',
63947
64084
  ...colDef,
63948
64085
  });
@@ -64037,6 +64174,9 @@ const GridPopoverMenu = (colDef, custom) => GridCell({
64037
64174
  editable: colDef.editable != null ? colDef.editable : true,
64038
64175
  exportable: false,
64039
64176
  cellStyle: { flex: 1, justifyContent: 'center' },
64177
+ suppressMovable: true,
64178
+ lockPosition: 'right',
64179
+ resizable: false,
64040
64180
  cellRenderer: GridRenderPopoutMenuCell,
64041
64181
  // Menus open on single click, this parameter is picked up in Grid.tsx
64042
64182
  singleClickEdit: true,
@@ -64530,15 +64670,19 @@ const GridContextProvider = (props) => {
64530
64670
  if (!gridApi) {
64531
64671
  return true;
64532
64672
  }
64673
+ /**
64674
+ * @returns true if editable with a popup, false if editable without popup, null if not editable
64675
+ */
64533
64676
  const focusedCellIsEditable = () => {
64534
64677
  const focusedCell = gridApi.isDestroyed() ? null : gridApi.getFocusedCell();
64535
64678
  const nextColumn = focusedCell?.column;
64536
64679
  const nextColDef = nextColumn?.getColDef();
64537
64680
  const rowNode = focusedCell && gridApi.getDisplayedRowAtIndex(focusedCell?.rowIndex);
64681
+ const popupable = String(nextColDef?.cellClass).indexOf("GridCellEditableWithNoPopup") === -1;
64538
64682
  return (!!(rowNode && nextColumn && nextColDef) &&
64539
64683
  nextColumn.isCellEditable(rowNode) &&
64540
64684
  !nextColDef.cellEditorParams?.preventAutoEdit &&
64541
- !nextColDef.cellRendererParams?.editAction);
64685
+ !nextColDef.cellRendererParams?.editAction) ? popupable : null;
64542
64686
  };
64543
64687
  // Just in case I've missed something, we don't want the loop to hang everything
64544
64688
  for (let maxIterations = 0; maxIterations < 50; maxIterations++) {
@@ -64571,7 +64715,10 @@ const GridContextProvider = (props) => {
64571
64715
  break;
64572
64716
  }
64573
64717
  }
64574
- if (focusedCellIsEditable()) {
64718
+ // checkbox cells are editable but don't have a popup
64719
+ // we need to end bulk editing and just select the cell
64720
+ const editable = focusedCellIsEditable();
64721
+ if (editable !== null) {
64575
64722
  const focusedCell = gridApi.getFocusedCell();
64576
64723
  if (focusedCell) {
64577
64724
  const rowNode = gridApi.getDisplayedRowAtIndex(focusedCell.rowIndex);
@@ -64579,8 +64726,15 @@ const GridContextProvider = (props) => {
64579
64726
  if (rowId == null) {
64580
64727
  return false;
64581
64728
  }
64582
- await startCellEditing({ rowId, colId: focusedCell.column.getColId() });
64583
- return true;
64729
+ if (editable) {
64730
+ await startCellEditing({ rowId, colId: focusedCell.column.getColId() });
64731
+ // Continue bulk edit
64732
+ return true;
64733
+ }
64734
+ else {
64735
+ // This will terminate bulk editing as there was no popup editor
64736
+ return false;
64737
+ }
64584
64738
  }
64585
64739
  }
64586
64740
  }