@vuu-ui/vuu-layout 2.1.19-beta.1 → 2.1.19-beta.2

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 (89) hide show
  1. package/package.json +12 -13
  2. package/src/Component.js +14 -0
  3. package/src/LayoutContainer.css.js +24 -0
  4. package/src/LayoutContainer.js +26 -0
  5. package/src/debug.js +16 -0
  6. package/src/dock-layout/DockLayout.css.js +36 -0
  7. package/src/dock-layout/DockLayout.js +43 -0
  8. package/src/dock-layout/Drawer.css.js +150 -0
  9. package/src/dock-layout/Drawer.js +82 -0
  10. package/src/dock-layout/index.js +2 -0
  11. package/src/drag-drop/BoxModel.js +337 -0
  12. package/src/drag-drop/DragState.js +119 -0
  13. package/src/drag-drop/Draggable.js +140 -0
  14. package/src/drag-drop/DropTarget.js +286 -0
  15. package/src/drag-drop/DropTargetRenderer.js +275 -0
  16. package/src/drag-drop/dragDropTypes.js +0 -0
  17. package/src/drag-drop/index.js +3 -0
  18. package/src/flexbox/Flexbox.css.js +45 -0
  19. package/src/flexbox/Flexbox.js +45 -0
  20. package/src/flexbox/FlexboxLayout.js +27 -0
  21. package/src/flexbox/Splitter.css.js +57 -0
  22. package/src/flexbox/Splitter.js +112 -0
  23. package/src/flexbox/flexbox-utils.js +92 -0
  24. package/src/flexbox/flexboxTypes.js +0 -0
  25. package/src/flexbox/index.js +2 -0
  26. package/src/flexbox/useSplitterResizing.js +181 -0
  27. package/src/index.js +20 -0
  28. package/src/layout-action.js +20 -0
  29. package/src/layout-header/ActionButton.js +15 -0
  30. package/src/layout-header/Header.css.js +32 -0
  31. package/src/layout-header/Header.js +99 -0
  32. package/src/layout-header/index.js +1 -0
  33. package/src/layout-header/useHeader.js +76 -0
  34. package/src/layout-provider/LayoutProvider.js +242 -0
  35. package/src/layout-provider/LayoutProviderContext.js +13 -0
  36. package/src/layout-provider/index.js +2 -0
  37. package/src/layout-provider/useLayoutDragDrop.js +147 -0
  38. package/src/layout-reducer/flexUtils.js +186 -0
  39. package/src/layout-reducer/index.js +4 -0
  40. package/src/layout-reducer/insert-layout-element.js +179 -0
  41. package/src/layout-reducer/layout-reducer.js +121 -0
  42. package/src/layout-reducer/layoutTypes.js +34 -0
  43. package/src/layout-reducer/layoutUtils.js +178 -0
  44. package/src/layout-reducer/move-layout-element.js +18 -0
  45. package/src/layout-reducer/remove-layout-element.js +161 -0
  46. package/src/layout-reducer/replace-layout-element.js +70 -0
  47. package/src/layout-reducer/resize-flex-children.js +48 -0
  48. package/src/layout-reducer/wrap-layout-element.js +134 -0
  49. package/src/layout-view/View.css.js +76 -0
  50. package/src/layout-view/View.js +116 -0
  51. package/src/layout-view/useView.js +69 -0
  52. package/src/layout-view/useViewBroadcastChannel.js +29 -0
  53. package/src/layout-view/useViewResize.js +27 -0
  54. package/src/layout-view/viewTypes.js +0 -0
  55. package/src/layout-view-actions/ViewContext.js +11 -0
  56. package/src/layout-view-actions/useViewActionDispatcher.js +116 -0
  57. package/src/layout-view-actions/useViewContributions.js +31 -0
  58. package/src/palette/Palette.css.js +31 -0
  59. package/src/palette/Palette.js +85 -0
  60. package/src/palette/index.js +1 -0
  61. package/src/placeholder/LayoutStartPanel.css.js +35 -0
  62. package/src/placeholder/LayoutStartPanel.js +57 -0
  63. package/src/placeholder/Placeholder.css.js +20 -0
  64. package/src/placeholder/Placeholder.js +33 -0
  65. package/src/placeholder/index.js +1 -0
  66. package/src/responsive/index.js +2 -0
  67. package/src/responsive/measureMinimumNodeSize.js +24 -0
  68. package/src/responsive/useResizeObserver.js +109 -0
  69. package/src/responsive/utils.js +27 -0
  70. package/src/stack/Stack.css.js +44 -0
  71. package/src/stack/Stack.js +98 -0
  72. package/src/stack/StackLayout.js +116 -0
  73. package/src/stack/index.js +3 -0
  74. package/src/stack/stackTypes.js +0 -0
  75. package/src/tabs/TabPanel.css.js +17 -0
  76. package/src/tabs/TabPanel.js +11 -0
  77. package/src/tabs/index.js +1 -0
  78. package/src/tools/config-wrapper/ConfigWrapper.js +55 -0
  79. package/src/tools/config-wrapper/index.js +1 -0
  80. package/src/tools/devtools-box/layout-configurator.css.js +113 -0
  81. package/src/tools/devtools-tree/layout-tree-viewer.css.js +17 -0
  82. package/src/tools/index.js +3 -0
  83. package/src/use-persistent-state.js +43 -0
  84. package/src/utils/index.js +5 -0
  85. package/src/utils/pathUtils.js +212 -0
  86. package/src/utils/propUtils.js +15 -0
  87. package/src/utils/refUtils.js +5 -0
  88. package/src/utils/styleUtils.js +9 -0
  89. package/src/utils/typeOf.js +15 -0
@@ -0,0 +1,29 @@
1
+ import { useCallback, useEffect, useRef } from "react";
2
+ const isMessageForSelf = (message, id, path)=>{
3
+ if (id && message.targetId === id) return true;
4
+ if (message.path && path?.startsWith(message.path)) return true;
5
+ return false;
6
+ };
7
+ const useViewBroadcastChannel = (id, path, onMessageReceived)=>{
8
+ const broadcastChannelRef = useRef(void 0);
9
+ useEffect(()=>{
10
+ const broadcastChannel = new BroadcastChannel("vuu");
11
+ broadcastChannel.onmessage = (evt)=>{
12
+ if (isMessageForSelf(evt.data, id, path)) onMessageReceived?.(evt.data);
13
+ };
14
+ broadcastChannelRef.current = broadcastChannel;
15
+ return ()=>{
16
+ broadcastChannel.close();
17
+ broadcastChannelRef.current = void 0;
18
+ };
19
+ }, [
20
+ id,
21
+ onMessageReceived,
22
+ path
23
+ ]);
24
+ const sendMessage = useCallback((message)=>{
25
+ broadcastChannelRef.current?.postMessage(message);
26
+ }, []);
27
+ return sendMessage;
28
+ };
29
+ export { useViewBroadcastChannel };
@@ -0,0 +1,27 @@
1
+ import { WidthHeight, useResizeObserver } from "../responsive/index.js";
2
+ import { useCallback, useRef } from "react";
3
+ const NO_MEASUREMENT = [];
4
+ const useViewResize = ({ mainRef, resize = "responsive", rootRef })=>{
5
+ const deferResize = "defer" === resize;
6
+ const mainSize = useRef({});
7
+ const resizeHandle = useRef(void 0);
8
+ const setMainSize = useCallback(()=>{
9
+ if (mainRef.current) {
10
+ mainRef.current.style.height = mainSize.current.height + "px";
11
+ mainRef.current.style.width = mainSize.current.width + "px";
12
+ }
13
+ resizeHandle.current = void 0;
14
+ }, [
15
+ mainRef
16
+ ]);
17
+ const onResize = useCallback(({ height, width })=>{
18
+ mainSize.current.height = height;
19
+ mainSize.current.width = width;
20
+ if (null !== resizeHandle.current) clearTimeout(resizeHandle.current);
21
+ resizeHandle.current = window.setTimeout(setMainSize, 40);
22
+ }, [
23
+ setMainSize
24
+ ]);
25
+ useResizeObserver(rootRef, deferResize ? WidthHeight : NO_MEASUREMENT, onResize, deferResize);
26
+ };
27
+ export { useViewResize };
File without changes
@@ -0,0 +1,11 @@
1
+ import react, { useContext } from "react";
2
+ const NO_CONTEXT = {
3
+ dispatch: null
4
+ };
5
+ const ViewContext = react.createContext(NO_CONTEXT);
6
+ const useViewDispatch = ()=>{
7
+ const context = useContext(ViewContext);
8
+ return context?.dispatch ?? null;
9
+ };
10
+ const useViewContext = ()=>useContext(ViewContext);
11
+ export { ViewContext, useViewContext, useViewDispatch };
@@ -0,0 +1,116 @@
1
+ import { useSessionDataSource } from "@vuu-ui/vuu-data-react";
2
+ import { useCallback } from "react";
3
+ import { useLayoutProviderDispatch } from "../layout-provider/LayoutProvider.js";
4
+ import { useViewBroadcastChannel } from "../layout-view/useViewBroadcastChannel.js";
5
+ import { usePersistentState } from "../use-persistent-state.js";
6
+ import { useViewContributions } from "./useViewContributions.js";
7
+ const useViewActionDispatcher = (id, rootRef, viewPath, dropTargets)=>{
8
+ const { purgeState } = usePersistentState();
9
+ const { clearDataSource: clearDataSourceFromSessionState } = useSessionDataSource();
10
+ const { clearContributions, contributions, updateContributions } = useViewContributions({
11
+ sessionKey: id
12
+ });
13
+ const dispatchLayoutAction = useLayoutProviderDispatch();
14
+ const unsubscribeAndClearState = useCallback(()=>{
15
+ clearDataSourceFromSessionState(id, true);
16
+ purgeState(id);
17
+ }, [
18
+ clearDataSourceFromSessionState,
19
+ id,
20
+ purgeState
21
+ ]);
22
+ const handleRemove = useCallback(()=>{
23
+ unsubscribeAndClearState();
24
+ dispatchLayoutAction({
25
+ type: "remove",
26
+ path: viewPath
27
+ });
28
+ }, [
29
+ unsubscribeAndClearState,
30
+ dispatchLayoutAction,
31
+ viewPath
32
+ ]);
33
+ const handleMouseDown = useCallback(async (evt, index, preDragActivity)=>{
34
+ evt.stopPropagation();
35
+ const dragRect = rootRef.current?.getBoundingClientRect();
36
+ return new Promise((resolve, reject)=>{
37
+ dispatchLayoutAction({
38
+ type: "drag-start",
39
+ evt,
40
+ path: void 0 === index ? viewPath : `${viewPath}.${index}`,
41
+ dragRect,
42
+ preDragActivity,
43
+ dropTargets,
44
+ resolveDragStart: resolve,
45
+ rejectDragStart: reject
46
+ });
47
+ });
48
+ }, [
49
+ rootRef,
50
+ dispatchLayoutAction,
51
+ viewPath,
52
+ dropTargets
53
+ ]);
54
+ const handleMessageReceived = useCallback((message)=>{
55
+ switch(message.type){
56
+ case "highlight-on":
57
+ rootRef?.current?.classList.add("vuuHighlighted");
58
+ break;
59
+ case "highlight-off":
60
+ rootRef?.current?.classList.remove("vuuHighlighted");
61
+ break;
62
+ case "layout-closed":
63
+ unsubscribeAndClearState();
64
+ break;
65
+ default:
66
+ console.log(`received ${message.type} message`);
67
+ }
68
+ }, [
69
+ rootRef,
70
+ unsubscribeAndClearState
71
+ ]);
72
+ const sendMessage = useViewBroadcastChannel(id, viewPath, handleMessageReceived);
73
+ const dispatchAction = useCallback(async (action, evt)=>{
74
+ const { type } = action;
75
+ switch(type){
76
+ case "collapse":
77
+ case "expand":
78
+ return dispatchLayoutAction({
79
+ type,
80
+ path: action.path ?? viewPath
81
+ });
82
+ case "remove":
83
+ return handleRemove();
84
+ case "mousedown":
85
+ return handleMouseDown(evt, action.index, action.preDragActivity);
86
+ case "add-toolbar-contribution":
87
+ return updateContributions(action.location, action.content);
88
+ case "remove-toolbar-contribution":
89
+ return clearContributions();
90
+ case "query":
91
+ return dispatchLayoutAction({
92
+ type,
93
+ path: action.path,
94
+ query: action.query
95
+ });
96
+ case "broadcast-message":
97
+ sendMessage(action.message);
98
+ break;
99
+ default:
100
+ return;
101
+ }
102
+ }, [
103
+ dispatchLayoutAction,
104
+ viewPath,
105
+ handleRemove,
106
+ handleMouseDown,
107
+ updateContributions,
108
+ clearContributions,
109
+ sendMessage
110
+ ]);
111
+ return [
112
+ dispatchAction,
113
+ contributions
114
+ ];
115
+ };
116
+ export { useViewActionDispatcher };
@@ -0,0 +1,31 @@
1
+ import { useCallback, useState } from "react";
2
+ const sessionState = new Map();
3
+ const EMPTY_ARRAY = [];
4
+ const useViewContributions = ({ sessionKey })=>{
5
+ const [contributions, setContributions] = useState(EMPTY_ARRAY);
6
+ const updateContributions = useCallback((location, content)=>{
7
+ const updatedContributions = contributions.concat([
8
+ {
9
+ location,
10
+ content
11
+ }
12
+ ]);
13
+ sessionState.set(sessionKey, updatedContributions);
14
+ setContributions(updatedContributions);
15
+ }, [
16
+ contributions,
17
+ sessionKey
18
+ ]);
19
+ const clearContributions = useCallback(()=>{
20
+ sessionState.delete(sessionKey);
21
+ setContributions([]);
22
+ }, [
23
+ sessionKey
24
+ ]);
25
+ return {
26
+ clearContributions,
27
+ contributions,
28
+ updateContributions
29
+ };
30
+ };
31
+ export { useViewContributions };
@@ -0,0 +1,31 @@
1
+ const css = `
2
+ .vuuPalette {
3
+ --vuuList-borderStyle: none;
4
+ }
5
+
6
+ .vuuPalette-horizontal {
7
+ align-items: center;
8
+ display: flex;
9
+ }
10
+
11
+ .vuuPaletteItem {
12
+ --vuu-icon-color: var(--salt-content-secondary-foreground);
13
+ --list-item-text-padding: 0 0 0 calc(var(--salt-size-unit) * 3);
14
+ gap: var(--salt-spacing-200);
15
+ padding-left: 12px;
16
+ font-size: 12px;
17
+ font-weight: 600;
18
+ }
19
+
20
+ .vuuSimpleDraggableWrapper > .vuuPaletteItem {
21
+ --vuu-icon-color: var(--salt-selectable-foreground);
22
+ }
23
+
24
+ .salt-theme .vuuPaletteItem {
25
+ font-size: 11px;
26
+ font-weight: normal;
27
+ }
28
+
29
+
30
+ `;
31
+ export default css;
@@ -0,0 +1,85 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { ListBox, Option } from "@salt-ds/core";
3
+ import { queryClosest, registerComponent, uuid } from "@vuu-ui/vuu-utils";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import clsx from "clsx";
7
+ import { cloneElement, memo, useCallback } from "react";
8
+ import { useLayoutProviderDispatch } from "../layout-provider/index.js";
9
+ import { View } from "../layout-view/View.js";
10
+ import Palette from "./Palette.css";
11
+ const classBase = "vuuPalette";
12
+ const clonePaletteItem = (paletteItem)=>{
13
+ const dolly = paletteItem.cloneNode(true);
14
+ dolly.id = "";
15
+ delete dolly.dataset.idx;
16
+ return dolly;
17
+ };
18
+ const wrapInView = (component, viewProps)=>{
19
+ const id = uuid();
20
+ return /*#__PURE__*/ jsx(View, {
21
+ id: id,
22
+ ...viewProps,
23
+ children: component
24
+ }, id);
25
+ };
26
+ const PaletteItem = /*#__PURE__*/ memo(({ className, component, idx, key, value, ViewProps, ...props })=>{
27
+ const targetWindow = useWindow();
28
+ useComponentCssInjection({
29
+ testId: "vuu-palette",
30
+ css: Palette,
31
+ window: targetWindow
32
+ });
33
+ const dispatch = useLayoutProviderDispatch();
34
+ const handleMouseDown = useCallback((e)=>{
35
+ const el = queryClosest(e.target, ".vuuPaletteItem", true);
36
+ const { height, left, top, width } = el.getBoundingClientRect();
37
+ dispatch({
38
+ dragRect: {
39
+ left,
40
+ top,
41
+ right: left + width,
42
+ bottom: top + 150,
43
+ width,
44
+ height
45
+ },
46
+ dragElement: clonePaletteItem(el),
47
+ evt: e.nativeEvent,
48
+ instructions: {
49
+ DoNotRemove: true,
50
+ DoNotTransform: true,
51
+ DriftHomeIfNoDropTarget: true,
52
+ RemoveDraggableOnDragEnd: true,
53
+ dragThreshold: 10
54
+ },
55
+ path: "*",
56
+ payload: wrapInView(component, ViewProps),
57
+ type: "drag-start"
58
+ });
59
+ }, [
60
+ ViewProps,
61
+ component,
62
+ dispatch
63
+ ]);
64
+ return /*#__PURE__*/ jsx(Option, {
65
+ className: clsx("vuuPaletteItem", className),
66
+ "data-draggable": true,
67
+ "data-index": idx,
68
+ onMouseDown: handleMouseDown,
69
+ value: value,
70
+ ...props
71
+ });
72
+ });
73
+ PaletteItem.displayName = "PaletteItem";
74
+ const Palette_Palette = ({ ListBoxProps, ViewProps, children, className, orientation = "horizontal", ...props })=>/*#__PURE__*/ jsx(ListBox, {
75
+ ...ListBoxProps,
76
+ ...props,
77
+ className: clsx(classBase, className, `${classBase}-${orientation}`),
78
+ selected: [],
79
+ children: children.map((child, idx)=>child.type === PaletteItem ? /*#__PURE__*/ cloneElement(child, {
80
+ idx,
81
+ key: idx
82
+ }) : child)
83
+ });
84
+ registerComponent("Palette", Palette_Palette, "view");
85
+ export { PaletteItem, Palette_Palette as Palette };
@@ -0,0 +1 @@
1
+ export * from "./Palette.js";
@@ -0,0 +1,35 @@
1
+ const css = `
2
+ .vuuLayoutStartPanel {
3
+ flex-direction: column;
4
+ align-items: center;
5
+ gap: 12px;
6
+ font-weight: 700;
7
+ display: flex;
8
+ flex: none !important;
9
+ }
10
+
11
+ .vuuLayoutStartPanel-title {
12
+ color: var(--vuu-color-gray-80);
13
+ text-align: center;
14
+ font-size: 28px;
15
+ line-height: 1.5;
16
+ }
17
+
18
+ .vuuLayoutStartPanel-text {
19
+ color: var(--vuu-color-gray-50);
20
+ max-width: 460px;
21
+ font-size: 18px;
22
+ line-height: 1.5;
23
+ }
24
+
25
+ .vuuLayoutStartPanel-addButton {
26
+ --vuu-icon-size: 20px;
27
+ --saltButton-height: 56px;
28
+ --saltButton-width: 56px;
29
+ box-shadow: 0 4px 4px #00000040;
30
+ --saltButton-borderRadius: 28px !important;
31
+ }
32
+
33
+
34
+ `;
35
+ export default css;
@@ -0,0 +1,57 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { IconButton } from "@vuu-ui/vuu-ui-controls";
3
+ import { VuuShellLocation } from "@vuu-ui/vuu-utils";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import { useMemo, useState } from "react";
7
+ import { useViewContext } from "../layout-view-actions/ViewContext.js";
8
+ import LayoutStartPanel from "./LayoutStartPanel.css";
9
+ const classBase = "vuuLayoutStartPanel";
10
+ const LayoutStartPanel_LayoutStartPanel = (htmlAttributes)=>{
11
+ const targetWindow = useWindow();
12
+ useComponentCssInjection({
13
+ testId: "vuu-layout-start-panel",
14
+ css: LayoutStartPanel,
15
+ window: targetWindow
16
+ });
17
+ const { dispatch, path } = useViewContext();
18
+ const [displayState, setDisplayState] = useState();
19
+ useMemo(()=>{
20
+ dispatch?.({
21
+ type: "query",
22
+ path,
23
+ query: "PARENT_CONTAINER"
24
+ }).then((response)=>{
25
+ response?.parentContainerId === VuuShellLocation.Workspace ? setDisplayState("initial") : setDisplayState("nested");
26
+ });
27
+ }, [
28
+ dispatch,
29
+ path
30
+ ]);
31
+ if (void 0 === displayState) return null;
32
+ const showInitialState = "initial" === displayState;
33
+ return /*#__PURE__*/ jsxs("div", {
34
+ ...htmlAttributes,
35
+ className: classBase,
36
+ children: [
37
+ showInitialState ? /*#__PURE__*/ jsxs(Fragment, {
38
+ children: [
39
+ /*#__PURE__*/ jsx("div", {
40
+ className: `${classBase}-title`,
41
+ children: "Start by adding a table"
42
+ }),
43
+ /*#__PURE__*/ jsx("div", {
44
+ className: `${classBase}-text`,
45
+ children: "To add a table, drag any of the Vuu Tables to this area or click the button below"
46
+ })
47
+ ]
48
+ }) : null,
49
+ /*#__PURE__*/ jsx(IconButton, {
50
+ className: `${classBase}-addButton`,
51
+ icon: "add",
52
+ variant: "cta"
53
+ })
54
+ ]
55
+ });
56
+ };
57
+ export { LayoutStartPanel_LayoutStartPanel as LayoutStartPanel };
@@ -0,0 +1,20 @@
1
+ const css = `
2
+ .vuuPlaceholder {
3
+ --vuuView-justify: center;
4
+ flex: 1 1 0;
5
+ align-items: center;
6
+ display: flex;
7
+ }
8
+
9
+ .vuuPlaceholder-nested {
10
+ background-color: red;
11
+ }
12
+
13
+ .vuuPlaceholder-shim {
14
+ flex-grow: 0;
15
+ flex-shrink: 0;
16
+ }
17
+
18
+
19
+ `;
20
+ export default css;
@@ -0,0 +1,33 @@
1
+ import { Fragment, jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import { registerComponent } from "@vuu-ui/vuu-utils";
6
+ import { LayoutStartPanel } from "./LayoutStartPanel.js";
7
+ import { View } from "../layout-view/View.js";
8
+ import Placeholder from "./Placeholder.css";
9
+ const classBase = "vuuPlaceholder";
10
+ const PlaceholderCore = ({ showStartMenu = true })=>/*#__PURE__*/ jsx(Fragment, {
11
+ children: showStartMenu ? /*#__PURE__*/ jsx(LayoutStartPanel, {}) : null
12
+ });
13
+ const Placeholder_Placeholder = ({ className: classNameProp, showStartMenu, ...viewProps })=>{
14
+ const targetWindow = useWindow();
15
+ useComponentCssInjection({
16
+ testId: "vuu-placeholder",
17
+ css: Placeholder,
18
+ window: targetWindow
19
+ });
20
+ const className = clsx(classBase, classNameProp);
21
+ return /*#__PURE__*/ jsx(View, {
22
+ ...viewProps,
23
+ className: className,
24
+ "data-placeholder": true,
25
+ resizeable: true,
26
+ children: /*#__PURE__*/ jsx(PlaceholderCore, {
27
+ showStartMenu: showStartMenu
28
+ })
29
+ });
30
+ };
31
+ Placeholder_Placeholder.displayName = "Placeholder";
32
+ registerComponent("Placeholder", Placeholder_Placeholder, "component");
33
+ export { Placeholder_Placeholder as Placeholder };
@@ -0,0 +1 @@
1
+ export * from "./Placeholder.js";
@@ -0,0 +1,2 @@
1
+ export * from "./useResizeObserver.js";
2
+ export * from "./utils.js";
@@ -0,0 +1,24 @@
1
+ const LEFT_RIGHT = [
2
+ 'left',
3
+ 'right'
4
+ ];
5
+ const TOP_BOTTOM = [
6
+ 'top',
7
+ 'bottom'
8
+ ];
9
+ function measureMinimumNodeSize(node, dimension = 'width') {
10
+ const { [dimension]: size } = node.getBoundingClientRect();
11
+ const { padRight = false, padLeft = false } = node.dataset;
12
+ const style = getComputedStyle(node);
13
+ const [start, end] = 'width' === dimension ? LEFT_RIGHT : TOP_BOTTOM;
14
+ const marginStart = padLeft ? 0 : parseInt(style.getPropertyValue(`margin-${start}`), 10);
15
+ const marginEnd = padRight ? 0 : parseInt(style.getPropertyValue(`margin-${end}`), 10);
16
+ let minWidth = size;
17
+ const flexShrink = parseInt(style.getPropertyValue('flex-shrink'), 10);
18
+ if (flexShrink > 0) {
19
+ const flexBasis = parseInt(style.getPropertyValue('flex-basis'), 10);
20
+ if (!isNaN(flexBasis)) minWidth = flexBasis;
21
+ }
22
+ return marginStart + minWidth + marginEnd;
23
+ }
24
+ export { measureMinimumNodeSize };
@@ -0,0 +1,109 @@
1
+ import { useCallback, useLayoutEffect, useRef } from "react";
2
+ const WidthHeight = [
3
+ "height",
4
+ "width"
5
+ ];
6
+ const HeightOnly = [
7
+ "height"
8
+ ];
9
+ const WidthOnly = [
10
+ "width"
11
+ ];
12
+ const observedMap = new WeakMap();
13
+ const getTargetSize = (element, contentRect, dimension)=>{
14
+ switch(dimension){
15
+ case "height":
16
+ return contentRect.height;
17
+ case "scrollHeight":
18
+ return element.scrollHeight;
19
+ case "scrollWidth":
20
+ return element.scrollWidth;
21
+ case "width":
22
+ return contentRect.width;
23
+ default:
24
+ return 0;
25
+ }
26
+ };
27
+ const resizeObserver = new ResizeObserver((entries)=>{
28
+ for (const entry of entries){
29
+ const { target, contentRect } = entry;
30
+ const observedTarget = observedMap.get(target);
31
+ if (observedTarget) {
32
+ const { onResize, measurements } = observedTarget;
33
+ let sizeChanged = false;
34
+ for (const [dimension, size] of Object.entries(measurements)){
35
+ const newSize = getTargetSize(target, contentRect, dimension);
36
+ if (newSize !== size) {
37
+ sizeChanged = true;
38
+ measurements[dimension] = newSize;
39
+ }
40
+ }
41
+ if (sizeChanged) onResize && onResize(measurements);
42
+ }
43
+ }
44
+ });
45
+ function useResizeObserver(ref, dimensions, onResize, reportInitialSize = false) {
46
+ const dimensionsRef = useRef(dimensions);
47
+ const measure = useCallback((target)=>{
48
+ const rect = target.getBoundingClientRect();
49
+ return dimensionsRef.current.reduce((map, dim)=>{
50
+ map[dim] = getTargetSize(target, rect, dim);
51
+ return map;
52
+ }, {});
53
+ }, []);
54
+ useLayoutEffect(()=>{
55
+ const target = ref.current;
56
+ let cleanedUp = false;
57
+ async function registerObserver() {
58
+ observedMap.set(target, {
59
+ measurements: {}
60
+ });
61
+ cleanedUp = false;
62
+ const { fonts } = document;
63
+ if (fonts) await fonts.ready;
64
+ if (!cleanedUp) {
65
+ const observedTarget = observedMap.get(target);
66
+ if (observedTarget) {
67
+ const measurements = measure(target);
68
+ observedTarget.measurements = measurements;
69
+ resizeObserver.observe(target);
70
+ if (reportInitialSize) onResize(measurements);
71
+ }
72
+ }
73
+ }
74
+ if (target) {
75
+ if (observedMap.has(target)) throw Error("useResizeObserver attemping to observe same element twice");
76
+ registerObserver();
77
+ }
78
+ return ()=>{
79
+ if (target && observedMap.has(target)) {
80
+ resizeObserver.unobserve(target);
81
+ observedMap.delete(target);
82
+ cleanedUp = true;
83
+ }
84
+ };
85
+ }, [
86
+ ref,
87
+ measure,
88
+ reportInitialSize,
89
+ onResize
90
+ ]);
91
+ useLayoutEffect(()=>{
92
+ const target = ref.current;
93
+ const record = observedMap.get(target);
94
+ if (record) {
95
+ if (dimensionsRef.current !== dimensions) {
96
+ dimensionsRef.current = dimensions;
97
+ const measurements = measure(target);
98
+ record.measurements = measurements;
99
+ }
100
+ record.onResize = onResize;
101
+ }
102
+ }, [
103
+ dimensions,
104
+ measure,
105
+ ref,
106
+ onResize
107
+ ]);
108
+ }
109
+ export { HeightOnly, WidthHeight, WidthOnly, resizeObserver, useResizeObserver };
@@ -0,0 +1,27 @@
1
+ const COLLAPSIBLE = 'data-collapsible';
2
+ const RESPONSIVE_ATTRIBUTE = {
3
+ [COLLAPSIBLE]: true,
4
+ 'data-pad-start': true,
5
+ 'data-pad-end': true
6
+ };
7
+ const isResponsiveAttribute = (propName)=>RESPONSIVE_ATTRIBUTE[propName] ?? false;
8
+ const isCollapsible = (propName)=>propName === COLLAPSIBLE;
9
+ const COLLAPSIBLE_VALUE = {
10
+ dynamic: 'dynamic',
11
+ instant: 'instant',
12
+ true: 'instant'
13
+ };
14
+ const collapsibleValue = (value)=>COLLAPSIBLE_VALUE[value] ?? 'none';
15
+ const extractResponsiveProps = (props)=>Object.keys(props).reduce((result, propName)=>{
16
+ const [toolbarProps, rest] = result;
17
+ if (isResponsiveAttribute(propName)) {
18
+ const value = isCollapsible(propName) ? collapsibleValue(props[propName]) : props[propName];
19
+ toolbarProps[propName] = value;
20
+ rest[propName] = void 0;
21
+ }
22
+ return result;
23
+ }, [
24
+ {},
25
+ {}
26
+ ]);
27
+ export { extractResponsiveProps, isResponsiveAttribute };