@corvu/resizable
Version:
Unstyled, accessible and customizable UI primitives for SolidJS
1,384 lines (1,381 loc) • 56.7 kB
JavaScript
import { createContext, useContext, mergeProps, splitProps, createSignal, createMemo, createEffect, onCleanup, on, Show, createUniqueId, untrack, batch } from 'solid-js';
import { useKeyedContext, createKeyedContext } from '@corvu/utils/create/keyedContext';
import { createComponent, mergeProps as mergeProps$1, effect, memo, template } from 'solid-js/web';
import { combineStyle, callEventHandler, sortByDocumentPosition } from '@corvu/utils/dom';
import { Dynamic } from '@corvu/utils/dynamic';
import { mergeRefs, some } from '@corvu/utils/reactivity';
import { dataIf, isFunction } from '@corvu/utils';
import createOnce from '@corvu/utils/create/once';
import createControllableSignal from '@corvu/utils/create/controllableSignal';
import createSize from '@corvu/utils/create/size';
// src/context.ts
var ResizableContext = createContext();
var createResizableContext = (contextId) => {
if (contextId === undefined) return ResizableContext;
const context = createKeyedContext(
`resizable-${contextId}`
);
return context;
};
var useResizableContext = (contextId) => {
if (contextId === undefined) {
const context2 = useContext(ResizableContext);
if (!context2) {
throw new Error(
"[corvu]: Resizable context not found. Make sure to wrap Resizable components in <Resizable.Root>"
);
}
return context2;
}
const context = useKeyedContext(
`resizable-${contextId}`
);
if (!context) {
throw new Error(
`[corvu]: Resizable context with id "${contextId}" not found. Make sure to wrap Resizable components in <Resizable.Root contextId="${contextId}">`
);
}
return context;
};
var InternalResizableContext = createContext();
var createInternalResizableContext = (contextId) => {
if (contextId === undefined) return InternalResizableContext;
const context = createKeyedContext(
`resizable-internal-${contextId}`
);
return context;
};
var useInternalResizableContext = (contextId) => {
if (contextId === undefined) {
const context2 = useContext(InternalResizableContext);
if (!context2) {
throw new Error(
"[corvu]: Resizable context not found. Make sure to wrap Resizable components in <Resizable.Root>"
);
}
return context2;
}
const context = useKeyedContext(
`resizable-internal-${contextId}`
);
if (!context) {
throw new Error(
`[corvu]: Resizable context with id "${contextId}" not found. Make sure to wrap Resizable components in <Resizable.Root contextId="${contextId}">`
);
}
return context;
};
// src/lib/utils.ts
var resolveSize = (size, rootSize) => {
if (typeof size === "number") {
return size;
}
if (!size.endsWith("px")) {
throw new Error(
`[corvu] Sizes must be a number or a string ending with 'px'. Got ${size}`
);
}
return fixToPrecision(parseFloat(size) / rootSize);
};
var splitPanels = (props) => {
const precedingPanels = props.panels.filter(
(panel) => !!(props.focusedElement.compareDocumentPosition(panel.data.element) & Node.DOCUMENT_POSITION_PRECEDING)
);
const followingPanels = props.panels.filter(
(panel) => !!(props.focusedElement.compareDocumentPosition(panel.data.element) & Node.DOCUMENT_POSITION_FOLLOWING)
);
return [precedingPanels, followingPanels];
};
var PRECISION = 6;
var fixToPrecision = (value) => parseFloat(value.toFixed(PRECISION));
// src/lib/cursor.ts
var globalCursorStyle = null;
var cursorStyleElement = null;
var globalResizeConstraints = 0;
var constraintToCursorMap = {
1: "e-resize",
2: "w-resize",
3: "ew-resize",
4: "s-resize",
8: "n-resize",
12: "ns-resize",
5: "se-resize",
9: "ne-resize",
6: "sw-resize",
10: "nw-resize"
};
var cachedCursorStyle = null;
var updateCursorStyle = () => {
if (!globalCursorStyle) {
if (cursorStyleElement) {
cachedCursorStyle = null;
cursorStyleElement.remove();
cursorStyleElement = null;
}
return;
}
let cursorStyle = constraintToCursorMap[globalResizeConstraints] ?? null;
if (cursorStyle === null) {
switch (globalCursorStyle) {
case "horizontal":
cursorStyle = "col-resize";
break;
case "vertical":
cursorStyle = "row-resize";
break;
case "both":
cursorStyle = "move";
break;
}
}
if (cursorStyle === cachedCursorStyle) return;
cachedCursorStyle = cursorStyle;
if (!cursorStyleElement) {
cursorStyleElement = document.createElement("style");
document.head.appendChild(cursorStyleElement);
}
cursorStyleElement.innerHTML = `*{cursor: ${cursorStyle}!important;}`;
};
var reportResizeConstraints = (orientation, constraints) => {
switch (orientation) {
case "horizontal":
if (constraints === 1) {
globalResizeConstraints |= 1;
globalResizeConstraints &= -3;
} else if (constraints === 2) {
globalResizeConstraints |= 2;
globalResizeConstraints &= -2;
} else if (constraints === 3) {
globalResizeConstraints |= 3;
} else {
globalResizeConstraints &= -4;
}
break;
case "vertical":
if (constraints === 1) {
globalResizeConstraints |= 4;
globalResizeConstraints &= -9;
} else if (constraints === 2) {
globalResizeConstraints |= 8;
globalResizeConstraints &= -5;
} else if (constraints === 3) {
globalResizeConstraints |= 12;
} else {
globalResizeConstraints &= -13;
}
break;
}
updateCursorStyle();
};
var resetResizeConstraints = () => {
globalResizeConstraints = 0;
updateCursorStyle();
};
var setGlobalCursorStyle = (cursorStyle) => {
globalCursorStyle = cursorStyle;
updateCursorStyle();
};
var handleResizeConstraints = (props) => {
if (fixToPrecision(props.distributablePercentage) !== fixToPrecision(props.desiredPercentage)) {
let constraints = null;
if (props.betweenCollapse === true) {
constraints = 3;
} else if (props.desiredPercentage < props.distributablePercentage && props.revertConstraints !== true || props.desiredPercentage > props.distributablePercentage && props.revertConstraints === true) {
constraints = 1;
} else {
constraints = 2;
}
reportResizeConstraints(props.orientation, constraints);
} else {
reportResizeConstraints(props.orientation, 0);
}
};
var handles = [];
var dragStartPos = null;
var globalHovered = null;
var INTERSECTION_TOLERANCE = 1;
var equalsWithTolerance = (a, b) => Math.abs(a - b) <= INTERSECTION_TOLERANCE;
var registerHandle = (handle) => {
handles.push(handle);
for (const handle2 of handles) {
for (const compareHandle of handles) {
if (handle2.orientation === compareHandle.orientation || handle2.element === compareHandle.element) {
continue;
}
const handleRect = handle2.element.getBoundingClientRect();
const compareHandleRect = compareHandle.element.getBoundingClientRect();
if (handle2.orientation === "horizontal") {
if (handleRect.left > compareHandleRect.right || handleRect.right < compareHandleRect.left) {
continue;
}
}
if (handle2.orientation === "vertical") {
if (handleRect.top > compareHandleRect.bottom || handleRect.bottom < compareHandleRect.top) {
continue;
}
}
const isStartIntersection = handle2.orientation === "horizontal" ? equalsWithTolerance(handleRect.top, compareHandleRect.bottom) : equalsWithTolerance(handleRect.left, compareHandleRect.right);
const isEndIntersection = handle2.orientation === "horizontal" ? equalsWithTolerance(handleRect.bottom, compareHandleRect.top) : equalsWithTolerance(handleRect.right, compareHandleRect.left);
if (isStartIntersection) {
handle2.startIntersection.setHandle(compareHandle);
}
if (isEndIntersection) {
handle2.endIntersection.setHandle(compareHandle);
}
}
}
return {
onDragStart: (event, target) => onDragStart(handle, event, target),
onHoveredChange: (state) => {
globalHovered = state;
const dragging = !!dragStartPos;
let cursorStyle = null;
batch(() => {
switch (state) {
case "handle": {
const startHandle = handle.startIntersection.handle();
const endHandle = handle.endIntersection.handle();
if (!dragging) {
handle.setActive(true);
startHandle?.setActive(false);
endHandle?.setActive(false);
}
startHandle?.setHoveredAsIntersection(false);
endHandle?.setHoveredAsIntersection(false);
cursorStyle = handle.orientation;
break;
}
case "startIntersection": {
const startHandle = handle.startIntersection.handle();
if (!dragging) {
startHandle?.setActive(true);
}
startHandle?.setHoveredAsIntersection(true);
cursorStyle = "both";
break;
}
case "endIntersection": {
const endHandle = handle.endIntersection.handle();
if (!dragging) {
endHandle?.setActive(true);
}
endHandle?.setHoveredAsIntersection(true);
cursorStyle = "both";
break;
}
case null: {
const startHandle = handle.startIntersection.handle();
const endHandle = handle.endIntersection.handle();
if (!dragging && !handle.focused()) {
handle.setActive(false);
startHandle?.setActive(false);
endHandle?.setActive(false);
}
startHandle?.setHoveredAsIntersection(false);
endHandle?.setHoveredAsIntersection(false);
cursorStyle = null;
break;
}
}
if (!dragging && handle.handleCursorStyle()) {
setGlobalCursorStyle(cursorStyle);
}
});
}
};
};
var unregisterHandle = (handle) => {
handles.splice(handles.indexOf(handle), 1);
};
var onDragStart = (handle, event, target) => {
dragStartPos = { x: event.clientX, y: event.clientY };
batch(() => {
handle.setDragging(true);
if (target === "startIntersection") {
handle.startIntersection.handle()?.setDragging(true);
}
if (target === "endIntersection") {
handle.endIntersection.handle()?.setDragging(true);
}
});
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("touchmove", onTouchMove);
window.addEventListener("pointerup", onDragEnd);
window.addEventListener("touchend", onDragEnd);
window.addEventListener("contextmenu", onDragEnd);
};
var onPointerMove = (event) => onMove(event.clientX, event.clientY, event.altKey);
var onTouchMove = (event) => {
if (!event.touches[0]) return;
onMove(event.touches[0].clientX, event.touches[0].clientY, event.altKey);
};
var altKeyCache = false;
var onMove = (x, y, altKey) => {
if (!dragStartPos) return;
if (handles.some((handle) => handle.dragging() && handle.altKey === "only")) {
altKey = true;
}
if (handles.some((handle) => handle.dragging() && handle.altKey === false)) {
altKey = false;
}
if (altKeyCache !== altKey) {
dragStartPos = { x, y };
altKeyCache = altKey;
}
for (const handle of handles) {
if (!handle.dragging()) continue;
handle.onDrag(
handle.orientation === "horizontal" ? x - dragStartPos.x : y - dragStartPos.y,
altKey
);
}
};
var onDragEnd = (event) => {
batch(() => {
for (const handle of handles) {
if (!handle.dragging()) {
if (some(handle.hovered, handle.focused, handle.hoveredAsIntersection)) {
handle.setActive(true);
if (handle.handleCursorStyle()) {
setGlobalCursorStyle(handle.orientation);
}
}
} else {
handle.setDragging(false);
handle.onDragEnd(event);
if (!handle.hovered() && !handle.hoveredAsIntersection()) {
handle.setActive(false);
}
if (!globalHovered && handle.handleCursorStyle()) {
setGlobalCursorStyle(null);
}
}
}
});
resetResizeConstraints();
dragStartPos = null;
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("touchmove", onTouchMove);
window.removeEventListener("pointerup", onDragEnd);
window.removeEventListener("touchend", onDragEnd);
window.removeEventListener("contextmenu", onDragEnd);
};
var _tmpl$ = /* @__PURE__ */ template(`<div data-corvu-resizable-handle-start-intersection>`);
var _tmpl$2 = /* @__PURE__ */ template(`<div data-corvu-resizable-handle-end-intersection>`);
var ResizableHandle = (props) => {
const defaultedProps = mergeProps({
startIntersection: true,
endIntersection: true,
altKey: true
}, props);
const [localProps, otherProps] = splitProps(defaultedProps, ["startIntersection", "endIntersection", "altKey", "onHandleDragStart", "onHandleDrag", "onHandleDragEnd", "contextId", "ref", "style", "disabled", "children", "onMouseEnter", "onMouseLeave", "onKeyDown", "onKeyUp", "onFocus", "onBlur", "onPointerDown"]);
const [ref, setRef] = createSignal(null);
const [hoveredAsIntersection, setHoveredAsIntersection] = createSignal(false);
const [hovered, setHovered] = createSignal(null);
const [focused, setFocused] = createSignal(false);
const [active, setActive] = createSignal(false);
const [dragging, setDragging] = createSignal(false);
const [startIntersection, setStartIntersection] = createSignal(null);
const [endIntersection, setEndIntersection] = createSignal(null);
const context = createMemo(() => useInternalResizableContext(localProps.contextId));
const ariaInformation = createMemo(() => {
const handle = ref();
if (!handle) {
return undefined;
}
const panels = context().panels();
const [precidingPanels, followingPanels] = splitPanels({
panels,
focusedElement: handle
});
const ariaControls = precidingPanels[precidingPanels.length - 1]?.data.id;
const ariaValueMax = followingPanels.reduce((acc, panel) => acc - resolveSize(panel.data.minSize, context().rootSize()), 1);
const ariaValueMin = precidingPanels.reduce((acc, panel) => acc + resolveSize(panel.data.minSize, context().rootSize()), 0);
const ariaValueNow = precidingPanels.reduce((acc, panel) => acc + resolveSize(panel.size(), context().rootSize()), 0);
return {
ariaControls,
ariaValueMax: fixToPrecision(ariaValueMax),
ariaValueMin: fixToPrecision(ariaValueMin),
ariaValueNow: fixToPrecision(ariaValueNow)
};
});
let globalHandleCallbacks = null;
createEffect(() => {
if (localProps.disabled === true) return;
const element = ref();
if (!element) return;
const globalHandle = {
element,
orientation: context().orientation(),
handleCursorStyle: context().handleCursorStyle,
altKey: localProps.altKey,
startIntersection: {
handle: startIntersection,
setHandle: (handle) => {
if (localProps.startIntersection !== true) return;
setStartIntersection(handle);
}
},
endIntersection: {
handle: endIntersection,
setHandle: (handle) => {
if (localProps.endIntersection !== true) return;
setEndIntersection(handle);
}
},
hovered,
focused,
hoveredAsIntersection,
setHoveredAsIntersection,
active,
setActive,
dragging,
setDragging,
onDrag: (delta, altKey) => {
if (localProps.onHandleDrag !== undefined) {
const dragEvent = new CustomEvent("drag", {
cancelable: true
});
localProps.onHandleDrag(dragEvent);
if (dragEvent.defaultPrevented) return;
}
context().onDrag(element, delta, altKey);
},
onDragEnd: (event) => {
localProps.onHandleDragEnd?.(event);
context().onDragEnd();
}
};
globalHandleCallbacks = registerHandle(globalHandle);
onCleanup(() => {
unregisterHandle(globalHandle);
globalHandleCallbacks = null;
});
});
createEffect(on(hovered, () => {
globalHandleCallbacks?.onHoveredChange(hovered());
}));
const onMouseEnter = (e) => {
if (callEventHandler(localProps.onMouseEnter, e) || localProps.disabled === true) return;
setHovered("handle");
};
const onMouseLeave = (e) => {
if (callEventHandler(localProps.onMouseLeave, e)) return;
setHovered(null);
};
const onKeyDown = (e) => {
if (callEventHandler(localProps.onKeyDown, e) || dragging()) return;
const element = ref();
if (!element) return;
const altKey = localProps.altKey === "only" || localProps.altKey !== false && e.altKey;
context().onKeyDown(element, e, altKey);
};
const onKeyUp = (e) => {
if (callEventHandler(localProps.onKeyUp, e) || e.key !== "Tab") return;
setFocused(true);
};
const onFocus = (e) => {
if (callEventHandler(localProps.onFocus, e) || hovered()) return;
setFocused(true);
setActive(true);
};
const onBlur = (e) => {
if (callEventHandler(localProps.onBlur, e)) return;
setFocused(false);
if (hovered()) return;
setActive(false);
};
const onPointerDown = (e) => {
if (callEventHandler(localProps.onPointerDown, e)) return;
if (callEventHandler(localProps.onHandleDragStart, e)) return;
const targetElement = e.target;
let target = "handle";
if (targetElement.hasAttribute("data-corvu-resizable-handle-start-intersection")) {
target = "startIntersection";
}
if (targetElement.hasAttribute("data-corvu-resizable-handle-end-intersection")) {
target = "endIntersection";
}
globalHandleCallbacks?.onDragStart(e, target);
};
return createComponent(Dynamic, mergeProps$1({
as: "button",
ref(r$) {
var _ref$ = mergeRefs(setRef, localProps.ref);
typeof _ref$ === "function" && _ref$(r$);
},
get style() {
return combineStyle({
position: "relative",
cursor: context().handleCursorStyle() ? "inherit" : undefined,
"touch-action": "none",
"flex-shrink": 0
}, localProps.style);
},
get disabled() {
return localProps.disabled;
},
onBlur,
onFocus,
onKeyDown,
onKeyUp,
onMouseEnter,
onMouseLeave,
onPointerDown,
role: "separator",
get ["aria-controls"]() {
return ariaInformation()?.ariaControls;
},
get ["aria-orientation"]() {
return context().orientation();
},
get ["aria-valuemax"]() {
return ariaInformation()?.ariaValueMax;
},
get ["aria-valuemin"]() {
return ariaInformation()?.ariaValueMin;
},
get ["aria-valuenow"]() {
return ariaInformation()?.ariaValueNow;
},
get ["data-active"]() {
return dataIf(active());
},
get ["data-dragging"]() {
return dataIf(dragging());
},
get ["data-orientation"]() {
return context().orientation();
},
"data-corvu-resizable-handle": ""
}, otherProps, {
get children() {
return [createComponent(Show, {
get when() {
return startIntersection();
},
get children() {
var _el$ = _tmpl$();
_el$.addEventListener("mouseleave", (e) => {
if (ref()?.contains(e.relatedTarget) === true) {
setHovered("handle");
} else {
setHovered(null);
}
});
_el$.addEventListener("mouseenter", () => setHovered("startIntersection"));
_el$.style.setProperty("position", "absolute");
_el$.style.setProperty("aspect-ratio", "1 / 1");
_el$.style.setProperty("top", "0");
_el$.style.setProperty("left", "0");
_el$.style.setProperty("z-index", "1");
effect((_p$) => {
var _v$ = context().orientation() === "horizontal" ? undefined : "100%", _v$2 = context().orientation() === "horizontal" ? "100%" : undefined, _v$3 = context().orientation() === "horizontal" ? "translate3d(0, -100%, 0)" : "translate3d(-100%, 0, 0)";
_v$ !== _p$.e && ((_p$.e = _v$) != null ? _el$.style.setProperty("height", _v$) : _el$.style.removeProperty("height"));
_v$2 !== _p$.t && ((_p$.t = _v$2) != null ? _el$.style.setProperty("width", _v$2) : _el$.style.removeProperty("width"));
_v$3 !== _p$.a && ((_p$.a = _v$3) != null ? _el$.style.setProperty("transform", _v$3) : _el$.style.removeProperty("transform"));
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined
});
return _el$;
}
}), memo(() => localProps.children), createComponent(Show, {
get when() {
return endIntersection();
},
get children() {
var _el$2 = _tmpl$2();
_el$2.addEventListener("mouseleave", (e) => {
if (ref()?.contains(e.relatedTarget) === true) {
setHovered("handle");
} else {
setHovered(null);
}
});
_el$2.addEventListener("mouseenter", () => setHovered("endIntersection"));
_el$2.style.setProperty("position", "absolute");
_el$2.style.setProperty("aspect-ratio", "1 / 1");
_el$2.style.setProperty("bottom", "0");
_el$2.style.setProperty("right", "0");
_el$2.style.setProperty("z-index", "1");
effect((_p$) => {
var _v$4 = context().orientation() === "horizontal" ? undefined : "100%", _v$5 = context().orientation() === "horizontal" ? "100%" : undefined, _v$6 = context().orientation() === "horizontal" ? "translate3d(0, 100%, 0)" : "translate3d(100%, 0, 0)";
_v$4 !== _p$.e && ((_p$.e = _v$4) != null ? _el$2.style.setProperty("height", _v$4) : _el$2.style.removeProperty("height"));
_v$5 !== _p$.t && ((_p$.t = _v$5) != null ? _el$2.style.setProperty("width", _v$5) : _el$2.style.removeProperty("width"));
_v$6 !== _p$.a && ((_p$.a = _v$6) != null ? _el$2.style.setProperty("transform", _v$6) : _el$2.style.removeProperty("transform"));
return _p$;
}, {
e: undefined,
t: undefined,
a: undefined
});
return _el$2;
}
})];
}
}));
};
var Handle_default = ResizableHandle;
var ResizablePanelContext = createContext();
var createResizablePanelContext = (contextId) => {
if (contextId === undefined) return ResizablePanelContext;
const context = createKeyedContext(
`resizable-panel-${contextId}`
);
return context;
};
var useResizablePanelContext = (contextId) => {
if (contextId === undefined) {
const context2 = useContext(ResizablePanelContext);
if (!context2) {
throw new Error(
"[corvu]: Resizable panel context not found. Make sure to call usePanelContext under <Resizable.Panel>"
);
}
return context2;
}
const context = useKeyedContext(
`resizable-panel-${contextId}`
);
if (!context) {
throw new Error(
`[corvu]: Resizable context with id "${contextId}" not found. Make sure to call usePanelContext under <Resizable.Panel contextId="${contextId}">`
);
}
return context;
};
var ResizablePanel = (props) => {
const defaultedProps = mergeProps({
initialSize: null,
minSize: 0,
maxSize: 1,
collapsible: false,
collapsedSize: 0,
collapseThreshold: 0.05,
panelId: createUniqueId()
}, props);
const [localProps, otherProps] = splitProps(defaultedProps, ["initialSize", "minSize", "maxSize", "collapsible", "collapsedSize", "collapseThreshold", "onResize", "onCollapse", "onExpand", "contextId", "panelId", "ref", "style", "children"]);
const [ref, setRef] = createSignal(null);
const context = createMemo(() => useInternalResizableContext(localProps.contextId));
const [panelInstance, setPanelInstance] = createSignal(null);
createEffect(() => {
const element = ref();
if (!element) return;
const _context = context();
const instance = untrack(() => {
return _context.registerPanel({
id: localProps.panelId,
element,
initialSize: localProps.initialSize,
minSize: localProps.minSize,
maxSize: localProps.maxSize,
collapsible: localProps.collapsible,
collapsedSize: localProps.collapsedSize,
collapseThreshold: localProps.collapseThreshold,
onResize: localProps.onResize
});
});
setPanelInstance(instance);
onCleanup(() => {
_context.unregisterPanel(instance.data.id);
});
});
const panelSize = () => {
const instance = panelInstance();
if (!instance) {
if (typeof localProps.initialSize === "number") {
return localProps.initialSize;
}
return 1;
}
return instance.size();
};
const collapsed = createMemo((prev) => {
const instance = panelInstance();
if (localProps.collapsible !== true) {
return false;
}
const collapsed2 = instance ? instance.size() === resolveSize(localProps.collapsedSize, context().rootSize()) : false;
if (instance && prev !== collapsed2) {
if (collapsed2 && localProps.onCollapse !== undefined) {
localProps.onCollapse(instance.size());
} else if (!collapsed2 && localProps.onExpand !== undefined) {
localProps.onExpand(instance.size());
}
}
return collapsed2;
});
const resize2 = (size, strategy) => {
const instance = panelInstance();
if (!instance) {
return;
}
instance.resize(size, strategy ?? "both");
};
const collapse = (strategy) => {
const instance = panelInstance();
if (!instance) {
return;
}
instance.collapse(strategy ?? "both");
};
const expand = (strategy) => {
const instance = panelInstance();
if (!instance) {
return;
}
instance.expand(strategy ?? "both");
};
const childrenProps = {
get size() {
return panelSize();
},
get minSize() {
return localProps.minSize;
},
get maxSize() {
return localProps.maxSize;
},
get collapsible() {
return localProps.collapsible;
},
get collapsedSize() {
return localProps.collapsedSize;
},
get collapseThreshold() {
return localProps.collapseThreshold;
},
get collapsed() {
return collapsed();
},
resize: resize2,
collapse,
expand,
get panelId() {
return localProps.panelId;
}
};
const memoizedChildren = createOnce(() => localProps.children);
const resolveChildren = () => {
const children = memoizedChildren()();
if (isFunction(children)) {
return children(childrenProps);
}
return children;
};
const memoizedResizablePanel = createMemo(() => {
const ResizablePanelContext2 = createResizablePanelContext(localProps.contextId);
return createComponent(ResizablePanelContext2.Provider, {
value: {
size: panelSize,
minSize: () => localProps.minSize,
maxSize: () => localProps.maxSize,
collapsible: () => localProps.collapsible,
collapsedSize: () => localProps.collapsedSize,
collapseThreshold: () => localProps.collapseThreshold,
collapsed,
resize: resize2,
collapse,
expand,
panelId: () => localProps.panelId
},
get children() {
return createComponent(Dynamic, mergeProps$1({
as: "div",
ref(r$) {
var _ref$ = mergeRefs(setRef, localProps.ref);
typeof _ref$ === "function" && _ref$(r$);
},
get style() {
return combineStyle({
"flex-basis": panelSize() * 100 + "%"
}, localProps.style);
},
get id() {
return localProps.panelId;
},
get ["data-collapsed"]() {
return dataIf(collapsed());
},
get ["data-expanded"]() {
return dataIf(localProps.collapsible === true && !collapsed());
},
get ["data-orientation"]() {
return context().orientation();
},
"data-corvu-resizable-panel": ""
}, otherProps, {
get children() {
return untrack(() => resolveChildren());
}
}));
}
});
});
return memoizedResizablePanel;
};
var Panel_default = ResizablePanel;
var getDistributablePercentage = (props) => {
let distributablePercentage = props.desiredPercentage >= 0 ? Infinity : -Infinity;
let newSizes = props.initialSizes;
for (const resizeAction of props.resizeActions) {
const desiredPercentage = resizeAction.negate !== true ? props.desiredPercentage : -props.desiredPercentage;
let [
distributedPercentagePreceding,
// eslint-disable-next-line prefer-const
distributedSizesPreceding,
// eslint-disable-next-line prefer-const
collapsedPreceding
] = distributePercentage({
desiredPercentage,
side: "preceding",
panels: resizeAction.precedingPanels,
initialSizes: newSizes,
initialSizesStartIndex: 0,
collapsible: props.collapsible,
rootSize: props.resizableData.rootSize
});
let [
distributedPercentageFollowing,
// eslint-disable-next-line prefer-const
distributedSizesFollowing,
// eslint-disable-next-line prefer-const
collapsedFollowing
] = distributePercentage({
desiredPercentage,
side: "following",
panels: resizeAction.followingPanels,
initialSizes: newSizes,
initialSizesStartIndex: resizeAction.precedingPanels.length,
collapsible: props.collapsible,
rootSize: props.resizableData.rootSize
});
if (resizeAction.negate === true) {
distributedPercentagePreceding = -distributedPercentagePreceding;
distributedPercentageFollowing = -distributedPercentageFollowing;
}
if (collapsedPreceding) {
distributedPercentageFollowing = distributedPercentagePreceding;
}
if (collapsedFollowing) {
distributedPercentagePreceding = distributedPercentageFollowing;
}
if (props.desiredPercentage >= 0) {
distributablePercentage = Math.min(
distributablePercentage,
Math.min(
distributedPercentagePreceding,
distributedPercentageFollowing
)
);
} else {
distributablePercentage = Math.max(
distributablePercentage,
Math.max(
distributedPercentagePreceding,
distributedPercentageFollowing
)
);
}
newSizes = [...distributedSizesPreceding, ...distributedSizesFollowing];
}
return distributablePercentage;
};
var distributePercentage = (props) => {
props.desiredPercentage = fixToPrecision(props.desiredPercentage);
const resizeDirection = getResizeDirection({
side: props.side,
desiredPercentage: props.desiredPercentage
});
let distributedPercentage = 0;
const distributedSizes = props.initialSizes.slice(
props.initialSizesStartIndex,
props.initialSizesStartIndex + props.panels.length
);
for (let i = props.side === "preceding" ? props.panels.length - 1 : 0; props.side === "preceding" ? i >= 0 : i < props.panels.length; props.side === "preceding" ? i-- : i++) {
const panel2 = props.panels[i];
const panelSize2 = props.initialSizes[i + props.initialSizesStartIndex];
const collapsedSize2 = resolveSize(
panel2.data.collapsedSize ?? 0,
props.rootSize
);
if (panel2.data.collapsible && panelSize2 === collapsedSize2) continue;
const availablePercentage2 = fixToPrecision(
props.desiredPercentage - distributedPercentage
);
if (availablePercentage2 === 0) break;
switch (resizeDirection) {
case "precedingDecreasing": {
const minSize2 = resolveSize(panel2.data.minSize, props.rootSize);
distributedSizes[i] = Math.max(minSize2, panelSize2 + availablePercentage2);
distributedPercentage += distributedSizes[i] - panelSize2;
break;
}
case "followingDecreasing": {
const minSize2 = resolveSize(panel2.data.minSize, props.rootSize);
distributedSizes[i] = Math.max(minSize2, panelSize2 - availablePercentage2);
distributedPercentage -= distributedSizes[i] - panelSize2;
break;
}
case "precedingIncreasing": {
const maxSize = resolveSize(panel2.data.maxSize, props.rootSize);
distributedSizes[i] = Math.min(maxSize, panelSize2 + availablePercentage2);
distributedPercentage += distributedSizes[i] - panelSize2;
break;
}
case "followingIncreasing": {
const maxSize = resolveSize(panel2.data.maxSize, props.rootSize);
distributedSizes[i] = Math.min(maxSize, panelSize2 - availablePercentage2);
distributedPercentage -= distributedSizes[i] - panelSize2;
break;
}
}
}
distributedPercentage = fixToPrecision(distributedPercentage);
if (!props.collapsible || distributedPercentage === props.desiredPercentage) {
return [distributedPercentage, distributedSizes, false];
}
const panelIndex = props.side === "preceding" ? props.panels.length - 1 : 0;
const panel = props.panels[panelIndex];
if (!panel.data.collapsible) {
return [distributedPercentage, distributedSizes, false];
}
const availablePercentage = fixToPrecision(
props.desiredPercentage - distributedPercentage
);
let collapsed = false;
const panelSize = props.initialSizes[panelIndex + props.initialSizesStartIndex];
const minSize = resolveSize(panel.data.minSize, props.rootSize);
const collapsedSize = resolveSize(
panel.data.collapsedSize ?? 0,
props.rootSize
);
const collapseThreshold = Math.min(
resolveSize(panel.data.collapseThreshold ?? 0, props.rootSize),
minSize - collapsedSize
);
const isCollapsed = panelSize === collapsedSize;
if (resizeDirection === "precedingDecreasing" && !isCollapsed && Math.abs(availablePercentage) >= collapseThreshold) {
distributedPercentage -= distributedSizes[panelIndex] - panelSize;
distributedSizes[panelIndex] = collapsedSize;
distributedPercentage += distributedSizes[panelIndex] - panelSize;
collapsed = true;
} else if (resizeDirection === "precedingIncreasing" && isCollapsed && Math.abs(availablePercentage) >= collapseThreshold) {
const minSize2 = resolveSize(panel.data.minSize, props.rootSize);
distributedSizes[panelIndex] = minSize2;
if (Math.abs(availablePercentage) >= minSize2 - collapsedSize) {
const maxSize = resolveSize(panel.data.maxSize, props.rootSize);
distributedSizes[panelIndex] = Math.min(
maxSize,
panelSize + availablePercentage
);
} else {
collapsed = true;
}
distributedPercentage += distributedSizes[panelIndex] - panelSize;
} else if (resizeDirection === "followingDecreasing" && !isCollapsed && Math.abs(availablePercentage) >= collapseThreshold) {
distributedPercentage += distributedSizes[panelIndex] - panelSize;
distributedSizes[panelIndex] = collapsedSize;
distributedPercentage -= distributedSizes[panelIndex] - panelSize;
collapsed = true;
} else if (resizeDirection === "followingIncreasing" && isCollapsed && Math.abs(availablePercentage) >= collapseThreshold) {
const minSize2 = resolveSize(panel.data.minSize, props.rootSize);
distributedSizes[panelIndex] = minSize2;
if (Math.abs(availablePercentage) >= minSize2 - collapsedSize) {
const maxSize = resolveSize(panel.data.maxSize, props.rootSize);
distributedSizes[panelIndex] = Math.min(
maxSize,
panelSize - availablePercentage
);
} else {
collapsed = true;
}
distributedPercentage -= distributedSizes[panelIndex] - panelSize;
}
return [distributedPercentage, distributedSizes, collapsed];
};
var getResizeDirection = (props) => {
switch (props.side) {
case "preceding":
return props.desiredPercentage >= 0 ? "precedingIncreasing" : "precedingDecreasing";
case "following":
return props.desiredPercentage >= 0 ? "followingDecreasing" : "followingIncreasing";
}
};
var resize = (props) => {
let newSizes = props.initialSizes;
for (const resizeAction of props.resizeActions) {
const [, distributedSizesPreceding] = distributePercentage({
desiredPercentage: resizeAction.deltaPercentage,
side: "preceding",
panels: resizeAction.precedingPanels,
initialSizes: newSizes,
initialSizesStartIndex: 0,
collapsible: props.collapsible,
rootSize: props.resizableData.rootSize
});
const [, distributedSizesFollowing] = distributePercentage({
desiredPercentage: resizeAction.deltaPercentage,
side: "following",
panels: resizeAction.followingPanels,
initialSizes: newSizes,
initialSizesStartIndex: resizeAction.precedingPanels.length,
collapsible: props.collapsible,
rootSize: props.resizableData.rootSize
});
newSizes = [...distributedSizesPreceding, ...distributedSizesFollowing];
}
newSizes = newSizes.map(fixToPrecision);
const totalSize = newSizes.reduce((totalSize2, size) => totalSize2 + size, 0);
if (totalSize !== 1) {
const offset = totalSize - 1;
const offsetPerPanel = offset / newSizes.length;
newSizes = newSizes.map((size) => size - offsetPerPanel);
}
props.resizableData.setSizes(newSizes.map(fixToPrecision));
};
var resizePanel = (props) => {
let [precedingPanels, followingPanels] = splitPanels({
panels: props.panels,
focusedElement: props.panel.data.element
});
const panelIndex = props.panels.indexOf(props.panel);
if (panelIndex === 0) {
props.strategy = "following";
} else if (panelIndex === props.panels.length - 1) {
props.strategy = "preceding";
}
if (props.strategy === "both") {
const precedingPanelsIncluding = [...precedingPanels, props.panel];
const followingPanelsIncluding = [props.panel, ...followingPanels];
const distributablePercentage = getDistributablePercentage({
desiredPercentage: props.deltaPercentage / 2,
initialSizes: props.initialSizes,
collapsible: true,
resizeActions: [
{
precedingPanels: precedingPanelsIncluding,
followingPanels
},
{
precedingPanels,
followingPanels: followingPanelsIncluding,
negate: true
}
],
resizableData: {
rootSize: props.resizableData.rootSize
}
});
resize({
initialSizes: props.initialSizes,
collapsible: true,
resizeActions: [
{
precedingPanels: precedingPanelsIncluding,
followingPanels,
deltaPercentage: distributablePercentage
},
{
precedingPanels,
followingPanels: followingPanelsIncluding,
deltaPercentage: -distributablePercentage
}
],
resizableData: props.resizableData
});
} else {
precedingPanels = props.strategy === "preceding" ? precedingPanels : [...precedingPanels, props.panel];
followingPanels = props.strategy === "following" ? followingPanels : [props.panel, ...followingPanels];
if (props.strategy === "preceding") {
props.deltaPercentage = -props.deltaPercentage;
}
const distributablePercentage = getDistributablePercentage({
desiredPercentage: props.deltaPercentage,
initialSizes: props.initialSizes,
collapsible: props.collapsible,
resizeActions: [
{
precedingPanels,
followingPanels
}
],
resizableData: {
rootSize: props.resizableData.rootSize
}
});
resize({
initialSizes: props.initialSizes,
collapsible: true,
resizeActions: [
{
precedingPanels,
followingPanels,
deltaPercentage: distributablePercentage
}
],
resizableData: props.resizableData
});
}
};
var deltaResize = (props) => {
if (props.altKey && props.panels.length > 2) {
let panelIndex = props.panels.filter(
(panel2) => !!(props.handle.compareDocumentPosition(panel2.data.element) & Node.DOCUMENT_POSITION_PRECEDING)
).length - 1;
const isPrecedingHandle = panelIndex === 0;
if (isPrecedingHandle) {
panelIndex++;
props.deltaPercentage = -props.deltaPercentage;
}
const panel = props.panels[panelIndex];
const panelSize = props.initialSizes[panelIndex];
const minDelta = resolveSize(panel.data.minSize, props.resizableData.rootSize) - panelSize;
const maxDelta = resolveSize(panel.data.maxSize, props.resizableData.rootSize) - panelSize;
const cappedDeltaPercentage = Math.max(minDelta, Math.min(props.deltaPercentage * 2, maxDelta)) / 2;
const [precedingPanels, followingPanels] = splitPanels({
panels: props.panels,
focusedElement: panel.data.element
});
const precedingPanelsIncluding = [...precedingPanels, panel];
const followingPanelsIncluding = [panel, ...followingPanels];
const distributablePercentage = getDistributablePercentage({
desiredPercentage: cappedDeltaPercentage,
initialSizes: props.initialSizes,
collapsible: false,
resizeActions: [
{
precedingPanels: precedingPanelsIncluding,
followingPanels
},
{
precedingPanels,
followingPanels: followingPanelsIncluding,
negate: true
}
],
resizableData: {
rootSize: props.resizableData.rootSize
}
});
if (props.resizableData.handleCursorStyle === true) {
handleResizeConstraints({
orientation: props.resizableData.orientation,
desiredPercentage: props.deltaPercentage,
distributablePercentage,
revertConstraints: isPrecedingHandle
});
}
resize({
initialSizes: props.initialSizes,
collapsible: false,
resizeActions: [
{
precedingPanels: precedingPanelsIncluding,
followingPanels,
deltaPercentage: distributablePercentage
},
{
precedingPanels,
followingPanels: followingPanelsIncluding,
deltaPercentage: -distributablePercentage
}
],
resizableData: props.resizableData
});
} else {
const [precedingPanels, followingPanels] = splitPanels({
panels: props.panels,
focusedElement: props.handle
});
const distributablePercentage = getDistributablePercentage({
desiredPercentage: props.deltaPercentage,
initialSizes: props.initialSizes,
collapsible: true,
resizeActions: [
{
precedingPanels,
followingPanels
}
],
resizableData: {
rootSize: props.resizableData.rootSize
}
});
resize({
initialSizes: props.initialSizes,
collapsible: true,
resizeActions: [
{
precedingPanels,
followingPanels,
deltaPercentage: distributablePercentage
}
],
resizableData: props.resizableData
});
if (props.resizableData.handleCursorStyle) {
const fixedDesiredPercentage = fixToPrecision(props.deltaPercentage);
const fixedDistributablePercentage = fixToPrecision(
distributablePercentage
);
let betweenCollapse = false;
const precedingPanel = precedingPanels[precedingPanels.length - 1];
if (precedingPanel.data.collapsible) {
const precedingCollapsedSize = resolveSize(
precedingPanel.data.collapsedSize ?? 0,
props.resizableData.rootSize
);
if (precedingPanel.size() === precedingCollapsedSize && fixedDesiredPercentage > fixedDistributablePercentage || precedingPanel.size() !== precedingCollapsedSize && fixedDesiredPercentage < fixedDistributablePercentage) {
betweenCollapse = true;
}
}
const followingPanel = followingPanels[0];
if (followingPanel.data.collapsible) {
const followingCollapsedSize = resolveSize(
followingPanel.data.collapsedSize ?? 0,
props.resizableData.rootSize
);
if (followingPanel.size() === followingCollapsedSize && fixedDesiredPercentage < fixedDistributablePercentage || followingPanel.size() !== followingCollapsedSize && fixedDesiredPercentage > fixedDistributablePercentage) {
betweenCollapse = true;
}
}
handleResizeConstraints({
orientation: props.resizableData.orientation,
desiredPercentage: props.deltaPercentage,
distributablePercentage,
betweenCollapse
});
}
}
};
var ResizableRoot = (props) => {
const defaultedProps = mergeProps({
orientation: "horizontal",
initialSizes: [],
keyboardDelta: 0.1,
handleCursorStyle: true
}, props);
const [localProps, otherProps] = splitProps(defaultedProps, ["orientation", "sizes", "onSizesChange", "initialSizes", "keyboardDelta", "handleCursorStyle", "contextId", "ref", "style", "children"]);
const [sizes, setSizes] = createControllableSignal({
value: () => localProps.sizes,
initialValue: [],
onChange: localProps.onSizesChange
});
const [ref, setRef] = createSignal(null);
const rootSize = createSize({
element: ref,
dimension: () => localProps.orientation === "horizontal" ? "width" : "height"
});
const [panels, setPanels] = createSignal([]);
const sizesToIds = [];
const registerPanel = (panelData) => {
const _panels = panels();
const panelIndex = _panels.filter((panel2) => !!(panelData.element.compareDocumentPosition(panel2.data.element) & Node.DOCUMENT_POSITION_PRECEDING)).length;
const idExists = sizesToIds[panelIndex] === undefined || sizesToIds[panelIndex] === panelData.id;
const sizeExists = sizes()[panelIndex] !== undefined;
let panelSize = null;
if (panelData.initialSize !== null) {
panelSize = resolveSize(panelData.initialSize, rootSize());
} else if (localProps.initialSizes[panelIndex] !== undefined && idExists) {
panelSize = resolveSize(localProps.initialSizes[panelIndex], rootSize());
}
panelSize = panelSize ?? 0.5;
setSizes((sizes2) => {
let newSizes = [...sizes2];
const previousTotalSize = newSizes.reduce((totalSize, size) => totalSize + size, 0);
if ((idExists && !sizeExists || !idExists) && previousTotalSize === 1) {
const offsetPerPanel = panelSize / newSizes.length;
newSizes = newSizes.map((size) => size - offsetPerPanel);
}
if (idExists) {
if (!sizeExists) {
newSizes[panelIndex] = panelSize;
}
sizesToIds[panelIndex] = panelData.id;
} else {
newSizes.splice(panelIndex, 0, panelSize);
sizesToIds.splice(panelIndex, 0, panelData.id);
}
return newSizes;
});
const panelSizeMemo = createMemo(() => {
const index = sizesToIds.indexOf(panelData.id);
return sizes()[index];
});
createEffect(() => panelData.onResize?.(panelSizeMemo()));
const panel = {
data: panelData,
size: panelSizeMemo,
resize: (size, strategy) => resize2(sizesToIds.indexOf(panelData.id), size, strategy),
collapse: (strategy) => collapse(sizesToIds.indexOf(panelData.id), strategy),
expand: (strategy) => expand(sizesToIds.indexOf(panelData.id), strategy)
};
setPanels((panels2) => {
const newPanels = [...panels2];
newPanels.push(panel);
newPanels.sort((a, b) => sortByDocumentPosition(a.data.element, b.data.element));
return newPanels;
});
return panel;
};
const unregisterPanel = (id) => {
setPanels((panels2) => panels2.filter((panel) => panel.data.id !== id));
const panelSizeIndex = sizesToIds.indexOf(id);
sizesToIds.splice(panelSizeIndex, 1);
setSizes((sizes2) => {
let newSizes = [...sizes2];
newSizes.splice(panelSizeIndex, 1);
const totalSize = newSizes.reduce((totalSize2, size) => totalSize2 + size, 0);
const offset = totalSize - 1;
const offsetPerPanel = offset / newSizes.length;
newSizes = newSizes.map((size) => size + offsetPerPanel);
return newSizes;
});
};
createEffect(() => {
if (localProps.onSizesChange !== undefined) {
localProps.onSizesChange(sizes());
}
});
const resize2 = (panelIndex, size, strategy) => {
untrack(() => {
const panel = panels()[panelIndex];
if (!panel) return;
const minSize = resolveSize(panel.data.minSize, rootSize());
const maxSize = resolveSize(panel.data.maxSize, rootSize());
const newSize = resolveSize(size, rootSize());
const allowedSize = Math.max(minSize, Math.min(newSize, maxSize));
const deltaPercentage = allowedSize - sizes()[panelIndex];
resizePanel({
deltaPercentage,
strategy: strategy ?? "both",
panel,
panels: panels(),
initialSizes: panels().map((panel2) => panel2.size()),
collapsible: false,
resizableData: {
rootSize: rootSize(),
orientation: localProps.orientation,
setSizes
}
});
});
};
const collapse = (panelIndex, strategy) => {
untrack(() => {
const panel = panels()[panelIndex];
if (!panel) return;
const panelSize = sizes()[panelIndex];
const collapsedSize = resolveSize(panel.data.collapsedSize ?? 0, rootSize());
if (!panel.data.collapsible || panelSize === collapsedSize) return;
const deltaPercentage = collapsedSize - panelSize;
resizePanel({
deltaPercentage,
strategy: strategy ?? "both",
panel,
panels: panels(),
initialSizes: panels().map((panel2) => panel2.size(