@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
206 lines (205 loc) • 5.4 kB
JavaScript
import { AXES } from "./axes.mjs";
import { isRtl, lockBody } from "./dom.mjs";
import { timing } from "./transition.mjs";
import { clamp, emitter } from "./utils.mjs";
import { animate, motionValue } from "motion/react";
//#region src/base-ui/DraggablePanel/core/controller.ts
const KEY_STEP = 10;
const KEY_STEP_FAST = 50;
const SCREEN_DELTA = {
ArrowDown: 1,
ArrowLeft: -1,
ArrowRight: 1,
ArrowUp: -1
};
const createPanelController = (initial) => {
const { emit: notify, subscribe } = emitter();
let element = null;
let options = initial;
let target = initial.expand ? initial.size : 0;
let state = {
collapsing: false,
dragging: false,
folding: false
};
let unlock;
let foldTo = target;
const size = motionValue(target);
const content = motionValue(initial.size);
const session = {
collapsing: false,
max: 0,
min: 0,
sign: 1,
start: 0
};
const patch = (next) => {
if (Object.entries(next).some(([key, value]) => state[key] !== value)) {
state = {
...state,
...next
};
notify();
}
};
const bounds = () => {
const min = Math.max(0, options.min);
return {
max: options.max === void 0 ? Number.POSITIVE_INFINITY : Math.max(min, options.max),
min
};
};
const growSign = () => {
const axis = AXES[options.placement];
return axis.grow * (axis.vertical || !isRtl(element) ? 1 : -1);
};
const fold = (to) => {
foldTo = to;
patch({ folding: true });
if (size.get() === 0) content.jump(to || content.get());
else if (to > 0) animate(content, to, timing());
animate(size, to, timing());
};
const release = () => {
unlock?.();
unlock = void 0;
};
const stopDragging = () => {
release();
patch({
collapsing: false,
dragging: false
});
};
const drag = {
cancel: () => {
if (!state.dragging) return;
size.jump(session.start);
content.jump(session.start);
stopDragging();
},
end: () => {
if (!state.dragging) return;
const { collapsing, start } = session;
const committed = collapsing ? start : size.get();
stopDragging();
if (collapsing) {
content.jump(start);
options.onExpandChange?.(false);
}
options.onSizeChange?.(committed, committed - start);
},
move: (offset) => {
if (!state.dragging) return;
const axis = AXES[options.placement];
const pixels = session.start + offset[axis.point] * session.sign;
const collapsing = options.collapseThreshold !== void 0 && pixels < options.collapseThreshold;
const next = collapsing ? 0 : Math.round(clamp(pixels, session.min, session.max));
size.jump(next);
if (!collapsing) content.jump(next);
if (collapsing !== session.collapsing) {
session.collapsing = collapsing;
patch({ collapsing });
}
const reported = collapsing ? session.start : next;
options.onSizeDragging?.(reported, reported - session.start);
},
start: () => {
Object.assign(session, {
...bounds(),
collapsing: false,
sign: growSign(),
start: size.get()
});
release();
unlock = lockBody(AXES[options.placement].cursor, drag.cancel);
patch({
dragging: true,
folding: false
});
}
};
const resizeByKey = (event) => {
if (event.key === "Enter" || event.key === " ") {
if (!options.onExpandChange) return;
event.preventDefault();
options.onExpandChange(!options.expand);
return;
}
const { max, min } = bounds();
const step = (event.shiftKey || event.key === "PageUp" || event.key === "PageDown" ? KEY_STEP_FAST : KEY_STEP) * growSign();
const screen = (AXES[options.placement].vertical ? event.key === "ArrowUp" || event.key === "ArrowDown" : event.key === "ArrowLeft" || event.key === "ArrowRight") ? SCREEN_DELTA[event.key] : void 0;
const moves = {
End: Number.isFinite(max) ? max : target,
Home: min,
PageDown: target + step,
PageUp: target - step
};
const next = screen === void 0 ? moves[event.key] : target + screen * step;
if (next === void 0) return;
event.preventDefault();
const value = clamp(next, min, max);
if (!options.expand && value > 0) options.onExpandChange?.(true);
options.onSizeChange?.(value, value - target);
};
const sync = (next) => {
options = next;
const value = next.expand ? next.size : 0;
if (size.get() === 0 && value > 0) content.jump(next.size);
if (value !== target) {
target = value;
notify();
}
if (state.dragging) return;
if (size.get() === target) {
size.stop();
content.stop();
if (state.folding) {
patch({ folding: false });
content.jump(target || content.get());
}
foldTo = target;
} else if (!state.folding || foldTo !== target) fold(target);
};
return {
attach: (node) => {
element = node;
const stopSettle = size.on("animationComplete", () => {
if (size.get() === target) patch({ folding: false });
});
return () => {
stopSettle();
release();
element = null;
};
},
get axis() {
return AXES[options.placement];
},
bounds,
drag,
motion: {
content,
size
},
get options() {
return options;
},
reset: () => {
if (!options.expand) options.onExpandChange?.(true);
options.onSizeChange?.(options.defaultSize, options.defaultSize - target);
},
resizeByKey,
get state() {
return state;
},
subscribe,
sync,
get target() {
return target;
}
};
};
//#endregion
export { createPanelController };
//# sourceMappingURL=controller.mjs.map