@vuu-ui/vuu-layout 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/README.md +1 -0
  2. package/cjs/index.js +4 -7379
  3. package/cjs/index.js.map +7 -0
  4. package/esm/index.js +20 -0
  5. package/esm/index.js.map +7 -0
  6. package/index.css +1 -951
  7. package/index.css.map +3 -3
  8. package/package.json +6 -9
  9. package/src/Component.css +0 -2
  10. package/src/Component.tsx +0 -20
  11. package/src/DraggableLayout.css +0 -18
  12. package/src/DraggableLayout.tsx +0 -29
  13. package/src/__tests__/flexbox-utils.spec.js +0 -90
  14. package/src/action-buttons/index.ts +0 -1
  15. package/src/chest-of-drawers/Chest.css +0 -36
  16. package/src/chest-of-drawers/Chest.tsx +0 -42
  17. package/src/chest-of-drawers/Drawer.css +0 -159
  18. package/src/chest-of-drawers/Drawer.tsx +0 -118
  19. package/src/chest-of-drawers/index.ts +0 -2
  20. package/src/common-types.ts +0 -9
  21. package/src/debug.ts +0 -16
  22. package/src/dialog/Dialog.css +0 -16
  23. package/src/dialog/Dialog.tsx +0 -59
  24. package/src/dialog/index.ts +0 -1
  25. package/src/drag-drop/BoxModel.ts +0 -546
  26. package/src/drag-drop/DragState.ts +0 -222
  27. package/src/drag-drop/Draggable.ts +0 -282
  28. package/src/drag-drop/DropMenu.css +0 -71
  29. package/src/drag-drop/DropMenu.tsx +0 -61
  30. package/src/drag-drop/DropTarget.ts +0 -392
  31. package/src/drag-drop/DropTargetRenderer.css +0 -40
  32. package/src/drag-drop/DropTargetRenderer.tsx +0 -279
  33. package/src/drag-drop/dragDropTypes.ts +0 -49
  34. package/src/drag-drop/index.ts +0 -4
  35. package/src/editable-label/EditableLabel.css +0 -28
  36. package/src/editable-label/EditableLabel.tsx +0 -99
  37. package/src/editable-label/index.ts +0 -1
  38. package/src/flexbox/Flexbox.css +0 -45
  39. package/src/flexbox/Flexbox.tsx +0 -70
  40. package/src/flexbox/FlexboxLayout.jsx +0 -26
  41. package/src/flexbox/FluidGrid.css +0 -134
  42. package/src/flexbox/FluidGrid.tsx +0 -84
  43. package/src/flexbox/FluidGridLayout.tsx +0 -10
  44. package/src/flexbox/Splitter.css +0 -140
  45. package/src/flexbox/Splitter.tsx +0 -135
  46. package/src/flexbox/flexbox-utils.ts +0 -128
  47. package/src/flexbox/flexboxTypes.ts +0 -63
  48. package/src/flexbox/index.ts +0 -4
  49. package/src/flexbox/useResponsiveSizing.ts +0 -85
  50. package/src/flexbox/useSplitterResizing.ts +0 -272
  51. package/src/index.ts +0 -21
  52. package/src/layout-action.ts +0 -21
  53. package/src/layout-header/ActionButton.tsx +0 -23
  54. package/src/layout-header/Header.css +0 -8
  55. package/src/layout-header/Header.tsx +0 -222
  56. package/src/layout-header/index.ts +0 -1
  57. package/src/layout-provider/LayoutProvider.tsx +0 -160
  58. package/src/layout-provider/LayoutProviderContext.ts +0 -17
  59. package/src/layout-provider/index.ts +0 -2
  60. package/src/layout-provider/useLayoutDragDrop.ts +0 -241
  61. package/src/layout-reducer/flexUtils.ts +0 -281
  62. package/src/layout-reducer/index.ts +0 -4
  63. package/src/layout-reducer/insert-layout-element.ts +0 -365
  64. package/src/layout-reducer/layout-reducer.ts +0 -255
  65. package/src/layout-reducer/layoutTypes.ts +0 -151
  66. package/src/layout-reducer/layoutUtils.ts +0 -302
  67. package/src/layout-reducer/remove-layout-element.ts +0 -240
  68. package/src/layout-reducer/replace-layout-element.ts +0 -118
  69. package/src/layout-reducer/resize-flex-children.ts +0 -56
  70. package/src/layout-reducer/wrap-layout-element.ts +0 -317
  71. package/src/layout-view/View.css +0 -61
  72. package/src/layout-view/View.tsx +0 -149
  73. package/src/layout-view/ViewContext.ts +0 -31
  74. package/src/layout-view/index.ts +0 -4
  75. package/src/layout-view/useView.tsx +0 -104
  76. package/src/layout-view/useViewActionDispatcher.ts +0 -133
  77. package/src/layout-view/useViewResize.ts +0 -53
  78. package/src/layout-view/viewTypes.ts +0 -37
  79. package/src/menu/ContextMenu.css +0 -22
  80. package/src/menu/ContextMenu.jsx +0 -121
  81. package/src/menu/MenuList.css +0 -150
  82. package/src/menu/MenuList.jsx +0 -179
  83. package/src/menu/aim/aim.js +0 -92
  84. package/src/menu/aim/corners.js +0 -114
  85. package/src/menu/aim/point-in-polygon.js +0 -25
  86. package/src/menu/aim/utils.js +0 -19
  87. package/src/menu/context-menu-provider.jsx +0 -135
  88. package/src/menu/index.js +0 -4
  89. package/src/menu/key-code.js +0 -61
  90. package/src/menu/list-dom-utils.js +0 -22
  91. package/src/menu/use-cascade.js +0 -292
  92. package/src/menu/use-click-away.js +0 -22
  93. package/src/menu/use-items-with-ids.js +0 -75
  94. package/src/menu/use-keyboard-navigation.js +0 -162
  95. package/src/menu/utils.js +0 -5
  96. package/src/palette/Palette.css +0 -37
  97. package/src/palette/Palette.tsx +0 -140
  98. package/src/palette/PaletteSalt.css +0 -9
  99. package/src/palette/PaletteSalt.tsx +0 -79
  100. package/src/palette/index.ts +0 -2
  101. package/src/placeholder/Placeholder.css +0 -10
  102. package/src/placeholder/Placeholder.tsx +0 -39
  103. package/src/placeholder/index.ts +0 -1
  104. package/src/popup/index.js +0 -2
  105. package/src/popup/popup-provider.js +0 -0
  106. package/src/popup/popup-service.css +0 -15
  107. package/src/popup/popup-service.js +0 -281
  108. package/src/portal/Portal.jsx +0 -50
  109. package/src/portal/index.ts +0 -3
  110. package/src/portal/render-portal.jsx +0 -68
  111. package/src/portal/utils.js +0 -16
  112. package/src/registry/ComponentRegistry.ts +0 -35
  113. package/src/registry/index.ts +0 -1
  114. package/src/responsive/OverflowMenu.css +0 -31
  115. package/src/responsive/OverflowMenu.jsx +0 -56
  116. package/src/responsive/breakpoints.ts +0 -62
  117. package/src/responsive/index.ts +0 -4
  118. package/src/responsive/measureMinimumNodeSize.ts +0 -23
  119. package/src/responsive/overflowUtils.js +0 -14
  120. package/src/responsive/use-breakpoints.ts +0 -100
  121. package/src/responsive/useOverflowObserver.ts +0 -606
  122. package/src/responsive/useResizeObserver.ts +0 -154
  123. package/src/responsive/utils.ts +0 -37
  124. package/src/stack/Stack.css +0 -39
  125. package/src/stack/Stack.tsx +0 -161
  126. package/src/stack/StackLayout.tsx +0 -137
  127. package/src/stack/index.ts +0 -3
  128. package/src/stack/stackTypes.ts +0 -19
  129. package/src/tabs/TabPanel.css +0 -12
  130. package/src/tabs/TabPanel.tsx +0 -17
  131. package/src/tabs/index.ts +0 -1
  132. package/src/tools/config-wrapper/ConfigWrapper.jsx +0 -53
  133. package/src/tools/config-wrapper/index.js +0 -1
  134. package/src/tools/devtools-box/layout-configurator.css +0 -112
  135. package/src/tools/devtools-box/layout-configurator.jsx +0 -369
  136. package/src/tools/devtools-tree/layout-tree-viewer.css +0 -15
  137. package/src/tools/devtools-tree/layout-tree-viewer.jsx +0 -36
  138. package/src/tools/index.js +0 -3
  139. package/src/use-persistent-state.ts +0 -115
  140. package/src/utils/apply-handlers.js +0 -15
  141. package/src/utils/componentFromLayout.tsx +0 -30
  142. package/src/utils/index.ts +0 -6
  143. package/src/utils/pathUtils.ts +0 -294
  144. package/src/utils/propUtils.ts +0 -24
  145. package/src/utils/refUtils.ts +0 -16
  146. package/src/utils/styleUtils.ts +0 -14
  147. package/src/utils/typeOf.ts +0 -22
@@ -1,133 +0,0 @@
1
- import {
2
- ReactElement,
3
- RefObject,
4
- SyntheticEvent,
5
- useCallback,
6
- useState,
7
- } from "react";
8
- import { useLayoutProviderDispatch } from "../layout-provider";
9
- import { DragStartAction } from "../layout-reducer";
10
- import { ViewDispatch } from "./ViewContext";
11
- import { ViewAction } from "./viewTypes";
12
- import { usePersistentState } from "../use-persistent-state";
13
- import { DataSource } from "@vuu-ui/vuu-data";
14
-
15
- export type Contribution = {
16
- index?: number;
17
- location?: string;
18
- content: ReactElement;
19
- };
20
-
21
- export const useViewActionDispatcher = (
22
- id: string,
23
- root: RefObject<HTMLDivElement>,
24
- viewPath?: string,
25
- dropTargets?: string[]
26
- ): [ViewDispatch, Contribution[] | undefined] => {
27
- const { loadSessionState, purgeSessionState, purgeState, saveSessionState } =
28
- usePersistentState();
29
-
30
- const [contributions, setContributions] = useState<Contribution[]>(
31
- loadSessionState(id, "contributions") ?? []
32
- );
33
- const dispatchLayoutAction = useLayoutProviderDispatch();
34
- const updateContributions = useCallback(
35
- (location: string, content: ReactElement) => {
36
- const updatedContributions = contributions.concat([
37
- { location, content },
38
- ]);
39
- saveSessionState(id, "contributions", updatedContributions);
40
- setContributions(updatedContributions);
41
- },
42
- [contributions, id, saveSessionState]
43
- );
44
-
45
- const clearContributions = useCallback(() => {
46
- purgeSessionState(id, "contributions");
47
- setContributions([]);
48
- }, [id, purgeSessionState]);
49
-
50
- const handleRemove = useCallback(() => {
51
- // TODO this requires a bit more thought. I works BECAUSE filteredGrid has
52
- // stored its datasource in sessionState. It is highly pretty much a
53
- // requirement for features to do so - how do we enforce it.
54
- const ds = loadSessionState(id, "data-source") as DataSource;
55
- if (ds) {
56
- ds.unsubscribe();
57
- }
58
- purgeSessionState(id);
59
- purgeState(id);
60
- dispatchLayoutAction({ type: "remove", path: viewPath });
61
- }, [
62
- dispatchLayoutAction,
63
- id,
64
- loadSessionState,
65
- purgeSessionState,
66
- purgeState,
67
- viewPath,
68
- ]);
69
-
70
- const handleMouseDown = useCallback(
71
- async (evt, index, preDragActivity): Promise<boolean> => {
72
- evt.stopPropagation();
73
- const dragRect = root.current?.getBoundingClientRect();
74
- return new Promise((resolve, reject) => {
75
- // TODO should we check if we are allowed to drag ?
76
- dispatchLayoutAction({
77
- type: "drag-start",
78
- evt,
79
- path: index === undefined ? viewPath : `${viewPath}.${index}`,
80
- dragRect,
81
- preDragActivity,
82
- dropTargets,
83
- resolveDragStart: resolve,
84
- rejectDragStart: reject,
85
- } as DragStartAction);
86
- });
87
- },
88
- [root, dispatchLayoutAction, viewPath, dropTargets]
89
- );
90
-
91
- // TODO should be event, action, then this method can bea assigned directly to a html element
92
- // as an event hander
93
- const dispatchAction = useCallback(
94
- async <A extends ViewAction = ViewAction>(
95
- action: A,
96
- evt?: SyntheticEvent
97
- ): Promise<boolean | void> => {
98
- const { type } = action;
99
- switch (type) {
100
- case "maximize":
101
- case "minimize":
102
- case "restore":
103
- // case Action.TEAR_OUT:
104
- return dispatchLayoutAction({ type, path: action.path ?? viewPath });
105
- case "remove":
106
- return handleRemove();
107
- case "mousedown":
108
- console.log("2) ViewActionDispatch Hook dispatch Action mousedown");
109
- return handleMouseDown(evt, action.index, action.preDragActivity);
110
- case "add-toolbar-contribution":
111
- return updateContributions(action.location, action.content);
112
- case "remove-toolbar-contribution":
113
- return clearContributions();
114
- default: {
115
- // if (Object.values(Action).includes(type)) {
116
- // dispatch(action);
117
- // }
118
- return undefined;
119
- }
120
- }
121
- },
122
- [
123
- dispatchLayoutAction,
124
- viewPath,
125
- handleRemove,
126
- handleMouseDown,
127
- updateContributions,
128
- clearContributions,
129
- ]
130
- );
131
-
132
- return [dispatchAction, contributions];
133
- };
@@ -1,53 +0,0 @@
1
- import { useResizeObserver, WidthHeight } from "@heswell/salt-lab";
2
- import { RefObject, useCallback, useRef } from "react";
3
-
4
- const NO_MEASUREMENT: string[] = [];
5
-
6
- type size = {
7
- height?: number;
8
- width?: number;
9
- };
10
-
11
- export interface ViewResizeHookProps {
12
- mainRef: RefObject<HTMLDivElement>;
13
- resize?: "defer" | "responsive";
14
- rootRef: RefObject<HTMLDivElement>;
15
- }
16
-
17
- export const useViewResize = ({
18
- mainRef,
19
- resize = "responsive",
20
- rootRef,
21
- }: ViewResizeHookProps) => {
22
- const deferResize = resize === "defer";
23
-
24
- const mainSize = useRef<size>({});
25
- const resizeHandle = useRef<number>();
26
-
27
- const setMainSize = useCallback(() => {
28
- if (mainRef.current) {
29
- mainRef.current.style.height = mainSize.current.height + "px";
30
- mainRef.current.style.width = mainSize.current.width + "px";
31
- }
32
- resizeHandle.current = undefined;
33
- }, []);
34
-
35
- const onResize = useCallback(
36
- ({ height, width }) => {
37
- mainSize.current.height = height;
38
- mainSize.current.width = width;
39
- if (resizeHandle.current !== null) {
40
- clearTimeout(resizeHandle.current);
41
- }
42
- resizeHandle.current = window.setTimeout(setMainSize, 40);
43
- },
44
- [setMainSize]
45
- );
46
-
47
- useResizeObserver(
48
- rootRef,
49
- deferResize ? WidthHeight : NO_MEASUREMENT,
50
- onResize,
51
- deferResize
52
- );
53
- };
@@ -1,37 +0,0 @@
1
- import { HTMLAttributes } from "react";
2
- import { HeaderProps } from "../layout-header";
3
- import {
4
- MaximizeAction,
5
- MinimizeAction,
6
- MousedownViewAction,
7
- RemoveAction,
8
- RestoreAction,
9
- TearoutAction,
10
- AddToolbarContributionViewAction,
11
- RemoveToolbarContributionViewAction,
12
- } from "../layout-reducer";
13
-
14
- export type ViewAction =
15
- | MaximizeAction
16
- | MinimizeAction
17
- | MousedownViewAction
18
- | RemoveAction
19
- | RestoreAction
20
- | TearoutAction
21
- | AddToolbarContributionViewAction
22
- | RemoveToolbarContributionViewAction;
23
-
24
- export interface ViewProps extends HTMLAttributes<HTMLDivElement> {
25
- closeable?: boolean;
26
- collapsed?: boolean;
27
- "data-resizeable"?: boolean;
28
- dropTargets?: string[];
29
- expanded?: boolean;
30
- flexFill?: any;
31
- header?: boolean | Partial<HeaderProps>;
32
- orientation?: "vertical" | "horizontal";
33
- path?: string;
34
- resize?: "defer" | "responsive";
35
- resizeable?: boolean;
36
- tearOut?: boolean;
37
- }
@@ -1,22 +0,0 @@
1
- .hwPopupContainer.top-bottom-right-right .popup-menu {
2
- left: auto;
3
- right: 0;
4
- }
5
-
6
- .popup-menu .menu-item.showing > button,
7
- .popup-menu .menu-item > button:focus,
8
- .popup-menu .menu-item > button:hover {
9
- text-decoration: none;
10
- color: rgb(0, 0, 0);
11
- background-color: rgb(220, 220, 220);
12
- }
13
-
14
- .popup-menu .menu-item.disabled > button {
15
- clear: both;
16
- font-weight: normal;
17
- line-height: 1.5;
18
- color: rgb(120, 120, 120);
19
- white-space: nowrap;
20
- text-decoration: none;
21
- cursor: default;
22
- }
@@ -1,121 +0,0 @@
1
- import { useIdMemo as useId } from "@salt-ds/core";
2
- import { useCallback, useRef } from "react";
3
- import { Portal } from "../portal";
4
-
5
- import { getItemId, getMenuId, useCascade } from "./use-cascade";
6
- import { useItemsWithIds } from "./use-items-with-ids";
7
-
8
- import MenuList from "./MenuList";
9
-
10
- import "./ContextMenu.css";
11
- import { useClickAway } from "./use-click-away";
12
-
13
- const ContextMenu = ({
14
- activatedWithKeyboard = false,
15
- children: childrenProp,
16
- id: idProp,
17
- onClose = () => undefined,
18
- position = { x: 0, y: 0 },
19
- source: sourceProp,
20
- style,
21
- }) => {
22
- const id = useId(idProp);
23
- const closeMenuRef = useRef(null);
24
- const [menus, actions] = useItemsWithIds(sourceProp, childrenProp);
25
- const navigatingWithKeyboard = useRef(activatedWithKeyboard);
26
- const handleMouseEnterItem = useCallback(() => {
27
- navigatingWithKeyboard.current = false;
28
- }, []);
29
-
30
- const handleActivate = useCallback(
31
- (menuId) => {
32
- const { action, options } = actions[menuId];
33
- closeMenuRef.current("root");
34
- onClose(action, options);
35
- },
36
- [actions, onClose]
37
- );
38
-
39
- const { closeMenu, listItemProps, openMenu, openMenus, handleRender } =
40
- useCascade({
41
- id,
42
- onActivate: handleActivate,
43
- onMouseEnterItem: handleMouseEnterItem,
44
- position,
45
- });
46
- closeMenuRef.current = closeMenu;
47
-
48
- console.log({ openMenus });
49
-
50
- const handleClose = useCallback(() => {
51
- closeMenu();
52
- onClose();
53
- }, [closeMenu, onClose]);
54
-
55
- useClickAway({
56
- containerClassName: "hwMenuList",
57
- onClose: handleClose,
58
- isOpen: openMenus.length > 0,
59
- });
60
-
61
- const handleOpenMenu = (id) => {
62
- const itemId = getItemId(id);
63
- const menuId = getMenuId(itemId);
64
- navigatingWithKeyboard.current = true;
65
- openMenu(menuId, itemId);
66
- };
67
- const handleCloseMenu = () => {
68
- navigatingWithKeyboard.current = true;
69
- closeMenu();
70
- };
71
-
72
- const handleHighlightMenuItem = () => {
73
- // console.log(`highlight ${idx}`);
74
- };
75
-
76
- const lastMenu = openMenus.length - 1;
77
-
78
- const getChildMenuIndex = (i) => {
79
- if (i >= lastMenu) {
80
- return -1;
81
- } else {
82
- const { id: menuId } = openMenus[i + 1];
83
- const pos = menuId.lastIndexOf(".");
84
- const idx =
85
- pos === -1 ? parseInt(menuId, 10) : parseInt(menuId.slice(-pos), 10);
86
- return idx;
87
- }
88
- };
89
-
90
- return (
91
- <>
92
- {openMenus.map(({ id: menuId, left, top }, i) => {
93
- const childMenuIndex = getChildMenuIndex(i);
94
-
95
- return (
96
- <Portal key={i} x={left} y={top} onRender={handleRender}>
97
- <MenuList
98
- activatedByKeyboard={navigatingWithKeyboard.current}
99
- childMenuShowing={childMenuIndex}
100
- id={id}
101
- menuId={menuId}
102
- isRoot={i === 0}
103
- key={i}
104
- listItemProps={listItemProps}
105
- onActivate={handleActivate}
106
- onHighlightMenuItem={handleHighlightMenuItem}
107
- onCloseMenu={handleCloseMenu}
108
- onOpenMenu={handleOpenMenu}
109
- style={style}
110
- >
111
- {menus[menuId]}
112
- </MenuList>
113
- </Portal>
114
- );
115
- })}
116
- </>
117
- );
118
- };
119
-
120
- ContextMenu.displayName = "ContextMenu";
121
- export default ContextMenu;
@@ -1,150 +0,0 @@
1
- .hwMenuList {
2
- --context-menu-color: #161616;
3
- --context-menu-padding: var(--hw-list-item-padding, 0 6px);
4
- --context-menu-shadow: var(--hw-dialog-shadow, 0 6px 12px rgba(0, 0, 0, 0.175));
5
- --focus-visible-border-color: var(--hw-focus-visible-border-color, rgb(141, 154, 179));
6
- --context-menu-highlight-bg: #a4d5f4;
7
- --context-menu-blur-focus-bg: #e0e4e9;
8
- --menu-item-height: var(--hw-list-item-height, 24px);
9
- --menu-item-icon-color: black;
10
- --menu-item-twisty-color: black;
11
- --menu-item-twisty-content: '';
12
- --menu-item-twisty-top: 50%;
13
- --menu-item-twisty-left: auto;
14
- --menu-item-twisty-right: 0px;
15
- --menu-icon-size: 12px;
16
-
17
- background-clip: padding-box;
18
- background-color: white;
19
- border-radius: 4px;
20
- border: solid 1px rgba(0, 0, 0, 0.15);
21
- box-shadow: var(--context-menu-shadow);
22
- font-size: 13px;
23
- list-style: none;
24
- margin: 2px 0 0;
25
- outline: 0;
26
- padding: 0;
27
- position: absolute;
28
- }
29
-
30
- .hwMenuItem {
31
- align-items: center;
32
- color: var(--context-menu-color);
33
- display: flex;
34
- gap: 6px;
35
- height: var(--menu-item-height);
36
- padding: var(--context-menu-padding);
37
- padding-right: 24px;
38
- position: relative;
39
- white-space: nowrap;
40
- }
41
-
42
- .hwIconContainer {
43
- display: inline-block;
44
- flex: 12px 0 0;
45
- height: var(--menu-icon-size);
46
- mask-repeat: no-repeat;
47
- width: var(--menu-icon-size);
48
- }
49
-
50
- [data-icon='filter'] {
51
- --menu-icon-size: 12px;
52
- --menu-icon: var(--svg-filter);
53
- }
54
-
55
- [data-icon='filter'] {
56
- --menu-icon: var(--svg-filter);
57
- }
58
-
59
- [data-icon='sort'] {
60
- --menu-icon: var(--svg-sort-order-down);
61
- }
62
-
63
- [data-icon='sorted-asc'] {
64
- --menu-icon: var(--svg-sorted-asc);
65
- }
66
-
67
- [data-icon='sorted-dsc'] {
68
- --menu-icon: var(--svg-sorted-dsc);
69
- }
70
-
71
- [data-icon] > .hwIconContainer {
72
- background-color: var(--menu-item-icon-color);
73
- -webkit-mask: var(--menu-icon) center center/var(--menu-icon-size) var(--menu-icon-size);
74
- mask: var(--menu-icon) center center/var(--menu-icon-size) var(--menu-icon-size);
75
- }
76
-
77
- .hwMenuItem[aria-expanded='true'] {
78
- background-color: var(--context-menu-blur-focus-bg);
79
- }
80
-
81
- .hwMenuItem-separator {
82
- border-top: solid 1px var(--context-menu-blur-focus-bg);
83
- }
84
-
85
- .hwMenuItem[aria-haspopup='true']:after {
86
- content: var(--menu-item-twisty-content);
87
- -webkit-mask: var(--svg-chevron-right) center center/12px 12px;
88
- mask: var(--svg-chevron-down) center center/12px 12px;
89
- mask-repeat: no-repeat;
90
- background-color: var(--menu-item-twisty-color);
91
- height: 16px;
92
- left: var(--menu-item-twisty-left);
93
- right: var(--menu-item-twisty-right);
94
- margin-top: -8px;
95
- position: absolute;
96
- top: var(--menu-item-twisty-top);
97
- transition: transform 0.3s;
98
- width: 16px;
99
- }
100
-
101
- .hwMenuItem[data-highlighted] {
102
- background-color: var(--context-menu-highlight-bg);
103
- }
104
-
105
- .hwMenuItem:hover {
106
- background-color: var(--context-menu-highlight-bg);
107
- cursor: default;
108
- }
109
-
110
- .hwMenuList-childMenuShowing .hwMenuItem[data-highlighted] {
111
- background-color: var(--context-menu-blur-focus-bg);
112
- }
113
-
114
- .hwMenuItem.focusVisible:before {
115
- content: '';
116
- position: absolute;
117
- top: 0;
118
- left: 0;
119
- right: 0;
120
- bottom: 0px;
121
- border: dotted var(--focus-visible-border-color) 2px;
122
- }
123
-
124
- .hwPopupContainer.top-bottom-right-right .popup-menu {
125
- left: auto;
126
- right: 0;
127
- }
128
-
129
- .popup-menu .menu-item.showing > button,
130
- .popup-menu .menu-item > button:focus,
131
- .popup-menu .menu-item > button:hover {
132
- text-decoration: none;
133
- color: rgb(0, 0, 0);
134
- background-color: rgb(220, 220, 220);
135
- }
136
-
137
- .hwMenuItem-button:active,
138
- .hwMenuItem-button:hover {
139
- outline: 0;
140
- }
141
-
142
- .popup-menu .menu-item.disabled > button {
143
- clear: both;
144
- font-weight: normal;
145
- line-height: 1.5;
146
- color: rgb(120, 120, 120);
147
- white-space: nowrap;
148
- text-decoration: none;
149
- cursor: default;
150
- }
@@ -1,179 +0,0 @@
1
- import React, { useLayoutEffect, useMemo, useRef } from "react";
2
- import cx from "classnames";
3
- import { useIdMemo as useId } from "@salt-ds/core";
4
- import { useKeyboardNavigation } from "./use-keyboard-navigation";
5
- import { isMenuItemGroup } from "./use-items-with-ids";
6
-
7
- import "./MenuList.css";
8
-
9
- const classBase = "hwMenuList";
10
-
11
- export const Separator = () => <li className="hwMenuItem-divider" />;
12
-
13
- // Purely used as markers, props will be extracted
14
- export const MenuItemGroup = () => null;
15
- // eslint-disable-next-line no-unused-vars
16
- export const MenuItem = ({ children, idx, ...props }) => {
17
- return <div {...props}>{children}</div>;
18
- };
19
-
20
- const hasIcon = (child) => child.props["data-icon"];
21
-
22
- const MenuList = ({
23
- activatedByKeyboard,
24
- childMenuShowing = -1,
25
- children,
26
- highlightedIdx: highlightedIdxProp,
27
- id: idProp,
28
- isRoot,
29
- listItemProps,
30
- menuId,
31
- onHighlightMenuItem,
32
- onActivate,
33
- onCloseMenu,
34
- onOpenMenu,
35
- ...props
36
- }) => {
37
- const id = useId(idProp);
38
- const root = useRef(null);
39
-
40
- // The id generation be,ongs in useIttemsWithIds
41
- const mapIdxToId = useMemo(() => new Map(), []);
42
-
43
- const handleOpenMenu = (idx) => {
44
- const el = root.current.querySelector(`:scope > [data-idx='${idx}']`);
45
- onOpenMenu(el.id);
46
- };
47
-
48
- const handleActivate = (idx) => {
49
- const el = root.current.querySelector(`:scope > [data-idx='${idx}']`);
50
- onActivate(el.id);
51
- };
52
-
53
- const { focusVisible, highlightedIdx, listProps } = useKeyboardNavigation({
54
- count: children.length,
55
- highlightedIdx: highlightedIdxProp,
56
- onActivate: handleActivate,
57
- onHighlight: onHighlightMenuItem,
58
- onOpenMenu: handleOpenMenu,
59
- onCloseMenu,
60
- id,
61
- });
62
-
63
- const appliedFocusVisible = childMenuShowing == -1 ? focusVisible : -1;
64
-
65
- useLayoutEffect(() => {
66
- if (childMenuShowing === -1 && activatedByKeyboard) {
67
- root.current.focus();
68
- }
69
- }, [activatedByKeyboard, childMenuShowing]);
70
-
71
- const getActiveDescendant = () =>
72
- highlightedIdx === undefined || highlightedIdx === -1
73
- ? undefined
74
- : mapIdxToId.get(highlightedIdx);
75
-
76
- return (
77
- <div
78
- {...props}
79
- {...listProps}
80
- aria-activedescendant={getActiveDescendant()}
81
- className={cx(classBase, {
82
- [`${classBase}-childMenuShowing`]: childMenuShowing !== -1,
83
- })}
84
- data-root={isRoot || undefined}
85
- id={`${id}-${menuId}`}
86
- ref={root}
87
- role="menu"
88
- tabIndex={0}
89
- >
90
- {renderContent()}
91
- </div>
92
- );
93
-
94
- function renderContent() {
95
- const propsCommonToAllListItems = {
96
- ...listItemProps,
97
- role: "menuitem",
98
- };
99
-
100
- const maybeIcon = (children, withIcon) =>
101
- withIcon
102
- ? [<span className="hwIconContainer" key="icon" />].concat(children)
103
- : children;
104
-
105
- function addClonedChild(list, child, idx, withIcon) {
106
- const {
107
- children,
108
- className,
109
- id: itemId,
110
- hasSeparator,
111
- label,
112
- ...props
113
- } = child.props;
114
- const hasSubMenu = isMenuItemGroup(child);
115
- const subMenuShowing = hasSubMenu && childMenuShowing === idx;
116
- const ariaControls = subMenuShowing ? `${id}-${itemId}` : undefined;
117
-
118
- list.push(
119
- <MenuItem
120
- {...props}
121
- {...propsCommonToAllListItems}
122
- {...getMenuItemProps(
123
- `${id}-${menuId}`,
124
- itemId,
125
- idx,
126
- child.key,
127
- highlightedIdx,
128
- appliedFocusVisible,
129
- className,
130
- hasSeparator
131
- )}
132
- aria-controls={ariaControls}
133
- aria-haspopup={hasSubMenu || undefined}
134
- aria-expanded={subMenuShowing || undefined}
135
- >
136
- {hasSubMenu
137
- ? maybeIcon(label, withIcon)
138
- : maybeIcon(children, withIcon)}
139
- </MenuItem>
140
- );
141
- // mapIdxToId.set(idx, itemId);
142
- }
143
-
144
- const listItems = [];
145
-
146
- if (children && children.length > 0) {
147
- const withIcon = children.some(hasIcon);
148
-
149
- children.forEach((child, idx) => {
150
- addClonedChild(listItems, child, idx, withIcon);
151
- });
152
- }
153
-
154
- return listItems;
155
- }
156
- };
157
-
158
- const getMenuItemProps = (
159
- baseId,
160
- itemId,
161
- idx,
162
- key,
163
- highlightedIdx,
164
- focusVisible,
165
- className,
166
- hasSeparator
167
- ) => ({
168
- id: `${baseId}-${itemId}`,
169
- key: key ?? idx,
170
- "data-idx": idx,
171
- "data-highlighted": idx === highlightedIdx || undefined,
172
- className: cx("hwMenuItem", className, {
173
- "hwMenuItem-separator": hasSeparator,
174
- focusVisible: focusVisible === idx,
175
- }),
176
- });
177
-
178
- MenuList.displayName = "MenuList";
179
- export default MenuList;