@xihan-ui/vue 1.0.0-alpha.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/CHANGELOG.md ADDED
@@ -0,0 +1,86 @@
1
+ # @xihan-ui/vue
2
+
3
+ ## 1.0.0-alpha.0
4
+
5
+ ### Major Changes
6
+
7
+ - bc65cb7: 首个公开版本:框架无关的 UI 基座。
8
+
9
+ 自研薄 FSM 内核 + headless(anatomy / machine / connect)+ 设计令牌与主题运行时 + 样式层,
10
+ 102 个组件在 Vue 与 Web Components 两套适配器上共用同一份内核,跨适配器一致性套件与
11
+ 真实 Chromium 里的无障碍扫描、浮层定位契约全绿。
12
+
13
+ 浮层定位、虚拟滚动、Web Components 响应式基类、代码着色、流式 Markdown 均为自研,
14
+ 运行时不带第三方依赖。
15
+
16
+ - 84b1aa3: 新增 Icon 原语,`@xihan-ui/icons` 整包重写为首方图标集。
17
+
18
+ 旧的 `@xihan-ui/icons` 是 27 个第三方图标集的聚合(约四万个图标),已整体移除并在
19
+ npm 上弃用。新包只收自研图标,第一批 29 个覆盖组件库自用的全部语义,24×24 单色
20
+ 描边、`stroke-width` 2。
21
+
22
+ 用法:
23
+
24
+ - `@xihan-ui/kernel` 导出 `IconRecord` / `IconNode` / `IconTag` 三个类型
25
+ - `@xihan-ui/headless` 导出 `connectIcon` / `iconAnatomy` / `iconMeta` / `iconKeyboard`
26
+ - `@xihan-ui/vue` 导出 `XhIcon`,`@xihan-ui/web-components` 注册 `<xh-icon>`
27
+ - `@xihan-ui/styles` 新增 `icon.css`,`data-size` 与 `data-weight` 各三档
28
+
29
+ 图标记录是结构化节点数组而不是 SVG 字符串,渲染端逐节点建元素,运行期不经 HTML
30
+ 解析器。图标数据传的是记录本身而不是名字:按名字查表要把全表静态引进来,摇树会
31
+ 整个失效。
32
+
33
+ WC 侧要在 `<svg data-xh-part="root">` 里留一个空的 `<g data-xh-part="glyph"></g>`
34
+ 作为授权点,元素只在它内部铺图元;不留这个空壳就一个节点都不动,手写内联 SVG 与
35
+ `<use>` 引用两种写法因此都还能用。`icon` 是对象,只能走 property 传,属性里写不出来。
36
+
37
+ 可及名字两态互斥:`label` 给了非空白文本就输出 `role="img"` 与 `aria-label`,否则
38
+ 输出 `aria-hidden="true"`。只有图标的按钮请把名字写在按钮上而不是图标上,两处都写
39
+ 读屏会念两遍。
40
+
41
+ - e788896: Select 支持多选,选中值由单值改为集合,公开 API 破坏性变更。
42
+
43
+ 多选打开方式是 `multiple`:点中条目即在集合里增删该项,列表不收起;单选行为不变,只是选中值
44
+ 的容器形状统一成了数组(单选恒为长度 ≤ 1)。
45
+
46
+ 迁移点:
47
+
48
+ - `SelectValueChangeDetails.value` 由 `string | null` 变 `string[]`。原先判空写 `details.value === null`
49
+ 的,改判 `details.value.length === 0`;取单选值写 `details.value[0]`。
50
+ - `SelectApi` 的 `value` 与 `valueText` 由单值变数组,两者逐项对应;`setValue` 签名变
51
+ `(next: string | string[]) => void`,裸串按单选简写处理;新增 `multiple`。
52
+ 想拿「显示成什么字」不必自己拼,用 `displayText`:有选中取选中项文本(多选按半角逗号加空格连起来),
53
+ 否则取 `placeholder`。
54
+ - Vue 默认插槽暴露的 `value` 与 `setValue` 随之变化;`update:value` 的载荷由单值变数组,
55
+ 因此 `v-model:value` 绑定的变量类型要一并改。`value` / `default-value` 两个 prop 仍接受裸串与 `null`。
56
+ - WC `value-change` 事件的 `detail` 由 `{ value: string | null }` 变 `{ value: string[] }`;
57
+ 新增 `multiple` 属性。`value` 属性只递得进单值,多选集合请写 property。
58
+ 表单影子 `hidden-select` 不再写 `value`,选中态一律由 `option` 的 `selected` 表达(多选时开原生
59
+ `multiple`)—— 靠读 `hidden-select.value` 反查选中项的代码要改成读 `selectedOptions`。
60
+
61
+ ### Minor Changes
62
+
63
+ - c5c5f7f: 两个适配器接上视觉层,各自走独立子入口 `@xihan-ui/vue/backgrounds` 与 `@xihan-ui/web-components/backgrounds`。
64
+
65
+ `@xihan-ui/backgrounds` 声明为**可选 peer**:主入口一行都不引它,不用视觉效果的应用不会因为装了适配器
66
+ 而多出一个 WebGL 引擎。
67
+
68
+ Vue 侧三种用法,从轻到重:`v-background` 指令、`XhBackground` 组件、`useBackground` 组合式函数。
69
+ 指令用在组件上时 Vue 会把它落到该组件的单一根元素上,所以给现成组件加背景不必改动组件本身。
70
+
71
+ WC 侧是 `<xh-background>`:元素自身就是画布容器,内容照常写在里面,效果铺在内容底下,
72
+ 画布 `pointer-events: none` 不挡交互。参数走 `.params` property,点云走 `.setCloud()`。
73
+
74
+ ### Patch Changes
75
+
76
+ - Updated dependencies [bc65cb7]
77
+ - Updated dependencies [84b1aa3]
78
+ - Updated dependencies [e788896]
79
+ - Updated dependencies [46b82b0]
80
+ - @xihan-ui/kernel@1.0.0-alpha.0
81
+ - @xihan-ui/machine@1.0.0-alpha.0
82
+ - @xihan-ui/behavior@1.0.0-alpha.0
83
+ - @xihan-ui/headless@1.0.0-alpha.0
84
+ - @xihan-ui/position@1.0.0-alpha.0
85
+ - @xihan-ui/code-highlight@1.0.0-alpha.0
86
+ - @xihan-ui/backgrounds@1.0.0-alpha.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021-Present XiHanFun and contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @xihan-ui/vue
2
+
3
+ Vue 3 适配器:102 个组件的 Vue 形态,外加对应的 composables。行为全部来自 `@xihan-ui/headless`,这一层只负责把它接到 Vue 的响应式与渲染上。
4
+
5
+ **谁会装它**:在 Vue 里用这套组件的人装它。样式另装 `@xihan-ui/styles`,或者自己写。
6
+
7
+ ## 用法
8
+
9
+ ```ts
10
+ import { XhButton, XhDialog } from '@xihan-ui/vue'
11
+ import '@xihan-ui/styles'
12
+ ```
13
+
14
+ ## 装
15
+
16
+ ```bash
17
+ pnpm add @xihan-ui/vue
18
+ ```
19
+
20
+ 完整文档见 [https://ui.docs.xihanfun.com](https://ui.docs.xihanfun.com)。这个包属于 `adapters/` 组,组的含义见仓库里的 `ui/packages/README.md`。
21
+
22
+ 许可:MIT
@@ -0,0 +1,158 @@
1
+ import { Directive, PropType, ShallowRef } from "vue";
2
+ import { BackgroundEffect, BackgroundQuality, BackgroundSurface, MorphOptions, ParamValue, PointCloud } from "@xihan-ui/backgrounds";
3
+ //#region src/backgrounds.d.ts
4
+ interface UseBackgroundOptions {
5
+ effect: BackgroundEffect | string;
6
+ params?: Record<string, ParamValue>;
7
+ quality?: BackgroundQuality;
8
+ /** 自动绑定指针事件,默认 true。 */
9
+ pointer?: boolean;
10
+ /** 创建后立即播放,默认 true。 */
11
+ autoplay?: boolean;
12
+ /** 系统开启减弱动态效果时冻结时间轴,默认 true。 */
13
+ respectReducedMotion?: boolean;
14
+ /** 滚出视口时暂停绘制,默认 true。 */
15
+ pauseOffscreen?: boolean;
16
+ }
17
+ interface UseBackgroundReturn {
18
+ readonly surface: ShallowRef<BackgroundSurface | null>;
19
+ /**
20
+ * 挂载点。直接当模板 ref 用:`<div :ref="visual.attach">`,
21
+ * 渲染器会在元素进出 DOM 时把元素或 null 交进来。
22
+ */
23
+ attach: (element: Element | null) => void;
24
+ setEffect: (effect: BackgroundEffect | string) => void;
25
+ setParams: (patch: Record<string, ParamValue>) => void;
26
+ setCloud: (cloud: PointCloud, options?: MorphOptions) => void;
27
+ play: () => void;
28
+ pause: () => void;
29
+ destroy: () => void;
30
+ }
31
+ /**
32
+ * 建一张受 Vue 生命周期管理的视觉画面。
33
+ *
34
+ * 用**函数式 ref** 而不是监听一个模板 ref:元素由渲染器直接交到手上,
35
+ * 不经过响应式与调度队列,因此不受 flush 时序影响,元素换了也一定收得到。
36
+ * 组件卸载时自动销毁;在 setup 之外调用则需自己调 destroy。
37
+ */
38
+ declare function useBackground(options: UseBackgroundOptions): UseBackgroundReturn;
39
+ /**
40
+ * `<XhBackground>` —— 独立视觉组件。默认插槽的内容浮在效果之上;
41
+ * 画布铺满根元素且 pointer-events: none,不会挡住插槽里的交互。
42
+ */
43
+ declare const XhBackground: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
44
+ effect: {
45
+ type: PropType<BackgroundEffect | string>;
46
+ required: true;
47
+ };
48
+ params: {
49
+ type: PropType<Record<string, ParamValue>>;
50
+ default: undefined;
51
+ };
52
+ quality: {
53
+ type: PropType<BackgroundQuality>;
54
+ default: undefined;
55
+ };
56
+ /** 数据驱动点云。效果的粒子通道是 cloud 模式时才有意义。 */
57
+ cloud: {
58
+ type: PropType<PointCloud | null>;
59
+ default: null;
60
+ };
61
+ /** 换点云时的形变时长(秒)。 */
62
+ morphDuration: {
63
+ type: NumberConstructor;
64
+ default: undefined;
65
+ };
66
+ pointer: {
67
+ type: BooleanConstructor;
68
+ default: boolean;
69
+ };
70
+ autoplay: {
71
+ type: BooleanConstructor;
72
+ default: boolean;
73
+ };
74
+ respectReducedMotion: {
75
+ type: BooleanConstructor;
76
+ default: boolean;
77
+ };
78
+ pauseOffscreen: {
79
+ type: BooleanConstructor;
80
+ default: boolean;
81
+ };
82
+ /** 渲染成什么标签。 */
83
+ as: {
84
+ type: StringConstructor;
85
+ default: string;
86
+ };
87
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
88
+ [key: string]: any;
89
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
90
+ effect: {
91
+ type: PropType<BackgroundEffect | string>;
92
+ required: true;
93
+ };
94
+ params: {
95
+ type: PropType<Record<string, ParamValue>>;
96
+ default: undefined;
97
+ };
98
+ quality: {
99
+ type: PropType<BackgroundQuality>;
100
+ default: undefined;
101
+ };
102
+ /** 数据驱动点云。效果的粒子通道是 cloud 模式时才有意义。 */
103
+ cloud: {
104
+ type: PropType<PointCloud | null>;
105
+ default: null;
106
+ };
107
+ /** 换点云时的形变时长(秒)。 */
108
+ morphDuration: {
109
+ type: NumberConstructor;
110
+ default: undefined;
111
+ };
112
+ pointer: {
113
+ type: BooleanConstructor;
114
+ default: boolean;
115
+ };
116
+ autoplay: {
117
+ type: BooleanConstructor;
118
+ default: boolean;
119
+ };
120
+ respectReducedMotion: {
121
+ type: BooleanConstructor;
122
+ default: boolean;
123
+ };
124
+ pauseOffscreen: {
125
+ type: BooleanConstructor;
126
+ default: boolean;
127
+ };
128
+ /** 渲染成什么标签。 */
129
+ as: {
130
+ type: StringConstructor;
131
+ default: string;
132
+ };
133
+ }>> & Readonly<{}>, {
134
+ params: Record<string, ParamValue>;
135
+ quality: BackgroundQuality;
136
+ cloud: PointCloud | null;
137
+ morphDuration: number;
138
+ pointer: boolean;
139
+ autoplay: boolean;
140
+ respectReducedMotion: boolean;
141
+ pauseOffscreen: boolean;
142
+ as: string;
143
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
144
+ type BackgroundDirectiveValue = BackgroundEffect | string | UseBackgroundOptions;
145
+ /**
146
+ * `v-background` —— 给任意元素铺一层视觉背景。
147
+ *
148
+ * 用在组件上时,Vue 会把指令落到该组件的单一根元素上,所以给现成组件加背景不需要改组件:
149
+ *
150
+ * ```vue
151
+ * <XhButton v-background="fluidEffect">提交</XhButton>
152
+ * <div v-background="{ effect: 'aurora', params: { speed: 1.6 } }" />
153
+ * ```
154
+ */
155
+ declare const vBackground: Directive<HTMLElement, BackgroundDirectiveValue>;
156
+ //#endregion
157
+ export { BackgroundDirectiveValue, UseBackgroundOptions, UseBackgroundReturn, XhBackground, useBackground, vBackground };
158
+ //# sourceMappingURL=backgrounds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgrounds.d.ts","names":[],"sources":["../src/backgrounds.ts"],"mappings":";;;UA4BiB;EACf,QAAQ;EACR,SAAS,eAAe;EACxB,UAAU;;EAEV;;EAEA;;EAEA;;EAEA;;UAGe;WACN,SAAS,WAAW;;;;;EAK7B,SAAS,SAAS;EAClB,YAAY,QAAQ;EACpB,YAAY,OAAO,eAAe;EAClC,WAAW,OAAO,YAAY,UAAU;EACxC;EACA;EACA;;;;;;;;;iBAUc,cAAc,SAAS,uBAAuB;;;;;cAwCjD,4BAAY,8BAAA;;IAGe,MAAA,SAAS;;;;IACnB,MAAA,SAAS,eAAe;;;;IACvB,MAAA,SAAS;;;;;IAEX,MAAA,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAJE,MAAA,SAAS;;;;IACnB,MAAA,SAAS,eAAe;;;;IACvB,MAAA,SAAS;;;;;IAEX,MAAA,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCAoFpC;KAEU,2BAA2B,4BAA4B;;;;;;;;;;;cAoBtD,aAAa,UAAU,aAAa"}
@@ -0,0 +1,193 @@
1
+ import { defineComponent, getCurrentScope, h, onScopeDispose, shallowRef } from "vue";
2
+ import { createBackgroundSurface } from "@xihan-ui/backgrounds";
3
+ //#region src/backgrounds.ts
4
+ /**
5
+ * 建一张受 Vue 生命周期管理的视觉画面。
6
+ *
7
+ * 用**函数式 ref** 而不是监听一个模板 ref:元素由渲染器直接交到手上,
8
+ * 不经过响应式与调度队列,因此不受 flush 时序影响,元素换了也一定收得到。
9
+ * 组件卸载时自动销毁;在 setup 之外调用则需自己调 destroy。
10
+ */
11
+ function useBackground(options) {
12
+ const surface = shallowRef(null);
13
+ let attached = null;
14
+ function attach(element) {
15
+ const el = element instanceof HTMLElement ? element : null;
16
+ if (el === attached) return;
17
+ attached = el;
18
+ surface.value?.destroy();
19
+ surface.value = el === null ? null : createBackgroundSurface(el, options);
20
+ }
21
+ function destroy() {
22
+ attached = null;
23
+ surface.value?.destroy();
24
+ surface.value = null;
25
+ }
26
+ if (getCurrentScope()) onScopeDispose(destroy);
27
+ return {
28
+ surface,
29
+ attach,
30
+ setEffect: (effect) => surface.value?.setEffect(effect),
31
+ setParams: (patch) => surface.value?.setParams(patch),
32
+ setCloud: (cloud, morph) => surface.value?.setCloud(cloud, morph),
33
+ play: () => surface.value?.play(),
34
+ pause: () => surface.value?.pause(),
35
+ destroy
36
+ };
37
+ }
38
+ /**
39
+ * `<XhBackground>` —— 独立视觉组件。默认插槽的内容浮在效果之上;
40
+ * 画布铺满根元素且 pointer-events: none,不会挡住插槽里的交互。
41
+ */
42
+ const XhBackground = defineComponent({
43
+ name: "XhBackground",
44
+ props: {
45
+ effect: {
46
+ type: [Object, String],
47
+ required: true
48
+ },
49
+ params: {
50
+ type: Object,
51
+ default: void 0
52
+ },
53
+ quality: {
54
+ type: String,
55
+ default: void 0
56
+ },
57
+ /** 数据驱动点云。效果的粒子通道是 cloud 模式时才有意义。 */
58
+ cloud: {
59
+ type: Object,
60
+ default: null
61
+ },
62
+ /** 换点云时的形变时长(秒)。 */
63
+ morphDuration: {
64
+ type: Number,
65
+ default: void 0
66
+ },
67
+ pointer: {
68
+ type: Boolean,
69
+ default: true
70
+ },
71
+ autoplay: {
72
+ type: Boolean,
73
+ default: true
74
+ },
75
+ respectReducedMotion: {
76
+ type: Boolean,
77
+ default: true
78
+ },
79
+ pauseOffscreen: {
80
+ type: Boolean,
81
+ default: true
82
+ },
83
+ /** 渲染成什么标签。 */
84
+ as: {
85
+ type: String,
86
+ default: "div"
87
+ }
88
+ },
89
+ setup(props, { slots, expose }) {
90
+ const api = useBackground({
91
+ effect: props.effect,
92
+ params: props.params,
93
+ quality: props.quality,
94
+ pointer: props.pointer,
95
+ autoplay: props.autoplay,
96
+ respectReducedMotion: props.respectReducedMotion,
97
+ pauseOffscreen: props.pauseOffscreen
98
+ });
99
+ let appliedEffect;
100
+ let appliedQuality;
101
+ let appliedCloud = null;
102
+ let appliedAutoplay = props.autoplay;
103
+ /**
104
+ * 把当前 props 推到画面上。在渲染函数里调用而不是挂 watcher:
105
+ * 渲染函数一定会随 props 变化重跑,且这里读到的参数值同时建立了依赖追踪,
106
+ * 调用方原地改参数对象里的某一项也收得到。
107
+ */
108
+ function sync() {
109
+ const surface = api.surface.value;
110
+ if (surface === null) return;
111
+ if (props.effect !== appliedEffect) {
112
+ surface.setEffect(props.effect);
113
+ appliedEffect = props.effect;
114
+ }
115
+ if (props.quality !== void 0 && props.quality !== appliedQuality) {
116
+ surface.setQuality(props.quality);
117
+ appliedQuality = props.quality;
118
+ }
119
+ if (props.params !== void 0) surface.setParams(props.params);
120
+ if (props.cloud && props.cloud !== appliedCloud) {
121
+ surface.setCloud(props.cloud, { duration: appliedCloud === null ? 0 : props.morphDuration });
122
+ appliedCloud = props.cloud;
123
+ }
124
+ if (props.autoplay !== appliedAutoplay) {
125
+ if (props.autoplay) surface.play();
126
+ else surface.pause();
127
+ appliedAutoplay = props.autoplay;
128
+ }
129
+ }
130
+ const mount = (element) => {
131
+ const previous = api.surface.value;
132
+ api.attach(element instanceof HTMLElement ? element : null);
133
+ const current = api.surface.value;
134
+ if (current === previous || current === null) return;
135
+ appliedEffect = props.effect;
136
+ appliedQuality = props.quality;
137
+ appliedCloud = null;
138
+ appliedAutoplay = props.autoplay;
139
+ sync();
140
+ };
141
+ expose(api);
142
+ return () => {
143
+ sync();
144
+ return h(props.as, {
145
+ "ref": mount,
146
+ "data-scope": "background",
147
+ "data-part": "root"
148
+ }, slots.default?.());
149
+ };
150
+ }
151
+ });
152
+ function toOptions(value) {
153
+ if (typeof value === "string") return { effect: value };
154
+ return "effect" in value ? value : { effect: value };
155
+ }
156
+ const mounted = /* @__PURE__ */ new WeakMap();
157
+ /**
158
+ * `v-background` —— 给任意元素铺一层视觉背景。
159
+ *
160
+ * 用在组件上时,Vue 会把指令落到该组件的单一根元素上,所以给现成组件加背景不需要改组件:
161
+ *
162
+ * ```vue
163
+ * <XhButton v-background="fluidEffect">提交</XhButton>
164
+ * <div v-background="{ effect: 'aurora', params: { speed: 1.6 } }" />
165
+ * ```
166
+ */
167
+ const vBackground = {
168
+ mounted(el, binding) {
169
+ const options = toOptions(binding.value);
170
+ mounted.set(el, {
171
+ surface: createBackgroundSurface(el, options),
172
+ effect: options.effect
173
+ });
174
+ },
175
+ updated(el, binding) {
176
+ const entry = mounted.get(el);
177
+ if (entry === void 0) return;
178
+ const options = toOptions(binding.value);
179
+ if (options.effect !== entry.effect) {
180
+ entry.surface.setEffect(options.effect);
181
+ entry.effect = options.effect;
182
+ }
183
+ if (options.params !== void 0) entry.surface.setParams(options.params);
184
+ },
185
+ unmounted(el) {
186
+ mounted.get(el)?.surface.destroy();
187
+ mounted.delete(el);
188
+ }
189
+ };
190
+ //#endregion
191
+ export { XhBackground, useBackground, vBackground };
192
+
193
+ //# sourceMappingURL=backgrounds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgrounds.js","names":[],"sources":["../src/backgrounds.ts"],"sourcesContent":["// @xihan-ui/vue/backgrounds —— 视觉层的 Vue 适配。\n//\n// 与主入口分开:@xihan-ui/backgrounds 是可选 peer,不用视觉效果的应用不会因为装了本包\n// 而多出一个 WebGL 引擎。用之前先装 @xihan-ui/backgrounds。\n//\n// 三种用法,从轻到重:\n// v-background 给任意元素或组件的根元素铺一层背景,一个字都不用改组件\n// XhBackground 独立的视觉组件,插槽内容浮在效果之上\n// useBackground 自己拿画面实例,接自定义调度或调参面板\n\nimport type {\n BackgroundEffect,\n BackgroundQuality,\n BackgroundSurface,\n MorphOptions,\n ParamValue,\n PointCloud,\n} from '@xihan-ui/backgrounds'\nimport type { Directive, PropType, ShallowRef, VNodeRef } from 'vue'\nimport { createBackgroundSurface } from '@xihan-ui/backgrounds'\nimport {\n defineComponent,\n getCurrentScope,\n h,\n onScopeDispose,\n shallowRef,\n} from 'vue'\n\nexport interface UseBackgroundOptions {\n effect: BackgroundEffect | string\n params?: Record<string, ParamValue>\n quality?: BackgroundQuality\n /** 自动绑定指针事件,默认 true。 */\n pointer?: boolean\n /** 创建后立即播放,默认 true。 */\n autoplay?: boolean\n /** 系统开启减弱动态效果时冻结时间轴,默认 true。 */\n respectReducedMotion?: boolean\n /** 滚出视口时暂停绘制,默认 true。 */\n pauseOffscreen?: boolean\n}\n\nexport interface UseBackgroundReturn {\n readonly surface: ShallowRef<BackgroundSurface | null>\n /**\n * 挂载点。直接当模板 ref 用:`<div :ref=\"visual.attach\">`,\n * 渲染器会在元素进出 DOM 时把元素或 null 交进来。\n */\n attach: (element: Element | null) => void\n setEffect: (effect: BackgroundEffect | string) => void\n setParams: (patch: Record<string, ParamValue>) => void\n setCloud: (cloud: PointCloud, options?: MorphOptions) => void\n play: () => void\n pause: () => void\n destroy: () => void\n}\n\n/**\n * 建一张受 Vue 生命周期管理的视觉画面。\n *\n * 用**函数式 ref** 而不是监听一个模板 ref:元素由渲染器直接交到手上,\n * 不经过响应式与调度队列,因此不受 flush 时序影响,元素换了也一定收得到。\n * 组件卸载时自动销毁;在 setup 之外调用则需自己调 destroy。\n */\nexport function useBackground(options: UseBackgroundOptions): UseBackgroundReturn {\n const surface = shallowRef<BackgroundSurface | null>(null)\n // 元素没换就不动画面:函数式 ref 每次 patch 都会被再调一遍,\n // 不挡住的话每渲染一帧就销毁重建一次,画面永远停在创建时那份参数上\n let attached: HTMLElement | null = null\n\n function attach(element: Element | null): void {\n const el = element instanceof HTMLElement ? element : null\n if (el === attached)\n return\n attached = el\n surface.value?.destroy()\n surface.value = el === null ? null : createBackgroundSurface(el, options)\n }\n\n function destroy(): void {\n attached = null\n surface.value?.destroy()\n surface.value = null\n }\n\n if (getCurrentScope())\n onScopeDispose(destroy)\n\n return {\n surface,\n attach,\n setEffect: (effect): void => surface.value?.setEffect(effect),\n setParams: (patch): void => surface.value?.setParams(patch),\n setCloud: (cloud, morph): void => surface.value?.setCloud(cloud, morph),\n play: (): void => surface.value?.play(),\n pause: (): void => surface.value?.pause(),\n destroy,\n }\n}\n\n/**\n * `<XhBackground>` —— 独立视觉组件。默认插槽的内容浮在效果之上;\n * 画布铺满根元素且 pointer-events: none,不会挡住插槽里的交互。\n */\nexport const XhBackground = defineComponent({\n name: 'XhBackground',\n props: {\n effect: { type: [Object, String] as PropType<BackgroundEffect | string>, required: true },\n params: { type: Object as PropType<Record<string, ParamValue>>, default: undefined },\n quality: { type: String as PropType<BackgroundQuality>, default: undefined },\n /** 数据驱动点云。效果的粒子通道是 cloud 模式时才有意义。 */\n cloud: { type: Object as PropType<PointCloud | null>, default: null },\n /** 换点云时的形变时长(秒)。 */\n morphDuration: { type: Number, default: undefined },\n pointer: { type: Boolean, default: true },\n autoplay: { type: Boolean, default: true },\n respectReducedMotion: { type: Boolean, default: true },\n pauseOffscreen: { type: Boolean, default: true },\n /** 渲染成什么标签。 */\n as: { type: String, default: 'div' },\n },\n setup(props, { slots, expose }) {\n const api = useBackground({\n effect: props.effect,\n params: props.params,\n quality: props.quality,\n pointer: props.pointer,\n autoplay: props.autoplay,\n respectReducedMotion: props.respectReducedMotion,\n pauseOffscreen: props.pauseOffscreen,\n })\n\n let appliedEffect: BackgroundEffect | string | undefined\n let appliedQuality: BackgroundQuality | undefined\n let appliedCloud: PointCloud | null = null\n let appliedAutoplay = props.autoplay\n\n /**\n * 把当前 props 推到画面上。在渲染函数里调用而不是挂 watcher:\n * 渲染函数一定会随 props 变化重跑,且这里读到的参数值同时建立了依赖追踪,\n * 调用方原地改参数对象里的某一项也收得到。\n */\n function sync(): void {\n const surface = api.surface.value\n if (surface === null)\n return\n if (props.effect !== appliedEffect) {\n surface.setEffect(props.effect)\n appliedEffect = props.effect\n }\n if (props.quality !== undefined && props.quality !== appliedQuality) {\n surface.setQuality(props.quality)\n appliedQuality = props.quality\n }\n if (props.params !== undefined)\n surface.setParams(props.params)\n if (props.cloud && props.cloud !== appliedCloud) {\n // 第一份点云直接就位,之后的换形态才走过渡\n surface.setCloud(props.cloud, { duration: appliedCloud === null ? 0 : props.morphDuration })\n appliedCloud = props.cloud\n }\n if (props.autoplay !== appliedAutoplay) {\n if (props.autoplay)\n surface.play()\n else surface.pause()\n appliedAutoplay = props.autoplay\n }\n }\n\n // 签名照 VNodeRef 的函数式 ref 写:渲染器在元素进出 DOM 时把元素或 null 交进来。\n // 同一个元素会被反复交进来,attach 对此幂等;只有真换了画面才重置记账。\n const mount: VNodeRef = (element): void => {\n const previous = api.surface.value\n api.attach(element instanceof HTMLElement ? element : null)\n const current = api.surface.value\n if (current === previous || current === null)\n return\n appliedEffect = props.effect\n appliedQuality = props.quality\n appliedCloud = null\n appliedAutoplay = props.autoplay\n sync()\n }\n\n expose(api)\n\n return () => {\n sync()\n return h(\n props.as,\n { 'ref': mount, 'data-scope': 'background', 'data-part': 'root' },\n slots.default?.(),\n )\n }\n },\n})\n\nexport type BackgroundDirectiveValue = BackgroundEffect | string | UseBackgroundOptions\n\nfunction toOptions(value: BackgroundDirectiveValue): UseBackgroundOptions {\n if (typeof value === 'string')\n return { effect: value }\n return 'effect' in value ? value : { effect: value }\n}\n\nconst mounted = new WeakMap<HTMLElement, { surface: BackgroundSurface, effect: BackgroundEffect | string }>()\n\n/**\n * `v-background` —— 给任意元素铺一层视觉背景。\n *\n * 用在组件上时,Vue 会把指令落到该组件的单一根元素上,所以给现成组件加背景不需要改组件:\n *\n * ```vue\n * <XhButton v-background=\"fluidEffect\">提交</XhButton>\n * <div v-background=\"{ effect: 'aurora', params: { speed: 1.6 } }\" />\n * ```\n */\nexport const vBackground: Directive<HTMLElement, BackgroundDirectiveValue> = {\n mounted(el, binding) {\n const options = toOptions(binding.value)\n mounted.set(el, { surface: createBackgroundSurface(el, options), effect: options.effect })\n },\n updated(el, binding) {\n const entry = mounted.get(el)\n if (entry === undefined)\n return\n const options = toOptions(binding.value)\n if (options.effect !== entry.effect) {\n entry.surface.setEffect(options.effect)\n entry.effect = options.effect\n }\n if (options.params !== undefined)\n entry.surface.setParams(options.params)\n },\n unmounted(el) {\n mounted.get(el)?.surface.destroy()\n mounted.delete(el)\n },\n}\n"],"mappings":"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"}