@linzjs/step-ag-grid 15.0.2 → 15.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/step-ag-grid.cjs.js +1329 -1275
- package/dist/step-ag-grid.cjs.js.map +1 -1
- package/dist/step-ag-grid.esm.js +1367 -1314
- 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/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
|
|
|
@@ -2585,339 +1959,1007 @@ function MenuFr({ "aria-label": ariaLabel, menuButton, instanceRef, onMenuChange
|
|
|
2585
1959
|
// Focus (hover) the first menu item when onClick event is trigger by keyboard
|
|
2586
1960
|
openMenu(e.detail === 0 ? FocusPositions.FIRST : undefined);
|
|
2587
1961
|
};
|
|
2588
|
-
const onKeyDown = (e) => {
|
|
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
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
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);
|
|
2372
|
+
}
|
|
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;
|
|
2677
2398
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
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,
|
|
@@ -28628,6 +28681,7 @@ exports.typeOtherTextArea = typeOtherTextArea;
|
|
|
28628
28681
|
exports.useDeferredPromise = useDeferredPromise;
|
|
28629
28682
|
exports.useGenerateOrDefaultId = useGenerateOrDefaultId;
|
|
28630
28683
|
exports.useGridContext = useGridContext;
|
|
28684
|
+
exports.useGridContextMenu = useGridContextMenu;
|
|
28631
28685
|
exports.useGridFilter = useGridFilter;
|
|
28632
28686
|
exports.useGridPopoverContext = useGridPopoverContext;
|
|
28633
28687
|
exports.useGridPopoverHook = useGridPopoverHook;
|