@cut-workbench/workbench 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/features/media-library/components/DropZone.d.ts +6 -0
- package/dist/features/media-library/lib/pick-files.d.ts +24 -0
- package/dist/hooks/use-toast-safe.d.ts +9 -1
- package/dist/index.cjs +369 -281
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +249 -161
- package/dist/index.js.map +1 -1
- package/dist/lab.cjs +31 -15
- package/dist/lab.cjs.map +1 -1
- package/dist/lab.js +23 -7
- package/dist/lab.js.map +1 -1
- package/dist/lib/notify.d.ts +40 -0
- package/dist/notify-CEwFQgjC.js +233 -0
- package/dist/notify-CEwFQgjC.js.map +1 -0
- package/dist/{format--DhqHMgX.cjs → notify-CMNwDFGw.cjs} +173 -1
- package/dist/notify-CMNwDFGw.cjs.map +1 -0
- package/dist/umd/cut-workbench.umd.js +1 -1
- package/package.json +3 -3
- package/dist/format--DhqHMgX.cjs.map +0 -1
- package/dist/format-CV2aoZZC.js +0 -109
- package/dist/format-CV2aoZZC.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify-CEwFQgjC.js","names":[],"sources":["../src/lib/cn.ts","../src/components/ui/badge.tsx","../src/context.tsx","../src/components/ui/card.tsx","../src/lib/format.ts","../src/lib/notify.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\n/**\n * 合并 className:tailwind-merge 负责消解冲突,clsx 负责条件拼接。\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/cn\"\nimport { Slot } from \"radix-ui\"\n\nconst badgeVariants = cva(\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90\",\n outline:\n \"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n ghost: \"[a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 [a&]:hover:underline\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction Badge({\n className,\n variant = \"default\",\n asChild = false,\n ...props\n}: React.ComponentProps<\"span\"> &\n VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot.Root : \"span\"\n\n return (\n <Comp\n data-slot=\"badge\"\n data-variant={variant}\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nexport { Badge, badgeVariants }\n","/**\r\n * 工作台自身的 Context:UI 状态(面板尺寸 / 缩放 / 对话框)+ 宿主桥。\r\n *\r\n * 与 `@cut-workbench/react` 的编辑器 Context 分开:编辑器是「数据与引擎」,\r\n * 这里是「某一次挂载的布局与交互」。两者生命周期可以不同(例如宿主切换项目时\r\n * 复用同一个编辑器、只重建 UI)。\r\n */\r\n\r\nimport { createContext, useContext, useSyncExternalStore, type ReactNode } from 'react'\r\nimport type { HostBridge } from '@cut-workbench/core'\r\nimport type { UiState, UiStore } from './store/ui-store'\r\n\r\nexport interface WorkbenchTheme {\r\n /**\r\n * 宿主请求的主题:\r\n * - `light` / `dark`:固定主题;\r\n * - `inherit`:跟随宿主 `<html class=\"dark\">`(见 theme.ts)。\r\n */\r\n mode?: 'light' | 'dark' | 'inherit'\r\n /**\r\n * 实际生效的主题(`inherit` 已折算成亮/暗)。\r\n *\r\n * 消费方(例如 Sonner 的 Toaster)必须用这个值,而不是 `mode` ——\r\n * `mode` 可能是 `inherit`,那不是 Sonner 认识的主题名。\r\n */\r\n resolved?: 'light' | 'dark'\r\n}\r\n\r\nexport interface WorkbenchConfig {\r\n /** 功能开关 */\r\n features: {\r\n mediaLibrary: boolean\r\n preview: boolean\r\n timeline: boolean\r\n inspector: boolean\r\n export: boolean\r\n canvasSettings: boolean\r\n /** 能力实验室:默认关闭,属于内部验证用途 */\r\n lab: boolean\r\n }\r\n /** 键盘快捷键作用域:global 全局、scoped 仅容器聚焦时、off 关闭 */\r\n keyboard: 'global' | 'scoped' | 'off'\r\n /** 是否允许拖拽调整面板尺寸 */\r\n resizable: boolean\r\n}\r\n\r\nexport interface WorkbenchContextValue {\r\n ui: UiStore\r\n bridge: HostBridge\r\n config: WorkbenchConfig\r\n theme: Required<WorkbenchTheme>\r\n /** Radix portal 的挂载点(作用域化样式与 z-index 依赖它) */\r\n portalContainer: HTMLElement | null\r\n}\r\n\r\nexport const WorkbenchContext = createContext<WorkbenchContextValue | null>(null)\r\n\r\nexport function useWorkbench(): WorkbenchContextValue {\r\n const value = useContext(WorkbenchContext)\r\n if (value == null) {\r\n throw new Error('[cut-workbench] 缺少 <WorkbenchProvider>:组件必须挂在工作台内部')\r\n }\r\n return value\r\n}\r\n\r\n/** 订阅 UI 状态(带 selector) */\r\nexport function useUi<T>(selector: (state: UiState) => T): T {\r\n const { ui } = useWorkbench()\r\n return useSyncExternalStore(\r\n ui.subscribe,\r\n () => selector(ui.getState()),\r\n () => selector(ui.getState()),\r\n )\r\n}\r\n\r\n/** 取 UI store 本体(事件回调里读写用) */\r\nexport function useUiStoreApi(): UiStore {\r\n return useWorkbench().ui\r\n}\r\n\r\nexport interface WorkbenchProviderProps {\r\n value: WorkbenchContextValue\r\n children: ReactNode\r\n}\r\n\r\nexport function WorkbenchProvider({ value, children }: WorkbenchProviderProps) {\r\n return <WorkbenchContext.Provider value={value}>{children}</WorkbenchContext.Provider>\r\n}\r\n\r\n/** 默认功能开关 */\r\nexport const DEFAULT_FEATURES: WorkbenchConfig['features'] = {\r\n mediaLibrary: true,\r\n preview: true,\r\n timeline: true,\r\n inspector: true,\r\n export: true,\r\n canvasSettings: true,\r\n lab: false,\r\n}\r\n\r\nexport const DEFAULT_CONFIG: WorkbenchConfig = {\r\n features: DEFAULT_FEATURES,\r\n keyboard: 'global',\r\n resizable: true,\r\n}\r\n","import * as React from \"react\"\nimport { cn } from \"@/lib/cn\"\n\nfunction Card({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card\"\n className={cn(\n \"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-header\"\n className={cn(\n \"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-title\"\n className={cn(\"leading-none font-semibold\", className)}\n {...props}\n />\n )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-action\"\n className={cn(\n \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-content\"\n className={cn(\"px-6\", className)}\n {...props}\n />\n )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-footer\"\n className={cn(\"flex items-center px-6 [.border-t]:pt-6\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardAction,\n CardDescription,\n CardContent,\n}\n","import { formatDuration, formatTimecode, usToSec } from '@cut-workbench/core'\n\nexport { formatDuration, formatTimecode }\n\n/** 字节数 → 人类可读 */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n const units = ['KB', 'MB', 'GB']\n let value = bytes / 1024\n let i = 0\n while (value >= 1024 && i < units.length - 1) {\n value /= 1024\n i += 1\n }\n return `${value.toFixed(value >= 100 ? 0 : 1)} ${units[i]}`\n}\n\n/** 像素尺寸 → `1920×1080` */\nexport function formatResolution(width: number, height: number): string {\n if (!width || !height) return '—'\n return `${width}×${height}`\n}\n\n/** 秒数 → `12.3s` */\nexport function formatElapsed(seconds: number): string {\n if (seconds < 60) return `${seconds.toFixed(1)}s`\n const m = Math.floor(seconds / 60)\n const s = Math.round(seconds % 60)\n return `${m}m ${s}s`\n}\n\n/** 微秒 → 秒(保留一位小数,用于表单展示) */\nexport function formatSeconds(us: number): string {\n return usToSec(us).toFixed(2)\n}\n\n/** 百分比 0~1 → `42%` */\nexport function formatPercent(ratio: number): string {\n return `${Math.round(ratio * 100)}%`\n}\n","/**\n * 通知统一出口。\n *\n * 为什么需要这一层:`sonner` 的 toast 队列是**模块级全局单例**,每个挂载的\n * `<Toaster>` 都会渲染队列里的全部消息。宿主一旦自己也挂了 Toaster(常见于\n * 已有的设计系统),同一条消息就会被渲染两次 —— 一条是宿主的样式,一条是\n * 工作台的样式,肉眼看到就是「两个不同样式的相同提示」。\n *\n * 因此所有提示都必须走这里,由 `HostBridge.notify` 决定最终去向:\n * 1. 宿主实现了 `notify` → 完全交给宿主(系统通知 / 自己的 toast / 日志),\n * 工作台不再碰 sonner,也就不会重复;\n * 2. 宿主没实现 → 退回库内的 sonner toast,保证独立使用时仍然可见。\n *\n * 注意:这里是「二选一」,两边不会同时收到同一条消息。\n */\n\nimport { toast } from 'sonner'\nimport type { HostBridge } from '@cut-workbench/core'\n\nexport type NotifyLevel = 'info' | 'success' | 'warning' | 'error'\n\nexport interface NotifyOptions {\n /** 次要说明(多行用 \\n 分隔) */\n description?: string\n /** 仅对库内 toast 生效的显示时长(毫秒);宿主实现自行决定 */\n duration?: number\n}\n\n/** 一次通知的完整描述,便于宿主侧统一处理 */\nexport interface NotifyPayload extends NotifyOptions {\n level: NotifyLevel\n title: string\n}\n\n/**\n * 把 sonner 的选项收敛成桥的签名。\n *\n * 宿主的 `notify` 只收 `(level, title, detail)`,所以 `duration` 之类的展示\n * 细节在接管后会被丢弃 —— 这是刻意的:显示多久属于宿主决定,库不应该规定。\n */\nfunction toBridgeArgs(payload: NotifyPayload): [NotifyLevel, string, string?] {\n const { level, title, description } = payload\n return description == null ? [level, title] : [level, title, description]\n}\n\nfunction showInLibrary(payload: NotifyPayload): void {\n const { level, title, description, duration } = payload\n const options: { description?: string; duration?: number } = {}\n if (description != null) options.description = description\n if (duration != null) options.duration = duration\n const hasOptions = Object.keys(options).length > 0\n\n // 同时打一份到 console:toast 会自动消失,控制台是排查问题的唯一痕迹。\n logToConsole(payload)\n\n switch (level) {\n case 'success':\n toast.success(title, hasOptions ? options : undefined)\n break\n case 'warning':\n toast.warning(title, hasOptions ? options : undefined)\n break\n case 'error':\n toast.error(title, hasOptions ? options : undefined)\n break\n default:\n toast.info(title, hasOptions ? options : undefined)\n }\n}\n\nfunction logToConsole(payload: NotifyPayload): void {\n const { level, title, description } = payload\n const line = description == null ? title : `${title} · ${description}`\n if (level === 'error') console.error('[cut-workbench]', line)\n else if (level === 'warning') console.warn('[cut-workbench]', line)\n else console.info('[cut-workbench]', line)\n}\n\n/**\n * 当前挂载的默认宿主桥。\n *\n * 为什么需要它:`reportError` 这类**非 hook**函数拿不到 React context\n * (例如 `useAvCanvas` 的初始化失败、事件回调)。由 `WorkbenchProvider`\n * 挂载时注册,卸载时清空,使这些路径也能尊重宿主的 `notify` 实现。\n *\n * 多实例场景下这里是「最后挂载者生效」—— 提示本身不区分实例,可以接受;\n * 需要精确归属的地方走 `useWorkbench().bridge`。\n */\nlet defaultBridge: HostBridge | null = null\n\nexport function setDefaultBridge(bridge: HostBridge | null): void {\n defaultBridge = bridge\n}\n\nexport function getDefaultBridge(): HostBridge | null {\n return defaultBridge\n}\n\n/**\n * 发一条通知。优先走宿主桥,没有宿主实现才用库内 toast。\n *\n * @param bridge 显式传入的宿主桥;传 `undefined` 时使用已注册的默认桥\n * (见 `setDefaultBridge`),仍没有则退回库内 toast\n */\nexport function notify(\n bridge: HostBridge | null | undefined,\n payload: NotifyPayload,\n): void {\n const target = bridge === undefined ? defaultBridge : bridge\n if (target?.notify != null) {\n target.notify(...toBridgeArgs(payload))\n return\n }\n showInLibrary(payload)\n}\n\n/** 便捷构造:只需要一句错误提示时用 */\nexport function notifyError(\n bridge: HostBridge | null | undefined,\n title: string,\n description?: string,\n): void {\n notify(bridge, { level: 'error', title, description })\n}\n\nexport type { HostBridge }\n"],"mappings":";;;;;;;;;;;;AAMA,SAAgB,GAAG,GAAG,QAAsB;CAC1C,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;;ACHA,IAAM,gBAAgB,IACpB,uaACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EACT,WACE;EACF,aACE;EACF,SACE;EACF,OAAO;EACP,MAAM;CACR,EACF;CACA,iBAAiB,EACf,SAAS,UACX;AACF,CACF;AAEA,SAAS,MAAM,EACb,WACA,UAAU,WACV,UAAU,OACV,GAAG,SAEyD;CAC5D,MAAM,OAAO,UAAU,KAAK,OAAO;CAEnC,OACE,oBAAC,MAAD;EACE,aAAU;EACV,gBAAc;EACd,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EACnD,GAAI;CACL,CAAA;AAEL;;;;;;;;;ACWA,IAAa,mBAAmB,cAA4C,IAAI;AAEhF,SAAgB,eAAsC;CACpD,MAAM,QAAQ,WAAW,gBAAgB;CACzC,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,oDAAoD;CAEtE,OAAO;AACT;;AAGA,SAAgB,MAAS,UAAoC;CAC3D,MAAM,EAAE,OAAO,aAAa;CAC5B,OAAO,qBACL,GAAG,iBACG,SAAS,GAAG,SAAS,CAAC,SACtB,SAAS,GAAG,SAAS,CAAC,CAC9B;AACF;;AAGA,SAAgB,gBAAyB;CACvC,OAAO,aAAa,CAAC,CAAC;AACxB;;AAYA,IAAa,mBAAgD;CAC3D,cAAc;CACd,SAAS;CACT,UAAU;CACV,WAAW;CACX,QAAQ;CACR,gBAAgB;CAChB,KAAK;AACP;AAEA,IAAa,iBAAkC;CAC7C,UAAU;CACV,UAAU;CACV,WAAW;AACb;;ACrGA,SAAS,KAAK,EAAE,WAAW,GAAG,SAAsC;CAClE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,qFACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,GAAG,SAAsC;CACxE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,4JACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,SAAsC;CACvE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,8BAA8B,SAAS;EACrD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,SAAsC;CAC7E,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,iCAAiC,SAAS;EACxD,GAAI;CACL,CAAA;AAEL;AAeA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAsC;CACzE,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,QAAQ,SAAS;EAC/B,GAAI;CACL,CAAA;AAEL;;;ACjEA,SAAgB,YAAY,OAAuB;CACjD,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,MAAM,QAAQ;EAAC;EAAM;EAAM;CAAI;CAC/B,IAAI,QAAQ,QAAQ;CACpB,IAAI,IAAI;CACR,OAAO,SAAS,QAAQ,IAAI,MAAM,SAAS,GAAG;EAC5C,SAAS;EACT,KAAK;CACP;CACA,OAAO,GAAG,MAAM,QAAQ,SAAS,MAAM,IAAI,CAAC,EAAE,GAAG,MAAM;AACzD;;AAGA,SAAgB,iBAAiB,OAAe,QAAwB;CACtE,IAAI,CAAC,SAAS,CAAC,QAAQ,OAAO;CAC9B,OAAO,GAAG,MAAM,GAAG;AACrB;;AAGA,SAAgB,cAAc,SAAyB;CACrD,IAAI,UAAU,IAAI,OAAO,GAAG,QAAQ,QAAQ,CAAC,EAAE;CAG/C,OAAO,GAFG,KAAK,MAAM,UAAU,EAErB,EAAE,IADF,KAAK,MAAM,UAAU,EACf,EAAE;AACpB;;AAGA,SAAgB,cAAc,IAAoB;CAChD,OAAO,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC;AAC9B;;AAGA,SAAgB,cAAc,OAAuB;CACnD,OAAO,GAAG,KAAK,MAAM,QAAQ,GAAG,EAAE;AACpC;;;;;;;;;;;;;;;;;;;;;;;ACCA,SAAS,aAAa,SAAwD;CAC5E,MAAM,EAAE,OAAO,OAAO,gBAAgB;CACtC,OAAO,eAAe,OAAO,CAAC,OAAO,KAAK,IAAI;EAAC;EAAO;EAAO;CAAW;AAC1E;AAEA,SAAS,cAAc,SAA8B;CACnD,MAAM,EAAE,OAAO,OAAO,aAAa,aAAa;CAChD,MAAM,UAAuD,CAAC;CAC9D,IAAI,eAAe,MAAM,QAAQ,cAAc;CAC/C,IAAI,YAAY,MAAM,QAAQ,WAAW;CACzC,MAAM,aAAa,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS;CAGjD,aAAa,OAAO;CAEpB,QAAQ,OAAR;EACE,KAAK;GACH,MAAM,QAAQ,OAAO,aAAa,UAAU,KAAA,CAAS;GACrD;EACF,KAAK;GACH,MAAM,QAAQ,OAAO,aAAa,UAAU,KAAA,CAAS;GACrD;EACF,KAAK;GACH,MAAM,MAAM,OAAO,aAAa,UAAU,KAAA,CAAS;GACnD;EACF,SACE,MAAM,KAAK,OAAO,aAAa,UAAU,KAAA,CAAS;CACtD;AACF;AAEA,SAAS,aAAa,SAA8B;CAClD,MAAM,EAAE,OAAO,OAAO,gBAAgB;CACtC,MAAM,OAAO,eAAe,OAAO,QAAQ,GAAG,MAAM,KAAK;CACzD,IAAI,UAAU,SAAS,QAAQ,MAAM,mBAAmB,IAAI;MACvD,IAAI,UAAU,WAAW,QAAQ,KAAK,mBAAmB,IAAI;MAC7D,QAAQ,KAAK,mBAAmB,IAAI;AAC3C;;;;;;;;;;;AAYA,IAAI,gBAAmC;AAEvC,SAAgB,iBAAiB,QAAiC;CAChE,gBAAgB;AAClB;;;;;;;AAYA,SAAgB,OACd,QACA,SACM;CACN,MAAM,SAAS,WAAW,KAAA,IAAY,gBAAgB;CACtD,IAAI,QAAQ,UAAU,MAAM;EAC1B,OAAO,OAAO,GAAG,aAAa,OAAO,CAAC;EACtC;CACF;CACA,cAAc,OAAO;AACvB"}
|
|
@@ -27,6 +27,7 @@ let tailwind_merge = require("tailwind-merge");
|
|
|
27
27
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
28
28
|
let class_variance_authority = require("class-variance-authority");
|
|
29
29
|
let radix_ui = require("radix-ui");
|
|
30
|
+
let sonner = require("sonner");
|
|
30
31
|
// src/lib/cn.ts
|
|
31
32
|
/**
|
|
32
33
|
* 合并 className:tailwind-merge 负责消解冲突,clsx 负责条件拼接。
|
|
@@ -55,6 +56,44 @@ function Badge({ className, variant = "default", asChild = false, ...props }) {
|
|
|
55
56
|
...props
|
|
56
57
|
});
|
|
57
58
|
}
|
|
59
|
+
// src/context.tsx
|
|
60
|
+
/**
|
|
61
|
+
* 工作台自身的 Context:UI 状态(面板尺寸 / 缩放 / 对话框)+ 宿主桥。
|
|
62
|
+
*
|
|
63
|
+
* 与 `@cut-workbench/react` 的编辑器 Context 分开:编辑器是「数据与引擎」,
|
|
64
|
+
* 这里是「某一次挂载的布局与交互」。两者生命周期可以不同(例如宿主切换项目时
|
|
65
|
+
* 复用同一个编辑器、只重建 UI)。
|
|
66
|
+
*/
|
|
67
|
+
var WorkbenchContext = (0, react.createContext)(null);
|
|
68
|
+
function useWorkbench() {
|
|
69
|
+
const value = (0, react.useContext)(WorkbenchContext);
|
|
70
|
+
if (value == null) throw new Error("[cut-workbench] 缺少 <WorkbenchProvider>:组件必须挂在工作台内部");
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
/** 订阅 UI 状态(带 selector) */
|
|
74
|
+
function useUi(selector) {
|
|
75
|
+
const { ui } = useWorkbench();
|
|
76
|
+
return (0, react.useSyncExternalStore)(ui.subscribe, () => selector(ui.getState()), () => selector(ui.getState()));
|
|
77
|
+
}
|
|
78
|
+
/** 取 UI store 本体(事件回调里读写用) */
|
|
79
|
+
function useUiStoreApi() {
|
|
80
|
+
return useWorkbench().ui;
|
|
81
|
+
}
|
|
82
|
+
/** 默认功能开关 */
|
|
83
|
+
var DEFAULT_FEATURES = {
|
|
84
|
+
mediaLibrary: true,
|
|
85
|
+
preview: true,
|
|
86
|
+
timeline: true,
|
|
87
|
+
inspector: true,
|
|
88
|
+
export: true,
|
|
89
|
+
canvasSettings: true,
|
|
90
|
+
lab: false
|
|
91
|
+
};
|
|
92
|
+
var DEFAULT_CONFIG = {
|
|
93
|
+
features: DEFAULT_FEATURES,
|
|
94
|
+
keyboard: "global",
|
|
95
|
+
resizable: true
|
|
96
|
+
};
|
|
58
97
|
// src/components/ui/card.tsx
|
|
59
98
|
function Card({ className, ...props }) {
|
|
60
99
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -126,6 +165,91 @@ function formatSeconds(us) {
|
|
|
126
165
|
function formatPercent(ratio) {
|
|
127
166
|
return `${Math.round(ratio * 100)}%`;
|
|
128
167
|
}
|
|
168
|
+
// src/lib/notify.ts
|
|
169
|
+
/**
|
|
170
|
+
* 通知统一出口。
|
|
171
|
+
*
|
|
172
|
+
* 为什么需要这一层:`sonner` 的 toast 队列是**模块级全局单例**,每个挂载的
|
|
173
|
+
* `<Toaster>` 都会渲染队列里的全部消息。宿主一旦自己也挂了 Toaster(常见于
|
|
174
|
+
* 已有的设计系统),同一条消息就会被渲染两次 —— 一条是宿主的样式,一条是
|
|
175
|
+
* 工作台的样式,肉眼看到就是「两个不同样式的相同提示」。
|
|
176
|
+
*
|
|
177
|
+
* 因此所有提示都必须走这里,由 `HostBridge.notify` 决定最终去向:
|
|
178
|
+
* 1. 宿主实现了 `notify` → 完全交给宿主(系统通知 / 自己的 toast / 日志),
|
|
179
|
+
* 工作台不再碰 sonner,也就不会重复;
|
|
180
|
+
* 2. 宿主没实现 → 退回库内的 sonner toast,保证独立使用时仍然可见。
|
|
181
|
+
*
|
|
182
|
+
* 注意:这里是「二选一」,两边不会同时收到同一条消息。
|
|
183
|
+
*/
|
|
184
|
+
/**
|
|
185
|
+
* 把 sonner 的选项收敛成桥的签名。
|
|
186
|
+
*
|
|
187
|
+
* 宿主的 `notify` 只收 `(level, title, detail)`,所以 `duration` 之类的展示
|
|
188
|
+
* 细节在接管后会被丢弃 —— 这是刻意的:显示多久属于宿主决定,库不应该规定。
|
|
189
|
+
*/
|
|
190
|
+
function toBridgeArgs(payload) {
|
|
191
|
+
const { level, title, description } = payload;
|
|
192
|
+
return description == null ? [level, title] : [
|
|
193
|
+
level,
|
|
194
|
+
title,
|
|
195
|
+
description
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
function showInLibrary(payload) {
|
|
199
|
+
const { level, title, description, duration } = payload;
|
|
200
|
+
const options = {};
|
|
201
|
+
if (description != null) options.description = description;
|
|
202
|
+
if (duration != null) options.duration = duration;
|
|
203
|
+
const hasOptions = Object.keys(options).length > 0;
|
|
204
|
+
logToConsole(payload);
|
|
205
|
+
switch (level) {
|
|
206
|
+
case "success":
|
|
207
|
+
sonner.toast.success(title, hasOptions ? options : void 0);
|
|
208
|
+
break;
|
|
209
|
+
case "warning":
|
|
210
|
+
sonner.toast.warning(title, hasOptions ? options : void 0);
|
|
211
|
+
break;
|
|
212
|
+
case "error":
|
|
213
|
+
sonner.toast.error(title, hasOptions ? options : void 0);
|
|
214
|
+
break;
|
|
215
|
+
default: sonner.toast.info(title, hasOptions ? options : void 0);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function logToConsole(payload) {
|
|
219
|
+
const { level, title, description } = payload;
|
|
220
|
+
const line = description == null ? title : `${title} · ${description}`;
|
|
221
|
+
if (level === "error") console.error("[cut-workbench]", line);
|
|
222
|
+
else if (level === "warning") console.warn("[cut-workbench]", line);
|
|
223
|
+
else console.info("[cut-workbench]", line);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 当前挂载的默认宿主桥。
|
|
227
|
+
*
|
|
228
|
+
* 为什么需要它:`reportError` 这类**非 hook**函数拿不到 React context
|
|
229
|
+
* (例如 `useAvCanvas` 的初始化失败、事件回调)。由 `WorkbenchProvider`
|
|
230
|
+
* 挂载时注册,卸载时清空,使这些路径也能尊重宿主的 `notify` 实现。
|
|
231
|
+
*
|
|
232
|
+
* 多实例场景下这里是「最后挂载者生效」—— 提示本身不区分实例,可以接受;
|
|
233
|
+
* 需要精确归属的地方走 `useWorkbench().bridge`。
|
|
234
|
+
*/
|
|
235
|
+
var defaultBridge = null;
|
|
236
|
+
function setDefaultBridge(bridge) {
|
|
237
|
+
defaultBridge = bridge;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 发一条通知。优先走宿主桥,没有宿主实现才用库内 toast。
|
|
241
|
+
*
|
|
242
|
+
* @param bridge 显式传入的宿主桥;传 `undefined` 时使用已注册的默认桥
|
|
243
|
+
* (见 `setDefaultBridge`),仍没有则退回库内 toast
|
|
244
|
+
*/
|
|
245
|
+
function notify(bridge, payload) {
|
|
246
|
+
const target = bridge === void 0 ? defaultBridge : bridge;
|
|
247
|
+
if (target?.notify != null) {
|
|
248
|
+
target.notify(...toBridgeArgs(payload));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
showInLibrary(payload);
|
|
252
|
+
}
|
|
129
253
|
Object.defineProperty(exports, "Badge", {
|
|
130
254
|
enumerable: true,
|
|
131
255
|
get: function() {
|
|
@@ -162,6 +286,24 @@ Object.defineProperty(exports, "CardTitle", {
|
|
|
162
286
|
return CardTitle;
|
|
163
287
|
}
|
|
164
288
|
});
|
|
289
|
+
Object.defineProperty(exports, "DEFAULT_CONFIG", {
|
|
290
|
+
enumerable: true,
|
|
291
|
+
get: function() {
|
|
292
|
+
return DEFAULT_CONFIG;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
Object.defineProperty(exports, "DEFAULT_FEATURES", {
|
|
296
|
+
enumerable: true,
|
|
297
|
+
get: function() {
|
|
298
|
+
return DEFAULT_FEATURES;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
Object.defineProperty(exports, "WorkbenchContext", {
|
|
302
|
+
enumerable: true,
|
|
303
|
+
get: function() {
|
|
304
|
+
return WorkbenchContext;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
165
307
|
Object.defineProperty(exports, "__toESM", {
|
|
166
308
|
enumerable: true,
|
|
167
309
|
get: function() {
|
|
@@ -204,5 +346,35 @@ Object.defineProperty(exports, "formatSeconds", {
|
|
|
204
346
|
return formatSeconds;
|
|
205
347
|
}
|
|
206
348
|
});
|
|
349
|
+
Object.defineProperty(exports, "notify", {
|
|
350
|
+
enumerable: true,
|
|
351
|
+
get: function() {
|
|
352
|
+
return notify;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
Object.defineProperty(exports, "setDefaultBridge", {
|
|
356
|
+
enumerable: true,
|
|
357
|
+
get: function() {
|
|
358
|
+
return setDefaultBridge;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
Object.defineProperty(exports, "useUi", {
|
|
362
|
+
enumerable: true,
|
|
363
|
+
get: function() {
|
|
364
|
+
return useUi;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
Object.defineProperty(exports, "useUiStoreApi", {
|
|
368
|
+
enumerable: true,
|
|
369
|
+
get: function() {
|
|
370
|
+
return useUiStoreApi;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
Object.defineProperty(exports, "useWorkbench", {
|
|
374
|
+
enumerable: true,
|
|
375
|
+
get: function() {
|
|
376
|
+
return useWorkbench;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
207
379
|
|
|
208
|
-
//# sourceMappingURL=
|
|
380
|
+
//# sourceMappingURL=notify-CMNwDFGw.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify-CMNwDFGw.cjs","names":["twMerge","cva","Slot","createContext","useContext","useSyncExternalStore","usToSec"],"sources":["../src/lib/cn.ts","../src/components/ui/badge.tsx","../src/context.tsx","../src/components/ui/card.tsx","../src/lib/format.ts","../src/lib/notify.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\n/**\n * 合并 className:tailwind-merge 负责消解冲突,clsx 负责条件拼接。\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n","import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { cn } from \"@/lib/cn\"\nimport { Slot } from \"radix-ui\"\n\nconst badgeVariants = cva(\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3\",\n {\n variants: {\n variant: {\n default: \"bg-primary text-primary-foreground [a&]:hover:bg-primary/90\",\n secondary:\n \"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90\",\n destructive:\n \"bg-destructive text-destructive-foreground focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90\",\n outline:\n \"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n ghost: \"[a&]:hover:bg-accent [a&]:hover:text-accent-foreground\",\n link: \"text-primary underline-offset-4 [a&]:hover:underline\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n },\n }\n)\n\nfunction Badge({\n className,\n variant = \"default\",\n asChild = false,\n ...props\n}: React.ComponentProps<\"span\"> &\n VariantProps<typeof badgeVariants> & { asChild?: boolean }) {\n const Comp = asChild ? Slot.Root : \"span\"\n\n return (\n <Comp\n data-slot=\"badge\"\n data-variant={variant}\n className={cn(badgeVariants({ variant }), className)}\n {...props}\n />\n )\n}\n\nexport { Badge, badgeVariants }\n","/**\r\n * 工作台自身的 Context:UI 状态(面板尺寸 / 缩放 / 对话框)+ 宿主桥。\r\n *\r\n * 与 `@cut-workbench/react` 的编辑器 Context 分开:编辑器是「数据与引擎」,\r\n * 这里是「某一次挂载的布局与交互」。两者生命周期可以不同(例如宿主切换项目时\r\n * 复用同一个编辑器、只重建 UI)。\r\n */\r\n\r\nimport { createContext, useContext, useSyncExternalStore, type ReactNode } from 'react'\r\nimport type { HostBridge } from '@cut-workbench/core'\r\nimport type { UiState, UiStore } from './store/ui-store'\r\n\r\nexport interface WorkbenchTheme {\r\n /**\r\n * 宿主请求的主题:\r\n * - `light` / `dark`:固定主题;\r\n * - `inherit`:跟随宿主 `<html class=\"dark\">`(见 theme.ts)。\r\n */\r\n mode?: 'light' | 'dark' | 'inherit'\r\n /**\r\n * 实际生效的主题(`inherit` 已折算成亮/暗)。\r\n *\r\n * 消费方(例如 Sonner 的 Toaster)必须用这个值,而不是 `mode` ——\r\n * `mode` 可能是 `inherit`,那不是 Sonner 认识的主题名。\r\n */\r\n resolved?: 'light' | 'dark'\r\n}\r\n\r\nexport interface WorkbenchConfig {\r\n /** 功能开关 */\r\n features: {\r\n mediaLibrary: boolean\r\n preview: boolean\r\n timeline: boolean\r\n inspector: boolean\r\n export: boolean\r\n canvasSettings: boolean\r\n /** 能力实验室:默认关闭,属于内部验证用途 */\r\n lab: boolean\r\n }\r\n /** 键盘快捷键作用域:global 全局、scoped 仅容器聚焦时、off 关闭 */\r\n keyboard: 'global' | 'scoped' | 'off'\r\n /** 是否允许拖拽调整面板尺寸 */\r\n resizable: boolean\r\n}\r\n\r\nexport interface WorkbenchContextValue {\r\n ui: UiStore\r\n bridge: HostBridge\r\n config: WorkbenchConfig\r\n theme: Required<WorkbenchTheme>\r\n /** Radix portal 的挂载点(作用域化样式与 z-index 依赖它) */\r\n portalContainer: HTMLElement | null\r\n}\r\n\r\nexport const WorkbenchContext = createContext<WorkbenchContextValue | null>(null)\r\n\r\nexport function useWorkbench(): WorkbenchContextValue {\r\n const value = useContext(WorkbenchContext)\r\n if (value == null) {\r\n throw new Error('[cut-workbench] 缺少 <WorkbenchProvider>:组件必须挂在工作台内部')\r\n }\r\n return value\r\n}\r\n\r\n/** 订阅 UI 状态(带 selector) */\r\nexport function useUi<T>(selector: (state: UiState) => T): T {\r\n const { ui } = useWorkbench()\r\n return useSyncExternalStore(\r\n ui.subscribe,\r\n () => selector(ui.getState()),\r\n () => selector(ui.getState()),\r\n )\r\n}\r\n\r\n/** 取 UI store 本体(事件回调里读写用) */\r\nexport function useUiStoreApi(): UiStore {\r\n return useWorkbench().ui\r\n}\r\n\r\nexport interface WorkbenchProviderProps {\r\n value: WorkbenchContextValue\r\n children: ReactNode\r\n}\r\n\r\nexport function WorkbenchProvider({ value, children }: WorkbenchProviderProps) {\r\n return <WorkbenchContext.Provider value={value}>{children}</WorkbenchContext.Provider>\r\n}\r\n\r\n/** 默认功能开关 */\r\nexport const DEFAULT_FEATURES: WorkbenchConfig['features'] = {\r\n mediaLibrary: true,\r\n preview: true,\r\n timeline: true,\r\n inspector: true,\r\n export: true,\r\n canvasSettings: true,\r\n lab: false,\r\n}\r\n\r\nexport const DEFAULT_CONFIG: WorkbenchConfig = {\r\n features: DEFAULT_FEATURES,\r\n keyboard: 'global',\r\n resizable: true,\r\n}\r\n","import * as React from \"react\"\nimport { cn } from \"@/lib/cn\"\n\nfunction Card({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card\"\n className={cn(\n \"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction CardHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-header\"\n className={cn(\n \"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction CardTitle({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-title\"\n className={cn(\"leading-none font-semibold\", className)}\n {...props}\n />\n )\n}\n\nfunction CardDescription({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n )\n}\n\nfunction CardAction({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-action\"\n className={cn(\n \"col-start-2 row-span-2 row-start-1 self-start justify-self-end\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction CardContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-content\"\n className={cn(\"px-6\", className)}\n {...props}\n />\n )\n}\n\nfunction CardFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"card-footer\"\n className={cn(\"flex items-center px-6 [.border-t]:pt-6\", className)}\n {...props}\n />\n )\n}\n\nexport {\n Card,\n CardHeader,\n CardFooter,\n CardTitle,\n CardAction,\n CardDescription,\n CardContent,\n}\n","import { formatDuration, formatTimecode, usToSec } from '@cut-workbench/core'\n\nexport { formatDuration, formatTimecode }\n\n/** 字节数 → 人类可读 */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n const units = ['KB', 'MB', 'GB']\n let value = bytes / 1024\n let i = 0\n while (value >= 1024 && i < units.length - 1) {\n value /= 1024\n i += 1\n }\n return `${value.toFixed(value >= 100 ? 0 : 1)} ${units[i]}`\n}\n\n/** 像素尺寸 → `1920×1080` */\nexport function formatResolution(width: number, height: number): string {\n if (!width || !height) return '—'\n return `${width}×${height}`\n}\n\n/** 秒数 → `12.3s` */\nexport function formatElapsed(seconds: number): string {\n if (seconds < 60) return `${seconds.toFixed(1)}s`\n const m = Math.floor(seconds / 60)\n const s = Math.round(seconds % 60)\n return `${m}m ${s}s`\n}\n\n/** 微秒 → 秒(保留一位小数,用于表单展示) */\nexport function formatSeconds(us: number): string {\n return usToSec(us).toFixed(2)\n}\n\n/** 百分比 0~1 → `42%` */\nexport function formatPercent(ratio: number): string {\n return `${Math.round(ratio * 100)}%`\n}\n","/**\n * 通知统一出口。\n *\n * 为什么需要这一层:`sonner` 的 toast 队列是**模块级全局单例**,每个挂载的\n * `<Toaster>` 都会渲染队列里的全部消息。宿主一旦自己也挂了 Toaster(常见于\n * 已有的设计系统),同一条消息就会被渲染两次 —— 一条是宿主的样式,一条是\n * 工作台的样式,肉眼看到就是「两个不同样式的相同提示」。\n *\n * 因此所有提示都必须走这里,由 `HostBridge.notify` 决定最终去向:\n * 1. 宿主实现了 `notify` → 完全交给宿主(系统通知 / 自己的 toast / 日志),\n * 工作台不再碰 sonner,也就不会重复;\n * 2. 宿主没实现 → 退回库内的 sonner toast,保证独立使用时仍然可见。\n *\n * 注意:这里是「二选一」,两边不会同时收到同一条消息。\n */\n\nimport { toast } from 'sonner'\nimport type { HostBridge } from '@cut-workbench/core'\n\nexport type NotifyLevel = 'info' | 'success' | 'warning' | 'error'\n\nexport interface NotifyOptions {\n /** 次要说明(多行用 \\n 分隔) */\n description?: string\n /** 仅对库内 toast 生效的显示时长(毫秒);宿主实现自行决定 */\n duration?: number\n}\n\n/** 一次通知的完整描述,便于宿主侧统一处理 */\nexport interface NotifyPayload extends NotifyOptions {\n level: NotifyLevel\n title: string\n}\n\n/**\n * 把 sonner 的选项收敛成桥的签名。\n *\n * 宿主的 `notify` 只收 `(level, title, detail)`,所以 `duration` 之类的展示\n * 细节在接管后会被丢弃 —— 这是刻意的:显示多久属于宿主决定,库不应该规定。\n */\nfunction toBridgeArgs(payload: NotifyPayload): [NotifyLevel, string, string?] {\n const { level, title, description } = payload\n return description == null ? [level, title] : [level, title, description]\n}\n\nfunction showInLibrary(payload: NotifyPayload): void {\n const { level, title, description, duration } = payload\n const options: { description?: string; duration?: number } = {}\n if (description != null) options.description = description\n if (duration != null) options.duration = duration\n const hasOptions = Object.keys(options).length > 0\n\n // 同时打一份到 console:toast 会自动消失,控制台是排查问题的唯一痕迹。\n logToConsole(payload)\n\n switch (level) {\n case 'success':\n toast.success(title, hasOptions ? options : undefined)\n break\n case 'warning':\n toast.warning(title, hasOptions ? options : undefined)\n break\n case 'error':\n toast.error(title, hasOptions ? options : undefined)\n break\n default:\n toast.info(title, hasOptions ? options : undefined)\n }\n}\n\nfunction logToConsole(payload: NotifyPayload): void {\n const { level, title, description } = payload\n const line = description == null ? title : `${title} · ${description}`\n if (level === 'error') console.error('[cut-workbench]', line)\n else if (level === 'warning') console.warn('[cut-workbench]', line)\n else console.info('[cut-workbench]', line)\n}\n\n/**\n * 当前挂载的默认宿主桥。\n *\n * 为什么需要它:`reportError` 这类**非 hook**函数拿不到 React context\n * (例如 `useAvCanvas` 的初始化失败、事件回调)。由 `WorkbenchProvider`\n * 挂载时注册,卸载时清空,使这些路径也能尊重宿主的 `notify` 实现。\n *\n * 多实例场景下这里是「最后挂载者生效」—— 提示本身不区分实例,可以接受;\n * 需要精确归属的地方走 `useWorkbench().bridge`。\n */\nlet defaultBridge: HostBridge | null = null\n\nexport function setDefaultBridge(bridge: HostBridge | null): void {\n defaultBridge = bridge\n}\n\nexport function getDefaultBridge(): HostBridge | null {\n return defaultBridge\n}\n\n/**\n * 发一条通知。优先走宿主桥,没有宿主实现才用库内 toast。\n *\n * @param bridge 显式传入的宿主桥;传 `undefined` 时使用已注册的默认桥\n * (见 `setDefaultBridge`),仍没有则退回库内 toast\n */\nexport function notify(\n bridge: HostBridge | null | undefined,\n payload: NotifyPayload,\n): void {\n const target = bridge === undefined ? defaultBridge : bridge\n if (target?.notify != null) {\n target.notify(...toBridgeArgs(payload))\n return\n }\n showInLibrary(payload)\n}\n\n/** 便捷构造:只需要一句错误提示时用 */\nexport function notifyError(\n bridge: HostBridge | null | undefined,\n title: string,\n description?: string,\n): void {\n notify(bridge, { level: 'error', title, description })\n}\n\nexport type { HostBridge }\n"],"mappings":"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"}
|