mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 10:31:15 +00:00
167 lines
4.5 KiB
TypeScript
167 lines
4.5 KiB
TypeScript
import { type ResizeData } from '@n8n/design-system';
|
|
import { useLocalStorage } from '@vueuse/core';
|
|
import { computed, type MaybeRef, ref, unref, watch } from 'vue';
|
|
|
|
type GetSize = number | ((containerSize: number) => number);
|
|
|
|
interface UseResizablePanelOptions {
|
|
/**
|
|
* Container element, to which relative size is calculated (doesn't necessarily have to be DOM parent node)
|
|
*/
|
|
container: MaybeRef<HTMLElement | null>;
|
|
/**
|
|
* Default size in pixels
|
|
*/
|
|
defaultSize: GetSize;
|
|
/**
|
|
* Minimum size in pixels
|
|
*/
|
|
minSize?: GetSize;
|
|
/**
|
|
* Maximum size in pixels
|
|
*/
|
|
maxSize?: GetSize;
|
|
/**
|
|
* Which end of the container the resizable element itself is located
|
|
*/
|
|
position?: 'left' | 'bottom';
|
|
/**
|
|
* If set to true, snaps to default size when resizing close to it
|
|
*/
|
|
snap?: boolean;
|
|
/**
|
|
* If set to true, resizing beyond minSize sets size to 0 and isCollapsed to true
|
|
* until onResizeEnd is called
|
|
*/
|
|
allowCollapse?: boolean;
|
|
/**
|
|
* If set to true, resizing beyond maxSize sets size to the container size and
|
|
* isFullSize to true until onResizeEnd is called
|
|
*/
|
|
allowFullSize?: boolean;
|
|
}
|
|
|
|
export function useResizablePanel(
|
|
localStorageKey: string,
|
|
{
|
|
container,
|
|
defaultSize,
|
|
snap = true,
|
|
minSize = 0,
|
|
maxSize = (size) => size,
|
|
position = 'left',
|
|
allowCollapse,
|
|
allowFullSize,
|
|
}: UseResizablePanelOptions,
|
|
) {
|
|
const containerSize = ref(0);
|
|
const persistedSize = useLocalStorage(localStorageKey, -1, { writeDefaults: false });
|
|
const isResizing = ref(false);
|
|
const sizeOnResizeStart = ref<number>();
|
|
const minSizeValue = computed(() => resolveSize(minSize, containerSize.value));
|
|
const maxSizeValue = computed(() => resolveSize(maxSize, containerSize.value));
|
|
const constrainedSize = computed(() => {
|
|
const sizeInPixels =
|
|
persistedSize.value >= 0 && persistedSize.value <= 1
|
|
? containerSize.value * persistedSize.value
|
|
: -1;
|
|
|
|
if (isResizing.value && allowCollapse && sizeInPixels < 30) {
|
|
return 0;
|
|
}
|
|
|
|
if (isResizing.value && allowFullSize && sizeInPixels > containerSize.value - 30) {
|
|
return containerSize.value;
|
|
}
|
|
|
|
const defaultSizeValue = resolveSize(defaultSize, containerSize.value);
|
|
|
|
if (Number.isNaN(sizeInPixels) || !Number.isFinite(sizeInPixels) || sizeInPixels < 0) {
|
|
return defaultSizeValue;
|
|
}
|
|
|
|
return Math.max(
|
|
minSizeValue.value,
|
|
Math.min(
|
|
snap && Math.abs(defaultSizeValue - sizeInPixels) < 30 ? defaultSizeValue : sizeInPixels,
|
|
maxSizeValue.value,
|
|
),
|
|
);
|
|
});
|
|
|
|
function getSize(el: { width: number; height: number }) {
|
|
return position === 'bottom' ? el.height : el.width;
|
|
}
|
|
|
|
function getOffsetSize(el: { offsetWidth: number; offsetHeight: number }) {
|
|
return position === 'bottom' ? el.offsetHeight : el.offsetWidth;
|
|
}
|
|
|
|
function getValue(data: { x: number; y: number }) {
|
|
return position === 'bottom' ? data.y : data.x;
|
|
}
|
|
|
|
function resolveSize(getter: GetSize, containerSizeValue: number): number {
|
|
return typeof getter === 'number' ? getter : getter(containerSizeValue);
|
|
}
|
|
|
|
function onResize(data: ResizeData) {
|
|
const containerRect = unref(container)?.getBoundingClientRect();
|
|
const newSizeInPixels = Math.max(
|
|
0,
|
|
position === 'bottom'
|
|
? (containerRect ? getSize(containerRect) : 0) - getValue(data)
|
|
: getValue(data) - (containerRect ? getValue(containerRect) : 0),
|
|
);
|
|
|
|
isResizing.value = true;
|
|
persistedSize.value = newSizeInPixels / containerSize.value;
|
|
|
|
if (sizeOnResizeStart.value === undefined) {
|
|
sizeOnResizeStart.value = persistedSize.value;
|
|
}
|
|
}
|
|
|
|
function onResizeEnd() {
|
|
// If resizing ends with either collapsing or maximizing the panel, restore size at the start of dragging
|
|
if (
|
|
(minSizeValue.value > 0 && constrainedSize.value <= 0) ||
|
|
(maxSizeValue.value < containerSize.value && constrainedSize.value >= containerSize.value)
|
|
) {
|
|
persistedSize.value = sizeOnResizeStart.value;
|
|
}
|
|
|
|
sizeOnResizeStart.value = undefined;
|
|
isResizing.value = false;
|
|
}
|
|
|
|
watch(
|
|
() => unref(container),
|
|
(el, _, onCleanUp) => {
|
|
if (!el) {
|
|
return;
|
|
}
|
|
|
|
const observer = new ResizeObserver(() => {
|
|
containerSize.value = getOffsetSize(el);
|
|
});
|
|
|
|
observer.observe(el);
|
|
|
|
containerSize.value = getOffsetSize(el);
|
|
|
|
onCleanUp(() => observer.disconnect());
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
|
|
return {
|
|
isResizing: computed(() => isResizing.value),
|
|
isCollapsed: computed(() => isResizing.value && constrainedSize.value <= 0),
|
|
isFullSize: computed(() => isResizing.value && constrainedSize.value >= containerSize.value),
|
|
size: constrainedSize,
|
|
onResize,
|
|
onResizeEnd,
|
|
};
|
|
}
|