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