| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- 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<FloatPanelInstance | undefined> {
- 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<P extends Record<string, any> = Record<string, unknown>, R = P>(
- Content?: Component,
- ) {
- let pending: PromiseWithResolvers<R | void> | 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<P>();
- const floatPanelRef = ref<FloatPanelInstance>();
- 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<P, R> = {
- open(props: P, text = ''): Promise<R | void> {
- pending?.resolve(void 0);
- pending = Promise.withResolvers<R | void>();
- 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<string, unknown>;
- 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<string, unknown>, {
- onComplete: (result?: R) => closePanel(result),
- onCancel: () => closePanel(),
- });
- if (Content) return h(Content, contentBindings);
- return slots.default?.(contentBindings as FloatPanelContentBindings<P, R>) ?? null;
- },
- },
- );
- };
- },
- });
- return [Wrapper, api] as const;
- }
|