@linzjs/step-ag-grid 15.0.2 → 15.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
1
+ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import { LuiMiniSpinner, LuiIcon, LuiButton, LuiCheckboxInput, LuiButtonGroup } from '@linzjs/lui';
3
3
  import { AgGridReact } from 'ag-grid-react';
4
- import { negate, isEmpty, xorBy, last, difference, defer as defer$1, omit, sortBy, findIndex, debounce as debounce$1, delay, partition, pick, groupBy, fromPairs, toPairs, isEqual, compact, filter, sumBy, pull, remove, castArray, flatten } from 'lodash-es';
4
+ import { negate, isEmpty, findIndex, defer as defer$1, debounce as debounce$1, xorBy, last, difference, omit, sortBy, delay, partition, pick, groupBy, fromPairs, toPairs, isEqual, compact, filter, sumBy, pull, remove, castArray, flatten } from 'lodash-es';
5
5
  import * as React from 'react';
6
- import { createContext, useContext, useRef, useEffect, useCallback, useState, useMemo, forwardRef, useLayoutEffect, memo, useReducer, cloneElement, useImperativeHandle, Fragment as Fragment$1 } from 'react';
6
+ import { createContext, useContext, useRef, useEffect, useCallback, useState, useMemo, useLayoutEffect, memo, forwardRef, useReducer, cloneElement, useImperativeHandle, Fragment } from 'react';
7
7
  import ReactDOM, { unstable_batchedUpdates, flushSync, createPortal } from 'react-dom';
8
8
  import require$$0 from 'util';
9
9
  import * as testUtils from 'react-dom/test-utils';
@@ -38,6 +38,9 @@ const GridContext = createContext({
38
38
  prePopupOps: () => {
39
39
  console.error("no context provider for prePopupOps");
40
40
  },
41
+ postPopupOps: () => {
42
+ console.error("no context provider for prePopupOps");
43
+ },
41
44
  externallySelectedItemsAreInSync: false,
42
45
  setApis: () => {
43
46
  console.error("no context provider for setApis");
@@ -454,1007 +457,378 @@ const GridHeaderSelect = ({ api }) => {
454
457
  } }) }));
455
458
  };
456
459
 
457
- /**
458
- * Wrapper for AgGrid to add commonly used functionality.
459
- */
460
- const Grid = ({ "data-testid": dataTestId, rowSelection = "multiple", suppressColumnVirtualization = true, theme = "ag-theme-alpine", sizeColumns = "auto", selectColumnPinned = null, ...params }) => {
461
- const { gridReady, setApis, ensureRowVisible, getFirstRowId, selectRowsById, focusByRowById, ensureSelectedRowIsVisible, autoSizeColumns, sizeColumnsToFit, externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync, isExternalFilterPresent, doesExternalFilterPass, setOnCellEditingComplete, getColDef, } = useContext(GridContext);
462
- const { checkUpdating, updatedDep, updatingCols } = useContext(GridUpdatingContext);
463
- const { prePopupOps } = useContext(GridContext);
464
- const gridDivRef = useRef(null);
465
- const lastSelectedIds = useRef([]);
466
- const [staleGrid, setStaleGrid] = useState(false);
467
- const postSortRows = usePostSortRowsHook({ setStaleGrid });
468
- /**
469
- * onContentSize should only be called at maximum twice.
470
- * Once when an empty grid is loaded.
471
- * And again when the grid has content.
472
- */
473
- const hasSetContentSize = useRef(false);
474
- const hasSetContentSizeEmpty = useRef(false);
475
- const needsAutoSize = useRef(false);
476
- const setInitialContentSize = useCallback(() => {
477
- if (!gridDivRef.current?.clientWidth) {
478
- // Don't resize grids if they are offscreen as it doesn't work.
479
- needsAutoSize.current = true;
480
- return;
481
- }
482
- const headerCellCount = gridDivRef.current?.getElementsByClassName("ag-header-cell-label")?.length;
483
- if (headerCellCount < 2) {
484
- // Don't resize grids until all the columns are visible
485
- // as `autoSizeColumns` will fail silently in this case
486
- needsAutoSize.current = true;
487
- return;
488
- }
489
- const skipHeader = sizeColumns === "auto-skip-headers" && !isEmpty(params.rowData);
490
- if (sizeColumns === "auto" || skipHeader) {
491
- const result = autoSizeColumns({ skipHeader, userSizedColIds: userSizedColIds.current });
492
- if (isEmpty(params.rowData)) {
493
- if (!hasSetContentSizeEmpty.current && result && !hasSetContentSize.current) {
494
- hasSetContentSizeEmpty.current = true;
495
- params.onContentSize && params.onContentSize(result);
496
- }
460
+ // Generate className following BEM methodology: http://getbem.com/naming/
461
+ // Modifier value can be one of the following types: boolean, string, undefined
462
+ const useBEM = ({ block, element, modifiers, className }) => useMemo(() => {
463
+ const blockElement = element ? `${block}__${element}` : block;
464
+ let classString = blockElement;
465
+ modifiers &&
466
+ Object.keys(modifiers).forEach((name) => {
467
+ const value = modifiers[name];
468
+ if (value)
469
+ classString += ` ${blockElement}--${value === true ? name : `${name}-${value}`}`;
470
+ });
471
+ let expandedClassName = typeof className === "function" ? className(modifiers) : className;
472
+ if (typeof expandedClassName === "string") {
473
+ expandedClassName = expandedClassName.trim();
474
+ if (expandedClassName)
475
+ classString += ` ${expandedClassName}`;
476
+ }
477
+ return classString;
478
+ }, [block, element, modifiers, className]);
479
+
480
+ // Adapted from material-ui
481
+ // https://github.com/mui-org/material-ui/blob/f996027d00e7e4bff3fc040786c1706f9c6c3f82/packages/material-ui-utils/src/useForkRef.ts
482
+ const setRef = (ref, instance) => {
483
+ if (typeof ref === "function") {
484
+ ref(instance);
485
+ }
486
+ else if (ref) {
487
+ ref.current = instance;
488
+ }
489
+ };
490
+ const useCombinedRef = (refA, refB) => {
491
+ return useMemo(() => {
492
+ return (instance) => {
493
+ setRef(refA, instance);
494
+ setRef(refB, instance);
495
+ };
496
+ }, [refA, refB]);
497
+ };
498
+
499
+ // Get around a warning when using useLayoutEffect on the server.
500
+ // https://github.com/reduxjs/react-redux/blob/b48d087d76f666e1c6c5a9713bbec112a1631841/src/utils/useIsomorphicLayoutEffect.js#L12
501
+ // https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
502
+ // https://github.com/facebook/react/issues/14927#issuecomment-549457471
503
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" &&
504
+ typeof window.document !== "undefined" &&
505
+ typeof window.document.createElement !== "undefined"
506
+ ? useLayoutEffect
507
+ : useEffect;
508
+
509
+ const menuContainerClass = "szh-menu-container";
510
+ const menuClass = "szh-menu";
511
+ const menuButtonClass = "szh-menu-button";
512
+ const menuArrowClass = "arrow";
513
+ const menuItemClass = "item";
514
+ const menuDividerClass = "divider";
515
+ const menuHeaderClass = "header";
516
+ const menuGroupClass = "group";
517
+ const subMenuClass = "submenu";
518
+ const radioGroupClass = "radio-group";
519
+ const Keys = Object.freeze({
520
+ ENTER: "Enter",
521
+ TAB: "Tab",
522
+ ESC: "Escape",
523
+ SPACE: " ",
524
+ HOME: "Home",
525
+ END: "End",
526
+ LEFT: "ArrowLeft",
527
+ RIGHT: "ArrowRight",
528
+ UP: "ArrowUp",
529
+ DOWN: "ArrowDown",
530
+ });
531
+ const HoverActionTypes = Object.freeze({
532
+ RESET: 0,
533
+ SET: 1,
534
+ UNSET: 2,
535
+ INCREASE: 3,
536
+ DECREASE: 4,
537
+ FIRST: 5,
538
+ LAST: 6,
539
+ SET_INDEX: 7,
540
+ });
541
+ const CloseReason = Object.freeze({
542
+ CLICK: "click",
543
+ CANCEL: "cancel",
544
+ BLUR: "blur",
545
+ SCROLL: "scroll",
546
+ TAB_FORWARD: "tab_forward",
547
+ TAB_BACKWARD: "tab_backward",
548
+ });
549
+ const FocusPositions = Object.freeze({
550
+ FIRST: "first",
551
+ LAST: "last",
552
+ });
553
+ const MenuStateMap = Object.freeze({
554
+ entering: "opening",
555
+ entered: "open",
556
+ exiting: "closing",
557
+ exited: "closed",
558
+ });
559
+
560
+ const isMenuOpen = (state) => !!state && state[0] === "o";
561
+ const batchedUpdates = unstable_batchedUpdates || ((callback) => callback());
562
+ const floatEqual = (a, b, diff = 0.0001) => Math.abs(a - b) < diff;
563
+ const getTransition = (transition, name) => transition === true || !!(transition && transition[name]);
564
+ function safeCall(fn, arg) {
565
+ return typeof fn === "function" ? fn(arg) : fn;
566
+ }
567
+ const internalKey = "_szhsinMenu";
568
+ const getName = (component) => component[internalKey];
569
+ const mergeProps = (target, source) => {
570
+ source &&
571
+ Object.keys(source).forEach((key) => {
572
+ const targetProp = target[key];
573
+ const sourceProp = source[key];
574
+ if (typeof sourceProp === "function" && targetProp) {
575
+ target[key] = (...arg) => {
576
+ sourceProp(...arg);
577
+ targetProp(...arg);
578
+ };
497
579
  }
498
580
  else {
499
- if (result && !hasSetContentSize.current) {
500
- hasSetContentSize.current = true;
501
- params.onContentSize && params.onContentSize(result);
502
- }
581
+ target[key] = sourceProp;
503
582
  }
583
+ });
584
+ return target;
585
+ };
586
+ const parsePadding = (paddingStr) => {
587
+ if (paddingStr == null)
588
+ return { top: 0, right: 0, bottom: 0, left: 0 };
589
+ const padding = paddingStr.trim().split(/\s+/, 4).map(parseFloat);
590
+ const top = !isNaN(padding[0]) ? padding[0] : 0;
591
+ const right = !isNaN(padding[1]) ? padding[1] : top;
592
+ return {
593
+ top,
594
+ right,
595
+ bottom: !isNaN(padding[2]) ? padding[2] : top,
596
+ left: !isNaN(padding[3]) ? padding[3] : right,
597
+ };
598
+ };
599
+ // Adapted from https://github.com/popperjs/popper-core/tree/v2.9.1/src/dom-utils
600
+ const getScrollAncestor = (node) => {
601
+ const thisWindow = (node?.ownerDocument ?? document).defaultView ?? window;
602
+ while (node) {
603
+ node = node.parentNode;
604
+ if (!node || node === thisWindow?.document?.body)
605
+ return null;
606
+ if (node instanceof Element) {
607
+ const { overflow, overflowX, overflowY } = thisWindow.getComputedStyle(node);
608
+ if (/auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX))
609
+ return node;
504
610
  }
505
- if (sizeColumns !== "none") {
506
- sizeColumnsToFit();
507
- }
508
- needsAutoSize.current = false;
509
- }, [autoSizeColumns, params, sizeColumns, sizeColumnsToFit]);
510
- const lastOwnerDocumentRef = useRef();
511
- /**
512
- * Auto-size windows that had deferred auto-size
513
- */
514
- useIntervalHook({
515
- callback: () => {
516
- // Check if window has been popped out and needs resize
517
- const currentDocument = gridDivRef.current?.ownerDocument;
518
- if (currentDocument !== lastOwnerDocumentRef.current) {
519
- lastOwnerDocumentRef.current = currentDocument;
520
- if (currentDocument) {
521
- needsAutoSize.current = true;
522
- }
523
- }
524
- if (needsAutoSize.current) {
525
- needsAutoSize.current = false;
526
- setInitialContentSize();
527
- }
528
- },
529
- timeoutMs: 1000,
611
+ }
612
+ return null;
613
+ };
614
+ function commonProps(isDisabled, isHovering) {
615
+ return {
616
+ "aria-disabled": isDisabled || undefined,
617
+ tabIndex: isHovering ? 0 : -1,
618
+ };
619
+ }
620
+ const indexOfNode = (nodeList, node) => findIndex(nodeList, node);
621
+ const focusFirstInput = (container) => {
622
+ // We can't use instanceof Element in portals, so I use querySelectorAll as a proxy here
623
+ if (!container || !("querySelectorAll" in container))
624
+ return false;
625
+ const inputs = container.querySelectorAll("input[type='text'],input:not([type]),textarea");
626
+ const input = inputs[0];
627
+ // Using focus as proxy for HTMLElement
628
+ if (!input || !("focus" in input))
629
+ return false;
630
+ input.focus();
631
+ // Text areas should start at end
632
+ // this is a proxy for instanceof HTMLTextAreaElement
633
+ if (["textarea", "text"].includes(input.type)) {
634
+ input.setSelectionRange(0, input.value.length);
635
+ }
636
+ return true;
637
+ };
638
+
639
+ const HoverItemContext = createContext(undefined);
640
+
641
+ const withHovering = (name, WrappedComponent) => {
642
+ const Component = memo(WrappedComponent);
643
+ const WithHovering = forwardRef((props, ref) => {
644
+ const menuItemRef = useRef(null);
645
+ return (jsx(Component, { ...props, menuItemRef: menuItemRef, externalRef: ref, isHovering: useContext(HoverItemContext) === menuItemRef.current }));
530
646
  });
531
- const previousGridReady = useRef(gridReady);
532
- useEffect(() => {
533
- if (!previousGridReady.current && gridReady) {
534
- previousGridReady.current = true;
535
- setInitialContentSize();
647
+ WithHovering.displayName = `WithHovering(${name})`;
648
+ return WithHovering;
649
+ };
650
+
651
+ const useItems = (menuRef, focusRef) => {
652
+ const [hoverItem, setHoverItem] = useState();
653
+ const stateRef = useRef({
654
+ items: [],
655
+ hoverIndex: -1,
656
+ sorted: false,
657
+ });
658
+ const mutableState = stateRef.current;
659
+ const updateItems = useCallback((item, isMounted) => {
660
+ const { items } = mutableState;
661
+ if (!item) {
662
+ mutableState.items = [];
536
663
  }
537
- }, [gridReady, setInitialContentSize]);
538
- /**
539
- * On data load select the first row of the grid if required.
540
- */
541
- const hasSelectedFirstItem = useRef(false);
542
- useEffect(() => {
543
- if (!gridReady || hasSelectedFirstItem.current || !params.rowData || !externallySelectedItemsAreInSync)
544
- return;
545
- hasSelectedFirstItem.current = true;
546
- if (isNotEmpty(params.rowData) && isEmpty(params.externalSelectedItems)) {
547
- const firstRowId = getFirstRowId();
548
- if (params.autoSelectFirstRow) {
549
- selectRowsById([firstRowId]);
664
+ else if (isMounted) {
665
+ items.push(item);
666
+ }
667
+ else {
668
+ const index = items.indexOf(item);
669
+ if (index > -1) {
670
+ items.splice(index, 1);
671
+ if (item.contains(document.activeElement)) {
672
+ focusRef?.current?.focus();
673
+ setHoverItem(undefined);
674
+ }
550
675
  }
551
- else {
552
- focusByRowById(firstRowId);
676
+ }
677
+ mutableState.hoverIndex = -1;
678
+ mutableState.sorted = false;
679
+ }, [mutableState, focusRef]);
680
+ const dispatch = useCallback((actionType, item, nextIndex) => {
681
+ const { items, hoverIndex } = mutableState;
682
+ const sortItems = () => {
683
+ if (mutableState.sorted)
684
+ return;
685
+ const orderedNodes = menuRef.current.querySelectorAll(".szh-menu__item");
686
+ items.sort((a, b) => indexOfNode(orderedNodes, a) - indexOfNode(orderedNodes, b));
687
+ mutableState.sorted = true;
688
+ };
689
+ let index = -1;
690
+ let newItem = undefined;
691
+ let newItemFn = undefined;
692
+ switch (actionType) {
693
+ case HoverActionTypes.RESET:
694
+ break;
695
+ case HoverActionTypes.SET:
696
+ newItem = item;
697
+ break;
698
+ case HoverActionTypes.UNSET:
699
+ newItemFn = (prevItem) => (prevItem === item ? undefined : prevItem);
700
+ break;
701
+ case HoverActionTypes.FIRST:
702
+ sortItems();
703
+ index = 0;
704
+ newItem = items[index];
705
+ break;
706
+ case HoverActionTypes.LAST:
707
+ sortItems();
708
+ index = items.length - 1;
709
+ newItem = items[index];
710
+ break;
711
+ case HoverActionTypes.SET_INDEX:
712
+ if (typeof nextIndex !== "number")
713
+ break;
714
+ sortItems();
715
+ index = nextIndex;
716
+ newItem = items[index];
717
+ defer$1(() => newItem.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }));
718
+ break;
719
+ case HoverActionTypes.INCREASE:
720
+ sortItems();
721
+ index = hoverIndex;
722
+ if (index < 0)
723
+ index = items.indexOf(item);
724
+ index++;
725
+ if (index >= items.length)
726
+ index = 0;
727
+ newItem = items[index];
728
+ focusFirstInput(newItem);
729
+ break;
730
+ case HoverActionTypes.DECREASE: {
731
+ sortItems();
732
+ index = hoverIndex;
733
+ if (index < 0)
734
+ index = items.indexOf(item);
735
+ index--;
736
+ if (index < 0)
737
+ index = items.length - 1;
738
+ newItem = items[index];
739
+ focusFirstInput(newItem);
740
+ break;
553
741
  }
742
+ default:
743
+ if (process.env.NODE_ENV !== "production")
744
+ throw new Error(`[React-Menu] Unknown hover action type: ${actionType}`);
554
745
  }
555
- }, [
556
- externallySelectedItemsAreInSync,
557
- focusByRowById,
558
- gridReady,
559
- params.externalSelectedItems,
560
- params.autoSelectFirstRow,
561
- params.rowData,
562
- selectRowsById,
563
- getFirstRowId,
564
- ]);
565
- /**
566
- * AgGrid checkbox select does not pass clicks within cell but not on the checkbox to checkbox.
567
- * This passes the event to the checkbox when you click anywhere in the cell.
568
- */
569
- const clickSelectorCheckboxWhenContainingCellClicked = useCallback(({ event }) => {
570
- if (!event)
571
- return;
572
- const input = event.target.querySelector("input");
573
- input?.dispatchEvent(event);
574
- }, []);
575
- /**
576
- * Ensure external selected items list is in sync with panel.
577
- */
578
- const synchroniseExternalStateToGridSelection = useCallback(({ api }) => {
579
- if (!params.externalSelectedItems || !params.setExternalSelectedItems) {
580
- setExternallySelectedItemsAreInSync(true);
746
+ if (!newItem && !newItemFn)
747
+ index = -1;
748
+ setHoverItem(newItem ?? newItemFn);
749
+ mutableState.hoverIndex = index;
750
+ }, [menuRef, mutableState]);
751
+ return { hoverItem, dispatch, updateItems };
752
+ };
753
+
754
+ const useItemEffect = (isDisabled, menuItemRef, updateItems) => {
755
+ useIsomorphicLayoutEffect(() => {
756
+ if (!menuItemRef)
581
757
  return;
758
+ if (process.env.NODE_ENV !== "production" && !updateItems) {
759
+ throw new Error(`[React-Menu] This menu item or submenu should be rendered under a menu: ${menuItemRef.current.outerHTML}`);
582
760
  }
583
- const selectedRows = api.getSelectedRows();
584
- // We don't want to update selected Items if it hasn't changed to prevent excess renders
585
- if (params.externalSelectedItems.length != selectedRows.length ||
586
- isNotEmpty(xorBy(selectedRows, params.externalSelectedItems, (row) => row.id))) {
587
- setExternallySelectedItemsAreInSync(false);
588
- params.setExternalSelectedItems([...selectedRows]);
761
+ if (isDisabled)
762
+ return;
763
+ const item = menuItemRef.current;
764
+ updateItems(item, true);
765
+ return () => {
766
+ updateItems(item);
767
+ };
768
+ }, [isDisabled, menuItemRef, updateItems]);
769
+ };
770
+
771
+ const ItemSettingsContext = createContext({
772
+ submenuOpenDelay: 0,
773
+ submenuCloseDelay: 0,
774
+ });
775
+
776
+ const MenuListItemContext = createContext({
777
+ dispatch: () => { },
778
+ updateItems: () => { },
779
+ setOpenSubmenuCount: () => 0,
780
+ });
781
+
782
+ // This hook includes some common stateful logic in MenuItem and FocusableItem
783
+ const useItemState = (menuItemRef, focusRef, isHovering, isDisabled) => {
784
+ const { submenuCloseDelay } = useContext(ItemSettingsContext);
785
+ const { isParentOpen, isSubmenuOpen, dispatch, updateItems } = useContext(MenuListItemContext);
786
+ const timeoutId = useRef();
787
+ const setHover = () => {
788
+ !isHovering && !isDisabled && dispatch(HoverActionTypes.SET, menuItemRef?.current, 0);
789
+ };
790
+ const unsetHover = () => {
791
+ !isDisabled && dispatch(HoverActionTypes.UNSET, menuItemRef?.current, 0);
792
+ };
793
+ const onBlur = (e) => {
794
+ // Focus has moved out of the entire item
795
+ // It handles situation such as clicking on a sibling disabled menu item
796
+ if (isHovering && !e.currentTarget.contains(e.relatedTarget))
797
+ unsetHover();
798
+ };
799
+ const onPointerMove = () => {
800
+ if (isSubmenuOpen) {
801
+ if (!timeoutId.current)
802
+ timeoutId.current = setTimeout(() => {
803
+ timeoutId.current = undefined;
804
+ setHover();
805
+ }, submenuCloseDelay);
589
806
  }
590
807
  else {
591
- setExternallySelectedItemsAreInSync(true);
808
+ setHover();
592
809
  }
593
- }, [params, setExternallySelectedItemsAreInSync]);
594
- /**
595
- * Synchronise externally selected items to grid.
596
- * If new ids are selected scroll them into view.
597
- */
598
- const synchroniseExternallySelectedItemsToGrid = useCallback(() => {
599
- if (!gridReady)
600
- return;
601
- if (!params.externalSelectedItems) {
602
- setExternallySelectedItemsAreInSync(true);
603
- return;
810
+ };
811
+ const onPointerLeave = (_, keepHover) => {
812
+ if (timeoutId.current) {
813
+ clearTimeout(timeoutId.current);
814
+ timeoutId.current = undefined;
604
815
  }
605
- const selectedIds = params.externalSelectedItems.map((row) => row.id);
606
- const lastNewId = last(difference(selectedIds, lastSelectedIds.current));
607
- if (lastNewId != null) {
608
- ensureRowVisible(lastNewId);
816
+ !keepHover && unsetHover();
817
+ };
818
+ useItemEffect(isDisabled, menuItemRef, updateItems);
819
+ useEffect(() => () => clearTimeout(timeoutId.current), []);
820
+ useEffect(() => {
821
+ // Don't set focus when parent menu is closed, otherwise focus will be lost
822
+ // and onBlur event will be fired with relatedTarget setting as null.
823
+ if (isHovering && isParentOpen) {
824
+ focusRef?.current && focusRef.current.focus();
609
825
  }
610
- lastSelectedIds.current = selectedIds;
611
- selectRowsById(selectedIds);
612
- setExternallySelectedItemsAreInSync(true);
613
- }, [gridReady, params.externalSelectedItems, ensureRowVisible, selectRowsById, setExternallySelectedItemsAreInSync]);
614
- /**
615
- * Combine grid and cell editable into one function
616
- */
617
- const combineEditables = (...editables) => (params) => {
618
- const results = editables.map((editable) => fnOrVar(editable, params));
619
- // If editable is not set anywhere then it's non-editable
620
- if (results.every((v) => v == null))
621
- return false;
622
- // If any editable value is or returns false then it's non-editable
623
- return !results.some((v) => v == false);
624
- };
625
- /**
626
- * Synchronise externally selected items to grid on externalSelectedItems change
627
- */
628
- useEffect(synchroniseExternallySelectedItemsToGrid, [synchroniseExternallySelectedItemsToGrid]);
629
- /**
630
- * Add selectable column to colDefs. Adjust column defs to block fit for auto sized columns.
631
- */
632
- const columnDefs = useMemo(() => {
633
- const adjustColDefs = params.columnDefs.map((colDef) => {
634
- const colDefEditable = colDef.editable;
635
- const editable = combineEditables(params.readOnly !== true, params.defaultColDef?.editable, colDefEditable);
636
- return {
637
- ...colDef,
638
- editable,
639
- cellClassRules: {
640
- ...colDef.cellClassRules,
641
- "GridCell-readonly": (ccp) => !editable(ccp),
642
- },
643
- };
644
- });
645
- return params.selectable
646
- ? [
647
- {
648
- colId: "selection",
649
- editable: false,
650
- minWidth: 42,
651
- maxWidth: 42,
652
- pinned: selectColumnPinned,
653
- headerComponentParams: {
654
- exportable: false,
655
- },
656
- checkboxSelection: true,
657
- headerComponent: rowSelection === "multiple" ? GridHeaderSelect : null,
658
- suppressHeaderKeyboardEvent: (e) => {
659
- if ((e.event.key === "Enter" || e.event.key === " ") && !e.event.repeat) {
660
- if (isEmpty(e.api.getSelectedRows())) {
661
- e.api.selectAllFiltered();
662
- }
663
- else {
664
- e.api.deselectAll();
665
- }
666
- return true;
667
- }
668
- return false;
669
- },
670
- onCellClicked: clickSelectorCheckboxWhenContainingCellClicked,
671
- },
672
- ...adjustColDefs,
673
- ]
674
- : adjustColDefs;
675
- }, [
676
- params.columnDefs,
677
- params.selectable,
678
- params.readOnly,
679
- params.defaultColDef?.editable,
680
- selectColumnPinned,
681
- rowSelection,
682
- clickSelectorCheckboxWhenContainingCellClicked,
683
- ]);
684
- /**
685
- * When grid is ready set the apis to the grid context and sync selected items to grid.
686
- */
687
- const onGridReady = useCallback((event) => {
688
- setApis(event.api, event.columnApi, dataTestId);
689
- synchroniseExternallySelectedItemsToGrid();
690
- }, [dataTestId, setApis, synchroniseExternallySelectedItemsToGrid]);
691
- /**
692
- * When the grid is being initialized the data may be empty.
693
- * This will resize columns when we have at least one row.
694
- */
695
- const previousRowDataLength = useRef(0);
696
- const onRowDataChanged = useCallback(() => {
697
- const length = params.rowData?.length ?? 0;
698
- if (previousRowDataLength.current !== length) {
699
- setInitialContentSize();
700
- previousRowDataLength.current = length;
701
- }
702
- if (lastUpdatedDep.current === updatedDep || isEmpty(colIdsEdited.current))
703
- return;
704
- lastUpdatedDep.current = updatedDep;
705
- // Don't update while there are spinners
706
- if (!isEmpty(updatingCols()))
707
- return;
708
- const skipHeader = sizeColumns === "auto-skip-headers";
709
- autoSizeColumns({ skipHeader, userSizedColIds: userSizedColIds.current, colIds: colIdsEdited.current });
710
- colIdsEdited.current.clear();
711
- }, [autoSizeColumns, params.rowData?.length, setInitialContentSize, sizeColumns, updatedDep, updatingCols]);
712
- /**
713
- * Show/hide no rows overlay when model changes.
714
- */
715
- const isShowingNoRowsOverlay = useRef(false);
716
- const onModelUpdated = useCallback((event) => {
717
- if (event.api.getDisplayedRowCount() === 0) {
718
- if (!isShowingNoRowsOverlay.current) {
719
- event.api.showNoRowsOverlay();
720
- isShowingNoRowsOverlay.current = true;
721
- }
722
- }
723
- else {
724
- if (isShowingNoRowsOverlay.current) {
725
- event.api.hideOverlay();
726
- isShowingNoRowsOverlay.current = false;
727
- }
728
- }
729
- }, []);
730
- /**
731
- * Force-refresh all selected rows to re-run class function, to update selection highlighting
732
- */
733
- const refreshSelectedRows = useCallback((event) => {
734
- event.api.refreshCells({
735
- force: true,
736
- rowNodes: event.api.getSelectedNodes(),
737
- });
738
- }, []);
739
- /**
740
- * Make sure node is selected for editing and start edit
741
- */
742
- const startCellEditing = useCallback((event) => {
743
- prePopupOps();
744
- if (!event.node.isSelected()) {
745
- event.node.setSelected(true, true);
746
- }
747
- // Cell already being edited, so don't re-edit until finished
748
- if (checkUpdating([event.colDef.field ?? ""], event.data.id)) {
749
- return;
750
- }
751
- if (event.rowIndex !== null) {
752
- event.api.startEditingCell({
753
- rowIndex: event.rowIndex,
754
- colKey: event.column.getColId(),
755
- });
756
- }
757
- }, [checkUpdating, prePopupOps]);
758
- /**
759
- * Handle double click edit
760
- */
761
- const onCellDoubleClick = useCallback((event) => {
762
- if (!invokeEditAction(event))
763
- startCellEditing(event);
764
- }, [startCellEditing]);
765
- /**
766
- * Handle single click edits
767
- */
768
- const onCellClicked = useCallback((event) => {
769
- if (event.colDef?.cellRendererParams?.singleClickEdit) {
770
- startCellEditing(event);
771
- }
772
- }, [startCellEditing]);
773
- /**
774
- * If cell has an edit action invoke it (if editable)
775
- */
776
- const invokeEditAction = (e) => {
777
- const editAction = e.colDef?.cellRendererParams?.editAction;
778
- if (!editAction)
779
- return false;
780
- const editable = fnOrVar(e.colDef?.editable, e);
781
- if (editable) {
782
- if (!e.node.isSelected()) {
783
- e.node.setSelected(true, true);
784
- }
785
- editAction([e.data, ...e.api.getSelectedRows().filter((row) => row.id !== e.data.id)]);
786
- }
787
- return true;
788
- };
789
- /**
790
- * Start editing on pressing Enter
791
- */
792
- const onCellKeyPress = useCallback((e) => {
793
- if (e.event.key === "Enter") {
794
- if (!invokeEditAction(e))
795
- startCellEditing(e);
796
- }
797
- }, [startCellEditing]);
798
- /**
799
- * Once the grid has auto-sized we want to run fit to fit the grid in its container,
800
- * but we don't want the non-flex auto-sized columns to "fit" size, so suppressSizeToFit is set to true.
801
- */
802
- const columnDefsAdjusted = useMemo(() => {
803
- const adjustColDefOrGroup = (colDef) => "children" in colDef ? adjustGroupColDef(colDef) : adjustColDef(colDef);
804
- const adjustGroupColDef = (colDef) => ({
805
- ...colDef,
806
- children: colDef.children.map((colDef) => adjustColDefOrGroup(colDef)),
807
- });
808
- const adjustColDef = (colDef) => ({
809
- ...colDef,
810
- suppressSizeToFit: (sizeColumns === "auto" || sizeColumns === "auto-skip-headers") && !colDef.flex,
811
- sortable: colDef.sortable && params.defaultColDef?.sortable !== false,
812
- });
813
- return columnDefs.map((colDef) => adjustColDefOrGroup(colDef));
814
- }, [columnDefs, params.defaultColDef?.sortable, sizeColumns]);
815
- /**
816
- * Set of colIds that need auto-sizing.
817
- */
818
- const colIdsEdited = useRef(new Set());
819
- const lastUpdatedDep = useRef(updatedDep);
820
- /**
821
- * When cell editing has completed the colId as needing auto-sizing
822
- */
823
- useEffect(() => {
824
- if (lastUpdatedDep.current === updatedDep)
825
- return;
826
- lastUpdatedDep.current = updatedDep;
827
- const colIds = updatingCols();
828
- // Updating possibly completed
829
- if (isEmpty(colIds)) {
830
- // Columns to resize?
831
- if (!isEmpty(colIdsEdited.current)) {
832
- const skipHeader = sizeColumns === "auto-skip-headers";
833
- if (sizeColumns === "auto" || skipHeader) {
834
- defer$1(() => autoSizeColumns({
835
- skipHeader,
836
- userSizedColIds: userSizedColIds.current,
837
- colIds: colIdsEdited.current,
838
- }));
839
- }
840
- colIdsEdited.current.clear();
841
- }
842
- }
843
- else {
844
- // Updates not complete, add them to a list of columns needing resize
845
- colIds.forEach((colId) => {
846
- if (colId && !getColDef(colId)?.flex) {
847
- colIdsEdited.current.add(colId);
848
- }
849
- });
850
- }
851
- }, [autoSizeColumns, getColDef, sizeColumns, updatedDep, updatingCols]);
852
- /**
853
- * Resize columns to fit if required on window/container resize
854
- */
855
- const onGridSizeChanged = useCallback(() => {
856
- if (sizeColumns !== "none") {
857
- sizeColumnsToFit();
858
- }
859
- }, [sizeColumns, sizeColumnsToFit]);
860
- /**
861
- * Set of column Id's that are prevented from auto-sizing as they are user set
862
- */
863
- const userSizedColIds = useRef(new Set());
864
- /**
865
- * Lock/unlock column width on user edit/reset.
866
- */
867
- const onColumnResized = useCallback((e) => {
868
- const colId = e.column?.getColId();
869
- if (colId == null)
870
- return;
871
- switch (e.source) {
872
- case "uiColumnDragged":
873
- userSizedColIds.current.add(colId);
874
- break;
875
- case "autosizeColumns":
876
- userSizedColIds.current.delete(colId);
877
- break;
878
- }
879
- }, []);
880
- setOnCellEditingComplete(params.onCellEditingComplete);
881
- return (jsx("div", { "data-testid": dataTestId, className: clsx("Grid-container", theme, staleGrid && "Grid-sortIsStale", gridReady && params.rowData && "Grid-ready"), children: jsx("div", { style: { flex: 1 }, ref: gridDivRef, children: jsx(AgGridReact, { rowHeight: params.rowHeight, animateRows: params.animateRows, rowClassRules: params.rowClassRules, getRowId: (params) => `${params.data.id}`, suppressRowClickSelection: true, rowSelection: rowSelection, suppressBrowserResizeObserver: true, onGridSizeChanged: onGridSizeChanged, suppressColumnVirtualisation: suppressColumnVirtualization, suppressClickEdit: true, onColumnVisible: () => {
882
- setInitialContentSize();
883
- }, onRowDataChanged: onRowDataChanged, onCellKeyPress: onCellKeyPress, onCellClicked: onCellClicked, onCellDoubleClicked: onCellDoubleClick, onCellEditingStarted: refreshSelectedRows, domLayout: params.domLayout, onColumnResized: onColumnResized, defaultColDef: { minWidth: 48, ...omit(params.defaultColDef, ["editable"]) }, columnDefs: columnDefsAdjusted, rowData: params.rowData, noRowsOverlayComponent: GridNoRowsOverlay, noRowsOverlayComponentParams: {
884
- rowData: params.rowData,
885
- noRowsOverlayText: params.noRowsOverlayText,
886
- }, onModelUpdated: onModelUpdated, onGridReady: onGridReady, onSortChanged: ensureSelectedRowIsVisible, postSortRows: params.postSortRows ?? postSortRows, onSelectionChanged: synchroniseExternalStateToGridSelection, onColumnMoved: params.onColumnMoved, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, isExternalFilterPresent: isExternalFilterPresent, doesExternalFilterPass: doesExternalFilterPass, maintainColumnOrder: true }) }) }));
887
- };
888
-
889
- const GridPopoverContext = createContext({
890
- anchorRef: { current: null },
891
- saving: false,
892
- setSaving: () => { },
893
- field: "",
894
- value: null,
895
- data: {},
896
- selectedRows: [],
897
- updateValue: async () => false,
898
- formatValue: () => "! No gridPopoverContextProvider !",
899
- });
900
- const useGridPopoverContext = () => useContext(GridPopoverContext);
901
-
902
- const GridPopoverContextProvider = ({ props, children }) => {
903
- const { getFilteredSelectedRows, updatingCells } = useContext(GridContext);
904
- const anchorRef = useRef(props.eGridCell);
905
- const hasSaved = useRef(false);
906
- const [saving, setSaving] = useState(false);
907
- const { colDef } = props;
908
- const { cellEditorParams } = colDef;
909
- const multiEdit = cellEditorParams?.multiEdit ?? false;
910
- // Then item that is clicked on will always be first in the list
911
- const selectedRows = useMemo(() => multiEdit ? sortBy(getFilteredSelectedRows(), (row) => row.id !== props.data.id) : [props.data], [getFilteredSelectedRows, multiEdit, props.data]);
912
- const field = props.colDef?.field ?? "";
913
- const updateValue = useCallback(async (saveFn, tabDirection) => {
914
- if (hasSaved.current)
915
- return true;
916
- hasSaved.current = true;
917
- return saving ? false : await updatingCells({ selectedRows, field }, saveFn, setSaving, tabDirection);
918
- }, [field, saving, selectedRows, updatingCells]);
919
- return (jsx(GridPopoverContext.Provider, { value: {
920
- anchorRef: anchorRef,
921
- saving,
922
- setSaving,
923
- selectedRows,
924
- field,
925
- data: props.data,
926
- value: props.value,
927
- updateValue,
928
- formatValue: props.formatValue,
929
- }, children: children }));
930
- };
931
-
932
- const GridCellMultiSelectClassRules = {
933
- "ag-selected-for-edit": (params) => {
934
- const { api, node, colDef } = params;
935
- const cep = colDef.cellEditorSelector
936
- ? colDef.cellEditorSelector(params)?.params
937
- : colDef.cellEditorParams;
938
- return (cep?.multiEdit &&
939
- api
940
- .getSelectedNodes()
941
- .map((row) => row.id)
942
- .includes(node.id) &&
943
- api.getEditingCells().some((cell) => cell.column.getColDef() === colDef));
944
- },
945
- };
946
-
947
- const GridIcon = (props) => (jsx(LuiIcon, { name: props.icon, title: props.title, alt: props.title, size: props.size ?? "md", className: clsx(`AgGridGenericCellRenderer-${props.icon}Icon`, props.className, props.disabled && "GridIcon-disabled") }));
948
-
949
- const GridLoadableCell = () => (jsx(LuiMiniSpinner, { size: 22, divProps: { className: "GridLoadableCell-container", role: "status", "aria-label": "Loading" } }));
950
-
951
- const GridCellRenderer = (props) => {
952
- const { checkUpdating } = useContext(GridUpdatingContext);
953
- const colDef = props.colDef;
954
- const rendererParams = colDef.cellRendererParams;
955
- const warningFn = rendererParams?.warning;
956
- let warningText = warningFn ? warningFn(props) : undefined;
957
- const infoFn = rendererParams?.info;
958
- let infoText = infoFn ? infoFn(props) : undefined;
959
- if (Array.isArray(warningText))
960
- warningText = warningText.join("\n");
961
- if (Array.isArray(infoText))
962
- infoText = infoText.join("\n");
963
- return checkUpdating(colDef.field ?? colDef.colId ?? "", props.data.id) ? (jsx(GridLoadableCell, {})) : (jsxs(Fragment, { children: [!!warningText && (jsx(GridIcon, { icon: "ic_warning_outline", title: typeof warningText === "string" ? warningText : "Warning" })), !!infoText && jsx(GridIcon, { icon: "ic_info_outline", title: typeof infoText === "string" ? infoText : "Info" }), jsx("div", { className: "GridCell-container", children: colDef.cellRendererParams?.originalCellRenderer ? (jsx(colDef.cellRendererParams.originalCellRenderer, { ...props })) : (jsx("span", { title: props.valueFormatted ?? undefined, children: props.valueFormatted })) }), fnOrVar(colDef.editable, props) && rendererParams?.rightHoverElement && (jsx("div", { className: "GridCell-hoverRight", children: rendererParams?.rightHoverElement }))] }));
964
- };
965
- const suppressCellKeyboardEvents = (e) => {
966
- const shortcutKeys = e.colDef.cellRendererParams?.shortcutKeys ?? {};
967
- const exec = shortcutKeys[e.event.key];
968
- if (!e.editing && !e.event.repeat && e.event.type === "keypress" && exec) {
969
- const editable = fnOrVar(e.colDef?.editable, e);
970
- return editable ? exec(e) ?? true : true;
971
- }
972
- // It's important that aggrid doesn't trigger edit on enter
973
- // as the incorrect selected rows will be returned
974
- return !["ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp", "Tab", " ", "Home", "End", "PageUp", "PageDown"].includes(e.event.key);
975
- };
976
- const generateFilterGetter = (field, filterValueGetter, valueFormatter) => {
977
- if (filterValueGetter)
978
- return filterValueGetter;
979
- // aggrid will default to valueGetter
980
- if (typeof valueFormatter !== "function" || !field)
981
- return undefined;
982
- return (params) => {
983
- const value = params.getValue(field);
984
- let formattedValue = valueFormatter({ ...params, value });
985
- // Search for null values using standard dash
986
- if (formattedValue === "–")
987
- formattedValue += " -";
988
- // Search by raw value as well as formatted
989
- const gotValue = ["string", "number"].includes(typeof value) ? value : undefined;
990
- return (formattedValue + (gotValue != null && formattedValue != gotValue ? " " + gotValue : "")) //
991
- .replaceAll(/\s+/g, " ")
992
- .trim();
993
- };
994
- };
995
- /*
996
- * All cells should use this.
997
- */
998
- const GridCell = (props, custom) => {
999
- // Generate a default filter value getter which uses the formatted value plus
1000
- // the editable value if it's a string and different from the formatted value.
1001
- // This is so that e.g. bearings can be searched for by DMS or raw number.
1002
- const valueFormatter = props.valueFormatter;
1003
- const filterValueGetter = generateFilterGetter(props.field, props.filterValueGetter, valueFormatter);
1004
- const exportable = props.exportable;
1005
- // Can't leave this here ag-grid will complain
1006
- delete props.exportable;
1007
- return {
1008
- colId: props.field ?? props.field,
1009
- headerTooltip: props.headerName,
1010
- sortable: !!(props?.field || props?.valueGetter),
1011
- resizable: true,
1012
- editable: props.editable ?? false,
1013
- ...(custom?.editor && {
1014
- cellClassRules: GridCellMultiSelectClassRules,
1015
- editable: props.editable ?? true,
1016
- cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
1017
- }),
1018
- suppressKeyboardEvent: suppressCellKeyboardEvents,
1019
- ...(custom?.editorParams && {
1020
- cellEditorParams: {
1021
- ...custom.editorParams,
1022
- multiEdit: custom.multiEdit,
1023
- preventAutoEdit: custom.preventAutoEdit ?? false,
1024
- },
1025
- }),
1026
- // If there's a valueFormatter and no filterValueGetter then create a filterValueGetter
1027
- filterValueGetter,
1028
- // Default value formatter, otherwise react freaks out on objects
1029
- valueFormatter: (params) => {
1030
- if (params.value == null)
1031
- return "–";
1032
- const types = ["number", "boolean", "string"];
1033
- if (types.includes(typeof params.value))
1034
- return `${params.value}`;
1035
- else
1036
- return JSON.stringify(params.value);
1037
- },
1038
- ...props,
1039
- cellRenderer: GridCellRenderer,
1040
- cellRendererParams: {
1041
- originalCellRenderer: props.cellRenderer,
1042
- ...props.cellRendererParams,
1043
- },
1044
- headerComponentParams: {
1045
- exportable,
1046
- ...props.headerComponentParams,
1047
- },
1048
- };
1049
- };
1050
- const GenericCellEditorComponentWrapper = (editor) => {
1051
- const obj = { editor };
1052
- return forwardRef(function GenericCellEditorComponentFr(cellEditorParams, _) {
1053
- const valueFormatted = cellEditorParams.formatValue
1054
- ? cellEditorParams.formatValue(cellEditorParams.value)
1055
- : "Missing formatter";
1056
- return (jsxs(GridPopoverContextProvider, { props: cellEditorParams, children: [jsx(cellEditorParams.colDef.cellRenderer, { ...cellEditorParams, valueFormatted: valueFormatted, ...cellEditorParams.colDef.cellRendererParams }), obj.editor && jsx(obj.editor, { ...cellEditorParams })] }));
1057
- });
1058
- };
1059
-
1060
- const GridCellFillerColId = "gridCellFiller";
1061
- const isGridCellFiller = (col) => col.colId === GridCellFillerColId;
1062
- const GridCellFiller = () => ({
1063
- colId: GridCellFillerColId,
1064
- headerName: "",
1065
- flex: 1,
1066
- });
1067
-
1068
- const Editor = (props) => ({
1069
- component: GenericCellEditorComponentWrapper(props.editor),
1070
- params: { ...props.editorParams, multiEdit: props.multiEdit },
1071
- });
1072
- /**
1073
- * Used to choose between cell editors based in data.
1074
- */
1075
- const GridCellMultiEditor = (props, cellEditorSelector) => GridCell({
1076
- cellClassRules: GridCellMultiSelectClassRules,
1077
- cellEditorSelector,
1078
- editable: props.editable ?? true,
1079
- ...props,
1080
- });
1081
-
1082
- const GridFilterHeaderIconButton = forwardRef(function columnsButton({ icon, title, onClick, buttonProps, disabled = false, size = "md" }, ref) {
1083
- return (jsx(LuiButton, { ...buttonProps, type: "button", level: "tertiary", className: "lui-button-icon-only", ref: ref, "aria-label": title, title: title, onClick: onClick, disabled: disabled, children: jsx(LuiIcon, { name: icon, alt: "Menu", size: size }) }));
1084
- });
1085
-
1086
- // Generate className following BEM methodology: http://getbem.com/naming/
1087
- // Modifier value can be one of the following types: boolean, string, undefined
1088
- const useBEM = ({ block, element, modifiers, className }) => useMemo(() => {
1089
- const blockElement = element ? `${block}__${element}` : block;
1090
- let classString = blockElement;
1091
- modifiers &&
1092
- Object.keys(modifiers).forEach((name) => {
1093
- const value = modifiers[name];
1094
- if (value)
1095
- classString += ` ${blockElement}--${value === true ? name : `${name}-${value}`}`;
1096
- });
1097
- let expandedClassName = typeof className === "function" ? className(modifiers) : className;
1098
- if (typeof expandedClassName === "string") {
1099
- expandedClassName = expandedClassName.trim();
1100
- if (expandedClassName)
1101
- classString += ` ${expandedClassName}`;
1102
- }
1103
- return classString;
1104
- }, [block, element, modifiers, className]);
1105
-
1106
- // Adapted from material-ui
1107
- // https://github.com/mui-org/material-ui/blob/f996027d00e7e4bff3fc040786c1706f9c6c3f82/packages/material-ui-utils/src/useForkRef.ts
1108
- const setRef = (ref, instance) => {
1109
- if (typeof ref === "function") {
1110
- ref(instance);
1111
- }
1112
- else if (ref) {
1113
- ref.current = instance;
1114
- }
1115
- };
1116
- const useCombinedRef = (refA, refB) => {
1117
- return useMemo(() => {
1118
- return (instance) => {
1119
- setRef(refA, instance);
1120
- setRef(refB, instance);
1121
- };
1122
- }, [refA, refB]);
1123
- };
1124
-
1125
- // Get around a warning when using useLayoutEffect on the server.
1126
- // https://github.com/reduxjs/react-redux/blob/b48d087d76f666e1c6c5a9713bbec112a1631841/src/utils/useIsomorphicLayoutEffect.js#L12
1127
- // https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
1128
- // https://github.com/facebook/react/issues/14927#issuecomment-549457471
1129
- const useIsomorphicLayoutEffect = typeof window !== "undefined" &&
1130
- typeof window.document !== "undefined" &&
1131
- typeof window.document.createElement !== "undefined"
1132
- ? useLayoutEffect
1133
- : useEffect;
1134
-
1135
- const menuContainerClass = "szh-menu-container";
1136
- const menuClass = "szh-menu";
1137
- const menuButtonClass = "szh-menu-button";
1138
- const menuArrowClass = "arrow";
1139
- const menuItemClass = "item";
1140
- const menuDividerClass = "divider";
1141
- const menuHeaderClass = "header";
1142
- const menuGroupClass = "group";
1143
- const subMenuClass = "submenu";
1144
- const radioGroupClass = "radio-group";
1145
- const Keys = Object.freeze({
1146
- ENTER: "Enter",
1147
- TAB: "Tab",
1148
- ESC: "Escape",
1149
- SPACE: " ",
1150
- HOME: "Home",
1151
- END: "End",
1152
- LEFT: "ArrowLeft",
1153
- RIGHT: "ArrowRight",
1154
- UP: "ArrowUp",
1155
- DOWN: "ArrowDown",
1156
- });
1157
- const HoverActionTypes = Object.freeze({
1158
- RESET: 0,
1159
- SET: 1,
1160
- UNSET: 2,
1161
- INCREASE: 3,
1162
- DECREASE: 4,
1163
- FIRST: 5,
1164
- LAST: 6,
1165
- SET_INDEX: 7,
1166
- });
1167
- const CloseReason = Object.freeze({
1168
- CLICK: "click",
1169
- CANCEL: "cancel",
1170
- BLUR: "blur",
1171
- SCROLL: "scroll",
1172
- TAB_FORWARD: "tab_forward",
1173
- TAB_BACKWARD: "tab_backward",
1174
- });
1175
- const FocusPositions = Object.freeze({
1176
- FIRST: "first",
1177
- LAST: "last",
1178
- });
1179
- const MenuStateMap = Object.freeze({
1180
- entering: "opening",
1181
- entered: "open",
1182
- exiting: "closing",
1183
- exited: "closed",
1184
- });
1185
-
1186
- const isMenuOpen = (state) => !!state && state[0] === "o";
1187
- const batchedUpdates = unstable_batchedUpdates || ((callback) => callback());
1188
- const floatEqual = (a, b, diff = 0.0001) => Math.abs(a - b) < diff;
1189
- const getTransition = (transition, name) => transition === true || !!(transition && transition[name]);
1190
- function safeCall(fn, arg) {
1191
- return typeof fn === "function" ? fn(arg) : fn;
1192
- }
1193
- const internalKey = "_szhsinMenu";
1194
- const getName = (component) => component[internalKey];
1195
- const mergeProps = (target, source) => {
1196
- source &&
1197
- Object.keys(source).forEach((key) => {
1198
- const targetProp = target[key];
1199
- const sourceProp = source[key];
1200
- if (typeof sourceProp === "function" && targetProp) {
1201
- target[key] = (...arg) => {
1202
- sourceProp(...arg);
1203
- targetProp(...arg);
1204
- };
1205
- }
1206
- else {
1207
- target[key] = sourceProp;
1208
- }
1209
- });
1210
- return target;
1211
- };
1212
- const parsePadding = (paddingStr) => {
1213
- if (paddingStr == null)
1214
- return { top: 0, right: 0, bottom: 0, left: 0 };
1215
- const padding = paddingStr.trim().split(/\s+/, 4).map(parseFloat);
1216
- const top = !isNaN(padding[0]) ? padding[0] : 0;
1217
- const right = !isNaN(padding[1]) ? padding[1] : top;
1218
- return {
1219
- top,
1220
- right,
1221
- bottom: !isNaN(padding[2]) ? padding[2] : top,
1222
- left: !isNaN(padding[3]) ? padding[3] : right,
1223
- };
1224
- };
1225
- // Adapted from https://github.com/popperjs/popper-core/tree/v2.9.1/src/dom-utils
1226
- const getScrollAncestor = (node) => {
1227
- const thisWindow = (node?.ownerDocument ?? document).defaultView ?? window;
1228
- while (node) {
1229
- node = node.parentNode;
1230
- if (!node || node === thisWindow?.document?.body)
1231
- return null;
1232
- if (node instanceof Element) {
1233
- const { overflow, overflowX, overflowY } = thisWindow.getComputedStyle(node);
1234
- if (/auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX))
1235
- return node;
1236
- }
1237
- }
1238
- return null;
1239
- };
1240
- function commonProps(isDisabled, isHovering) {
1241
- return {
1242
- "aria-disabled": isDisabled || undefined,
1243
- tabIndex: isHovering ? 0 : -1,
1244
- };
1245
- }
1246
- const indexOfNode = (nodeList, node) => findIndex(nodeList, node);
1247
- const focusFirstInput = (container) => {
1248
- // We can't use instanceof Element in portals, so I use querySelectorAll as a proxy here
1249
- if (!container || !("querySelectorAll" in container))
1250
- return false;
1251
- const inputs = container.querySelectorAll("input[type='text'],input:not([type]),textarea");
1252
- const input = inputs[0];
1253
- // Using focus as proxy for HTMLElement
1254
- if (!input || !("focus" in input))
1255
- return false;
1256
- input.focus();
1257
- // Text areas should start at end
1258
- // this is a proxy for instanceof HTMLTextAreaElement
1259
- if (["textarea", "text"].includes(input.type)) {
1260
- input.setSelectionRange(0, input.value.length);
1261
- }
1262
- return true;
1263
- };
1264
-
1265
- const HoverItemContext = createContext(undefined);
1266
-
1267
- const withHovering = (name, WrappedComponent) => {
1268
- const Component = memo(WrappedComponent);
1269
- const WithHovering = forwardRef((props, ref) => {
1270
- const menuItemRef = useRef(null);
1271
- return (jsx(Component, { ...props, menuItemRef: menuItemRef, externalRef: ref, isHovering: useContext(HoverItemContext) === menuItemRef.current }));
1272
- });
1273
- WithHovering.displayName = `WithHovering(${name})`;
1274
- return WithHovering;
1275
- };
1276
-
1277
- const useItems = (menuRef, focusRef) => {
1278
- const [hoverItem, setHoverItem] = useState();
1279
- const stateRef = useRef({
1280
- items: [],
1281
- hoverIndex: -1,
1282
- sorted: false,
1283
- });
1284
- const mutableState = stateRef.current;
1285
- const updateItems = useCallback((item, isMounted) => {
1286
- const { items } = mutableState;
1287
- if (!item) {
1288
- mutableState.items = [];
1289
- }
1290
- else if (isMounted) {
1291
- items.push(item);
1292
- }
1293
- else {
1294
- const index = items.indexOf(item);
1295
- if (index > -1) {
1296
- items.splice(index, 1);
1297
- if (item.contains(document.activeElement)) {
1298
- focusRef?.current?.focus();
1299
- setHoverItem(undefined);
1300
- }
1301
- }
1302
- }
1303
- mutableState.hoverIndex = -1;
1304
- mutableState.sorted = false;
1305
- }, [mutableState, focusRef]);
1306
- const dispatch = useCallback((actionType, item, nextIndex) => {
1307
- const { items, hoverIndex } = mutableState;
1308
- const sortItems = () => {
1309
- if (mutableState.sorted)
1310
- return;
1311
- const orderedNodes = menuRef.current.querySelectorAll(".szh-menu__item");
1312
- items.sort((a, b) => indexOfNode(orderedNodes, a) - indexOfNode(orderedNodes, b));
1313
- mutableState.sorted = true;
1314
- };
1315
- let index = -1;
1316
- let newItem = undefined;
1317
- let newItemFn = undefined;
1318
- switch (actionType) {
1319
- case HoverActionTypes.RESET:
1320
- break;
1321
- case HoverActionTypes.SET:
1322
- newItem = item;
1323
- break;
1324
- case HoverActionTypes.UNSET:
1325
- newItemFn = (prevItem) => (prevItem === item ? undefined : prevItem);
1326
- break;
1327
- case HoverActionTypes.FIRST:
1328
- sortItems();
1329
- index = 0;
1330
- newItem = items[index];
1331
- break;
1332
- case HoverActionTypes.LAST:
1333
- sortItems();
1334
- index = items.length - 1;
1335
- newItem = items[index];
1336
- break;
1337
- case HoverActionTypes.SET_INDEX:
1338
- if (typeof nextIndex !== "number")
1339
- break;
1340
- sortItems();
1341
- index = nextIndex;
1342
- newItem = items[index];
1343
- defer$1(() => newItem.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }));
1344
- break;
1345
- case HoverActionTypes.INCREASE:
1346
- sortItems();
1347
- index = hoverIndex;
1348
- if (index < 0)
1349
- index = items.indexOf(item);
1350
- index++;
1351
- if (index >= items.length)
1352
- index = 0;
1353
- newItem = items[index];
1354
- focusFirstInput(newItem);
1355
- break;
1356
- case HoverActionTypes.DECREASE: {
1357
- sortItems();
1358
- index = hoverIndex;
1359
- if (index < 0)
1360
- index = items.indexOf(item);
1361
- index--;
1362
- if (index < 0)
1363
- index = items.length - 1;
1364
- newItem = items[index];
1365
- focusFirstInput(newItem);
1366
- break;
1367
- }
1368
- default:
1369
- if (process.env.NODE_ENV !== "production")
1370
- throw new Error(`[React-Menu] Unknown hover action type: ${actionType}`);
1371
- }
1372
- if (!newItem && !newItemFn)
1373
- index = -1;
1374
- setHoverItem(newItem ?? newItemFn);
1375
- mutableState.hoverIndex = index;
1376
- }, [menuRef, mutableState]);
1377
- return { hoverItem, dispatch, updateItems };
1378
- };
1379
-
1380
- const useItemEffect = (isDisabled, menuItemRef, updateItems) => {
1381
- useIsomorphicLayoutEffect(() => {
1382
- if (!menuItemRef)
1383
- return;
1384
- if (process.env.NODE_ENV !== "production" && !updateItems) {
1385
- throw new Error(`[React-Menu] This menu item or submenu should be rendered under a menu: ${menuItemRef.current.outerHTML}`);
1386
- }
1387
- if (isDisabled)
1388
- return;
1389
- const item = menuItemRef.current;
1390
- updateItems(item, true);
1391
- return () => {
1392
- updateItems(item);
1393
- };
1394
- }, [isDisabled, menuItemRef, updateItems]);
1395
- };
1396
-
1397
- const ItemSettingsContext = createContext({
1398
- submenuOpenDelay: 0,
1399
- submenuCloseDelay: 0,
1400
- });
1401
-
1402
- const MenuListItemContext = createContext({
1403
- dispatch: () => { },
1404
- updateItems: () => { },
1405
- setOpenSubmenuCount: () => 0,
1406
- });
1407
-
1408
- // This hook includes some common stateful logic in MenuItem and FocusableItem
1409
- const useItemState = (menuItemRef, focusRef, isHovering, isDisabled) => {
1410
- const { submenuCloseDelay } = useContext(ItemSettingsContext);
1411
- const { isParentOpen, isSubmenuOpen, dispatch, updateItems } = useContext(MenuListItemContext);
1412
- const timeoutId = useRef();
1413
- const setHover = () => {
1414
- !isHovering && !isDisabled && dispatch(HoverActionTypes.SET, menuItemRef?.current, 0);
1415
- };
1416
- const unsetHover = () => {
1417
- !isDisabled && dispatch(HoverActionTypes.UNSET, menuItemRef?.current, 0);
1418
- };
1419
- const onBlur = (e) => {
1420
- // Focus has moved out of the entire item
1421
- // It handles situation such as clicking on a sibling disabled menu item
1422
- if (isHovering && !e.currentTarget.contains(e.relatedTarget))
1423
- unsetHover();
1424
- };
1425
- const onPointerMove = () => {
1426
- if (isSubmenuOpen) {
1427
- if (!timeoutId.current)
1428
- timeoutId.current = setTimeout(() => {
1429
- timeoutId.current = undefined;
1430
- setHover();
1431
- }, submenuCloseDelay);
1432
- }
1433
- else {
1434
- setHover();
1435
- }
1436
- };
1437
- const onPointerLeave = (_, keepHover) => {
1438
- if (timeoutId.current) {
1439
- clearTimeout(timeoutId.current);
1440
- timeoutId.current = undefined;
1441
- }
1442
- !keepHover && unsetHover();
1443
- };
1444
- useItemEffect(isDisabled, menuItemRef, updateItems);
1445
- useEffect(() => () => clearTimeout(timeoutId.current), []);
1446
- useEffect(() => {
1447
- // Don't set focus when parent menu is closed, otherwise focus will be lost
1448
- // and onBlur event will be fired with relatedTarget setting as null.
1449
- if (isHovering && isParentOpen) {
1450
- focusRef?.current && focusRef.current.focus();
1451
- }
1452
- }, [focusRef, isHovering, isParentOpen]);
1453
- return {
1454
- setHover,
1455
- onBlur,
1456
- onPointerMove,
1457
- onPointerLeave,
826
+ }, [focusRef, isHovering, isParentOpen]);
827
+ return {
828
+ setHover,
829
+ onBlur,
830
+ onPointerMove,
831
+ onPointerLeave,
1458
832
  };
1459
833
  };
1460
834
 
@@ -2564,339 +1938,1007 @@ function MenuFr({ "aria-label": ariaLabel, menuButton, instanceRef, onMenuChange
2564
1938
  // Focus (hover) the first menu item when onClick event is trigger by keyboard
2565
1939
  openMenu(e.detail === 0 ? FocusPositions.FIRST : undefined);
2566
1940
  };
2567
- const onKeyDown = (e) => {
1941
+ const onKeyDown = (e) => {
1942
+ switch (e.key) {
1943
+ case Keys.UP:
1944
+ openMenu(FocusPositions.LAST);
1945
+ break;
1946
+ case Keys.DOWN:
1947
+ openMenu(FocusPositions.FIRST);
1948
+ break;
1949
+ default:
1950
+ return;
1951
+ }
1952
+ e.preventDefault();
1953
+ };
1954
+ // Too many mixed types here to figure out what to pick for button. Bad code.
1955
+ const button = safeCall(menuButton, { open: isOpen });
1956
+ if (!button || !button.type)
1957
+ throw new Error("Menu requires a menuButton prop.");
1958
+ const buttonProps = {
1959
+ ref: useCombinedRef(button.ref, buttonRef),
1960
+ ...mergeProps({ onClick, onKeyDown }, button.props),
1961
+ isOpen: false,
1962
+ };
1963
+ if (getName(button.type) === "MenuButton") {
1964
+ buttonProps.isOpen = isOpen;
1965
+ }
1966
+ const renderButton = cloneElement(button, buttonProps);
1967
+ useMenuChange(onMenuChange, isOpen);
1968
+ useImperativeHandle(instanceRef, () => ({
1969
+ openMenu,
1970
+ closeMenu: () => toggleMenu(false),
1971
+ }));
1972
+ return (jsxs(Fragment, { children: [renderButton, jsx(ControlledMenu, { ...restProps, ...stateProps, "aria-label": ariaLabel || (typeof button.props.children === "string" ? button.props.children : "Menu"), anchorRef: buttonRef, ref: externalRef, onClose: handleClose, skipOpen: skipOpen })] }));
1973
+ }
1974
+ const Menu = forwardRef(MenuFr);
1975
+
1976
+ const SubMenuFr = ({ "aria-label": ariaLabel, className, disabled, direction, label, openTrigger, onMenuChange, isHovering, instanceRef, menuItemRef, itemProps = {}, ...restProps }) => {
1977
+ const settings = useContext(SettingsContext);
1978
+ const { rootMenuRef } = settings;
1979
+ const { submenuOpenDelay, submenuCloseDelay } = useContext(ItemSettingsContext);
1980
+ const { parentMenuRef, parentDir, overflow: parentOverflow } = useContext(MenuListContext);
1981
+ const { isParentOpen, isSubmenuOpen, setOpenSubmenuCount, dispatch, updateItems } = useContext(MenuListItemContext);
1982
+ const isPortal = parentOverflow !== "visible";
1983
+ const [stateProps, toggleMenu, _openMenu] = useMenuStateAndFocus(settings);
1984
+ const { state } = stateProps;
1985
+ const isDisabled = !!disabled;
1986
+ const isOpen = isMenuOpen(state);
1987
+ const containerRef = useRef(null);
1988
+ const timeoutId = useRef();
1989
+ const stopTimer = () => {
1990
+ if (timeoutId.current) {
1991
+ clearTimeout(timeoutId.current);
1992
+ timeoutId.current = undefined;
1993
+ }
1994
+ };
1995
+ const openMenu = (...args) => {
1996
+ stopTimer();
1997
+ setHover();
1998
+ !isDisabled && _openMenu(...args);
1999
+ };
2000
+ const setHover = () => !isHovering && !isDisabled && dispatch(HoverActionTypes.SET, menuItemRef?.current, 0);
2001
+ const delayOpen = (delay) => {
2002
+ setHover();
2003
+ if (!openTrigger)
2004
+ timeoutId.current = setTimeout(() => batchedUpdates(openMenu), Math.max(delay, 0));
2005
+ };
2006
+ const handlePointerMove = () => {
2007
+ if (timeoutId.current || isOpen || isDisabled)
2008
+ return;
2009
+ if (isSubmenuOpen) {
2010
+ timeoutId.current = setTimeout(() => delayOpen(submenuOpenDelay - submenuCloseDelay), submenuCloseDelay);
2011
+ }
2012
+ else {
2013
+ delayOpen(submenuOpenDelay);
2014
+ }
2015
+ };
2016
+ const handlePointerLeave = () => {
2017
+ stopTimer();
2018
+ if (!isOpen)
2019
+ dispatch(HoverActionTypes.UNSET, menuItemRef?.current, 0);
2020
+ };
2021
+ const handleKeyDown = (e) => {
2022
+ let handled = false;
2023
+ switch (e.key) {
2024
+ // LEFT key is bubbled up from submenu items
2025
+ case Keys.LEFT:
2026
+ if (isOpen) {
2027
+ menuItemRef?.current && menuItemRef.current.focus();
2028
+ toggleMenu(false);
2029
+ handled = true;
2030
+ }
2031
+ break;
2032
+ // prevent browser from scrolling page to the right
2033
+ case Keys.RIGHT:
2034
+ if (!isOpen)
2035
+ handled = true;
2036
+ break;
2037
+ }
2038
+ if (handled) {
2039
+ e.preventDefault();
2040
+ e.stopPropagation();
2041
+ }
2042
+ };
2043
+ const handleItemKeyDown = (e) => {
2044
+ if (!isHovering)
2045
+ return;
2046
+ switch (e.key) {
2047
+ case Keys.ENTER:
2048
+ case Keys.SPACE:
2049
+ case Keys.RIGHT:
2050
+ openTrigger !== "none" && openMenu(FocusPositions.FIRST);
2051
+ break;
2052
+ }
2053
+ };
2054
+ useItemEffect(isDisabled, menuItemRef, updateItems);
2055
+ useMenuChange(onMenuChange, isOpen);
2056
+ useEffect(() => () => clearTimeout(timeoutId.current), []);
2057
+ useEffect(() => {
2058
+ // Don't set focus when parent menu is closed, otherwise focus will be lost
2059
+ // and onBlur event will be fired with relatedTarget setting as null.
2060
+ if (isHovering && isParentOpen) {
2061
+ menuItemRef?.current && menuItemRef.current.focus();
2062
+ }
2063
+ else {
2064
+ toggleMenu(false);
2065
+ }
2066
+ }, [isHovering, isParentOpen, toggleMenu, menuItemRef]);
2067
+ useEffect(() => {
2068
+ setOpenSubmenuCount((count) => (isOpen ? count + 1 : Math.max(count - 1, 0)));
2069
+ }, [setOpenSubmenuCount, isOpen]);
2070
+ useImperativeHandle(instanceRef, () => ({
2071
+ openMenu: (...args) => {
2072
+ isParentOpen && openMenu(...args);
2073
+ },
2074
+ closeMenu: () => {
2075
+ if (isOpen) {
2076
+ menuItemRef?.current && menuItemRef.current.focus();
2077
+ toggleMenu(false);
2078
+ }
2079
+ },
2080
+ }));
2081
+ const modifiers = useMemo(() => ({
2082
+ open: isOpen,
2083
+ hover: isHovering,
2084
+ disabled: isDisabled,
2085
+ submenu: true,
2086
+ }), [isOpen, isHovering, isDisabled]);
2087
+ const { ref: externalItemRef, className: itemClassName, ...restItemProps } = itemProps;
2088
+ const mergedItemProps = mergeProps({
2089
+ onPointerMove: handlePointerMove,
2090
+ onPointerLeave: handlePointerLeave,
2091
+ onKeyDown: handleItemKeyDown,
2092
+ onClick: () => openTrigger !== "none" && openMenu(),
2093
+ }, restItemProps);
2094
+ const getMenuList = () => {
2095
+ const menuList = (jsx(MenuList, { ...restProps, ...stateProps, ariaLabel: ariaLabel || (typeof label === "string" ? label : "Submenu"), anchorRef: menuItemRef, containerRef: isPortal ? rootMenuRef : containerRef, direction: direction || (parentDir === "right" || parentDir === "left" ? parentDir : "right"), parentScrollingRef: isPortal && parentMenuRef, isDisabled: isDisabled }));
2096
+ const container = rootMenuRef?.current;
2097
+ return isPortal && container ? createPortal(menuList, container) : menuList;
2098
+ };
2099
+ return (jsxs("li", { className: useBEM({ block: menuClass, element: subMenuClass, className }), style: { position: "relative" }, role: "presentation", ref: containerRef, onKeyDown: handleKeyDown, children: [jsx("div", { role: "menuitem", "aria-haspopup": true, "aria-expanded": isOpen, ...mergedItemProps, ...commonProps(isDisabled, isHovering), ref: useCombinedRef(externalItemRef, menuItemRef), className: useBEM({
2100
+ block: menuClass,
2101
+ element: menuItemClass,
2102
+ modifiers,
2103
+ className: itemClassName,
2104
+ }), children: useMemo(() => safeCall(label, modifiers), [label, modifiers]) }), state && getMenuList()] }));
2105
+ };
2106
+ const SubMenu = withHovering("SubMenu", SubMenuFr);
2107
+
2108
+ const RadioGroupContext = createContext({});
2109
+
2110
+ const MenuItemFr = ({ className, value, href, type, checked, disabled, children, onClick, isHovering, menuItemRef, externalRef, ...restProps }) => {
2111
+ const isDisabled = !!disabled;
2112
+ const { setHover, ...restStateProps } = useItemState(menuItemRef, menuItemRef, isHovering, isDisabled);
2113
+ const eventHandlers = useContext(EventHandlersContext);
2114
+ const radioGroup = useContext(RadioGroupContext);
2115
+ const isRadio = type === "radio";
2116
+ const isCheckBox = type === "checkbox";
2117
+ const isAnchor = !!href && !isDisabled && !isRadio && !isCheckBox;
2118
+ const isChecked = isRadio ? radioGroup.value === value : isCheckBox ? !!checked : false;
2119
+ // handle click seems to be a combination of multiple event types, bad code.
2120
+ const handleClick = (e) => {
2121
+ if (isDisabled) {
2122
+ if (e.syntheticEvent) {
2123
+ e.syntheticEvent.stopPropagation();
2124
+ e.syntheticEvent.preventDefault();
2125
+ }
2126
+ else {
2127
+ e.stopPropagation();
2128
+ e.preventDefault();
2129
+ }
2130
+ return;
2131
+ }
2132
+ const event = {
2133
+ value,
2134
+ syntheticEvent: e,
2135
+ };
2136
+ if (e.key !== undefined) {
2137
+ const ke = e;
2138
+ event.key = ke.key;
2139
+ event.shiftKey = ke.shiftKey;
2140
+ }
2141
+ if (isCheckBox)
2142
+ event.checked = !isChecked;
2143
+ if (isRadio)
2144
+ event.name = radioGroup.name;
2145
+ safeCall(onClick, event);
2146
+ if (isRadio)
2147
+ safeCall(radioGroup.onRadioChange, event);
2148
+ eventHandlers.handleClick(event, isCheckBox || isRadio);
2149
+ };
2150
+ const handleKeyDown = (e) => {
2151
+ // if tab is allowed the handleKeyUp event can't process the tab
2152
+ if (e.key === "Tab") {
2153
+ e.preventDefault();
2154
+ e.stopPropagation();
2155
+ }
2156
+ };
2157
+ /**
2158
+ * Keyboard events are triggered on up, otherwise subcomponents get spaces and enters typed in them
2159
+ */
2160
+ const handleKeyUp = (e) => {
2161
+ if (!isHovering)
2162
+ return;
2568
2163
  switch (e.key) {
2569
- case Keys.UP:
2570
- openMenu(FocusPositions.LAST);
2571
- break;
2572
- case Keys.DOWN:
2573
- openMenu(FocusPositions.FIRST);
2164
+ case Keys.ENTER:
2165
+ case Keys.TAB:
2166
+ case Keys.SPACE:
2167
+ e.preventDefault();
2168
+ e.stopPropagation();
2169
+ if (isAnchor) {
2170
+ menuItemRef?.current && menuItemRef.current.click();
2171
+ }
2172
+ else {
2173
+ handleClick(e);
2174
+ }
2574
2175
  break;
2575
- default:
2576
- return;
2577
2176
  }
2578
- e.preventDefault();
2579
2177
  };
2580
- // Too many mixed types here to figure out what to pick for button. Bad code.
2581
- const button = safeCall(menuButton, { open: isOpen });
2582
- if (!button || !button.type)
2583
- throw new Error("Menu requires a menuButton prop.");
2584
- const buttonProps = {
2585
- ref: useCombinedRef(button.ref, buttonRef),
2586
- ...mergeProps({ onClick, onKeyDown }, button.props),
2587
- isOpen: false,
2178
+ const modifiers = useMemo(() => ({
2179
+ type,
2180
+ disabled: isDisabled,
2181
+ hover: isHovering,
2182
+ checked: isChecked,
2183
+ anchor: isAnchor,
2184
+ }), [type, isDisabled, isHovering, isChecked, isAnchor]);
2185
+ const mergedProps = mergeProps({
2186
+ ...restStateProps,
2187
+ onPointerDown: setHover,
2188
+ onKeyDown: handleKeyDown,
2189
+ onKeyUp: handleKeyUp,
2190
+ onClick: handleClick,
2191
+ }, restProps);
2192
+ // Order of props overriding (same in all components):
2193
+ // 1. Preset props adhering to WAI-ARIA Authoring Practices.
2194
+ // 2. Merged outer and local props
2195
+ // 3. ref, className
2196
+ const menuItemProps = {
2197
+ role: isRadio ? "menuitemradio" : isCheckBox ? "menuitemcheckbox" : "menuitem",
2198
+ "aria-checked": isRadio || isCheckBox ? isChecked : undefined,
2199
+ ...mergedProps,
2200
+ ...commonProps(isDisabled, isHovering),
2201
+ ref: useCombinedRef(externalRef, menuItemRef),
2202
+ className: useBEM({ block: menuClass, element: menuItemClass, modifiers, className }),
2203
+ children: useMemo(() => safeCall(children, modifiers), [children, modifiers]),
2588
2204
  };
2589
- if (getName(button.type) === "MenuButton") {
2590
- buttonProps.isOpen = isOpen;
2205
+ if (isAnchor) {
2206
+ return (jsx("li", { role: "presentation", children: jsx("a", { href: href, ...menuItemProps }) }));
2591
2207
  }
2592
- const renderButton = cloneElement(button, buttonProps);
2593
- useMenuChange(onMenuChange, isOpen);
2594
- useImperativeHandle(instanceRef, () => ({
2595
- openMenu,
2596
- closeMenu: () => toggleMenu(false),
2597
- }));
2598
- return (jsxs(Fragment$1, { children: [renderButton, jsx(ControlledMenu, { ...restProps, ...stateProps, "aria-label": ariaLabel || (typeof button.props.children === "string" ? button.props.children : "Menu"), anchorRef: buttonRef, ref: externalRef, onClose: handleClose, skipOpen: skipOpen })] }));
2599
- }
2600
- const Menu = forwardRef(MenuFr);
2208
+ else {
2209
+ return jsx("li", { ...menuItemProps });
2210
+ }
2211
+ };
2212
+ const MenuItem = withHovering("MenuItem", MenuItemFr);
2601
2213
 
2602
- const SubMenuFr = ({ "aria-label": ariaLabel, className, disabled, direction, label, openTrigger, onMenuChange, isHovering, instanceRef, menuItemRef, itemProps = {}, ...restProps }) => {
2603
- const settings = useContext(SettingsContext);
2604
- const { rootMenuRef } = settings;
2605
- const { submenuOpenDelay, submenuCloseDelay } = useContext(ItemSettingsContext);
2606
- const { parentMenuRef, parentDir, overflow: parentOverflow } = useContext(MenuListContext);
2607
- const { isParentOpen, isSubmenuOpen, setOpenSubmenuCount, dispatch, updateItems } = useContext(MenuListItemContext);
2608
- const isPortal = parentOverflow !== "visible";
2609
- const [stateProps, toggleMenu, _openMenu] = useMenuStateAndFocus(settings);
2610
- const { state } = stateProps;
2214
+ const FocusableItemFr = ({ className, disabled, children, isHovering, menuItemRef, externalRef, ...restProps }) => {
2611
2215
  const isDisabled = !!disabled;
2612
- const isOpen = isMenuOpen(state);
2613
- const containerRef = useRef(null);
2614
- const timeoutId = useRef();
2615
- const stopTimer = () => {
2616
- if (timeoutId.current) {
2617
- clearTimeout(timeoutId.current);
2618
- timeoutId.current = undefined;
2216
+ const ref = useRef(null);
2217
+ const { setHover, onPointerLeave, ...restStateProps } = useItemState(menuItemRef, ref, isHovering, isDisabled);
2218
+ const { handleClose } = useContext(EventHandlersContext);
2219
+ const modifiers = useMemo(() => ({
2220
+ disabled: isDisabled,
2221
+ hover: isHovering,
2222
+ focusable: true,
2223
+ }), [isDisabled, isHovering]);
2224
+ const renderChildren = useMemo(() => safeCall(children, {
2225
+ ...modifiers,
2226
+ ref,
2227
+ closeMenu: handleClose,
2228
+ }), [children, modifiers, handleClose]);
2229
+ const mergedProps = mergeProps({
2230
+ ...restStateProps,
2231
+ onPointerLeave: (e) => onPointerLeave(e, true),
2232
+ onFocus: setHover,
2233
+ }, restProps);
2234
+ return (jsx("li", { role: "menuitem", ...mergedProps, ...commonProps(isDisabled), ref: useCombinedRef(externalRef, menuItemRef), className: useBEM({ block: menuClass, element: menuItemClass, modifiers, className }), children: renderChildren }));
2235
+ };
2236
+ const FocusableItem = withHovering("FocusableItem", FocusableItemFr);
2237
+
2238
+ const MenuDividerFr = ({ className, ...restProps }, externalRef) => {
2239
+ return (jsx("li", { role: "separator", ...restProps, ref: externalRef, className: useBEM({ block: menuClass, element: menuDividerClass, className }) }));
2240
+ };
2241
+ const MenuDivider = memo(forwardRef(MenuDividerFr));
2242
+
2243
+ const MenuHeaderFr = ({ className, ...restProps }, externalRef) => {
2244
+ return (jsx("li", { role: "presentation", ...restProps, ref: externalRef, className: useBEM({ block: menuClass, element: menuHeaderClass, className }) }));
2245
+ };
2246
+ const MenuHeader = memo(forwardRef(MenuHeaderFr));
2247
+
2248
+ const MenuGroupFr = ({ className, style, takeOverflow, ...restProps }, externalRef) => {
2249
+ const ref = useRef(null);
2250
+ const [overflowStyle, setOverflowStyle] = useState();
2251
+ const { overflow, overflowAmt } = useContext(MenuListContext);
2252
+ useIsomorphicLayoutEffect(() => {
2253
+ let maxHeight;
2254
+ if (takeOverflow && overflowAmt != null && overflowAmt >= 0 && ref.current) {
2255
+ maxHeight = ref.current.getBoundingClientRect().height - overflowAmt;
2256
+ if (maxHeight < 0)
2257
+ maxHeight = 0;
2619
2258
  }
2259
+ setOverflowStyle(maxHeight != null && maxHeight >= 0 ? { maxHeight, overflow } : undefined);
2260
+ }, [takeOverflow, overflow, overflowAmt]);
2261
+ useIsomorphicLayoutEffect(() => {
2262
+ if (overflowStyle && ref.current)
2263
+ ref.current.scrollTop = 0;
2264
+ }, [overflowStyle]);
2265
+ return (jsx("div", { ...restProps, ref: useCombinedRef(externalRef, ref), className: useBEM({ block: menuClass, element: menuGroupClass, className }), style: { ...style, ...overflowStyle } }));
2266
+ };
2267
+ const MenuGroup = forwardRef(MenuGroupFr);
2268
+
2269
+ const MenuRadioGroupFr = ({ "aria-label": ariaLabel, className, name, value, onRadioChange, ...restProps }, externalRef) => {
2270
+ const contextValue = useMemo(() => ({ name, value, onRadioChange }), [name, value, onRadioChange]);
2271
+ return (jsx(RadioGroupContext.Provider, { value: contextValue, children: jsx("li", { role: "presentation", children: jsx("ul", { role: "group", "aria-label": ariaLabel || name || "Radio group", ...restProps, ref: externalRef, className: useBEM({ block: menuClass, element: radioGroupClass, className }) }) }) }));
2272
+ };
2273
+ const MenuRadioGroup = forwardRef(MenuRadioGroupFr);
2274
+
2275
+ const useGridContextMenu = ({ contextMenuSelectRow, contextMenu: ContextMenu, }) => {
2276
+ const { redrawRows, prePopupOps, postPopupOps, getSelectedRows } = useContext(GridContext);
2277
+ const [isOpen, setOpen] = useState(false);
2278
+ const [anchorPoint, setAnchorPoint] = useState({ x: 0, y: 0 });
2279
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
2280
+ const clickedColDefRef = useRef(null);
2281
+ const selectedRowsRef = useRef([]);
2282
+ const clickedRowRef = useRef(null);
2283
+ const openMenu = useCallback((e) => {
2284
+ if (!e)
2285
+ return;
2286
+ prePopupOps();
2287
+ setAnchorPoint({ x: e.clientX, y: e.clientY });
2288
+ setOpen(true);
2289
+ }, [prePopupOps]);
2290
+ const closeMenu = useCallback(() => {
2291
+ setOpen(false);
2292
+ redrawRows();
2293
+ postPopupOps();
2294
+ }, [postPopupOps, redrawRows]);
2295
+ const cellContextMenu = useCallback((event) => {
2296
+ if (contextMenuSelectRow && !event.node.isSelected()) {
2297
+ event.node.setSelected(true, true);
2298
+ }
2299
+ clickedColDefRef.current = event.colDef;
2300
+ selectedRowsRef.current = getSelectedRows();
2301
+ clickedRowRef.current = event.data;
2302
+ // This is actually a pointer event
2303
+ openMenu(event.event);
2304
+ }, [contextMenuSelectRow, getSelectedRows, openMenu]);
2305
+ return {
2306
+ openMenu,
2307
+ cellContextMenu,
2308
+ component: ContextMenu ? (jsx(Fragment$1, { children: jsx(ControlledMenu, { anchorPoint: anchorPoint, state: isOpen ? "open" : "closed", direction: "right", onClose: closeMenu, children: isOpen && (jsx(ContextMenu, { selectedRows: selectedRowsRef.current, clickedRow: clickedRowRef.current, colDef: clickedColDefRef.current, close: closeMenu })) }) })) : null,
2620
2309
  };
2621
- const openMenu = (...args) => {
2622
- stopTimer();
2623
- setHover();
2624
- !isDisabled && _openMenu(...args);
2625
- };
2626
- const setHover = () => !isHovering && !isDisabled && dispatch(HoverActionTypes.SET, menuItemRef?.current, 0);
2627
- const delayOpen = (delay) => {
2628
- setHover();
2629
- if (!openTrigger)
2630
- timeoutId.current = setTimeout(() => batchedUpdates(openMenu), Math.max(delay, 0));
2631
- };
2632
- const handlePointerMove = () => {
2633
- if (timeoutId.current || isOpen || isDisabled)
2310
+ };
2311
+
2312
+ /**
2313
+ * Wrapper for AgGrid to add commonly used functionality.
2314
+ */
2315
+ const Grid = ({ "data-testid": dataTestId, rowSelection = "multiple", suppressColumnVirtualization = true, theme = "ag-theme-alpine", sizeColumns = "auto", selectColumnPinned = null, contextMenuSelectRow = false, ...params }) => {
2316
+ const { gridReady, setApis, ensureRowVisible, getFirstRowId, selectRowsById, focusByRowById, ensureSelectedRowIsVisible, autoSizeColumns, sizeColumnsToFit, externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync, isExternalFilterPresent, doesExternalFilterPass, setOnCellEditingComplete, getColDef, } = useContext(GridContext);
2317
+ const { checkUpdating, updatedDep, updatingCols } = useContext(GridUpdatingContext);
2318
+ const { prePopupOps } = useContext(GridContext);
2319
+ const gridDivRef = useRef(null);
2320
+ const lastSelectedIds = useRef([]);
2321
+ const [staleGrid, setStaleGrid] = useState(false);
2322
+ const postSortRows = usePostSortRowsHook({ setStaleGrid });
2323
+ /**
2324
+ * onContentSize should only be called at maximum twice.
2325
+ * Once when an empty grid is loaded.
2326
+ * And again when the grid has content.
2327
+ */
2328
+ const hasSetContentSize = useRef(false);
2329
+ const hasSetContentSizeEmpty = useRef(false);
2330
+ const needsAutoSize = useRef(false);
2331
+ const setInitialContentSize = useCallback(() => {
2332
+ if (!gridDivRef.current?.clientWidth) {
2333
+ // Don't resize grids if they are offscreen as it doesn't work.
2334
+ needsAutoSize.current = true;
2634
2335
  return;
2635
- if (isSubmenuOpen) {
2636
- timeoutId.current = setTimeout(() => delayOpen(submenuOpenDelay - submenuCloseDelay), submenuCloseDelay);
2637
2336
  }
2638
- else {
2639
- delayOpen(submenuOpenDelay);
2337
+ const headerCellCount = gridDivRef.current?.getElementsByClassName("ag-header-cell-label")?.length;
2338
+ if (headerCellCount < 2) {
2339
+ // Don't resize grids until all the columns are visible
2340
+ // as `autoSizeColumns` will fail silently in this case
2341
+ needsAutoSize.current = true;
2342
+ return;
2640
2343
  }
2641
- };
2642
- const handlePointerLeave = () => {
2643
- stopTimer();
2644
- if (!isOpen)
2645
- dispatch(HoverActionTypes.UNSET, menuItemRef?.current, 0);
2646
- };
2647
- const handleKeyDown = (e) => {
2648
- let handled = false;
2649
- switch (e.key) {
2650
- // LEFT key is bubbled up from submenu items
2651
- case Keys.LEFT:
2652
- if (isOpen) {
2653
- menuItemRef?.current && menuItemRef.current.focus();
2654
- toggleMenu(false);
2655
- handled = true;
2344
+ const skipHeader = sizeColumns === "auto-skip-headers" && !isEmpty(params.rowData);
2345
+ if (sizeColumns === "auto" || skipHeader) {
2346
+ const result = autoSizeColumns({ skipHeader, userSizedColIds: userSizedColIds.current });
2347
+ if (isEmpty(params.rowData)) {
2348
+ if (!hasSetContentSizeEmpty.current && result && !hasSetContentSize.current) {
2349
+ hasSetContentSizeEmpty.current = true;
2350
+ params.onContentSize && params.onContentSize(result);
2351
+ }
2352
+ }
2353
+ else {
2354
+ if (result && !hasSetContentSize.current) {
2355
+ hasSetContentSize.current = true;
2356
+ params.onContentSize && params.onContentSize(result);
2357
+ }
2358
+ }
2359
+ }
2360
+ if (sizeColumns !== "none") {
2361
+ sizeColumnsToFit();
2362
+ }
2363
+ needsAutoSize.current = false;
2364
+ }, [autoSizeColumns, params, sizeColumns, sizeColumnsToFit]);
2365
+ const lastOwnerDocumentRef = useRef();
2366
+ /**
2367
+ * Auto-size windows that had deferred auto-size
2368
+ */
2369
+ useIntervalHook({
2370
+ callback: () => {
2371
+ // Check if window has been popped out and needs resize
2372
+ const currentDocument = gridDivRef.current?.ownerDocument;
2373
+ if (currentDocument !== lastOwnerDocumentRef.current) {
2374
+ lastOwnerDocumentRef.current = currentDocument;
2375
+ if (currentDocument) {
2376
+ needsAutoSize.current = true;
2656
2377
  }
2657
- break;
2658
- // prevent browser from scrolling page to the right
2659
- case Keys.RIGHT:
2660
- if (!isOpen)
2661
- handled = true;
2662
- break;
2378
+ }
2379
+ if (needsAutoSize.current) {
2380
+ needsAutoSize.current = false;
2381
+ setInitialContentSize();
2382
+ }
2383
+ },
2384
+ timeoutMs: 1000,
2385
+ });
2386
+ const previousGridReady = useRef(gridReady);
2387
+ useEffect(() => {
2388
+ if (!previousGridReady.current && gridReady) {
2389
+ previousGridReady.current = true;
2390
+ setInitialContentSize();
2663
2391
  }
2664
- if (handled) {
2665
- e.preventDefault();
2666
- e.stopPropagation();
2392
+ }, [gridReady, setInitialContentSize]);
2393
+ /**
2394
+ * On data load select the first row of the grid if required.
2395
+ */
2396
+ const hasSelectedFirstItem = useRef(false);
2397
+ useEffect(() => {
2398
+ if (!gridReady || hasSelectedFirstItem.current || !params.rowData || !externallySelectedItemsAreInSync)
2399
+ return;
2400
+ hasSelectedFirstItem.current = true;
2401
+ if (isNotEmpty(params.rowData) && isEmpty(params.externalSelectedItems)) {
2402
+ const firstRowId = getFirstRowId();
2403
+ if (params.autoSelectFirstRow) {
2404
+ selectRowsById([firstRowId]);
2405
+ }
2406
+ else {
2407
+ focusByRowById(firstRowId);
2408
+ }
2667
2409
  }
2668
- };
2669
- const handleItemKeyDown = (e) => {
2670
- if (!isHovering)
2410
+ }, [
2411
+ externallySelectedItemsAreInSync,
2412
+ focusByRowById,
2413
+ gridReady,
2414
+ params.externalSelectedItems,
2415
+ params.autoSelectFirstRow,
2416
+ params.rowData,
2417
+ selectRowsById,
2418
+ getFirstRowId,
2419
+ ]);
2420
+ /**
2421
+ * AgGrid checkbox select does not pass clicks within cell but not on the checkbox to checkbox.
2422
+ * This passes the event to the checkbox when you click anywhere in the cell.
2423
+ */
2424
+ const clickSelectorCheckboxWhenContainingCellClicked = useCallback(({ event }) => {
2425
+ if (!event)
2426
+ return;
2427
+ const input = event.target.querySelector("input");
2428
+ input?.dispatchEvent(event);
2429
+ }, []);
2430
+ /**
2431
+ * Ensure external selected items list is in sync with panel.
2432
+ */
2433
+ const synchroniseExternalStateToGridSelection = useCallback(({ api }) => {
2434
+ if (!params.externalSelectedItems || !params.setExternalSelectedItems) {
2435
+ setExternallySelectedItemsAreInSync(true);
2671
2436
  return;
2672
- switch (e.key) {
2673
- case Keys.ENTER:
2674
- case Keys.SPACE:
2675
- case Keys.RIGHT:
2676
- openTrigger !== "none" && openMenu(FocusPositions.FIRST);
2677
- break;
2678
2437
  }
2679
- };
2680
- useItemEffect(isDisabled, menuItemRef, updateItems);
2681
- useMenuChange(onMenuChange, isOpen);
2682
- useEffect(() => () => clearTimeout(timeoutId.current), []);
2683
- useEffect(() => {
2684
- // Don't set focus when parent menu is closed, otherwise focus will be lost
2685
- // and onBlur event will be fired with relatedTarget setting as null.
2686
- if (isHovering && isParentOpen) {
2687
- menuItemRef?.current && menuItemRef.current.focus();
2438
+ const selectedRows = api.getSelectedRows();
2439
+ // We don't want to update selected Items if it hasn't changed to prevent excess renders
2440
+ if (params.externalSelectedItems.length != selectedRows.length ||
2441
+ isNotEmpty(xorBy(selectedRows, params.externalSelectedItems, (row) => row.id))) {
2442
+ setExternallySelectedItemsAreInSync(false);
2443
+ params.setExternalSelectedItems([...selectedRows]);
2688
2444
  }
2689
2445
  else {
2690
- toggleMenu(false);
2446
+ setExternallySelectedItemsAreInSync(true);
2691
2447
  }
2692
- }, [isHovering, isParentOpen, toggleMenu, menuItemRef]);
2693
- useEffect(() => {
2694
- setOpenSubmenuCount((count) => (isOpen ? count + 1 : Math.max(count - 1, 0)));
2695
- }, [setOpenSubmenuCount, isOpen]);
2696
- useImperativeHandle(instanceRef, () => ({
2697
- openMenu: (...args) => {
2698
- isParentOpen && openMenu(...args);
2699
- },
2700
- closeMenu: () => {
2701
- if (isOpen) {
2702
- menuItemRef?.current && menuItemRef.current.focus();
2703
- toggleMenu(false);
2704
- }
2705
- },
2706
- }));
2707
- const modifiers = useMemo(() => ({
2708
- open: isOpen,
2709
- hover: isHovering,
2710
- disabled: isDisabled,
2711
- submenu: true,
2712
- }), [isOpen, isHovering, isDisabled]);
2713
- const { ref: externalItemRef, className: itemClassName, ...restItemProps } = itemProps;
2714
- const mergedItemProps = mergeProps({
2715
- onPointerMove: handlePointerMove,
2716
- onPointerLeave: handlePointerLeave,
2717
- onKeyDown: handleItemKeyDown,
2718
- onClick: () => openTrigger !== "none" && openMenu(),
2719
- }, restItemProps);
2720
- const getMenuList = () => {
2721
- const menuList = (jsx(MenuList, { ...restProps, ...stateProps, ariaLabel: ariaLabel || (typeof label === "string" ? label : "Submenu"), anchorRef: menuItemRef, containerRef: isPortal ? rootMenuRef : containerRef, direction: direction || (parentDir === "right" || parentDir === "left" ? parentDir : "right"), parentScrollingRef: isPortal && parentMenuRef, isDisabled: isDisabled }));
2722
- const container = rootMenuRef?.current;
2723
- return isPortal && container ? createPortal(menuList, container) : menuList;
2448
+ }, [params, setExternallySelectedItemsAreInSync]);
2449
+ /**
2450
+ * Synchronise externally selected items to grid.
2451
+ * If new ids are selected scroll them into view.
2452
+ */
2453
+ const synchroniseExternallySelectedItemsToGrid = useCallback(() => {
2454
+ if (!gridReady)
2455
+ return;
2456
+ if (!params.externalSelectedItems) {
2457
+ setExternallySelectedItemsAreInSync(true);
2458
+ return;
2459
+ }
2460
+ const selectedIds = params.externalSelectedItems.map((row) => row.id);
2461
+ const lastNewId = last(difference(selectedIds, lastSelectedIds.current));
2462
+ if (lastNewId != null) {
2463
+ ensureRowVisible(lastNewId);
2464
+ }
2465
+ lastSelectedIds.current = selectedIds;
2466
+ selectRowsById(selectedIds);
2467
+ setExternallySelectedItemsAreInSync(true);
2468
+ }, [gridReady, params.externalSelectedItems, ensureRowVisible, selectRowsById, setExternallySelectedItemsAreInSync]);
2469
+ /**
2470
+ * Combine grid and cell editable into one function
2471
+ */
2472
+ const combineEditables = (...editables) => (params) => {
2473
+ const results = editables.map((editable) => fnOrVar(editable, params));
2474
+ // If editable is not set anywhere then it's non-editable
2475
+ if (results.every((v) => v == null))
2476
+ return false;
2477
+ // If any editable value is or returns false then it's non-editable
2478
+ return !results.some((v) => v == false);
2724
2479
  };
2725
- return (jsxs("li", { className: useBEM({ block: menuClass, element: subMenuClass, className }), style: { position: "relative" }, role: "presentation", ref: containerRef, onKeyDown: handleKeyDown, children: [jsx("div", { role: "menuitem", "aria-haspopup": true, "aria-expanded": isOpen, ...mergedItemProps, ...commonProps(isDisabled, isHovering), ref: useCombinedRef(externalItemRef, menuItemRef), className: useBEM({
2726
- block: menuClass,
2727
- element: menuItemClass,
2728
- modifiers,
2729
- className: itemClassName,
2730
- }), children: useMemo(() => safeCall(label, modifiers), [label, modifiers]) }), state && getMenuList()] }));
2731
- };
2732
- const SubMenu = withHovering("SubMenu", SubMenuFr);
2733
-
2734
- const RadioGroupContext = createContext({});
2735
-
2736
- const MenuItemFr = ({ className, value, href, type, checked, disabled, children, onClick, isHovering, menuItemRef, externalRef, ...restProps }) => {
2737
- const isDisabled = !!disabled;
2738
- const { setHover, ...restStateProps } = useItemState(menuItemRef, menuItemRef, isHovering, isDisabled);
2739
- const eventHandlers = useContext(EventHandlersContext);
2740
- const radioGroup = useContext(RadioGroupContext);
2741
- const isRadio = type === "radio";
2742
- const isCheckBox = type === "checkbox";
2743
- const isAnchor = !!href && !isDisabled && !isRadio && !isCheckBox;
2744
- const isChecked = isRadio ? radioGroup.value === value : isCheckBox ? !!checked : false;
2745
- // handle click seems to be a combination of multiple event types, bad code.
2746
- const handleClick = (e) => {
2747
- if (isDisabled) {
2748
- if (e.syntheticEvent) {
2749
- e.syntheticEvent.stopPropagation();
2750
- e.syntheticEvent.preventDefault();
2480
+ /**
2481
+ * Synchronise externally selected items to grid on externalSelectedItems change
2482
+ */
2483
+ useEffect(synchroniseExternallySelectedItemsToGrid, [synchroniseExternallySelectedItemsToGrid]);
2484
+ /**
2485
+ * Add selectable column to colDefs. Adjust column defs to block fit for auto sized columns.
2486
+ */
2487
+ const columnDefs = useMemo(() => {
2488
+ const adjustColDefs = params.columnDefs.map((colDef) => {
2489
+ const colDefEditable = colDef.editable;
2490
+ const editable = combineEditables(params.readOnly !== true, params.defaultColDef?.editable, colDefEditable);
2491
+ return {
2492
+ ...colDef,
2493
+ editable,
2494
+ cellClassRules: {
2495
+ ...colDef.cellClassRules,
2496
+ "GridCell-readonly": (ccp) => !editable(ccp),
2497
+ },
2498
+ };
2499
+ });
2500
+ return params.selectable
2501
+ ? [
2502
+ {
2503
+ colId: "selection",
2504
+ editable: false,
2505
+ minWidth: 42,
2506
+ maxWidth: 42,
2507
+ pinned: selectColumnPinned,
2508
+ headerComponentParams: {
2509
+ exportable: false,
2510
+ },
2511
+ checkboxSelection: true,
2512
+ headerComponent: rowSelection === "multiple" ? GridHeaderSelect : null,
2513
+ suppressHeaderKeyboardEvent: (e) => {
2514
+ if ((e.event.key === "Enter" || e.event.key === " ") && !e.event.repeat) {
2515
+ if (isEmpty(e.api.getSelectedRows())) {
2516
+ e.api.selectAllFiltered();
2517
+ }
2518
+ else {
2519
+ e.api.deselectAll();
2520
+ }
2521
+ return true;
2522
+ }
2523
+ return false;
2524
+ },
2525
+ onCellClicked: clickSelectorCheckboxWhenContainingCellClicked,
2526
+ },
2527
+ ...adjustColDefs,
2528
+ ]
2529
+ : adjustColDefs;
2530
+ }, [
2531
+ params.columnDefs,
2532
+ params.selectable,
2533
+ params.readOnly,
2534
+ params.defaultColDef?.editable,
2535
+ selectColumnPinned,
2536
+ rowSelection,
2537
+ clickSelectorCheckboxWhenContainingCellClicked,
2538
+ ]);
2539
+ /**
2540
+ * When grid is ready set the apis to the grid context and sync selected items to grid.
2541
+ */
2542
+ const onGridReady = useCallback((event) => {
2543
+ setApis(event.api, event.columnApi, dataTestId);
2544
+ synchroniseExternallySelectedItemsToGrid();
2545
+ }, [dataTestId, setApis, synchroniseExternallySelectedItemsToGrid]);
2546
+ /**
2547
+ * When the grid is being initialized the data may be empty.
2548
+ * This will resize columns when we have at least one row.
2549
+ */
2550
+ const previousRowDataLength = useRef(0);
2551
+ const onRowDataChanged = useCallback(() => {
2552
+ const length = params.rowData?.length ?? 0;
2553
+ if (previousRowDataLength.current !== length) {
2554
+ setInitialContentSize();
2555
+ previousRowDataLength.current = length;
2556
+ }
2557
+ if (lastUpdatedDep.current === updatedDep || isEmpty(colIdsEdited.current))
2558
+ return;
2559
+ lastUpdatedDep.current = updatedDep;
2560
+ // Don't update while there are spinners
2561
+ if (!isEmpty(updatingCols()))
2562
+ return;
2563
+ const skipHeader = sizeColumns === "auto-skip-headers";
2564
+ autoSizeColumns({ skipHeader, userSizedColIds: userSizedColIds.current, colIds: colIdsEdited.current });
2565
+ colIdsEdited.current.clear();
2566
+ }, [autoSizeColumns, params.rowData?.length, setInitialContentSize, sizeColumns, updatedDep, updatingCols]);
2567
+ /**
2568
+ * Show/hide no rows overlay when model changes.
2569
+ */
2570
+ const isShowingNoRowsOverlay = useRef(false);
2571
+ const onModelUpdated = useCallback((event) => {
2572
+ if (event.api.getDisplayedRowCount() === 0) {
2573
+ if (!isShowingNoRowsOverlay.current) {
2574
+ event.api.showNoRowsOverlay();
2575
+ isShowingNoRowsOverlay.current = true;
2751
2576
  }
2752
- else {
2753
- e.stopPropagation();
2754
- e.preventDefault();
2577
+ }
2578
+ else {
2579
+ if (isShowingNoRowsOverlay.current) {
2580
+ event.api.hideOverlay();
2581
+ isShowingNoRowsOverlay.current = false;
2755
2582
  }
2583
+ }
2584
+ }, []);
2585
+ /**
2586
+ * Force-refresh all selected rows to re-run class function, to update selection highlighting
2587
+ */
2588
+ const refreshSelectedRows = useCallback((event) => {
2589
+ event.api.refreshCells({
2590
+ force: true,
2591
+ rowNodes: event.api.getSelectedNodes(),
2592
+ });
2593
+ }, []);
2594
+ /**
2595
+ * Make sure node is selected for editing and start edit
2596
+ */
2597
+ const startCellEditing = useCallback((event) => {
2598
+ prePopupOps();
2599
+ if (!event.node.isSelected()) {
2600
+ event.node.setSelected(true, true);
2601
+ }
2602
+ // Cell already being edited, so don't re-edit until finished
2603
+ if (checkUpdating([event.colDef.field ?? ""], event.data.id)) {
2756
2604
  return;
2757
2605
  }
2758
- const event = {
2759
- value,
2760
- syntheticEvent: e,
2761
- };
2762
- if (e.key !== undefined) {
2763
- const ke = e;
2764
- event.key = ke.key;
2765
- event.shiftKey = ke.shiftKey;
2606
+ if (event.rowIndex !== null) {
2607
+ event.api.startEditingCell({
2608
+ rowIndex: event.rowIndex,
2609
+ colKey: event.column.getColId(),
2610
+ });
2766
2611
  }
2767
- if (isCheckBox)
2768
- event.checked = !isChecked;
2769
- if (isRadio)
2770
- event.name = radioGroup.name;
2771
- safeCall(onClick, event);
2772
- if (isRadio)
2773
- safeCall(radioGroup.onRadioChange, event);
2774
- eventHandlers.handleClick(event, isCheckBox || isRadio);
2775
- };
2776
- const handleKeyDown = (e) => {
2777
- // if tab is allowed the handleKeyUp event can't process the tab
2778
- if (e.key === "Tab") {
2779
- e.preventDefault();
2780
- e.stopPropagation();
2612
+ }, [checkUpdating, prePopupOps]);
2613
+ /**
2614
+ * Handle double click edit
2615
+ */
2616
+ const onCellDoubleClick = useCallback((event) => {
2617
+ if (!invokeEditAction(event))
2618
+ startCellEditing(event);
2619
+ }, [startCellEditing]);
2620
+ /**
2621
+ * Handle single click edits
2622
+ */
2623
+ const onCellClicked = useCallback((event) => {
2624
+ if (event.colDef?.cellRendererParams?.singleClickEdit) {
2625
+ startCellEditing(event);
2626
+ }
2627
+ }, [startCellEditing]);
2628
+ /**
2629
+ * If cell has an edit action invoke it (if editable)
2630
+ */
2631
+ const invokeEditAction = (e) => {
2632
+ const editAction = e.colDef?.cellRendererParams?.editAction;
2633
+ if (!editAction)
2634
+ return false;
2635
+ const editable = fnOrVar(e.colDef?.editable, e);
2636
+ if (editable) {
2637
+ if (!e.node.isSelected()) {
2638
+ e.node.setSelected(true, true);
2639
+ }
2640
+ editAction([e.data, ...e.api.getSelectedRows().filter((row) => row.id !== e.data.id)]);
2781
2641
  }
2642
+ return true;
2782
2643
  };
2783
2644
  /**
2784
- * Keyboard events are triggered on up, otherwise subcomponents get spaces and enters typed in them
2645
+ * Start editing on pressing Enter
2785
2646
  */
2786
- const handleKeyUp = (e) => {
2787
- if (!isHovering)
2647
+ const onCellKeyPress = useCallback((e) => {
2648
+ if (e.event.key === "Enter") {
2649
+ if (!invokeEditAction(e))
2650
+ startCellEditing(e);
2651
+ }
2652
+ }, [startCellEditing]);
2653
+ /**
2654
+ * Once the grid has auto-sized we want to run fit to fit the grid in its container,
2655
+ * but we don't want the non-flex auto-sized columns to "fit" size, so suppressSizeToFit is set to true.
2656
+ */
2657
+ const columnDefsAdjusted = useMemo(() => {
2658
+ const adjustColDefOrGroup = (colDef) => "children" in colDef ? adjustGroupColDef(colDef) : adjustColDef(colDef);
2659
+ const adjustGroupColDef = (colDef) => ({
2660
+ ...colDef,
2661
+ children: colDef.children.map((colDef) => adjustColDefOrGroup(colDef)),
2662
+ });
2663
+ const adjustColDef = (colDef) => ({
2664
+ ...colDef,
2665
+ suppressSizeToFit: (sizeColumns === "auto" || sizeColumns === "auto-skip-headers") && !colDef.flex,
2666
+ sortable: colDef.sortable && params.defaultColDef?.sortable !== false,
2667
+ });
2668
+ return columnDefs.map((colDef) => adjustColDefOrGroup(colDef));
2669
+ }, [columnDefs, params.defaultColDef?.sortable, sizeColumns]);
2670
+ /**
2671
+ * Set of colIds that need auto-sizing.
2672
+ */
2673
+ const colIdsEdited = useRef(new Set());
2674
+ const lastUpdatedDep = useRef(updatedDep);
2675
+ /**
2676
+ * When cell editing has completed the colId as needing auto-sizing
2677
+ */
2678
+ useEffect(() => {
2679
+ if (lastUpdatedDep.current === updatedDep)
2788
2680
  return;
2789
- switch (e.key) {
2790
- case Keys.ENTER:
2791
- case Keys.TAB:
2792
- case Keys.SPACE:
2793
- e.preventDefault();
2794
- e.stopPropagation();
2795
- if (isAnchor) {
2796
- menuItemRef?.current && menuItemRef.current.click();
2681
+ lastUpdatedDep.current = updatedDep;
2682
+ const colIds = updatingCols();
2683
+ // Updating possibly completed
2684
+ if (isEmpty(colIds)) {
2685
+ // Columns to resize?
2686
+ if (!isEmpty(colIdsEdited.current)) {
2687
+ const skipHeader = sizeColumns === "auto-skip-headers";
2688
+ if (sizeColumns === "auto" || skipHeader) {
2689
+ defer$1(() => autoSizeColumns({
2690
+ skipHeader,
2691
+ userSizedColIds: userSizedColIds.current,
2692
+ colIds: colIdsEdited.current,
2693
+ }));
2797
2694
  }
2798
- else {
2799
- handleClick(e);
2695
+ colIdsEdited.current.clear();
2696
+ }
2697
+ }
2698
+ else {
2699
+ // Updates not complete, add them to a list of columns needing resize
2700
+ colIds.forEach((colId) => {
2701
+ if (colId && !getColDef(colId)?.flex) {
2702
+ colIdsEdited.current.add(colId);
2800
2703
  }
2704
+ });
2705
+ }
2706
+ }, [autoSizeColumns, getColDef, sizeColumns, updatedDep, updatingCols]);
2707
+ /**
2708
+ * Resize columns to fit if required on window/container resize
2709
+ */
2710
+ const onGridSizeChanged = useCallback(() => {
2711
+ if (sizeColumns !== "none") {
2712
+ sizeColumnsToFit();
2713
+ }
2714
+ }, [sizeColumns, sizeColumnsToFit]);
2715
+ /**
2716
+ * Set of column Id's that are prevented from auto-sizing as they are user set
2717
+ */
2718
+ const userSizedColIds = useRef(new Set());
2719
+ /**
2720
+ * Lock/unlock column width on user edit/reset.
2721
+ */
2722
+ const onColumnResized = useCallback((e) => {
2723
+ const colId = e.column?.getColId();
2724
+ if (colId == null)
2725
+ return;
2726
+ switch (e.source) {
2727
+ case "uiColumnDragged":
2728
+ userSizedColIds.current.add(colId);
2729
+ break;
2730
+ case "autosizeColumns":
2731
+ userSizedColIds.current.delete(colId);
2801
2732
  break;
2802
2733
  }
2803
- };
2804
- const modifiers = useMemo(() => ({
2805
- type,
2806
- disabled: isDisabled,
2807
- hover: isHovering,
2808
- checked: isChecked,
2809
- anchor: isAnchor,
2810
- }), [type, isDisabled, isHovering, isChecked, isAnchor]);
2811
- const mergedProps = mergeProps({
2812
- ...restStateProps,
2813
- onPointerDown: setHover,
2814
- onKeyDown: handleKeyDown,
2815
- onKeyUp: handleKeyUp,
2816
- onClick: handleClick,
2817
- }, restProps);
2818
- // Order of props overriding (same in all components):
2819
- // 1. Preset props adhering to WAI-ARIA Authoring Practices.
2820
- // 2. Merged outer and local props
2821
- // 3. ref, className
2822
- const menuItemProps = {
2823
- role: isRadio ? "menuitemradio" : isCheckBox ? "menuitemcheckbox" : "menuitem",
2824
- "aria-checked": isRadio || isCheckBox ? isChecked : undefined,
2825
- ...mergedProps,
2826
- ...commonProps(isDisabled, isHovering),
2827
- ref: useCombinedRef(externalRef, menuItemRef),
2828
- className: useBEM({ block: menuClass, element: menuItemClass, modifiers, className }),
2829
- children: useMemo(() => safeCall(children, modifiers), [children, modifiers]),
2830
- };
2831
- if (isAnchor) {
2832
- return (jsx("li", { role: "presentation", children: jsx("a", { href: href, ...menuItemProps }) }));
2833
- }
2834
- else {
2835
- return jsx("li", { ...menuItemProps });
2734
+ }, []);
2735
+ const gridContextMenu = useGridContextMenu({ contextMenu: params.contextMenu, contextMenuSelectRow });
2736
+ // This is setting a ref in the GridContext so won't be triggering an update loop
2737
+ setOnCellEditingComplete(params.onCellEditingComplete);
2738
+ return (jsxs("div", { "data-testid": dataTestId, className: clsx("Grid-container", theme, staleGrid && "Grid-sortIsStale", gridReady && params.rowData && "Grid-ready"), children: [gridContextMenu.component, jsx("div", { style: { flex: 1 }, ref: gridDivRef, children: jsx(AgGridReact, { rowHeight: params.rowHeight, animateRows: params.animateRows, rowClassRules: params.rowClassRules, getRowId: (params) => `${params.data.id}`, suppressRowClickSelection: true, rowSelection: rowSelection, suppressBrowserResizeObserver: true, onGridSizeChanged: onGridSizeChanged, suppressColumnVirtualisation: suppressColumnVirtualization, suppressClickEdit: true, onColumnVisible: () => {
2739
+ setInitialContentSize();
2740
+ }, onRowDataChanged: onRowDataChanged, onCellKeyPress: onCellKeyPress, onCellClicked: onCellClicked, onCellDoubleClicked: onCellDoubleClick, onCellEditingStarted: refreshSelectedRows, domLayout: params.domLayout, onColumnResized: onColumnResized, defaultColDef: { minWidth: 48, ...omit(params.defaultColDef, ["editable"]) }, columnDefs: columnDefsAdjusted, rowData: params.rowData, noRowsOverlayComponent: GridNoRowsOverlay, noRowsOverlayComponentParams: {
2741
+ rowData: params.rowData,
2742
+ noRowsOverlayText: params.noRowsOverlayText,
2743
+ }, onModelUpdated: onModelUpdated, onGridReady: onGridReady, onSortChanged: ensureSelectedRowIsVisible, postSortRows: params.postSortRows ?? postSortRows, onSelectionChanged: synchroniseExternalStateToGridSelection, onColumnMoved: params.onColumnMoved, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, isExternalFilterPresent: isExternalFilterPresent, doesExternalFilterPass: doesExternalFilterPass, maintainColumnOrder: true, preventDefaultOnContextMenu: true, onCellContextMenu: gridContextMenu.cellContextMenu }) })] }));
2744
+ };
2745
+
2746
+ const GridPopoverContext = createContext({
2747
+ anchorRef: { current: null },
2748
+ saving: false,
2749
+ setSaving: () => { },
2750
+ field: "",
2751
+ value: null,
2752
+ data: {},
2753
+ selectedRows: [],
2754
+ updateValue: async () => false,
2755
+ formatValue: () => "! No gridPopoverContextProvider !",
2756
+ });
2757
+ const useGridPopoverContext = () => useContext(GridPopoverContext);
2758
+
2759
+ const GridPopoverContextProvider = ({ props, children }) => {
2760
+ const { getFilteredSelectedRows, updatingCells } = useContext(GridContext);
2761
+ const anchorRef = useRef(props.eGridCell);
2762
+ const hasSaved = useRef(false);
2763
+ const [saving, setSaving] = useState(false);
2764
+ const { colDef } = props;
2765
+ const { cellEditorParams } = colDef;
2766
+ const multiEdit = cellEditorParams?.multiEdit ?? false;
2767
+ // Then item that is clicked on will always be first in the list
2768
+ const selectedRows = useMemo(() => multiEdit ? sortBy(getFilteredSelectedRows(), (row) => row.id !== props.data.id) : [props.data], [getFilteredSelectedRows, multiEdit, props.data]);
2769
+ const field = props.colDef?.field ?? "";
2770
+ const updateValue = useCallback(async (saveFn, tabDirection) => {
2771
+ if (hasSaved.current)
2772
+ return true;
2773
+ hasSaved.current = true;
2774
+ return saving ? false : await updatingCells({ selectedRows, field }, saveFn, setSaving, tabDirection);
2775
+ }, [field, saving, selectedRows, updatingCells]);
2776
+ return (jsx(GridPopoverContext.Provider, { value: {
2777
+ anchorRef: anchorRef,
2778
+ saving,
2779
+ setSaving,
2780
+ selectedRows,
2781
+ field,
2782
+ data: props.data,
2783
+ value: props.value,
2784
+ updateValue,
2785
+ formatValue: props.formatValue,
2786
+ }, children: children }));
2787
+ };
2788
+
2789
+ const GridCellMultiSelectClassRules = {
2790
+ "ag-selected-for-edit": (params) => {
2791
+ const { api, node, colDef } = params;
2792
+ const cep = colDef.cellEditorSelector
2793
+ ? colDef.cellEditorSelector(params)?.params
2794
+ : colDef.cellEditorParams;
2795
+ return (cep?.multiEdit &&
2796
+ api
2797
+ .getSelectedNodes()
2798
+ .map((row) => row.id)
2799
+ .includes(node.id) &&
2800
+ api.getEditingCells().some((cell) => cell.column.getColDef() === colDef));
2801
+ },
2802
+ };
2803
+
2804
+ const GridIcon = (props) => (jsx(LuiIcon, { name: props.icon, title: props.title, alt: props.title, size: props.size ?? "md", className: clsx(`AgGridGenericCellRenderer-${props.icon}Icon`, props.className, props.disabled && "GridIcon-disabled") }));
2805
+
2806
+ const GridLoadableCell = () => (jsx(LuiMiniSpinner, { size: 22, divProps: { className: "GridLoadableCell-container", role: "status", "aria-label": "Loading" } }));
2807
+
2808
+ const GridCellRenderer = (props) => {
2809
+ const { checkUpdating } = useContext(GridUpdatingContext);
2810
+ const colDef = props.colDef;
2811
+ const rendererParams = colDef.cellRendererParams;
2812
+ const warningFn = rendererParams?.warning;
2813
+ let warningText = warningFn ? warningFn(props) : undefined;
2814
+ const infoFn = rendererParams?.info;
2815
+ let infoText = infoFn ? infoFn(props) : undefined;
2816
+ if (Array.isArray(warningText))
2817
+ warningText = warningText.join("\n");
2818
+ if (Array.isArray(infoText))
2819
+ infoText = infoText.join("\n");
2820
+ return checkUpdating(colDef.field ?? colDef.colId ?? "", props.data.id) ? (jsx(GridLoadableCell, {})) : (jsxs(Fragment$1, { children: [!!warningText && (jsx(GridIcon, { icon: "ic_warning_outline", title: typeof warningText === "string" ? warningText : "Warning" })), !!infoText && jsx(GridIcon, { icon: "ic_info_outline", title: typeof infoText === "string" ? infoText : "Info" }), jsx("div", { className: "GridCell-container", children: colDef.cellRendererParams?.originalCellRenderer ? (jsx(colDef.cellRendererParams.originalCellRenderer, { ...props })) : (jsx("span", { title: props.valueFormatted ?? undefined, children: props.valueFormatted })) }), fnOrVar(colDef.editable, props) && rendererParams?.rightHoverElement && (jsx("div", { className: "GridCell-hoverRight", children: rendererParams?.rightHoverElement }))] }));
2821
+ };
2822
+ const suppressCellKeyboardEvents = (e) => {
2823
+ const shortcutKeys = e.colDef.cellRendererParams?.shortcutKeys ?? {};
2824
+ const exec = shortcutKeys[e.event.key];
2825
+ if (!e.editing && !e.event.repeat && e.event.type === "keypress" && exec) {
2826
+ const editable = fnOrVar(e.colDef?.editable, e);
2827
+ return editable ? exec(e) ?? true : true;
2836
2828
  }
2829
+ // It's important that aggrid doesn't trigger edit on enter
2830
+ // as the incorrect selected rows will be returned
2831
+ return !["ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp", "Tab", " ", "Home", "End", "PageUp", "PageDown"].includes(e.event.key);
2837
2832
  };
2838
- const MenuItem = withHovering("MenuItem", MenuItemFr);
2839
-
2840
- const FocusableItemFr = ({ className, disabled, children, isHovering, menuItemRef, externalRef, ...restProps }) => {
2841
- const isDisabled = !!disabled;
2842
- const ref = useRef(null);
2843
- const { setHover, onPointerLeave, ...restStateProps } = useItemState(menuItemRef, ref, isHovering, isDisabled);
2844
- const { handleClose } = useContext(EventHandlersContext);
2845
- const modifiers = useMemo(() => ({
2846
- disabled: isDisabled,
2847
- hover: isHovering,
2848
- focusable: true,
2849
- }), [isDisabled, isHovering]);
2850
- const renderChildren = useMemo(() => safeCall(children, {
2851
- ...modifiers,
2852
- ref,
2853
- closeMenu: handleClose,
2854
- }), [children, modifiers, handleClose]);
2855
- const mergedProps = mergeProps({
2856
- ...restStateProps,
2857
- onPointerLeave: (e) => onPointerLeave(e, true),
2858
- onFocus: setHover,
2859
- }, restProps);
2860
- return (jsx("li", { role: "menuitem", ...mergedProps, ...commonProps(isDisabled), ref: useCombinedRef(externalRef, menuItemRef), className: useBEM({ block: menuClass, element: menuItemClass, modifiers, className }), children: renderChildren }));
2833
+ const generateFilterGetter = (field, filterValueGetter, valueFormatter) => {
2834
+ if (filterValueGetter)
2835
+ return filterValueGetter;
2836
+ // aggrid will default to valueGetter
2837
+ if (typeof valueFormatter !== "function" || !field)
2838
+ return undefined;
2839
+ return (params) => {
2840
+ const value = params.getValue(field);
2841
+ let formattedValue = valueFormatter({ ...params, value });
2842
+ // Search for null values using standard dash
2843
+ if (formattedValue === "–")
2844
+ formattedValue += " -";
2845
+ // Search by raw value as well as formatted
2846
+ const gotValue = ["string", "number"].includes(typeof value) ? value : undefined;
2847
+ return (formattedValue + (gotValue != null && formattedValue != gotValue ? " " + gotValue : "")) //
2848
+ .replaceAll(/\s+/g, " ")
2849
+ .trim();
2850
+ };
2861
2851
  };
2862
- const FocusableItem = withHovering("FocusableItem", FocusableItemFr);
2863
-
2864
- const MenuDividerFr = ({ className, ...restProps }, externalRef) => {
2865
- return (jsx("li", { role: "separator", ...restProps, ref: externalRef, className: useBEM({ block: menuClass, element: menuDividerClass, className }) }));
2852
+ /*
2853
+ * All cells should use this.
2854
+ */
2855
+ const GridCell = (props, custom) => {
2856
+ // Generate a default filter value getter which uses the formatted value plus
2857
+ // the editable value if it's a string and different from the formatted value.
2858
+ // This is so that e.g. bearings can be searched for by DMS or raw number.
2859
+ const valueFormatter = props.valueFormatter;
2860
+ const filterValueGetter = generateFilterGetter(props.field, props.filterValueGetter, valueFormatter);
2861
+ const exportable = props.exportable;
2862
+ // Can't leave this here ag-grid will complain
2863
+ delete props.exportable;
2864
+ return {
2865
+ colId: props.field ?? props.field,
2866
+ headerTooltip: props.headerName,
2867
+ sortable: !!(props?.field || props?.valueGetter),
2868
+ resizable: true,
2869
+ editable: props.editable ?? false,
2870
+ ...(custom?.editor && {
2871
+ cellClassRules: GridCellMultiSelectClassRules,
2872
+ editable: props.editable ?? true,
2873
+ cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
2874
+ }),
2875
+ suppressKeyboardEvent: suppressCellKeyboardEvents,
2876
+ ...(custom?.editorParams && {
2877
+ cellEditorParams: {
2878
+ ...custom.editorParams,
2879
+ multiEdit: custom.multiEdit,
2880
+ preventAutoEdit: custom.preventAutoEdit ?? false,
2881
+ },
2882
+ }),
2883
+ // If there's a valueFormatter and no filterValueGetter then create a filterValueGetter
2884
+ filterValueGetter,
2885
+ // Default value formatter, otherwise react freaks out on objects
2886
+ valueFormatter: (params) => {
2887
+ if (params.value == null)
2888
+ return "–";
2889
+ const types = ["number", "boolean", "string"];
2890
+ if (types.includes(typeof params.value))
2891
+ return `${params.value}`;
2892
+ else
2893
+ return JSON.stringify(params.value);
2894
+ },
2895
+ ...props,
2896
+ cellRenderer: GridCellRenderer,
2897
+ cellRendererParams: {
2898
+ originalCellRenderer: props.cellRenderer,
2899
+ ...props.cellRendererParams,
2900
+ },
2901
+ headerComponentParams: {
2902
+ exportable,
2903
+ ...props.headerComponentParams,
2904
+ },
2905
+ };
2866
2906
  };
2867
- const MenuDivider = memo(forwardRef(MenuDividerFr));
2907
+ const GenericCellEditorComponentWrapper = (editor) => {
2908
+ const obj = { editor };
2909
+ return forwardRef(function GenericCellEditorComponentFr(cellEditorParams, _) {
2910
+ const valueFormatted = cellEditorParams.formatValue
2911
+ ? cellEditorParams.formatValue(cellEditorParams.value)
2912
+ : "Missing formatter";
2913
+ return (jsxs(GridPopoverContextProvider, { props: cellEditorParams, children: [jsx(cellEditorParams.colDef.cellRenderer, { ...cellEditorParams, valueFormatted: valueFormatted, ...cellEditorParams.colDef.cellRendererParams }), obj.editor && jsx(obj.editor, { ...cellEditorParams })] }));
2914
+ });
2915
+ };
2868
2916
 
2869
- const MenuHeaderFr = ({ className, ...restProps }, externalRef) => {
2870
- return (jsx("li", { role: "presentation", ...restProps, ref: externalRef, className: useBEM({ block: menuClass, element: menuHeaderClass, className }) }));
2871
- };
2872
- const MenuHeader = memo(forwardRef(MenuHeaderFr));
2917
+ const GridCellFillerColId = "gridCellFiller";
2918
+ const isGridCellFiller = (col) => col.colId === GridCellFillerColId;
2919
+ const GridCellFiller = () => ({
2920
+ colId: GridCellFillerColId,
2921
+ headerName: "",
2922
+ flex: 1,
2923
+ });
2873
2924
 
2874
- const MenuGroupFr = ({ className, style, takeOverflow, ...restProps }, externalRef) => {
2875
- const ref = useRef(null);
2876
- const [overflowStyle, setOverflowStyle] = useState();
2877
- const { overflow, overflowAmt } = useContext(MenuListContext);
2878
- useIsomorphicLayoutEffect(() => {
2879
- let maxHeight;
2880
- if (takeOverflow && overflowAmt != null && overflowAmt >= 0 && ref.current) {
2881
- maxHeight = ref.current.getBoundingClientRect().height - overflowAmt;
2882
- if (maxHeight < 0)
2883
- maxHeight = 0;
2884
- }
2885
- setOverflowStyle(maxHeight != null && maxHeight >= 0 ? { maxHeight, overflow } : undefined);
2886
- }, [takeOverflow, overflow, overflowAmt]);
2887
- useIsomorphicLayoutEffect(() => {
2888
- if (overflowStyle && ref.current)
2889
- ref.current.scrollTop = 0;
2890
- }, [overflowStyle]);
2891
- return (jsx("div", { ...restProps, ref: useCombinedRef(externalRef, ref), className: useBEM({ block: menuClass, element: menuGroupClass, className }), style: { ...style, ...overflowStyle } }));
2892
- };
2893
- const MenuGroup = forwardRef(MenuGroupFr);
2925
+ const Editor = (props) => ({
2926
+ component: GenericCellEditorComponentWrapper(props.editor),
2927
+ params: { ...props.editorParams, multiEdit: props.multiEdit },
2928
+ });
2929
+ /**
2930
+ * Used to choose between cell editors based in data.
2931
+ */
2932
+ const GridCellMultiEditor = (props, cellEditorSelector) => GridCell({
2933
+ cellClassRules: GridCellMultiSelectClassRules,
2934
+ cellEditorSelector,
2935
+ editable: props.editable ?? true,
2936
+ ...props,
2937
+ });
2894
2938
 
2895
- const MenuRadioGroupFr = ({ "aria-label": ariaLabel, className, name, value, onRadioChange, ...restProps }, externalRef) => {
2896
- const contextValue = useMemo(() => ({ name, value, onRadioChange }), [name, value, onRadioChange]);
2897
- return (jsx(RadioGroupContext.Provider, { value: contextValue, children: jsx("li", { role: "presentation", children: jsx("ul", { role: "group", "aria-label": ariaLabel || name || "Radio group", ...restProps, ref: externalRef, className: useBEM({ block: menuClass, element: radioGroupClass, className }) }) }) }));
2898
- };
2899
- const MenuRadioGroup = forwardRef(MenuRadioGroupFr);
2939
+ const GridFilterHeaderIconButton = forwardRef(function columnsButton({ icon, title, onClick, buttonProps, disabled = false, size = "md" }, ref) {
2940
+ return (jsx(LuiButton, { ...buttonProps, type: "button", level: "tertiary", className: "lui-button-icon-only", ref: ref, "aria-label": title, title: title, onClick: onClick, disabled: disabled, children: jsx(LuiIcon, { name: icon, alt: "Menu", size: size }) }));
2941
+ });
2900
2942
 
2901
2943
  const GridFilterColumnsToggle = ({ saveState = true }) => {
2902
2944
  const [loaded, setLoaded] = useState(false);
@@ -3173,7 +3215,7 @@ var css_248z$2 = ".helpText{color:#6b6966;font-size:.75rem;font-weight:400}";
3173
3215
  styleInject(css_248z$2);
3174
3216
 
3175
3217
  const FormError = (props) => {
3176
- return (jsxs(Fragment, { children: [props.error && (jsx("span", { className: "LuiTextInput-error", style: { paddingLeft: 0 }, children: props.error })), props.helpText && !props.error && jsx("span", { className: "helpText", children: props.helpText })] }));
3218
+ return (jsxs(Fragment$1, { children: [props.error && (jsx("span", { className: "LuiTextInput-error", style: { paddingLeft: 0 }, children: props.error })), props.helpText && !props.error && jsx("span", { className: "helpText", children: props.helpText })] }));
3177
3219
  };
3178
3220
 
3179
3221
  function escapeStringRegexp(string) {
@@ -3361,7 +3403,7 @@ const useGridPopoverHook = (props) => {
3361
3403
  }
3362
3404
  }, [cancelEdit, props, stopEditing, updateValue]);
3363
3405
  const popoverWrapper = useCallback((children) => {
3364
- return (jsx(Fragment, { children: anchorRef.current && (jsxs(ControlledMenu, { state: isOpen ? "open" : "closed", portal: true, unmountOnClose: true, anchorRef: anchorRef, saveButtonRef: saveButtonRef, menuClassName: "step-ag-grid-react-menu", onClose: (event) => {
3406
+ return (jsx(Fragment$1, { children: anchorRef.current && (jsxs(ControlledMenu, { state: isOpen ? "open" : "closed", portal: true, unmountOnClose: true, anchorRef: anchorRef, saveButtonRef: saveButtonRef, menuClassName: "step-ag-grid-react-menu", onClose: (event) => {
3365
3407
  // Prevent menu from closing when modals are invoked
3366
3408
  if (event.reason === CloseReason.BLUR)
3367
3409
  return;
@@ -3497,19 +3539,19 @@ const GridFormDropDown = (props) => {
3497
3539
  });
3498
3540
  let lastHeader = null;
3499
3541
  let showHeader = null;
3500
- return popoverWrapper(jsxs(Fragment, { children: [props.filtered && (jsxs("div", { className: "GridFormDropDown-filter", children: [jsx(FocusableItem, { className: "filter-item", onFocus: () => {
3542
+ return popoverWrapper(jsxs(Fragment$1, { children: [props.filtered && (jsxs("div", { className: "GridFormDropDown-filter", children: [jsx(FocusableItem, { className: "filter-item", onFocus: () => {
3501
3543
  setSelectedItem(null);
3502
3544
  setSubSelectedValue(null);
3503
3545
  subComponentIsValid.current = true;
3504
3546
  }, children: ({ ref }) => (jsxs("div", { style: { display: "flex", flexDirection: "column", width: "100%" }, children: [jsx("input", { className: "LuiTextInput-input", ref: ref, type: "text", placeholder: props.filterPlaceholder ?? "Filter...", "data-testid": "filteredMenu-free-text-input", defaultValue: filter, "data-allowtabtosave": true, "data-disableenterautosave": !props.onSelectFilter &&
3505
- !(filteredValues && filteredValues.length === 1 && !filteredValues[0].subComponent), onChange: (e) => setFilter(e.target.value) }), props.filterHelpText && isNotEmpty(filter) && (jsx(FormError, { error: null, helpText: props.filterHelpText }))] })) }), jsx(MenuDivider, {}, `$$divider_filter`)] })), jsx(ComponentLoadingWrapper, { loading: !options, className: "GridFormDropDown-options", children: jsxs(Fragment, { children: [options && (isEmpty(options) || (filteredValues && isEmpty(filteredValues))) && (jsx(MenuItem, { className: "GridPopoverEditDropDown-noOptions", disabled: true, children: props.noOptionsMessage ?? "No Options" }, `${fieldToString(field)}-empty`)), options?.map((item, index) => {
3547
+ !(filteredValues && filteredValues.length === 1 && !filteredValues[0].subComponent), onChange: (e) => setFilter(e.target.value) }), props.filterHelpText && isNotEmpty(filter) && (jsx(FormError, { error: null, helpText: props.filterHelpText }))] })) }), jsx(MenuDivider, {}, `$$divider_filter`)] })), jsx(ComponentLoadingWrapper, { loading: !options, className: "GridFormDropDown-options", children: jsxs(Fragment$1, { children: [options && (isEmpty(options) || (filteredValues && isEmpty(filteredValues))) && (jsx(MenuItem, { className: "GridPopoverEditDropDown-noOptions", disabled: true, children: props.noOptionsMessage ?? "No Options" }, `${fieldToString(field)}-empty`)), options?.map((item, index) => {
3506
3548
  showHeader = null;
3507
3549
  if (item.value === MenuSeparatorString) {
3508
3550
  return jsx(MenuDivider, {}, `$$divider_${index}`);
3509
3551
  }
3510
3552
  else if (item.value === MenuHeaderString) {
3511
3553
  lastHeader = jsx(MenuHeader, { children: item.label }, `$$header_${index}`);
3512
- return jsx(Fragment, {});
3554
+ return jsx(Fragment$1, {});
3513
3555
  }
3514
3556
  else {
3515
3557
  if (lastHeader) {
@@ -3517,7 +3559,7 @@ const GridFormDropDown = (props) => {
3517
3559
  lastHeader = null;
3518
3560
  }
3519
3561
  }
3520
- return ((!filteredValues || filteredValues.includes(item)) && (jsxs(Fragment$1, { children: [showHeader, jsxs("div", { children: [jsxs(MenuItem, { disabled: !!item.disabled, title: item.disabled && typeof item.disabled !== "boolean" ? item.disabled : "", value: item.value, onFocus: () => {
3562
+ return ((!filteredValues || filteredValues.includes(item)) && (jsxs(Fragment, { children: [showHeader, jsxs("div", { children: [jsxs(MenuItem, { disabled: !!item.disabled, title: item.disabled && typeof item.disabled !== "boolean" ? item.disabled : "", value: item.value, onFocus: () => {
3521
3563
  if (selectedItem !== item) {
3522
3564
  setSelectedItem(item);
3523
3565
  setSubSelectedValue(null);
@@ -3710,7 +3752,7 @@ const GridFormMessage = (props) => {
3710
3752
  setMessage(await props.message(selectedRows));
3711
3753
  })().then();
3712
3754
  }, [props, selectedRows]);
3713
- return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: message === null, className: clsx("GridFormMessage-container", props.className), children: jsx(Fragment, { children: message }) }));
3755
+ return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: message === null, className: clsx("GridFormMessage-container", props.className), children: jsx(Fragment$1, { children: message }) }));
3714
3756
  };
3715
3757
 
3716
3758
  const GridFormMultiSelect = (props) => {
@@ -3789,10 +3831,10 @@ const GridFormMultiSelect = (props) => {
3789
3831
  invalid,
3790
3832
  save,
3791
3833
  });
3792
- return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: !options, className: "GridFormMultiSelect-container", children: options && (jsxs(Fragment, { children: [props.filtered && (jsx(FilterInput, { ...{ headerGroups, options, setOptions, filter, setFilter, triggerSave }, filterHelpText: props.filterHelpText, onSelectFilter: props.onSelectFilter, filterPlaceholder: props.filterPlaceholder })), headerGroups &&
3834
+ return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: !options, className: "GridFormMultiSelect-container", children: options && (jsxs(Fragment$1, { children: [props.filtered && (jsx(FilterInput, { ...{ headerGroups, options, setOptions, filter, setFilter, triggerSave }, filterHelpText: props.filterHelpText, onSelectFilter: props.onSelectFilter, filterPlaceholder: props.filterPlaceholder })), headerGroups &&
3793
3835
  (isEmpty(headerGroups) || !toPairs(headerGroups).some(([_, options]) => !isEmpty(options))) && (jsx(MenuItem, { className: "GridMultiSelect-noOptions", disabled: true, children: props.noOptionsMessage ?? "No Options" }, "noOptions")), headerGroups && !isEmpty(headerGroups) && (jsx("div", { className: "GridFormMultiSelect-options", children: headers.map((header, index) => {
3794
3836
  const subOptions = headerGroups[`${header.filter}`];
3795
- return (!isEmpty(subOptions) && (jsxs(Fragment$1, { children: [header.header && jsx(MenuHeader, { children: header.header }), subOptions.map((item, index) => item.value === MenuSeparatorString ? (jsx(MenuDivider, {}, `div_${index}`)) : (jsxs(Fragment$1, { children: [jsx(MenuRadioItem, { item: item, options: options, setOptions: setOptions }), item.checked && item.subComponent && (jsx(MenuSubComponent, { ...{ item, options, setOptions, data, triggerSave }, subComponentIsValid: subComponentIsValidRef.current }))] }, `val_${item.value}`)))] }, `group_${index}`)));
3837
+ return (!isEmpty(subOptions) && (jsxs(Fragment, { children: [header.header && jsx(MenuHeader, { children: header.header }), subOptions.map((item, index) => item.value === MenuSeparatorString ? (jsx(MenuDivider, {}, `div_${index}`)) : (jsxs(Fragment, { children: [jsx(MenuRadioItem, { item: item, options: options, setOptions: setOptions }), item.checked && item.subComponent && (jsx(MenuSubComponent, { ...{ item, options, setOptions, data, triggerSave }, subComponentIsValid: subComponentIsValidRef.current }))] }, `val_${item.value}`)))] }, `group_${index}`)));
3796
3838
  }) }))] })) }));
3797
3839
  };
3798
3840
  const FilterInput = (props) => {
@@ -3867,7 +3909,7 @@ const FilterInput = (props) => {
3867
3909
  lastKeyWasEnter.current = false;
3868
3910
  }
3869
3911
  }, [addCustomFilterValue, filter, onSelectFilter, setFilter, toggleSelectAllVisible, triggerSave]);
3870
- return (jsxs(Fragment, { children: [jsx(FocusableItem, { className: "filter-item", children: (_) => (jsxs("div", { style: { width: "100%" }, className: "GridFormMultiSelect-filter", children: [jsx("input", { className: "LuiTextInput-input", type: "text", placeholder: filterPlaceholder ?? "Filter...", "data-testid": "filteredMenu-free-text-input", value: filter, "data-disableenterautosave": true, "data-allowtabtosave": true, onChange: (e) => setFilter(e.target.value), onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }), filterHelpText && (jsx(FormError, { error: null, helpText: typeof filterHelpText === "function" ? filterHelpText(filter.trim(), options) : filterHelpText }))] })) }, "filter"), jsx(MenuDivider, {}, `$$divider_filter`)] }));
3912
+ return (jsxs(Fragment$1, { children: [jsx(FocusableItem, { className: "filter-item", children: (_) => (jsxs("div", { style: { width: "100%" }, className: "GridFormMultiSelect-filter", children: [jsx("input", { className: "LuiTextInput-input", type: "text", placeholder: filterPlaceholder ?? "Filter...", "data-testid": "filteredMenu-free-text-input", value: filter, "data-disableenterautosave": true, "data-allowtabtosave": true, onChange: (e) => setFilter(e.target.value), onKeyDown: handleKeyDown, onKeyUp: handleKeyUp }), filterHelpText && (jsx(FormError, { error: null, helpText: typeof filterHelpText === "function" ? filterHelpText(filter.trim(), options) : filterHelpText }))] })) }, "filter"), jsx(MenuDivider, {}, `$$divider_filter`)] }));
3871
3913
  };
3872
3914
  const MenuRadioItem = (props) => {
3873
3915
  const { item, options, setOptions } = props;
@@ -3881,7 +3923,7 @@ const MenuRadioItem = (props) => {
3881
3923
  e.keepOpen = true;
3882
3924
  toggleValue(item);
3883
3925
  }
3884
- }, children: jsx(LuiCheckboxInput, { isChecked: item.checked ?? false, value: `${item.value}`, label: jsxs(Fragment, { children: [item.warning && jsx(GridIcon, { icon: "ic_warning_outline", title: item.warning }), item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)] }), inputProps: {
3926
+ }, children: jsx(LuiCheckboxInput, { isChecked: item.checked ?? false, value: `${item.value}`, label: jsxs(Fragment$1, { children: [item.warning && jsx(GridIcon, { icon: "ic_warning_outline", title: item.warning }), item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)] }), inputProps: {
3885
3927
  onClick: (e) => {
3886
3928
  // Click is handled by MenuItem onClick
3887
3929
  e.preventDefault();
@@ -3963,20 +4005,20 @@ const GridFormMultiSelectGrid = (props) => {
3963
4005
  setOptions([...options]);
3964
4006
  }
3965
4007
  }, [options, setOptions]);
3966
- return popoverWrapper(jsx(Fragment, { children: jsx("div", { className: "Grid-popoverContainer", children: jsx("div", { style: {
4008
+ return popoverWrapper(jsx(Fragment$1, { children: jsx("div", { className: "Grid-popoverContainer", children: jsx("div", { style: {
3967
4009
  display: "grid",
3968
4010
  gridAutoFlow: "column",
3969
4011
  gridTemplateRows: `repeat(${props.maxRowCount ?? 10}, auto)`,
3970
4012
  maxWidth: "calc(min(100vw,500px))",
3971
4013
  overflowX: "auto",
3972
4014
  }, children: options &&
3973
- options.map((o) => (jsx(Fragment, { children: jsx(MenuItem, { onClick: (e) => {
4015
+ options.map((o) => (jsx(Fragment$1, { children: jsx(MenuItem, { onClick: (e) => {
3974
4016
  // Global react-menu MenuItem handler handles tabs
3975
4017
  if (e.key !== "Tab" && e.key !== "Enter") {
3976
4018
  e.keepOpen = true;
3977
4019
  toggleValue(o);
3978
4020
  }
3979
- }, children: jsx(LuiCheckboxInput, { isChecked: !!o.checked ?? false, isIndeterminate: o.checked === "partial", value: `${o.value}`, label: jsxs(Fragment, { children: [o.warning && jsx(GridIcon, { icon: "ic_warning_outline", title: o.warning }), jsx("span", { className: "GridMultiSelectGrid-Label", children: o.label ?? (o.value == null ? `<${o.value}>` : `${o.value}`) })] }), inputProps: {
4021
+ }, children: jsx(LuiCheckboxInput, { isChecked: !!o.checked ?? false, isIndeterminate: o.checked === "partial", value: `${o.value}`, label: jsxs(Fragment$1, { children: [o.warning && jsx(GridIcon, { icon: "ic_warning_outline", title: o.warning }), jsx("span", { className: "GridMultiSelectGrid-Label", children: o.label ?? (o.value == null ? `<${o.value}>` : `${o.value}`) })] }), inputProps: {
3980
4022
  onClick: (e) => {
3981
4023
  // Click is handled by MenuItem onClick
3982
4024
  e.preventDefault();
@@ -4062,7 +4104,7 @@ const GridFormPopoverMenu = (props) => {
4062
4104
  invalid: () => subComponentSelected && !subComponentIsValid.current,
4063
4105
  save,
4064
4106
  });
4065
- 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: [jsx(MenuItem, { onClick: (e) => onMenuItemClick(e, item), disabled: !!item.disabled, title: item.disabled && typeof item.disabled !== "boolean" ? item.disabled : "", children: item.label }, `${item.label}`), item.subComponent && subComponentSelected === item && (jsx(FocusableItem, { className: "LuiDeprecatedForms", children: () => (jsx(GridSubComponentContext.Provider, { value: {
4107
+ return popoverWrapper(jsx(ComponentLoadingWrapper, { loading: !options, className: "GridFormPopupMenu", children: jsx(Fragment$1, { 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, { children: [jsx(MenuItem, { onClick: (e) => onMenuItemClick(e, item), disabled: !!item.disabled, title: item.disabled && typeof item.disabled !== "boolean" ? item.disabled : "", children: item.label }, `${item.label}`), item.subComponent && subComponentSelected === item && (jsx(FocusableItem, { className: "LuiDeprecatedForms", children: () => (jsx(GridSubComponentContext.Provider, { value: {
4066
4108
  context: {},
4067
4109
  data,
4068
4110
  value: subSelectedValue,
@@ -4506,6 +4548,16 @@ const GridContextProvider = (props) => {
4506
4548
  const prePopupOps = useCallback(() => {
4507
4549
  prePopupFocusedCell.current = gridApi?.getFocusedCell() ?? undefined;
4508
4550
  }, [gridApi]);
4551
+ /**
4552
+ * After a popup refocus the cell.
4553
+ */
4554
+ const postPopupOps = useCallback(() => {
4555
+ if (!gridApi)
4556
+ return;
4557
+ if (prePopupFocusedCell.current) {
4558
+ gridApi?.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
4559
+ }
4560
+ }, [gridApi]);
4509
4561
  /**
4510
4562
  * Get all row id's in grid.
4511
4563
  */
@@ -4878,6 +4930,7 @@ const GridContextProvider = (props) => {
4878
4930
  setInvisibleColumnIds,
4879
4931
  gridReady,
4880
4932
  prePopupOps,
4933
+ postPopupOps,
4881
4934
  setApis,
4882
4935
  setQuickFilter,
4883
4936
  selectRowsById,
@@ -28318,14 +28371,14 @@ const getQuick = (filter, container) => {
28318
28371
  return el;
28319
28372
  };
28320
28373
  /**
28321
- * Find by filter. Waits up to 4 seconds to find filter.
28374
+ * Find by filter. Waits up to 5 seconds to find filter.
28322
28375
  *
28323
28376
  * @param filter Filter
28324
28377
  * @param container Optional container to look in
28325
28378
  * @return HTMLElement. Throws exception if not found.
28326
28379
  */
28327
28380
  const findQuick = async (filter, container) => {
28328
- const endTime = Date.now() + 4000;
28381
+ const endTime = Date.now() + 5000;
28329
28382
  while (Date.now() < endTime) {
28330
28383
  const el = queryQuick(filter, container);
28331
28384
  if (el)
@@ -28335,46 +28388,42 @@ const findQuick = async (filter, container) => {
28335
28388
  throw Error(`findQuick not found, selector: ${quickSelector(filter).selector}`);
28336
28389
  };
28337
28390
 
28391
+ let user = userEvent;
28392
+ /**
28393
+ * allow external userEvent to be used
28394
+ * @param customisedUserEvent
28395
+ */
28396
+ const setUpUserEvent = (customisedUserEvent) => {
28397
+ user = customisedUserEvent;
28398
+ };
28338
28399
  const countRows = async (within) => {
28339
28400
  return getAllQuick({ tagName: `div[row-id]:not(:empty)` }, within).length;
28340
28401
  };
28341
28402
  const findRow = async (rowId, within) => {
28342
- //if this is not wrapped in an act console errors are logged during testing
28343
- let row;
28344
- await act(async () => {
28345
- row = await findQuick({ tagName: `div[row-id='${rowId}']:not(:empty)` }, within);
28346
- });
28347
- return row;
28403
+ return await findQuick({ tagName: `div[row-id='${rowId}']:not(:empty)` }, within);
28348
28404
  };
28349
28405
  const queryRow = async (rowId, within) => {
28350
28406
  return queryQuick({ tagName: `div[row-id='${rowId}']:not(:empty)` }, within);
28351
28407
  };
28352
28408
  const _selectRow = async (select, rowId, within) => {
28353
- await act(async () => {
28354
- const row = await findRow(rowId, within);
28355
- const isSelected = row.className.includes("ag-row-selected");
28356
- if (select === "toggle" || (select === "select" && !isSelected) || (select === "deselect" && isSelected)) {
28357
- const cell = await findCell(rowId, "selection", within);
28358
- await userEvent.click(cell);
28359
- await waitForWrapper$1(async () => {
28360
- const row = await findRow(rowId, within);
28361
- const nowSelected = row.className.includes("ag-row-selected");
28362
- if (nowSelected == isSelected)
28363
- throw `Row ${rowId} won't select`;
28364
- });
28365
- }
28366
- });
28409
+ const row = await findRow(rowId, within);
28410
+ const isSelected = row.className.includes("ag-row-selected");
28411
+ if (select === "toggle" || (select === "select" && !isSelected) || (select === "deselect" && isSelected)) {
28412
+ const cell = await findCell(rowId, "selection", within);
28413
+ await user.click(cell);
28414
+ await waitForWrapper$1(async () => {
28415
+ const row = await findRow(rowId, within);
28416
+ const nowSelected = row.className.includes("ag-row-selected");
28417
+ if (nowSelected === isSelected)
28418
+ throw `Row ${rowId} won't select`;
28419
+ });
28420
+ }
28367
28421
  };
28368
28422
  const selectRow = async (rowId, within) => _selectRow("select", rowId, within);
28369
28423
  const deselectRow = async (rowId, within) => _selectRow("deselect", rowId, within);
28370
28424
  const findCell = async (rowId, colId, within) => {
28371
- //if this is not wrapped in an act console errors are logged during testing
28372
- let cell;
28373
- await act(async () => {
28374
- const row = await findRow(rowId, within);
28375
- cell = await findQuick({ tagName: `[col-id='${colId}']` }, row);
28376
- });
28377
- return cell;
28425
+ const row = await findRow(rowId, within);
28426
+ return await findQuick({ tagName: `[col-id='${colId}']` }, row);
28378
28427
  };
28379
28428
  const findCellContains = async (rowId, colId, text, within) => {
28380
28429
  return await waitForWrapper$1(async () => {
@@ -28383,16 +28432,12 @@ const findCellContains = async (rowId, colId, text, within) => {
28383
28432
  }, { timeout: 10000 });
28384
28433
  };
28385
28434
  const selectCell = async (rowId, colId, within) => {
28386
- await act(async () => {
28387
- const cell = await findCell(rowId, colId, within);
28388
- await userEvent.click(cell);
28389
- });
28435
+ const cell = await findCell(rowId, colId, within);
28436
+ await user.click(cell);
28390
28437
  };
28391
28438
  const editCell = async (rowId, colId, within) => {
28392
- await act(async () => {
28393
- const cell = await findCell(rowId, colId, within);
28394
- await userEvent.dblClick(cell);
28395
- });
28439
+ const cell = await findCell(rowId, colId, within);
28440
+ await user.dblClick(cell);
28396
28441
  await waitForWrapper$1(findOpenPopover);
28397
28442
  };
28398
28443
  const isCellReadOnly = async (rowId, colId, within) => {
@@ -28423,10 +28468,8 @@ const validateMenuOptions = async (rowId, colId, expectedMenuOptions) => {
28423
28468
  return isEqual(actualOptions, expectedMenuOptions);
28424
28469
  };
28425
28470
  const clickMenuOption = async (menuOptionText) => {
28426
- await act(async () => {
28427
- const menuOption = await findMenuOption(menuOptionText);
28428
- await userEvent.click(menuOption);
28429
- });
28471
+ const menuOption = await findMenuOption(menuOptionText);
28472
+ await user.click(menuOption);
28430
28473
  };
28431
28474
  const openAndClickMenuOption = async (rowId, colId, menuOptionText, within) => {
28432
28475
  await editCell(rowId, colId, within);
@@ -28451,21 +28494,21 @@ const findMultiSelectOption = async (value) => {
28451
28494
  };
28452
28495
  const clickMultiSelectOption = async (value) => {
28453
28496
  const menuItem = await findMultiSelectOption(value);
28454
- menuItem.parentElement && (await userEvent.click(menuItem.parentElement));
28497
+ menuItem.parentElement && (await user.click(menuItem.parentElement));
28455
28498
  };
28456
- const typeInput = async (value, filter) => act(async () => {
28499
+ const typeInput = async (value, filter) => {
28457
28500
  const openMenu = await findOpenPopover();
28458
28501
  const input = await findQuick(filter, openMenu);
28459
- await userEvent.clear(input);
28502
+ await user.clear(input);
28460
28503
  //'typing' an empty string will cause a console error, and it's also unnecessary after the previous clear call
28461
28504
  if (value.length > 0) {
28462
- await userEvent.type(input, value);
28505
+ await user.type(input, value);
28463
28506
  }
28464
- });
28507
+ };
28465
28508
  const typeOnlyInput = async (value) => typeInput(value, { child: { tagName: "input[type='text'], textarea" } });
28466
28509
  const typeInputByLabel = async (value, labelText) => {
28467
- const labels = getAllQuick({ child: { tagName: "label" } }).filter((l) => l.textContent == labelText);
28468
- if (labels.length == 0) {
28510
+ const labels = getAllQuick({ child: { tagName: "label" } }).filter((l) => l.textContent === labelText);
28511
+ if (labels.length === 0) {
28469
28512
  throw Error(`Label not found for text: ${labelText}`);
28470
28513
  }
28471
28514
  if (labels.length > 1) {
@@ -28483,11 +28526,9 @@ const closeMenu = () => userEvent.click(document.body);
28483
28526
  const closePopover = () => userEvent.click(document.body);
28484
28527
  const findActionButton = (text, container) => findQuick({ tagName: "button", child: { classes: ".ActionButton-minimalAreaDisplay", text: text } }, container);
28485
28528
  const clickActionButton = async (text, container) => {
28486
- await act(async () => {
28487
- const button = await findActionButton(text, container);
28488
- await userEvent.click(button);
28489
- });
28529
+ const button = await findActionButton(text, container);
28530
+ await user.click(button);
28490
28531
  };
28491
28532
 
28492
- export { ActionButton, ComponentLoadingWrapper, ControlledMenu, Editor, FocusableItem, FormError, GenericCellEditorComponentWrapper, Grid, GridCell, GridCellFiller, GridCellFillerColId, GridCellMultiEditor, GridCellMultiSelectClassRules, GridCellRenderer, GridContext, GridContextProvider, GridFilterButtons, GridFilterColumnsToggle, GridFilterDownloadCsvButton, GridFilterHeaderIconButton, GridFilterQuick, GridFilters, GridFormDropDown, GridFormEditBearing, GridFormMessage, GridFormMultiSelect, GridFormMultiSelectGrid, GridFormPopoverMenu, GridFormSubComponentTextArea, GridFormSubComponentTextInput, GridFormTextArea, GridFormTextInput, GridHeaderSelect, GridIcon, GridLoadableCell, GridNoRowsOverlay, GridPopoutEditMultiSelect, GridPopoutEditMultiSelectGrid, GridPopoverContext, GridPopoverContextProvider, GridPopoverEditBearing, GridPopoverEditBearingCorrection, GridPopoverEditBearingCorrectionEditorParams, GridPopoverEditBearingEditorParams, GridPopoverEditDropDown, GridPopoverMenu, GridPopoverMessage, GridPopoverTextArea, GridPopoverTextInput, GridRenderPopoutMenuCell, GridSubComponentContext, GridUpdatingContext, GridUpdatingContextProvider, GridWrapper, Menu, MenuButton, MenuDivider, MenuGroup, MenuHeader, MenuHeaderItem, MenuHeaderString, MenuItem, MenuRadioGroup, MenuSeparator, MenuSeparatorString, PopoutMenuSeparator, SubMenu, TextAreaInput, TextInputFormatted, bearingCorrectionRangeValidator, bearingCorrectionValueFormatter, bearingNumberParser, bearingRangeValidator, bearingStringValidator, bearingValueFormatter, clickActionButton, clickMenuOption, clickMultiSelectOption, closeMenu, closePopover, convertDDToDMS, countRows, deselectRow, downloadCsvUseValueFormattersProcessCellCallback, editCell, findActionButton, findCell, findCellContains, findMenuOption, findMultiSelectOption, findOpenPopover, findParentWithClass, findRow, fnOrVar, generateFilterGetter, getMultiSelectOptions, hasParentClass, isCellReadOnly, isFloat, isGridCellFiller, isNotEmpty, openAndClickMenuOption, openAndFindMenuOption, queryMenuOption, queryRow, sanitiseFileName, selectCell, selectRow, stringByteLengthIsInvalid, suppressCellKeyboardEvents, typeInputByLabel, typeInputByPlaceholder, typeOnlyInput, typeOtherInput, typeOtherTextArea, useDeferredPromise, useGenerateOrDefaultId, useGridContext, useGridFilter, useGridPopoverContext, useGridPopoverHook, useMenuState, usePostSortRowsHook, validateMenuOptions, wait$1 as wait };
28533
+ export { ActionButton, ComponentLoadingWrapper, ControlledMenu, Editor, FocusableItem, FormError, GenericCellEditorComponentWrapper, Grid, GridCell, GridCellFiller, GridCellFillerColId, GridCellMultiEditor, GridCellMultiSelectClassRules, GridCellRenderer, GridContext, GridContextProvider, GridFilterButtons, GridFilterColumnsToggle, GridFilterDownloadCsvButton, GridFilterHeaderIconButton, GridFilterQuick, GridFilters, GridFormDropDown, GridFormEditBearing, GridFormMessage, GridFormMultiSelect, GridFormMultiSelectGrid, GridFormPopoverMenu, GridFormSubComponentTextArea, GridFormSubComponentTextInput, GridFormTextArea, GridFormTextInput, GridHeaderSelect, GridIcon, GridLoadableCell, GridNoRowsOverlay, GridPopoutEditMultiSelect, GridPopoutEditMultiSelectGrid, GridPopoverContext, GridPopoverContextProvider, GridPopoverEditBearing, GridPopoverEditBearingCorrection, GridPopoverEditBearingCorrectionEditorParams, GridPopoverEditBearingEditorParams, GridPopoverEditDropDown, GridPopoverMenu, GridPopoverMessage, GridPopoverTextArea, GridPopoverTextInput, GridRenderPopoutMenuCell, GridSubComponentContext, GridUpdatingContext, GridUpdatingContextProvider, GridWrapper, Menu, MenuButton, MenuDivider, MenuGroup, MenuHeader, MenuHeaderItem, MenuHeaderString, MenuItem, MenuRadioGroup, MenuSeparator, MenuSeparatorString, PopoutMenuSeparator, SubMenu, TextAreaInput, TextInputFormatted, bearingCorrectionRangeValidator, bearingCorrectionValueFormatter, bearingNumberParser, bearingRangeValidator, bearingStringValidator, bearingValueFormatter, clickActionButton, clickMenuOption, clickMultiSelectOption, closeMenu, closePopover, convertDDToDMS, countRows, deselectRow, downloadCsvUseValueFormattersProcessCellCallback, editCell, findActionButton, findCell, findCellContains, findMenuOption, findMultiSelectOption, findOpenPopover, findParentWithClass, findRow, fnOrVar, generateFilterGetter, getMultiSelectOptions, hasParentClass, isCellReadOnly, isFloat, isGridCellFiller, isNotEmpty, openAndClickMenuOption, openAndFindMenuOption, queryMenuOption, queryRow, sanitiseFileName, selectCell, selectRow, setUpUserEvent, stringByteLengthIsInvalid, suppressCellKeyboardEvents, typeInputByLabel, typeInputByPlaceholder, typeOnlyInput, typeOtherInput, typeOtherTextArea, useDeferredPromise, useGenerateOrDefaultId, useGridContext, useGridContextMenu, useGridFilter, useGridPopoverContext, useGridPopoverHook, useMenuState, usePostSortRowsHook, validateMenuOptions, wait$1 as wait };
28493
28534
  //# sourceMappingURL=step-ag-grid.esm.js.map