@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
1 lines • 11.7 kB
Source Map (JSON)
{"version":3,"file":"controller.mjs","names":[],"sources":["../../../../src/base-ui/DraggablePanel/core/controller.ts"],"sourcesContent":["import { animate, type MotionValue, motionValue } from 'motion/react';\n\nimport type { Axis, Placement } from './axes';\nimport { AXES } from './axes';\nimport { isRtl, lockBody } from './dom';\nimport { timing } from './transition';\nimport { clamp, emitter } from './utils';\n\nconst KEY_STEP = 10;\nconst KEY_STEP_FAST = 50;\n\nconst SCREEN_DELTA: Record<string, number> = {\n ArrowDown: 1,\n ArrowLeft: -1,\n ArrowRight: 1,\n ArrowUp: -1,\n};\n\nexport interface PanelControllerOptions {\n collapseThreshold?: number;\n defaultSize: number;\n expand: boolean;\n max?: number;\n min: number;\n onExpandChange?: (expand: boolean) => void;\n onSizeChange?: (size: number, delta: number) => void;\n onSizeDragging?: (size: number, delta: number) => void;\n placement: Placement;\n size: number;\n}\n\nexport interface PanelState {\n collapsing: boolean;\n dragging: boolean;\n folding: boolean;\n}\n\nexport interface PanelKeyEvent {\n key: string;\n preventDefault: () => void;\n shiftKey: boolean;\n}\n\nexport interface PanelController {\n attach: (element: HTMLElement) => () => void;\n axis: Axis;\n bounds: () => { max: number; min: number };\n drag: {\n cancel: () => void;\n end: () => void;\n move: (offset: { x: number; y: number }) => void;\n start: () => void;\n };\n motion: { content: MotionValue<number>; size: MotionValue<number> };\n options: PanelControllerOptions;\n reset: () => void;\n resizeByKey: (event: PanelKeyEvent) => void;\n state: PanelState;\n subscribe: (listener: () => void) => () => void;\n sync: (options: PanelControllerOptions) => void;\n target: number;\n}\n\nexport const createPanelController = (initial: PanelControllerOptions): PanelController => {\n const { emit: notify, subscribe } = emitter();\n\n let element: HTMLElement | null = null;\n let options = initial;\n let target = initial.expand ? initial.size : 0;\n let state: PanelState = { collapsing: false, dragging: false, folding: false };\n let unlock: (() => void) | undefined;\n let foldTo = target;\n\n const size = motionValue(target);\n const content = motionValue(initial.size);\n\n const session = {\n collapsing: false,\n max: 0,\n min: 0,\n sign: 1,\n start: 0,\n };\n\n const patch = (next: Partial<PanelState>) => {\n const entries = Object.entries(next) as [keyof PanelState, boolean][];\n if (entries.some(([key, value]) => state[key] !== value)) {\n state = { ...state, ...next };\n notify();\n }\n };\n\n const bounds = () => {\n const min = Math.max(0, options.min);\n const max = options.max === undefined ? Number.POSITIVE_INFINITY : Math.max(min, options.max);\n return { max, min };\n };\n\n const growSign = () => {\n const axis = AXES[options.placement];\n return axis.grow * (axis.vertical || !isRtl(element) ? 1 : -1);\n };\n\n const fold = (to: number) => {\n foldTo = to;\n patch({ folding: true });\n if (size.get() === 0) content.jump(to || content.get());\n else if (to > 0) animate(content, to, timing());\n animate(size, to, timing());\n };\n\n const release = () => {\n unlock?.();\n unlock = undefined;\n };\n\n const stopDragging = () => {\n release();\n patch({ collapsing: false, dragging: false });\n };\n\n const drag = {\n cancel: () => {\n if (!state.dragging) return;\n size.jump(session.start);\n content.jump(session.start);\n stopDragging();\n },\n end: () => {\n if (!state.dragging) return;\n const { collapsing, start } = session;\n const committed = collapsing ? start : size.get();\n stopDragging();\n if (collapsing) {\n content.jump(start);\n options.onExpandChange?.(false);\n }\n options.onSizeChange?.(committed, committed - start);\n },\n move: (offset: { x: number; y: number }) => {\n if (!state.dragging) return;\n const axis = AXES[options.placement];\n const pixels = session.start + offset[axis.point] * session.sign;\n const collapsing =\n options.collapseThreshold !== undefined && pixels < options.collapseThreshold;\n const next = collapsing ? 0 : Math.round(clamp(pixels, session.min, session.max));\n\n size.jump(next);\n if (!collapsing) content.jump(next);\n if (collapsing !== session.collapsing) {\n session.collapsing = collapsing;\n patch({ collapsing });\n }\n const reported = collapsing ? session.start : next;\n options.onSizeDragging?.(reported, reported - session.start);\n },\n start: () => {\n Object.assign(session, {\n ...bounds(),\n collapsing: false,\n sign: growSign(),\n start: size.get(),\n });\n release();\n unlock = lockBody(AXES[options.placement].cursor, drag.cancel);\n patch({ dragging: true, folding: false });\n },\n };\n\n const resizeByKey = (event: PanelKeyEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n if (!options.onExpandChange) return;\n event.preventDefault();\n options.onExpandChange(!options.expand);\n return;\n }\n\n const { max, min } = bounds();\n const fast = event.shiftKey || event.key === 'PageUp' || event.key === 'PageDown';\n const step = (fast ? KEY_STEP_FAST : KEY_STEP) * growSign();\n const axis = AXES[options.placement];\n const onAxis = axis.vertical\n ? event.key === 'ArrowUp' || event.key === 'ArrowDown'\n : event.key === 'ArrowLeft' || event.key === 'ArrowRight';\n const screen = onAxis ? SCREEN_DELTA[event.key] : undefined;\n const moves: Record<string, number> = {\n End: Number.isFinite(max) ? max : target,\n Home: min,\n PageDown: target + step,\n PageUp: target - step,\n };\n const next = screen === undefined ? moves[event.key] : target + screen * step;\n if (next === undefined) return;\n\n event.preventDefault();\n const value = clamp(next, min, max);\n if (!options.expand && value > 0) options.onExpandChange?.(true);\n options.onSizeChange?.(value, value - target);\n };\n\n const sync = (next: PanelControllerOptions) => {\n options = next;\n const value = next.expand ? next.size : 0;\n if (size.get() === 0 && value > 0) content.jump(next.size);\n if (value !== target) {\n target = value;\n notify();\n }\n if (state.dragging) return;\n if (size.get() === target) {\n // A reversal can land back on the target before the outgoing animation\n // has moved; without this it keeps running past the new target.\n size.stop();\n content.stop();\n if (state.folding) {\n patch({ folding: false });\n content.jump(target || content.get());\n }\n // A settled panel still needs its content aligned after a controlled reset.\n foldTo = target;\n } else if (!state.folding || foldTo !== target) {\n fold(target);\n }\n };\n\n return {\n attach: (node) => {\n element = node;\n const stopSettle = size.on('animationComplete', () => {\n if (size.get() === target) patch({ folding: false });\n });\n return () => {\n stopSettle();\n release();\n element = null;\n };\n },\n get axis() {\n return AXES[options.placement];\n },\n bounds,\n drag,\n motion: { content, size },\n get options() {\n return options;\n },\n reset: () => {\n if (!options.expand) options.onExpandChange?.(true);\n options.onSizeChange?.(options.defaultSize, options.defaultSize - target);\n },\n resizeByKey,\n get state() {\n return state;\n },\n subscribe,\n sync,\n get target() {\n return target;\n },\n };\n};\n"],"mappings":"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"}