import type { Component, Ref } from 'vue'; import { defineComponent, h, inject, mergeProps, provide, ref, shallowRef } from 'vue'; import __FloatPanel_vue from './FloatPanel.vue'; import type { FloatPanelApi, FloatPanelContentBindings, FloatPanelInstance } from './types'; import { floatPanelContextKey } from './types'; /** * 在 `useFloatPanel` 提供的内容子树内调用,获取指向 `FloatPanelInstance` 的 ref。 * @throws 若在 provide 范围外调用 */ export function injectFloatPanelContext(): Ref { const injected = inject(floatPanelContextKey, undefined); if (injected === undefined) { throw new Error('injectFloatPanelContext() 须在 useFloatPanel 的 Wrapper 内容树内使用'); } return injected; } /** * 声明式挂载浮动面板:返回 `[Wrapper, api]`。 * * - **Wrapper**:放入模板根或任意位置;`open()` 后才会渲染内部 `FloatPanel`。 * - **api**:`open` / `close` 及与 `FloatPanelInstance` 一致的面板操作。 * * 关闭卸载由 `FloatPanel` 在动画结束后 **`emit('closed')`** 触发,不在此重复定时逻辑。 * * @param Content - 可选内容组件;不传则用 Wrapper 的默认插槽(作用域参数含 `onComplete` / `onCancel`) */ export function useFloatPanel

= Record, R = P>( Content?: Component, ) { let pending: PromiseWithResolvers | undefined; /** `closeAnimated` 结束时交给 `onClosed` 再 `settle` */ let dismissResult: R | void | undefined; const show = ref(false); const title = ref(''); /** 为 true 表示正在等待 FloatPanel `closed`,用于忽略重开后的陈旧 `closed` */ const pendingCloseUnmount = ref(false); const panelOpenKey = ref(0); const innerProps = shallowRef

(); const floatPanelRef = ref(); function settle(result?: R | void) { pending?.resolve(result); pending = undefined; } function closePanel(result?: R) { const inst = floatPanelRef.value; if (inst?.closeAnimated) { pendingCloseUnmount.value = true; dismissResult = result; inst.closeAnimated(); } else { pendingCloseUnmount.value = false; dismissResult = undefined; settle(result); show.value = false; } } const api: FloatPanelApi = { open(props: P, text = ''): Promise { pending?.resolve(void 0); pending = Promise.withResolvers(); dismissResult = undefined; pendingCloseUnmount.value = false; innerProps.value = props; panelOpenKey.value += 1; show.value = true; title.value = text; return pending.promise; }, close(result?: R) { closePanel(result); }, getAnchors() { return floatPanelRef.value?.getAnchors() ?? []; }, setAnchors(value, reset) { floatPanelRef.value?.setAnchors(value, reset); }, setHeight(value, updateAnchor) { floatPanelRef.value?.setHeight(value, updateAnchor); }, closeAnimated() { floatPanelRef.value?.closeAnimated(); }, snapMin() { return floatPanelRef.value?.snapMin() ?? false; }, snapMax() { return floatPanelRef.value?.snapMax() ?? false; }, snapFull() { return floatPanelRef.value?.snapFull() ?? false; }, snapToFirstAnchor() { return floatPanelRef.value?.snapToFirstAnchor() ?? false; }, snapToLastAnchor() { return floatPanelRef.value?.snapToLastAnchor() ?? false; }, snapToMaxContainerHeight() { return floatPanelRef.value?.snapToMaxContainerHeight() ?? false; }, }; const Wrapper = defineComponent({ name: 'FloatPanelWrapper', inheritAttrs: false, setup(_, { attrs, slots }) { provide(floatPanelContextKey, floatPanelRef); return () => { if (!show.value) return null; const attrObj = attrs as Record; const panelBindings = mergeProps(attrObj, { 'onUpdate:panelHeight': (value: number) => { if (value !== 0) { show.value = true; return; } if (!pendingCloseUnmount.value) { pendingCloseUnmount.value = true; } }, onClosed: () => { if (!pendingCloseUnmount.value) return; pendingCloseUnmount.value = false; const r = dismissResult; dismissResult = undefined; settle(r); show.value = false; }, }); return h( __FloatPanel_vue, { ref: floatPanelRef, key: panelOpenKey.value, closable: true, title: title.value, ...panelBindings }, { header: slots.header, content: () => { const slotProps = innerProps.value ?? ({} as P); const contentBindings = mergeProps(slotProps as Record, { onComplete: (result?: R) => closePanel(result), onCancel: () => closePanel(), }); if (Content) return h(Content, contentBindings); return slots.default?.(contentBindings as FloatPanelContentBindings) ?? null; }, }, ); }; }, }); return [Wrapper, api] as const; }