@vuu-ui/vuu-layout 0.0.27

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 (117) hide show
  1. package/README.md +1 -0
  2. package/package.json +30 -0
  3. package/src/Component.css +2 -0
  4. package/src/Component.tsx +20 -0
  5. package/src/DraggableLayout.css +18 -0
  6. package/src/DraggableLayout.tsx +29 -0
  7. package/src/__tests__/flexbox-utils.spec.js +90 -0
  8. package/src/action-buttons/action-buttons.css +12 -0
  9. package/src/action-buttons/action-buttons.tsx +30 -0
  10. package/src/action-buttons/index.ts +1 -0
  11. package/src/chest-of-drawers/Chest.css +36 -0
  12. package/src/chest-of-drawers/Chest.tsx +42 -0
  13. package/src/chest-of-drawers/Drawer.css +153 -0
  14. package/src/chest-of-drawers/Drawer.tsx +118 -0
  15. package/src/chest-of-drawers/index.ts +2 -0
  16. package/src/common-types.ts +9 -0
  17. package/src/debug.ts +16 -0
  18. package/src/dialog/Dialog.css +16 -0
  19. package/src/dialog/Dialog.tsx +59 -0
  20. package/src/dialog/index.ts +1 -0
  21. package/src/drag-drop/BoxModel.ts +546 -0
  22. package/src/drag-drop/DragState.ts +222 -0
  23. package/src/drag-drop/Draggable.ts +282 -0
  24. package/src/drag-drop/DropMenu.css +70 -0
  25. package/src/drag-drop/DropMenu.tsx +68 -0
  26. package/src/drag-drop/DropTarget.ts +392 -0
  27. package/src/drag-drop/DropTargetRenderer.css +40 -0
  28. package/src/drag-drop/DropTargetRenderer.tsx +284 -0
  29. package/src/drag-drop/dragDropTypes.ts +49 -0
  30. package/src/drag-drop/index.ts +4 -0
  31. package/src/editable-label/EditableLabel.css +28 -0
  32. package/src/editable-label/EditableLabel.tsx +99 -0
  33. package/src/editable-label/index.ts +1 -0
  34. package/src/flexbox/Flexbox.css +45 -0
  35. package/src/flexbox/Flexbox.tsx +70 -0
  36. package/src/flexbox/FlexboxLayout.jsx +26 -0
  37. package/src/flexbox/FluidGrid.css +134 -0
  38. package/src/flexbox/FluidGrid.tsx +84 -0
  39. package/src/flexbox/FluidGridLayout.tsx +10 -0
  40. package/src/flexbox/Splitter.css +140 -0
  41. package/src/flexbox/Splitter.tsx +135 -0
  42. package/src/flexbox/flexbox-utils.ts +128 -0
  43. package/src/flexbox/flexboxTypes.ts +63 -0
  44. package/src/flexbox/index.ts +4 -0
  45. package/src/flexbox/useResponsiveSizing.ts +85 -0
  46. package/src/flexbox/useSplitterResizing.ts +272 -0
  47. package/src/index.ts +20 -0
  48. package/src/layout-action.ts +21 -0
  49. package/src/layout-header/ActionButton.tsx +23 -0
  50. package/src/layout-header/Header.css +8 -0
  51. package/src/layout-header/Header.tsx +222 -0
  52. package/src/layout-header/index.ts +1 -0
  53. package/src/layout-provider/LayoutProvider.tsx +160 -0
  54. package/src/layout-provider/LayoutProviderContext.ts +17 -0
  55. package/src/layout-provider/index.ts +2 -0
  56. package/src/layout-provider/useLayoutDragDrop.ts +241 -0
  57. package/src/layout-reducer/flexUtils.ts +281 -0
  58. package/src/layout-reducer/index.ts +4 -0
  59. package/src/layout-reducer/insert-layout-element.ts +365 -0
  60. package/src/layout-reducer/layout-reducer.ts +255 -0
  61. package/src/layout-reducer/layoutTypes.ts +151 -0
  62. package/src/layout-reducer/layoutUtils.ts +302 -0
  63. package/src/layout-reducer/remove-layout-element.ts +240 -0
  64. package/src/layout-reducer/replace-layout-element.ts +118 -0
  65. package/src/layout-reducer/resize-flex-children.ts +56 -0
  66. package/src/layout-reducer/wrap-layout-element.ts +317 -0
  67. package/src/layout-view/View.css +58 -0
  68. package/src/layout-view/View.tsx +149 -0
  69. package/src/layout-view/ViewContext.ts +31 -0
  70. package/src/layout-view/index.ts +4 -0
  71. package/src/layout-view/useView.tsx +104 -0
  72. package/src/layout-view/useViewActionDispatcher.ts +133 -0
  73. package/src/layout-view/useViewResize.ts +53 -0
  74. package/src/layout-view/viewTypes.ts +37 -0
  75. package/src/palette/Palette.css +37 -0
  76. package/src/palette/Palette.tsx +140 -0
  77. package/src/palette/PaletteUitk.css +9 -0
  78. package/src/palette/PaletteUitk.tsx +79 -0
  79. package/src/palette/index.ts +2 -0
  80. package/src/placeholder/Placeholder.css +10 -0
  81. package/src/placeholder/Placeholder.tsx +39 -0
  82. package/src/placeholder/index.ts +1 -0
  83. package/src/registry/ComponentRegistry.ts +35 -0
  84. package/src/registry/index.ts +1 -0
  85. package/src/responsive/OverflowMenu.css +31 -0
  86. package/src/responsive/OverflowMenu.jsx +56 -0
  87. package/src/responsive/breakpoints.ts +48 -0
  88. package/src/responsive/index.ts +4 -0
  89. package/src/responsive/measureMinimumNodeSize.ts +23 -0
  90. package/src/responsive/overflowUtils.js +14 -0
  91. package/src/responsive/use-breakpoints.ts +100 -0
  92. package/src/responsive/useOverflowObserver.ts +606 -0
  93. package/src/responsive/useResizeObserver.ts +154 -0
  94. package/src/responsive/utils.ts +37 -0
  95. package/src/stack/Stack.css +39 -0
  96. package/src/stack/Stack.tsx +160 -0
  97. package/src/stack/StackLayout.tsx +137 -0
  98. package/src/stack/index.ts +3 -0
  99. package/src/stack/stackTypes.ts +19 -0
  100. package/src/tabs/TabPanel.css +12 -0
  101. package/src/tabs/TabPanel.tsx +17 -0
  102. package/src/tabs/index.ts +1 -0
  103. package/src/tools/config-wrapper/ConfigWrapper.jsx +53 -0
  104. package/src/tools/config-wrapper/index.js +1 -0
  105. package/src/tools/devtools-box/layout-configurator.css +112 -0
  106. package/src/tools/devtools-box/layout-configurator.jsx +369 -0
  107. package/src/tools/devtools-tree/layout-tree-viewer.css +15 -0
  108. package/src/tools/devtools-tree/layout-tree-viewer.jsx +36 -0
  109. package/src/tools/index.js +3 -0
  110. package/src/use-persistent-state.ts +115 -0
  111. package/src/utils/componentFromLayout.tsx +30 -0
  112. package/src/utils/index.ts +6 -0
  113. package/src/utils/pathUtils.ts +294 -0
  114. package/src/utils/propUtils.ts +24 -0
  115. package/src/utils/refUtils.ts +16 -0
  116. package/src/utils/styleUtils.ts +14 -0
  117. package/src/utils/typeOf.ts +22 -0
@@ -0,0 +1,154 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ import { useCallback, useLayoutEffect, useRef, RefObject } from 'react';
3
+ export const WidthHeight = ['height', 'width'];
4
+ export const HeightOnly = ['height'];
5
+ export const WidthOnly = ['width'];
6
+
7
+ export type measurements<T = string | number> = {
8
+ height?: T;
9
+ scrollHeight?: T;
10
+ scrollWidth?: T;
11
+ width?: T;
12
+ };
13
+ type measuredDimension = keyof measurements<number>;
14
+
15
+ export type ResizeHandler = (measurements: measurements<number>) => void;
16
+
17
+ type observedDetails = {
18
+ onResize?: ResizeHandler;
19
+ measurements: measurements<number>;
20
+ };
21
+ const observedMap = new WeakMap<HTMLElement, observedDetails>();
22
+
23
+ const getTargetSize = (
24
+ element: HTMLElement,
25
+ contentRect: DOMRectReadOnly,
26
+ dimension: measuredDimension
27
+ ): number => {
28
+ switch (dimension) {
29
+ case 'height':
30
+ return contentRect.height;
31
+ case 'scrollHeight':
32
+ return element.scrollHeight;
33
+ case 'scrollWidth':
34
+ return element.scrollWidth;
35
+ case 'width':
36
+ return contentRect.width;
37
+ default:
38
+ return 0;
39
+ }
40
+ };
41
+
42
+ const resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
43
+ for (const entry of entries) {
44
+ const { target, contentRect } = entry;
45
+ const observedTarget = observedMap.get(target as HTMLElement);
46
+ if (observedTarget) {
47
+ const { onResize, measurements } = observedTarget;
48
+ let sizeChanged = false;
49
+ for (const [dimension, size] of Object.entries(measurements)) {
50
+ const newSize = getTargetSize(
51
+ target as HTMLElement,
52
+ contentRect,
53
+ dimension as measuredDimension
54
+ );
55
+ if (newSize !== size) {
56
+ sizeChanged = true;
57
+ measurements[dimension as measuredDimension] = newSize;
58
+ }
59
+ }
60
+ if (sizeChanged) {
61
+ onResize && onResize(measurements);
62
+ }
63
+ }
64
+ }
65
+ });
66
+
67
+ // TODO use an optional lag (default to false) to ask to fire onResize
68
+ // with initial size
69
+ // Note asking for scrollHeight alone will not trigger onResize, this is only triggered by height,
70
+ // with scrollHeight returned as an auxilliary value
71
+ export function useResizeObserver(
72
+ ref: RefObject<Element | HTMLElement | null>,
73
+ dimensions: string[],
74
+ onResize: ResizeHandler,
75
+ reportInitialSize = false
76
+ ): void {
77
+ const dimensionsRef = useRef(dimensions);
78
+ const measure = useCallback((target: HTMLElement): measurements<number> => {
79
+ const rect = target.getBoundingClientRect();
80
+ return dimensionsRef.current.reduce((map: { [key: string]: number }, dim) => {
81
+ map[dim] = getTargetSize(target, rect, dim as measuredDimension);
82
+ return map;
83
+ }, {});
84
+ }, []);
85
+
86
+ // TODO use ref to store resizeHandler here
87
+ // resize handler registered with REsizeObserver will never change
88
+ // use ref to store user onResize callback here
89
+ // resizeHandler will call user callback.current
90
+
91
+ // Keep this effect separate in case user inadvertently passes different
92
+ // dimensions or callback instance each time - we only ever want to
93
+ // initiate new observation when ref changes.
94
+ useLayoutEffect(() => {
95
+ const target = ref.current as HTMLElement;
96
+ let cleanedUp = false;
97
+
98
+ async function registerObserver() {
99
+ // Create the map entry immediately. useEffect may fire below
100
+ // before fonts are ready and attempt to update entry
101
+ observedMap.set(target, { measurements: {} as measurements<number> });
102
+ cleanedUp = false;
103
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
104
+ const { fonts } = document as any;
105
+ if (fonts) {
106
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
107
+ await fonts.ready;
108
+ }
109
+ if (!cleanedUp) {
110
+ const observedTarget = observedMap.get(target);
111
+ if (observedTarget) {
112
+ const measurements = measure(target);
113
+ observedTarget.measurements = measurements;
114
+ resizeObserver.observe(target);
115
+ if (reportInitialSize) {
116
+ onResize(measurements);
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ if (target) {
123
+ // TODO might we want multiple callers to attach a listener to the same element ?
124
+ if (observedMap.has(target)) {
125
+ throw Error('useResizeObserver attemping to observe same element twice');
126
+ }
127
+ void registerObserver();
128
+ }
129
+ return () => {
130
+ if (target && observedMap.has(target)) {
131
+ resizeObserver.unobserve(target);
132
+ observedMap.delete(target);
133
+ cleanedUp = true;
134
+ }
135
+ };
136
+ }, [ref, measure]);
137
+
138
+ useLayoutEffect(() => {
139
+ const target = ref.current as HTMLElement;
140
+ const record = observedMap.get(target);
141
+ if (record) {
142
+ if (dimensionsRef.current !== dimensions) {
143
+ dimensionsRef.current = dimensions;
144
+ const measurements = measure(target);
145
+ record.measurements = measurements;
146
+ }
147
+ // Might not have changed, but no harm ...
148
+ record.onResize = onResize;
149
+ }
150
+ }, [dimensions, measure, ref, onResize]);
151
+
152
+ // TODO might be a good idea to ref and return the current measurememnts. That way, derived hooks
153
+ // e.g useBreakpoints don't have to measure and client cn make onResize callback simpler
154
+ }
@@ -0,0 +1,37 @@
1
+ const COLLAPSIBLE = 'data-collapsible';
2
+
3
+ const RESPONSIVE_ATTRIBUTE: { [key: string]: boolean } = {
4
+ [COLLAPSIBLE]: true,
5
+ 'data-pad-start': true,
6
+ 'data-pad-end': true
7
+ };
8
+
9
+ export const isResponsiveAttribute = (propName: string): boolean =>
10
+ RESPONSIVE_ATTRIBUTE[propName] ?? false;
11
+
12
+ const isCollapsible = (propName: string) => propName === COLLAPSIBLE;
13
+
14
+ const COLLAPSIBLE_VALUE: { [key: string]: string } = {
15
+ dynamic: 'dynamic',
16
+ instant: 'instant',
17
+ true: 'instant'
18
+ };
19
+
20
+ const collapsibleValue = (value: string) => COLLAPSIBLE_VALUE[value] ?? 'none';
21
+
22
+ type Props = { [key: string]: any };
23
+ export const extractResponsiveProps = (props: Props) => {
24
+ return Object.keys(props).reduce<[Props, Props]>(
25
+ (result, propName) => {
26
+ const [toolbarProps, rest] = result;
27
+ if (isResponsiveAttribute(propName)) {
28
+ const value = isCollapsible(propName) ? collapsibleValue(props[propName]) : props[propName];
29
+
30
+ toolbarProps[propName] = value;
31
+ rest[propName] = undefined;
32
+ }
33
+ return result;
34
+ },
35
+ [{}, {}]
36
+ );
37
+ };
@@ -0,0 +1,39 @@
1
+ .Tabs {
2
+ display: flex;
3
+ box-sizing: border-box;
4
+ flex-direction: column;
5
+ }
6
+
7
+ .Tabs .Toolbar:before {
8
+ left: 0;
9
+ width: 100%;
10
+ bottom: 0;
11
+ height: 1px;
12
+ content: '';
13
+ position: absolute;
14
+ background: var(--grey60);
15
+ }
16
+
17
+ .vuuTabHeader {
18
+ --uitkTabs-activationIndicator-background: transparent;
19
+ --uitkToolbarField-marginTop: calc(var(--uitk-size-unit) - 1px);
20
+ border-bottom: solid 1px var(--uitk-container-borderColor-medium);
21
+ }
22
+
23
+ .vuuTabHeader + .hwFlexbox,
24
+ .vuuTabHeader + * {
25
+ flex: 1;
26
+ }
27
+
28
+ .vuuTabHeader + .vuuView > .vuuHeader {
29
+ height: 0;
30
+ overflow: hidden;
31
+ }
32
+
33
+ /* .Splitter.column + .Flexbox > .Tabs:first-child {
34
+ border-top: solid 1px lightgrey;
35
+ } */
36
+
37
+ .Layout-svg-button {
38
+ --spacing-medium: 5px;
39
+ }
@@ -0,0 +1,160 @@
1
+ import { useIdMemo as useId } from "@heswell/uitk-core";
2
+ import { Tab, Tabstrip, Toolbar, ToolbarField } from "@heswell/uitk-lab";
3
+ import React, {
4
+ ForwardedRef,
5
+ forwardRef,
6
+ MouseEvent,
7
+ ReactElement,
8
+ ReactNode,
9
+ useCallback,
10
+ } from "react";
11
+ import { StackProps } from "./stackTypes";
12
+
13
+ import "./Stack.css";
14
+
15
+ const getDefaultTabLabel = (component: ReactElement, tabIndex: number) =>
16
+ component.props?.title ?? `Tab ${tabIndex + 1}`;
17
+
18
+ const getChildElements = <T extends ReactElement = ReactElement>(
19
+ children: ReactNode
20
+ ): T[] => {
21
+ const elements: T[] = [];
22
+ React.Children.forEach(children, (child) => {
23
+ if (React.isValidElement(child)) {
24
+ elements.push(child as T);
25
+ } else {
26
+ console.warn(`Stack has unexpected child element type`);
27
+ }
28
+ });
29
+ return elements;
30
+ };
31
+
32
+ export const Stack = forwardRef(function Stack(
33
+ {
34
+ active = 0,
35
+ children,
36
+ enableAddTab,
37
+ enableCloseTabs,
38
+ getTabLabel = getDefaultTabLabel,
39
+ id: idProp,
40
+ keyBoardActivation = "manual",
41
+ onMouseDown,
42
+ onTabAdd,
43
+ onTabClose,
44
+ onTabEdit,
45
+ onTabSelectionChanged,
46
+ showTabs,
47
+ style,
48
+ }: StackProps,
49
+ ref: ForwardedRef<HTMLDivElement>
50
+ ) {
51
+ const id = useId(idProp);
52
+
53
+ const handleTabSelection = (nextIdx: number) => {
54
+ // if uncontrolled, handle it internally
55
+ onTabSelectionChanged?.(nextIdx);
56
+ };
57
+
58
+ const handleTabClose = (tabIndex: number) => {
59
+ // if uncontrolled, handle it internally
60
+ onTabClose?.(tabIndex);
61
+ };
62
+
63
+ const handleAddTab = () => {
64
+ // if uncontrolled, handle it internally
65
+ onTabAdd?.(React.Children.count(children));
66
+ };
67
+
68
+ const handleMouseDown = (e: MouseEvent<HTMLDivElement>) => {
69
+ // if uncontrolled, handle it internally
70
+ const target = e.target as HTMLElement;
71
+ const tabElement = target.closest('[role^="tab"]') as HTMLDivElement;
72
+ const role = tabElement?.getAttribute("role");
73
+ if (role === "tab") {
74
+ const tabIndex = parseInt(tabElement.dataset.idx ?? "-1");
75
+ if (tabIndex !== -1) {
76
+ onMouseDown?.(e, tabIndex);
77
+ } else {
78
+ throw Error("Stack: mousedown on tab with unknown index");
79
+ }
80
+ } else if (role === "tablist") {
81
+ console.log(`Stack mousedown on tabstrip`);
82
+ }
83
+ };
84
+
85
+ const handleExitEditMode = useCallback(
86
+ (
87
+ _oldText: string,
88
+ newText: string,
89
+ _allowDeactivation: boolean,
90
+ tabIndex: number
91
+ ) => {
92
+ onTabEdit?.(tabIndex, newText);
93
+ },
94
+ [onTabEdit]
95
+ );
96
+
97
+ const activeChild = () => {
98
+ if (React.isValidElement(children)) {
99
+ return children;
100
+ } else if (Array.isArray(children)) {
101
+ return children[active] ?? null;
102
+ } else {
103
+ return null;
104
+ }
105
+ };
106
+
107
+ const renderTabs = () =>
108
+ getChildElements(children).map((child, idx) => {
109
+ const rootId = `${id}-${idx}`;
110
+ const { closeable, id: childId } = child.props;
111
+ return (
112
+ <Tab
113
+ ariaControls={`${rootId}-tab`}
114
+ draggable
115
+ key={childId} // Important that we key by child identifier, not using index
116
+ id={rootId}
117
+ label={getTabLabel(child, idx)}
118
+ closeable={closeable}
119
+ editable={true}
120
+ // onEdit={handleTabEdit}
121
+ />
122
+ );
123
+ });
124
+
125
+ const child = activeChild();
126
+
127
+ return (
128
+ <div className="Tabs" style={style} id={id} ref={ref}>
129
+ {showTabs ? (
130
+ <Toolbar
131
+ className="vuuTabHeader vuuHeader"
132
+ // onMouseDown={handleMouseDown}
133
+ >
134
+ <ToolbarField
135
+ disableFocusRing
136
+ data-collapsible="dynamic"
137
+ data-priority="3"
138
+ >
139
+ <Tabstrip
140
+ enableRenameTab
141
+ enableAddTab={enableAddTab}
142
+ enableCloseTab={enableCloseTabs}
143
+ keyBoardActivation={keyBoardActivation}
144
+ onActiveChange={handleTabSelection}
145
+ onAddTab={handleAddTab}
146
+ onCloseTab={handleTabClose}
147
+ onExitEditMode={handleExitEditMode}
148
+ onMouseDown={handleMouseDown}
149
+ activeTabIndex={active || (child === null ? -1 : 0)}
150
+ >
151
+ {renderTabs()}
152
+ </Tabstrip>
153
+ </ToolbarField>
154
+ </Toolbar>
155
+ ) : null}
156
+ {child}
157
+ </div>
158
+ );
159
+ });
160
+ Stack.displayName = "Stack";
@@ -0,0 +1,137 @@
1
+ import { useIdMemo as useId } from "@heswell/uitk-core";
2
+ import React, { ReactElement, useRef } from "react";
3
+ import { Stack } from "./Stack";
4
+ // import { Tooltray } from "../toolbar";
5
+ // import { CloseButton, MinimizeButton, MaximizeButton } from "../action-buttons";
6
+ import Component from "../Component";
7
+ import { useLayoutProviderDispatch } from "../layout-provider";
8
+ import { useViewActionDispatcher, View } from "../layout-view";
9
+ import { registerComponent } from "../registry/ComponentRegistry";
10
+ import { usePersistentState } from "../use-persistent-state";
11
+ import { StackProps } from "./stackTypes";
12
+
13
+ import "./Stack.css";
14
+
15
+ const defaultCreateNewChild = (index: number) => (
16
+ // Note make this width 100% and height 100% and we get a weird error where view continually resizes - growing
17
+ <View
18
+ resizeable
19
+ title={`Tab ${index}`}
20
+ style={{ flexGrow: 1, flexShrink: 0, flexBasis: 0 }}
21
+ header
22
+ closeable
23
+ >
24
+ <Component style={{ flex: 1 }} />
25
+ </View>
26
+ );
27
+
28
+ export const StackLayout = (props: StackProps) => {
29
+ const ref = useRef<HTMLDivElement>(null);
30
+ const dispatch = useLayoutProviderDispatch();
31
+ const { loadState, saveState } = usePersistentState();
32
+
33
+ const {
34
+ createNewChild = defaultCreateNewChild,
35
+ id: idProp,
36
+ onTabSelectionChanged,
37
+ path,
38
+ ...restProps
39
+ } = props;
40
+
41
+ const { children } = props;
42
+
43
+ const id = useId(idProp);
44
+
45
+ const [dispatchViewAction] = useViewActionDispatcher(id, ref, path);
46
+
47
+ const handleTabSelection = (nextIdx: number) => {
48
+ console.log(`StackLayout handleTabSelection nextTab = ${nextIdx}`);
49
+ if (path) {
50
+ dispatch({ type: "switch-tab", path, nextIdx });
51
+ onTabSelectionChanged?.(nextIdx);
52
+ }
53
+ };
54
+
55
+ const handleTabClose = (tabIndex: number) => {
56
+ if (Array.isArray(children)) {
57
+ const {
58
+ props: { "data-path": dataPath, path = dataPath },
59
+ } = children[tabIndex];
60
+ dispatch({ type: "remove", path });
61
+ }
62
+ };
63
+
64
+ const handleTabAdd = (e: any, tabIndex = React.Children.count(children)) => {
65
+ if (path) {
66
+ console.log(`[StackLayout] handleTabAdd`);
67
+ const component = createNewChild(tabIndex);
68
+ console.log({ component });
69
+ dispatch({
70
+ type: "add",
71
+ path,
72
+ component,
73
+ });
74
+ }
75
+ };
76
+
77
+ const handleMouseDown = async (e: any, index: number) => {
78
+ // If user drags the selected Tab, we need to select another Tab and re-render.
79
+ // This needs to be co-ordinated with drag Tab within Tabstrip, whcih can
80
+ // be handles within The Tabstrip until final release - much like Splitter
81
+ // dragging in Flexbox.
82
+ let readyToDrag: undefined | ((value: unknown) => void);
83
+
84
+ // Experimental
85
+ const preDragActivity = async () =>
86
+ new Promise((resolve) => {
87
+ console.log("preDragActivity: Ok, gonna release the drag");
88
+ readyToDrag = resolve;
89
+ });
90
+
91
+ const dragging = await dispatchViewAction(
92
+ { type: "mousedown", index, preDragActivity },
93
+ e
94
+ );
95
+
96
+ if (dragging) {
97
+ readyToDrag?.(undefined);
98
+ }
99
+ };
100
+
101
+ const handleTabEdit = (tabIndex: number, text: string) => {
102
+ // Save into state on behalf of the associated View
103
+ // Do we need a mechanism to get this into the JSPOMN when we serialize ?
104
+ // const { id } = children[tabIndex].props;
105
+ // saveState(id, 'view-title', text);
106
+ dispatch({ type: "set-title", path: `${path}.${tabIndex}`, title: text });
107
+ };
108
+
109
+ const getTabLabel = (component: ReactElement, idx: number) => {
110
+ const { id, title } = component.props;
111
+ return loadState(id, "view-title") || title || `Tab ${idx + 1}`;
112
+ };
113
+
114
+ return (
115
+ <Stack
116
+ {...restProps}
117
+ id={id}
118
+ getTabLabel={getTabLabel}
119
+ onMouseDown={handleMouseDown}
120
+ onTabAdd={handleTabAdd}
121
+ onTabClose={handleTabClose}
122
+ onTabEdit={handleTabEdit}
123
+ onTabSelectionChanged={handleTabSelection}
124
+ ref={ref}
125
+ // toolbarContent={
126
+ // <Tooltray data-align="right" className="layout-buttons">
127
+ // <MinimizeButton />
128
+ // <MaximizeButton />
129
+ // <CloseButton />
130
+ // </Tooltray>
131
+ // }
132
+ />
133
+ );
134
+ };
135
+ StackLayout.displayName = "Stack";
136
+
137
+ registerComponent("Stack", StackLayout, "container");
@@ -0,0 +1,3 @@
1
+ export * from "./Stack";
2
+ export * from "./StackLayout";
3
+ export * from "./stackTypes";
@@ -0,0 +1,19 @@
1
+ import { HTMLAttributes, MouseEvent, ReactElement, ReactNode } from "react";
2
+
3
+ export interface StackProps
4
+ extends Omit<HTMLAttributes<HTMLDivElement>, "onMouseDown"> {
5
+ active?: number;
6
+ createNewChild?: (index: number) => ReactElement;
7
+ enableAddTab?: boolean;
8
+ enableCloseTabs?: boolean;
9
+ getTabLabel?: (component: ReactElement, index: number) => string;
10
+ keyBoardActivation?: "automatic" | "manual";
11
+ onMouseDown?: (e: MouseEvent, tabIndex: number) => void;
12
+ onTabAdd?: (tabIndex: number) => void;
13
+ onTabClose?: (tabIndex: number) => void;
14
+ onTabEdit?: (tabIndex: number, label: string) => void;
15
+ onTabSelectionChanged?: (nextIndex: number) => void;
16
+ path?: string;
17
+ showTabs?: boolean;
18
+ toolbarContent?: ReactNode;
19
+ }
@@ -0,0 +1,12 @@
1
+ .TabPanel {
2
+ display: flex;
3
+ flex: 1;
4
+ }
5
+
6
+ .TabPanel > * {
7
+ height: 100%;
8
+ }
9
+
10
+ .TabPanel > .View > .Header {
11
+ display: none;
12
+ }
@@ -0,0 +1,17 @@
1
+ import React, { HTMLAttributes } from 'react';
2
+
3
+ import './TabPanel.css';
4
+
5
+ export interface TabPanelProps extends HTMLAttributes<HTMLDivElement>{
6
+ ariaLabelledBy: string;
7
+ }
8
+
9
+ const TabPanel = ({ ariaLabelledBy, children, id }: TabPanelProps) => {
10
+ return (
11
+ <div className="TabPanel" id={id} role="tabpanel" aria-labelledby={ariaLabelledBy}>
12
+ {children}
13
+ </div>
14
+ );
15
+ };
16
+
17
+ export default TabPanel;
@@ -0,0 +1 @@
1
+ export { default as TabPanel } from './TabPanel';
@@ -0,0 +1,53 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { LayoutConfigurator, LayoutTreeViewer } from '..';
4
+ import { followPathToComponent } from '../..';
5
+
6
+ export const ConfigWrapper = ({ children }) => {
7
+ const designMode = false;
8
+ // const [designMode, setDesignMode] = useState(false);
9
+ const [layout, setLayout] = useState(children);
10
+ const [selectedComponent, setSelectedComponent] = useState(children);
11
+
12
+ const handleSelection = (selectedPath) => {
13
+ const targetComponent = followPathToComponent(layout, selectedPath);
14
+ setSelectedComponent(targetComponent);
15
+ };
16
+
17
+ const handleChange = (property, value) => {
18
+ console.log(`change ${property} -> ${value}`);
19
+
20
+ // 2) replace selectedComponent and set layout
21
+ const newComponent = React.cloneElement(selectedComponent, {
22
+ style: {
23
+ ...selectedComponent.props.style,
24
+ [property]: value
25
+ }
26
+ });
27
+ setSelectedComponent(newComponent);
28
+ setLayout(React.cloneElement(layout, null, newComponent));
29
+ };
30
+
31
+ return (
32
+ <div data-design-mode={`${designMode}`}>
33
+ {layout}
34
+ <br />
35
+ <div style={{ display: 'flex' }}>
36
+ <LayoutConfigurator
37
+ height={300}
38
+ managedStyle={selectedComponent.props.style}
39
+ width={300}
40
+ onChange={handleChange}
41
+ />
42
+ <LayoutTreeViewer
43
+ layout={layout}
44
+ onSelect={handleSelection}
45
+ style={{ width: 300, height: 300, backgroundColor: '#ccc' }}
46
+ />
47
+ </div>
48
+ {/* <StateButton
49
+ defaultChecked={false}
50
+ onChange={(e, value) => setDesignMode(value)}>Design Mode</StateButton> */}
51
+ </div>
52
+ );
53
+ };
@@ -0,0 +1 @@
1
+ export * from './ConfigWrapper';