@firetable/project-xiaochun 0.1.15 → 0.1.16
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/README-CN.md +24 -14
- package/README.md +24 -14
- package/dist/avatar-element.d.ts +10 -4
- package/dist/chunks/{chunk-T6EHFEEZ.js → chunk-HZ2ADKG7.js} +39 -8
- package/dist/chunks/chunk-HZ2ADKG7.js.map +7 -0
- package/dist/chunks/{chunk-KKRZ3SUD.js → chunk-RHQW4QZH.js} +86 -3
- package/dist/chunks/chunk-RHQW4QZH.js.map +7 -0
- package/dist/chunks/{chunk-KQSKHPVF.js → chunk-UWL65W7D.js} +136 -10
- package/dist/chunks/chunk-UWL65W7D.js.map +7 -0
- package/dist/client.d.ts +64 -5
- package/dist/element.cjs +206 -12
- package/dist/element.cjs.map +2 -2
- package/dist/element.js +4 -4
- package/dist/index.cjs +247 -13
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +20 -6
- package/dist/index.js.map +1 -1
- package/dist/loader.global.js +2 -2
- package/dist/loader.global.js.map +3 -3
- package/dist/protocol.cjs +86 -3
- package/dist/protocol.cjs.map +2 -2
- package/dist/protocol.d.ts +104 -3
- package/dist/protocol.js +18 -4
- package/dist/react.cjs +190 -14
- package/dist/react.cjs.map +2 -2
- package/dist/react.d.ts +6 -0
- package/dist/react.js +23 -9
- package/dist/react.js.map +2 -2
- package/package.json +2 -2
- package/dist/chunks/chunk-KKRZ3SUD.js.map +0 -7
- package/dist/chunks/chunk-KQSKHPVF.js.map +0 -7
- package/dist/chunks/chunk-T6EHFEEZ.js.map +0 -7
package/README-CN.md
CHANGED
|
@@ -187,22 +187,24 @@ xc.on('scene-changed', (p) => console.log(p.id));
|
|
|
187
187
|
* **偏好保存**:iframe 用**自己的** localStorage 记住用户最近的服装 / 场景(键 `xiaochun_wearing_outfit` / `xiaochun_scene_theme`,与主站一致)。优先级:显式的 `outfit` / `scene`(URL 或 SDK 选项)> 已保存 > 默认;存的 id 不在白名单会被忽略并清掉;存储被拦截 / 分区时静默回退默认。第三方存储按顶层站点分区,每个宿主站各一份。
|
|
188
188
|
* **`persist`**(可选)另外把它们存在宿主页 localStorage,并作为显式值传回 iframe,因此会盖过 iframe 自己存的。默认 `false`。
|
|
189
189
|
* **`prefetch`**(默认关)只把服装文件下载进 iframe 的 IndexedDB(不解压不合成),串行、排在 EMAGE 加载之后,除非你点名否则不含婚纱。`await xc.prefetch(['xiaochun_cheongsam'])` 任何模式都能用;自动预取需要 `heavy: 'eager'`。用户开了省流量模式则跳过。之后 `setOutfit` 不再走网络(解压合成约 0.9 秒仍在)。
|
|
190
|
-
* **内置按钮**:见下一节(`ui: ['outfit', 'scene']`)。
|
|
190
|
+
* **内置按钮**:见下一节(`ui: ['outfit', 'scene', 'lang', 'github']`、`uiAutoHide`)。
|
|
191
191
|
* **自定义模型**:`setModel({ url })` **默认关闭**,需要 `allowCustomModel: true` 显式开启。
|
|
192
192
|
* **迁移**:选项 `model` → `outfit`;旧的 `setModel('base')` 不再可用(列表请用 `getOutfits()`)。
|
|
193
193
|
|
|
194
|
-
### 🔘
|
|
194
|
+
### 🔘 内置按钮(`ui`、`uiAutoHide`)
|
|
195
195
|
|
|
196
196
|
`ui` 是**部件名数组**,不写就什么都不显示。
|
|
197
197
|
|
|
198
198
|
```ts
|
|
199
|
-
createXiaochun({ container: '#avatar', ui: ['outfit', 'scene'] }); //
|
|
199
|
+
createXiaochun({ container: '#avatar', ui: ['outfit', 'scene', 'lang', 'github'] }); // 部件:chat · bubble · outfit · scene · lang · github
|
|
200
200
|
```
|
|
201
201
|
```html
|
|
202
|
-
<xiaochun-avatar ui="outfit,scene"></xiaochun-avatar> <!-- URL 写法: /embed?ui=
|
|
202
|
+
<xiaochun-avatar ui="outfit,scene,lang,github" ui-autohide="false"></xiaochun-avatar> <!-- URL 写法: /embed?ui=outfit,scene,lang,github&uiAutoHide=false -->
|
|
203
203
|
```
|
|
204
204
|
React:`<Xiaochun ui={['outfit', 'scene']} />`。运行时:`xc.setConfig({ ui: ['outfit'] })`。
|
|
205
205
|
|
|
206
|
+
* **何时显示(`uiAutoHide`,与桌面端一致)**:默认 `'transparent'`——**透明**场景下按钮和聊天栏初始隐藏,**单击角色**才出现(再点角色或点空白收起;10 秒无操作也自动收起,悬停在它们上面或菜单打开时不收);亮 / 暗场景一直显示。`true` 把"点击才出现"扩展到所有场景,`false` 保持一直显示。拖动 iframe(`draggable`)不算单击(位移 ≤ 6px 才算)。限制:透明场景下点宿主页空白处到不了 iframe(穿透),所以收起靠 10 秒超时或再点角色;触屏第一次轻触只用来唤醒命中检测。需要 iframe 声明 `capabilities.ui.autoHide`,旧 iframe 忽略该选项、常显。
|
|
207
|
+
* **语言 / GitHub 按钮**:`lang` 弹出菜单(简体中文 / English / 日本語),选择后立即生效,存在 **iframe 自己的** localStorage(`xiaochun_embed_lang`),并发 `lang-changed`(`{ lang, previous?, initial? }`,握手后会发一次 `initial: true`)。只记用户自己点的——显式 `lang` 选项和 `setConfig({ lang })` 不写存储。`github` 是 `<a target="_blank" rel="noopener noreferrer">`,指向项目仓库。
|
|
206
208
|
* **和主站 TopHeader 同一套组件**:玻璃质感按钮(触屏 44×44,桌面 36×36)、下拉菜单、`Shirt` / `MountainSnow` / `Check` / `Loader2` 图标、同一批 i18n 文案(随 `lang`)。放在右上角,不遮角色。服装列表来自 `capabilities.outfits`(不含裸模、没有自定义 URL),每行只显示名字(菜单里不显示文件体积)、加载中转圈、当前穿着打 ✓。
|
|
207
209
|
* **与 `setOutfit` / `setScene` 同一条路径**:同样的白名单、同一个串行 last-wins 队列,连点会出现轻提示"busy",并照常发 `outfit-changed` / `scene-changed`。宿主用 SDK 调用时按钮状态同步。点按钮不会触发转身/拖动手势。
|
|
208
210
|
* **透明场景**:按钮参与穿透命中(点按钮不会漏到宿主页,空白处仍穿透)。触屏 + 透明场景下,第一次轻触只用来唤醒命中检测(落在宿主页上),第二次才落到按钮。
|
|
@@ -237,6 +239,8 @@ xiaochun-avatar::part(iframe) { outline: 1px solid #0002; }
|
|
|
237
239
|
|
|
238
240
|
可用的 part:`mount` · `wrapper` · `iframe` · `placeholder`。
|
|
239
241
|
|
|
242
|
+
**不会被选区染蓝**:宿主页的文字选区(拖选划过、Cmd/Ctrl+A)跨过 iframe 时,Chrome 会给整块 iframe 盖一层蓝色高亮。SDK 给外壳、占位图、iframe 都设了 `user-select: none`(CSSOM 内联样式,不注入 `<style>`,不受宿主 CSP 影响);宿主自己的文字照常可选,`pointer-events` 和透明穿透不变。别在 `::part(iframe)` 上把 `user-select` 改回 `auto`。
|
|
243
|
+
|
|
240
244
|
---
|
|
241
245
|
|
|
242
246
|
## 🔌 `xiaochun://` 与 `xc.*`
|
|
@@ -357,18 +361,21 @@ createXiaochun({
|
|
|
357
361
|
| `placeholder` | 内置 SVG | 图片 URL、元素或 `false` |
|
|
358
362
|
| `transparent` | `false` | 背景透明叠在页面上(同时开启指针穿透),等价于 `scene: 'transparent'` |
|
|
359
363
|
| `scene` | — | 初始场景:`'light' \| 'dark' \| 'transparent'`(见 `getScenes()`)。未知 id 会被忽略并触发 `error { code: 'unknown_id' }` |
|
|
360
|
-
| `width`、`height` | `
|
|
364
|
+
| `width`、`height` | `600`、`1080` | px 或任意 CSS 长度。**默认值受视口限制**:宽 = `min(600px, 100vw)`(外壳另有 `max-width: 100%`,窄容器不溢出),高 = `min(1080px, 100svh)`(悬浮 `position` 还会扣掉 `--xc-offset-x/y` 边距,整块不会顶出屏幕);显式传入的值原样使用。运行时 `setSize(w, h)`(传 `undefined` = 恢复默认) |
|
|
361
365
|
| `position` | `'inline'` | `'inline' \| 'bottom-right' \| 'bottom-left'` |
|
|
362
366
|
| `draggable` | `false` | 手势拖动:按住角色(不要压在内置按钮上)拖 = 移动 iframe,限制在视口内;内联 / 悬浮模式都生效。见下文「手势」 |
|
|
363
367
|
| `borderRadius` | 非透明 `20px` / 透明 `0` | 外壳圆角(数字 = px 或任意 CSS 长度)。light / dark 场景默认与桌面版窗口圆角同值(20px),透明场景不裁角。运行时用 `setBorderRadius()`;`--xc-radius` CSS 变量优先;`0` = 方角 |
|
|
364
368
|
| `resizable` | `false` | 拖四个角缩放 iframe(与桌面版同一套 40px 热区 / 光标 / 圆弧),运行时状态,**不重建 iframe**。可传 `true` 或 `{ minWidth, minHeight, maxWidth, maxHeight }`(默认最小 120×180,最大 = 视口) |
|
|
365
|
-
| `lang` |
|
|
369
|
+
| `lang` | 自动 | `'zh-CN' \| 'en' \| 'ja'`。优先级:此选项 > 用户上次在内置语言按钮里选的(存在 iframe 自己的 localStorage `xiaochun_embed_lang`)> 浏览器语言 > `zh-CN`。运行时 `setConfig({ lang })` 不持久化。变化时发 `lang-changed` |
|
|
366
370
|
| `outfit` | 默认服装 | 初始服装 id(如 `xiaochun_maid`,见 `getOutfits()`)。未知 id 回退默认服装并触发 `error { code: 'unknown_id' }` |
|
|
367
371
|
| `model` | — | **已弃用**,请改用 `outfit`(会打印 `console.warn`)。不再接受 https URL,见 `allowCustomModel` |
|
|
368
372
|
| `allowCustomModel` | `false` | 显式开启后才允许 `setModel({ url })` 加载任意 https `.vrm` / `.vrmaddon` / `.vrmbase`。第三方文件会在 iframe 里解析,仅对可信 URL 打开 |
|
|
373
|
+
| `camera` | — | 相机取景 `{ fov, distance, height, intro }`:`fov` 15–60°(默认 30,视距按 fov 自动补偿,角色大小基本不变),`distance` 1–15 米(默认约 2.5;首次取景时盖过 iframe 保存的缩放 / 俯仰),`height` ±1 米取景高度偏移(iframe 不保存),`intro: false` 不播推镜头。越界值夹到范围;没有 `pitch`(相机只绕角色上下俯仰,左右是角色自身的 bodyTurn)。创建期写进 `?cameraFov=` 等 URL 参数;运行时用 `setConfig({ camera })`(缺省键不变、`null` 恢复默认,立即重新取景并取消进行中的推镜头)。旧版 `/embed`(无 `capabilities.camera`)忽略。优先级:显式 > iframe 保存的视角 > 默认。 |
|
|
374
|
+
| `persistBox` | `false` | 在**宿主页** localStorage 记住拖动 / 缩放后的位置和大小,下次创建时恢复:`true`(key `xiaochun:box`)或命名空间字符串(`xiaochun:box:<名字>`)。需要 `draggable` / `resizable`。优先级:显式 `width` / `height` > 已保存 > 默认;恢复时钳制到当前视口。`clearPersistedBox({ reset? })` 清除。见「手势」一节 |
|
|
369
375
|
| `persist` | `false` | 可选:另把服装 + 场景偏好保存在**宿主页**的 localStorage(iframe 本来就会存一份自己的):`false` \| `'host'`(`xiaochun:prefs`)\| 自定义 key。显式的 `outfit` / `scene` 选项优先于已保存的偏好;宿主保存的优先于 iframe 自己存的 |
|
|
370
376
|
| `prefetch` | `false` | `true`(全部服装,婚纱 13.9 MB 除外)或 `string[]`。首次加载完成后自动发一次,**仅在 `heavy: 'eager'` 时**;否则请自己调用 `prefetch()` |
|
|
371
|
-
| `ui` | `[]` | iframe 内要显示的内置界面部件,数组,可选 `'chat'`(聊天栏)· `'bubble'`(头顶气泡)· `'outfit'`(换装按钮)· `'scene'`(换场景按钮)。不写/空 = 都不显示;未知名字被忽略并 `console.warn
|
|
377
|
+
| `ui` | `[]` | iframe 内要显示的内置界面部件,数组,可选 `'chat'`(聊天栏)· `'bubble'`(头顶气泡)· `'outfit'`(换装按钮)· `'scene'`(换场景按钮)· `'lang'`(语言切换)· `'github'`(GitHub 链接,新标签页打开)。不写/空 = 都不显示;未知名字被忽略并 `console.warn`。见下文「内置按钮」 |
|
|
378
|
+
| `uiAutoHide` | `'transparent'` | 内置界面何时显示,**与桌面端一致**:`'transparent'` = 只有透明(桌宠)场景初始隐藏、单击角色才出现,亮 / 暗场景常显;`true` = 所有场景都点击才出现;`false` = 一直显示(旧行为)。运行时 `setConfig({ uiAutoHide })` 热切换 |
|
|
372
379
|
| `heavy` | `'lazy'` | `'lazy'`:首次使用才加载 WebLLM / EMAGE · `'eager'`:预加载 |
|
|
373
380
|
| `controls` | `true` | iframe 内滚轮缩放,默认开(与主站一致)。透明场景:只有指针在角色上才缩放,其余位置滚轮仍滚动宿主页;不透明场景:iframe 铺满,该区域内滚轮 = 缩放,**会吞掉该区域的页面滚动**。`false` 锁定(`?controls=0`) |
|
|
374
381
|
| `autoPause` | `true` | 滚出视口自动暂停 |
|
|
@@ -380,7 +387,7 @@ createXiaochun({
|
|
|
380
387
|
|
|
381
388
|
**实例**:`ready` · `say(text, { mode: 'speak' \| 'chat' })` · `speakAudio(source, opts)` · `speakAudioStream(opts)` · `motion(nameOrUrlOrOptions)` · `expression(name)` · `setOutfit(id)` · `setScene(id)` · `getOutfits()` · `getScenes()` · `prefetch(ids?)` · `outfit` / `scene`(只读)· `setModel(outfitOrUrl)` *(旧)* · `setConfig(cfg)` · `setSize(w, h)` · `getBox()` · `setDraggable(on)` · `setResizable(on | limits)` · `startListening()` / `stopListening()` / `mic(on)` · `pause()` / `resume()` · `activate()` · `destroy()` · `on(event, cb)` · `lookAt()` *(协议已预留,目前返回 `unsupported`)*。
|
|
382
389
|
|
|
383
|
-
**事件**:`handshake` · `ready` · `progress` · `state` · `stt` · `utterance`(`phase: 'start' | 'end'`,`kind: 'text' | 'audio'`)· `hit-region` · `outfit-changed` · `scene-changed` · `move` / `resize`(`{ phase: 'start' | 'move' | 'end', left, top, width, height }`)· `error`(新增 `busy`、`unknown_id`;旧 iframe 上开手势会收到一次 `unsupported`)· `destroy`。`progress.phase` 为 `'model' | 'outfit' | 'prefetch'`。
|
|
390
|
+
**事件**:`handshake` · `ready` · `progress` · `state` · `stt` · `utterance`(`phase: 'start' | 'end'`,`kind: 'text' | 'audio'`)· `hit-region` · `outfit-changed` · `scene-changed` · `lang-changed`(`{ lang, previous?, initial? }`)· `move` / `resize`(`{ phase: 'start' | 'move' | 'end', left, top, width, height }`)· `error`(新增 `busy`、`unknown_id`;旧 iframe 上开手势会收到一次 `unsupported`)· `destroy`。`progress.phase` 为 `'model' | 'outfit' | 'prefetch'`。
|
|
384
391
|
|
|
385
392
|
### `<xiaochun-avatar>`
|
|
386
393
|
|
|
@@ -390,8 +397,9 @@ createXiaochun({
|
|
|
390
397
|
| `outfit` | — | 服装 id;运行时修改 = `setOutfit`(**热更新,不重建 iframe**) |
|
|
391
398
|
| `scene` | — | `light` · `dark` · `transparent`;运行时修改 = `setScene`(热更新) |
|
|
392
399
|
| `model` | — | `outfit` 的**已弃用**别名(`outfit` 优先) |
|
|
393
|
-
| `
|
|
394
|
-
| `
|
|
400
|
+
| `camera-fov` / `camera-distance` / `camera-height` / `camera-intro` | — | 同 `camera`(热更新 `setConfig({ camera })`,不重建;去掉属性 = 恢复默认) |
|
|
401
|
+
| `persist` / `persist-box` / `prefetch` / `allow-custom-model` | — | 同对应选项(`persist-box=""` = 默认 key,其它字符串 = 命名空间);修改会重建 |
|
|
402
|
+
| `lang` | 自动 | `zh-CN` · `en` · `ja`;不写 = 用户上次的选择 > 浏览器语言 > `zh-CN`;运行时修改 = `setConfig`(不持久化) |
|
|
395
403
|
| `mic` | `false` | 开关听写(模型加载完成后生效) |
|
|
396
404
|
| `transparent` | `true` | `"false"` 关闭 |
|
|
397
405
|
| `draggable` | `false` | 手势拖动(内联 / 悬浮都行);运行时修改 = `setDraggable`(热更新) |
|
|
@@ -399,15 +407,16 @@ createXiaochun({
|
|
|
399
407
|
| `border-radius` | 非透明 `20px` / 透明 `0` | 外壳圆角(数字 = px 或 CSS 长度);运行时修改 = `setBorderRadius`(热更新) |
|
|
400
408
|
| `min-size` / `max-size` | — | 缩放限幅,格式同 `size`(如 `min-size="160x240"`) |
|
|
401
409
|
| `position` | `inline` | `inline` · `bottom-right` · `bottom-left` |
|
|
402
|
-
| `size` | `
|
|
410
|
+
| `size` | `600x1080`(受视口限制) | `"280"`(高 = 宽 × 1.5)、`"320x480"`、`"100%x480px"`;运行时修改 = `setSize`(**热更新,不重建 iframe**) |
|
|
403
411
|
| `lazy` | 空闲 + 视口 | `"click"` 仅点击;`"false"` 立即创建 |
|
|
404
412
|
| `paused` | `false` | `pause()` / `resume()` |
|
|
405
|
-
| `ui` | — | 逗号分隔的部件名,如 `ui="outfit,scene"`(不写 = 都不显示;未知项忽略并 warn)。改它会重建;运行时用 `setConfig({ ui })` |
|
|
413
|
+
| `ui` | — | 逗号分隔的部件名,如 `ui="outfit,scene,lang,github"`(不写 = 都不显示;未知项忽略并 warn)。改它会重建;运行时用 `setConfig({ ui })` |
|
|
414
|
+
| `ui-autohide` | `transparent` | `"transparent"`(默认,仅透明场景点击才出现)· `"true"`(所有场景)· `"false"`(一直显示)。改它会重建;运行时用 `setConfig({ uiAutoHide })` |
|
|
406
415
|
| `controls` | 开 | `controls="false"` 锁定 iframe 内滚轮缩放 |
|
|
407
416
|
| `placeholder` / `heavy` / `allowed-origins` | — | 同 `createXiaochun` |
|
|
408
417
|
| `cross-origin-isolated` | `false` | 同 `createXiaochun({ crossOriginIsolated })`;修改会重建 iframe |
|
|
409
418
|
|
|
410
|
-
**事件**(`CustomEvent`,`composed`,`detail` = 协议 payload):`xc-ready` · `xc-progress` · `xc-state` · `xc-stt` · `xc-utterance` · `xc-outfit-changed` · `xc-scene-changed` · `xc-move` · `xc-resize` · `xc-error`。
|
|
419
|
+
**事件**(`CustomEvent`,`composed`,`detail` = 协议 payload):`xc-ready` · `xc-progress` · `xc-state` · `xc-stt` · `xc-utterance` · `xc-outfit-changed` · `xc-scene-changed` · `xc-lang-changed` · `xc-move` · `xc-resize` · `xc-error`。
|
|
411
420
|
**方法**:`say` · `speakAudio` · `speakAudioStream` · `motion` · `expression` · `setOutfit` · `setScene` · `getOutfits` · `getScenes` · `prefetch` · `destroy`;`el.client` 可拿到完整的 SDK 实例。
|
|
412
421
|
|
|
413
422
|
---
|
|
@@ -433,7 +442,8 @@ xc.setSize(240, 360); // 程序化改尺寸(同样热更新)
|
|
|
433
442
|
* 透明场景 + 穿透:只有角色、(开了 `resizable` 时)四角 40px 热区会接管指针,其余位置仍穿透到你的页面;内置按钮不会成为拖动 / 缩放起点。
|
|
434
443
|
* 拖动时的选中蓝框两侧都已抑制(`user-select: none`、阻止 `selectstart` / `dragstart`、缩放时用 pointer capture)。
|
|
435
444
|
* 旧版 `/embed`(没有 `capabilities.gestures`)上开内联 `draggable` 或 `resizable`,会触发一次 `error { code: 'unsupported', command: 'gestures' }`。
|
|
436
|
-
*
|
|
445
|
+
* **记住位置和大小(`persistBox`,默认关)**:开了 `draggable` / `resizable` 后,`persistBox: true`(或命名空间字符串 → key `xiaochun:box:<名字>`)会在每次拖动 / 缩放**结束**时把盒子存进**宿主页**的 localStorage(读写都包了 try/catch,隐私模式 / 配额满只是不记忆),下次创建时恢复。优先级:显式 `width` / `height` > 已保存 > 默认(600×1080);位置没有显式选项(`position` 只是预设锚点),所以已保存的位置始终生效。**想恢复用户缩放后的大小,就不要传 `width` / `height`。** 恢复时钳制到*当前*视口(大小夹在 `[最小, 视口]`,最小 120×180 或你的 `resizable` 限幅;悬浮盒子整块拉回屏幕内;内联盒子横向夹进视口、纵向不越过文档顶部)。保存时的 `position` 模式与当前不同、或数据损坏,都会被忽略并清掉。只在开了手势时恢复。`xc.clearPersistedBox()` 删除已保存的值;`xc.clearPersistedBox({ reset: true })` 还会把盒子还原到初始位置 / 大小。元素属性 `persist-box`(`""` / `"true"` / 命名空间),React `persistBox` prop + `ref.clearPersistedBox()`。
|
|
446
|
+
* 试玩:`examples/embed-host.html?draggable=1&resizable=1&persistBox=1`。
|
|
437
447
|
|
|
438
448
|
## 🧪 本地试玩 (Try It Locally)
|
|
439
449
|
|
package/README.md
CHANGED
|
@@ -187,22 +187,24 @@ xc.on('scene-changed', (p) => console.log(p.id));
|
|
|
187
187
|
* **Preferences**: the iframe remembers the user's last outfit/scene in **its own** localStorage (`xiaochun_wearing_outfit` / `xiaochun_scene_theme`, the same keys as the main site). Priority: explicit `outfit` / `scene` (URL or SDK options) > saved > default; unknown saved ids are ignored and cleared; if storage is blocked or partitioned it silently falls back to the defaults. Third-party storage is partitioned per top-level site, so each host site has its own copy.
|
|
188
188
|
* **`persist`** (optional) additionally keeps them in the host page's localStorage and passes them back as explicit values, so it overrides the iframe's own copy. Default `false`.
|
|
189
189
|
* **`prefetch`** (off by default) downloads outfit files into the iframe's IndexedDB only (no decode/compose), serially, after EMAGE is loaded, never the wedding outfit unless you name it. `await xc.prefetch(['xiaochun_cheongsam'])` works in any mode; the automatic variant needs `heavy: 'eager'`. Skipped when the user enabled Data Saver. A later `setOutfit` then skips the network (the ~0.9 s decode/compose remains).
|
|
190
|
-
* **Built-in buttons**: see the next section (`ui: ['outfit', 'scene']`).
|
|
190
|
+
* **Built-in buttons**: see the next section (`ui: ['outfit', 'scene', 'lang', 'github']`, `uiAutoHide`).
|
|
191
191
|
* **Custom models**: `setModel({ url })` is **off by default**; pass `allowCustomModel: true` to opt in.
|
|
192
192
|
* **Migrating**: option `model` → `outfit`; the legacy `setModel('base')` no longer works (use `getOutfits()` for the list).
|
|
193
193
|
|
|
194
|
-
### 🔘 Built-in
|
|
194
|
+
### 🔘 Built-in buttons (`ui`, `uiAutoHide`)
|
|
195
195
|
|
|
196
196
|
`ui` is an **array of part names**; nothing is shown unless you list it.
|
|
197
197
|
|
|
198
198
|
```ts
|
|
199
|
-
createXiaochun({ container: '#avatar', ui: ['outfit', 'scene'] }); //
|
|
199
|
+
createXiaochun({ container: '#avatar', ui: ['outfit', 'scene', 'lang', 'github'] }); // parts: chat · bubble · outfit · scene · lang · github
|
|
200
200
|
```
|
|
201
201
|
```html
|
|
202
|
-
<xiaochun-avatar ui="outfit,scene"></xiaochun-avatar> <!-- URL form: /embed?ui=
|
|
202
|
+
<xiaochun-avatar ui="outfit,scene,lang,github" ui-autohide="false"></xiaochun-avatar> <!-- URL form: /embed?ui=outfit,scene,lang,github&uiAutoHide=false -->
|
|
203
203
|
```
|
|
204
204
|
React: `<Xiaochun ui={['outfit', 'scene']} />`. At runtime: `xc.setConfig({ ui: ['outfit'] })`.
|
|
205
205
|
|
|
206
|
+
* **When it shows (`uiAutoHide`, same as the desktop app)**: default `'transparent'` — in the **transparent** scene the buttons and chat bar start hidden and a **single click on the character** shows them (click the character again, or empty space, to hide; they also hide after 10 s idle, and stay while you hover them or a menu is open); in light / dark scenes they are always visible. `true` extends click-to-show to every scene, `false` keeps them always visible. Dragging the iframe (`draggable`) never counts as a click (≤ 6 px movement). Limits: in the transparent scene a click on blank host-page space never reaches the iframe (pass-through), so hiding relies on the 10 s timeout or clicking the character; on touch the first tap only wakes hit detection. Needs an iframe with `capabilities.ui.autoHide`; older iframes ignore it and always show.
|
|
207
|
+
* **Language / GitHub buttons**: `lang` opens a menu (简体中文 / English / 日本語); the choice applies immediately, is saved in the **iframe's own** localStorage (`xiaochun_embed_lang`) and fires `lang-changed` (`{ lang, previous?, initial? }`; one `initial: true` after the handshake). Only the user's own pick is saved — an explicit `lang` option and `setConfig({ lang })` are not. `github` is an `<a target="_blank" rel="noopener noreferrer">` to the project repo.
|
|
206
208
|
* **Same components as the main site's TopHeader**: glass buttons (44×44 on touch, 36×36 on desktop), the dropdown menu, the `Shirt` / `MountainSnow` / `Check` / `Loader2` icons and the same i18n strings (follows `lang`). Placed top-right so the character stays uncovered. The list comes from `capabilities.outfits` (no bare model, no custom URLs) and shows just the outfit names (no file sizes in the menu; `capabilities.outfits[].sizeMB` is still provided for your own UI), a spinner on the loading row and a ✓ on the worn one.
|
|
207
209
|
* **Same path as `setOutfit` / `setScene`**: same whitelist, same serial last-wins queue, a light "busy" hint when you click fast, and the usual `outfit-changed` / `scene-changed` events. Calls you make through the SDK keep the buttons in sync. Clicking a button never triggers the body-turn / drag gestures.
|
|
208
210
|
* **Transparent scene**: the buttons take part in the pass-through hit test (clicks on them do not fall through to your page; blank space still does). On touch + transparent, the first tap only wakes hit detection (it lands on your page), the second tap reaches the button.
|
|
@@ -237,6 +239,8 @@ xiaochun-avatar::part(iframe) { outline: 1px solid #0002; }
|
|
|
237
239
|
|
|
238
240
|
Parts: `mount` · `wrapper` · `iframe` · `placeholder`.
|
|
239
241
|
|
|
242
|
+
**No blue selection tint**: when a host-page text selection crosses the iframe (drag-select, Cmd/Ctrl+A), Chrome paints the whole iframe blue. The SDK sets `user-select: none` (inline CSSOM styles, no `<style>` injected, so host CSP is unaffected) on the shell, placeholder and iframe; your own text stays selectable and pointer-events / transparent pass-through are untouched. Don't override `user-select` on `::part(iframe)` back to `auto`.
|
|
243
|
+
|
|
240
244
|
---
|
|
241
245
|
|
|
242
246
|
## 🔌 `xiaochun://` and `xc.*`
|
|
@@ -357,18 +361,21 @@ createXiaochun({
|
|
|
357
361
|
| `placeholder` | built-in SVG | Image URL, element, or `false` |
|
|
358
362
|
| `transparent` | `false` | Transparent background over your page (with pointer pass-through). Same as `scene: 'transparent'` |
|
|
359
363
|
| `scene` | — | Initial scene: `'light' \| 'dark' \| 'transparent'` (see `getScenes()`). Unknown id → ignored + `error { code: 'unknown_id' }` |
|
|
360
|
-
| `width`, `height` | `
|
|
364
|
+
| `width`, `height` | `600`, `1080` | px or any CSS length. **Defaults are clamped to the viewport**: width = `min(600px, 100vw)` (and the shell has `max-width: 100%`, so it never overflows a narrow container), height = `min(1080px, 100svh)` (floating `position`s also subtract the `--xc-offset-x/y` margins so the box never sticks out of the screen). Explicit values are used as-is. Runtime: `setSize(w, h)` (`undefined` = back to the default) |
|
|
361
365
|
| `position` | `'inline'` | `'inline' \| 'bottom-right' \| 'bottom-left'` |
|
|
362
366
|
| `draggable` | `false` | Gesture drag: press and drag the character (not a built-in button) to move the iframe, clamped to the viewport. Works in inline and floating modes. See [Gestures](#gestures-drag-and-corner-resize) |
|
|
363
367
|
| `borderRadius` | opaque `20px` / transparent `0` | Shell corner radius (number = px, or any CSS length). The default for light / dark scenes equals the desktop app's window radius (20 px); transparent scenes are not clipped. Live via `setBorderRadius()`; the `--xc-radius` CSS variable wins; `0` = square corners |
|
|
364
368
|
| `resizable` | `false` | Drag a corner (same 40 px hot zone / cursors / arcs as the desktop app) to resize the iframe at runtime, **without rebuilding it**. `true` or `{ minWidth, minHeight, maxWidth, maxHeight }` (default min 120×180, max = viewport) |
|
|
365
|
-
| `lang` |
|
|
369
|
+
| `lang` | auto | `'zh-CN' \| 'en' \| 'ja'`. Priority: this option > the language the user last picked in the built-in language button (saved in the iframe's own localStorage `xiaochun_embed_lang`) > browser language > `zh-CN`. `setConfig({ lang })` at runtime is not persisted. Changes fire `lang-changed` |
|
|
366
370
|
| `outfit` | default outfit | Initial outfit id (e.g. `xiaochun_maid`, see `getOutfits()`). Unknown id → default outfit + `error { code: 'unknown_id' }` |
|
|
367
371
|
| `model` | — | **Deprecated**, use `outfit` (a `console.warn` is printed). An https URL is no longer accepted here; see `allowCustomModel` |
|
|
368
372
|
| `allowCustomModel` | `false` | Opt in to `setModel({ url })` for arbitrary https `.vrm` / `.vrmaddon` / `.vrmbase`. Third-party files are parsed inside the iframe, so enable it only for URLs you trust |
|
|
373
|
+
| `camera` | — | Camera framing `{ fov, distance, height, intro }`: `fov` 15–60° (default 30; distance auto-compensates so the character keeps its size), `distance` 1–15 m (default ≈2.5; overrides the iframe's saved zoom/pitch on first fit), `height` ±1 m framing offset (not saved by the iframe), `intro: false` skips the dolly-in. Out-of-range values are clamped; there is no `pitch` (the camera only tilts up/down around the character, left/right is the character's body turn). Sent as `?cameraFov=` etc. at create time; at runtime use `setConfig({ camera })` (omitted key = unchanged, `null` = back to default; re-frames immediately and cancels a running dolly-in). Old `/embed` (no `capabilities.camera`) ignores it. Priority: explicit > the iframe's saved view > default. |
|
|
374
|
+
| `persistBox` | `false` | Remember the iframe's position & size after drag / resize in **your page's** localStorage and restore it on the next create: `true` (key `xiaochun:box`) or a namespace string (`xiaochun:box:<name>`). Needs `draggable` / `resizable`. Priority: explicit `width` / `height` > saved > default; restore is clamped to the current viewport. `clearPersistedBox({ reset? })` clears it. See [Gestures](#gestures-drag-and-corner-resize) |
|
|
369
375
|
| `persist` | `false` | Optionally also remember outfit + scene in **the host page's** localStorage (the iframe already keeps its own copy): `false` \| `'host'` (`xiaochun:prefs`) \| a custom key. Explicit `outfit` / `scene` options win over saved values; the saved copy wins over the iframe's own |
|
|
370
376
|
| `prefetch` | `false` | `true` (all outfits except the 13.9 MB wedding) or `string[]`. Auto-sent once after the first load, **only with `heavy: 'eager'`**; otherwise call `prefetch()` yourself |
|
|
371
|
-
| `ui` | `[]` | Built-in UI parts to show inside the iframe: an array of `'chat'` (chat bar) · `'bubble'` (speech bubble) · `'outfit'` (outfit button) · `'scene'` (scene button). Unset / empty = none. Unknown names are ignored with a `console.warn`. See "Built-in
|
|
377
|
+
| `ui` | `[]` | Built-in UI parts to show inside the iframe: an array of `'chat'` (chat bar) · `'bubble'` (speech bubble) · `'outfit'` (outfit button) · `'scene'` (scene button) · `'lang'` (language switcher) · `'github'` (GitHub link, opens in a new tab). Unset / empty = none. Unknown names are ignored with a `console.warn`. See "Built-in buttons" below |
|
|
378
|
+
| `uiAutoHide` | `'transparent'` | When the built-in UI is visible, **same as the desktop app**: `'transparent'` = only the transparent (desk-pet) scene hides it until you click the character; light / dark scenes always show it. `true` = click-to-show in every scene; `false` = always visible (the previous behaviour). Live via `setConfig({ uiAutoHide })` |
|
|
372
379
|
| `heavy` | `'lazy'` | `'lazy'`: load WebLLM / EMAGE on first use · `'eager'`: preload |
|
|
373
380
|
| `controls` | `true` | Wheel zoom inside the iframe, on by default like the main site. Transparent scene: zooms only while the pointer is on the character, everywhere else the wheel scrolls your page. Opaque scenes: the iframe fills its area, so the wheel zooms there and **swallows page scrolling over that area**. `false` locks it (`?controls=0`) |
|
|
374
381
|
| `autoPause` | `true` | Pause when out of the viewport |
|
|
@@ -380,7 +387,7 @@ createXiaochun({
|
|
|
380
387
|
|
|
381
388
|
**Instance**: `ready` · `say(text, { mode: 'speak' \| 'chat' })` · `speakAudio(source, opts)` · `speakAudioStream(opts)` · `motion(nameOrUrlOrOptions)` · `expression(name)` · `setOutfit(id)` · `setScene(id)` · `getOutfits()` · `getScenes()` · `prefetch(ids?)` · `outfit` / `scene` (getters) · `setModel(outfitOrUrl)` *(legacy)* · `setConfig(cfg)` · `setSize(w, h)` · `getBox()` · `setDraggable(on)` · `setResizable(on | limits)` · `startListening()` / `stopListening()` / `mic(on)` · `pause()` / `resume()` · `activate()` · `destroy()` · `on(event, cb)` · `lookAt()` *(reserved in the protocol; currently returns `unsupported`)*.
|
|
382
389
|
|
|
383
|
-
**Events**: `handshake` · `ready` · `progress` · `state` · `stt` · `utterance` (`phase: 'start' | 'end'`, `kind: 'text' | 'audio'`) · `hit-region` · `outfit-changed` · `scene-changed` · `move` / `resize` (`{ phase: 'start' | 'move' | 'end', left, top, width, height }`) · `error` (now also `busy`, `unknown_id`; one `unsupported` if gestures are enabled on an old iframe) · `destroy`. `progress.phase` is `'model' | 'outfit' | 'prefetch'`.
|
|
390
|
+
**Events**: `handshake` · `ready` · `progress` · `state` · `stt` · `utterance` (`phase: 'start' | 'end'`, `kind: 'text' | 'audio'`) · `hit-region` · `outfit-changed` · `scene-changed` · `lang-changed` (`{ lang, previous?, initial? }`) · `move` / `resize` (`{ phase: 'start' | 'move' | 'end', left, top, width, height }`) · `error` (now also `busy`, `unknown_id`; one `unsupported` if gestures are enabled on an old iframe) · `destroy`. `progress.phase` is `'model' | 'outfit' | 'prefetch'`.
|
|
384
391
|
|
|
385
392
|
### `<xiaochun-avatar>`
|
|
386
393
|
|
|
@@ -390,8 +397,9 @@ createXiaochun({
|
|
|
390
397
|
| `outfit` | — | Outfit id; changing it at runtime = `setOutfit` (**live update, no iframe rebuild**) |
|
|
391
398
|
| `scene` | — | `light` · `dark` · `transparent`; runtime change = `setScene` (live) |
|
|
392
399
|
| `model` | — | **Deprecated** alias of `outfit` (`outfit` wins) |
|
|
393
|
-
| `
|
|
394
|
-
| `
|
|
400
|
+
| `camera-fov` / `camera-distance` / `camera-height` / `camera-intro` | — | Same as `camera` (hot-updated via `setConfig({ camera })`, no rebuild; removing an attribute = default) |
|
|
401
|
+
| `persist` / `persist-box` / `prefetch` / `allow-custom-model` | — | Same as the options (`persist-box=""` = default key, any other string = namespace); changing them rebuilds |
|
|
402
|
+
| `lang` | auto | `zh-CN` · `en` · `ja`; unset = the user's saved pick > browser language > `zh-CN`; runtime change = `setConfig` (not persisted) |
|
|
395
403
|
| `mic` | `false` | Toggle dictation (takes effect once the model is loaded) |
|
|
396
404
|
| `transparent` | `true` | `"false"` turns it off |
|
|
397
405
|
| `draggable` | `false` | Gesture drag (inline and floating). Runtime change = `setDraggable` (live) |
|
|
@@ -399,15 +407,16 @@ createXiaochun({
|
|
|
399
407
|
| `border-radius` | opaque `20px` / transparent `0` | Shell corner radius (number = px or CSS length); runtime change = `setBorderRadius` (live) |
|
|
400
408
|
| `min-size` / `max-size` | — | Resize limits, same format as `size` (e.g. `min-size="160x240"`) |
|
|
401
409
|
| `position` | `inline` | `inline` · `bottom-right` · `bottom-left` |
|
|
402
|
-
| `size` | `
|
|
410
|
+
| `size` | `600x1080` (viewport-clamped) | `"280"` (height = width × 1.5), `"320x480"`, `"100%x480px"`; runtime change = `setSize` (**live, no iframe rebuild**) |
|
|
403
411
|
| `lazy` | idle + viewport | `"click"` for click only; `"false"` for immediate |
|
|
404
412
|
| `paused` | `false` | `pause()` / `resume()` |
|
|
405
|
-
| `ui` | — | Comma-separated parts, e.g. `ui="outfit,scene"` (unset = none; unknown names ignored with a warn). Changing it rebuilds; use `setConfig({ ui })` at runtime |
|
|
413
|
+
| `ui` | — | Comma-separated parts, e.g. `ui="outfit,scene,lang,github"` (unset = none; unknown names ignored with a warn). Changing it rebuilds; use `setConfig({ ui })` at runtime |
|
|
414
|
+
| `ui-autohide` | `transparent` | `"transparent"` (default; click-to-show only in the transparent scene) · `"true"` (every scene) · `"false"` (always visible). Changing it rebuilds; use `setConfig({ uiAutoHide })` at runtime |
|
|
406
415
|
| `controls` | on | `controls="false"` locks wheel zoom inside the iframe |
|
|
407
416
|
| `placeholder` / `heavy` / `allowed-origins` | — | Same as `createXiaochun` |
|
|
408
417
|
| `cross-origin-isolated` | `false` | Same as `createXiaochun({ crossOriginIsolated })`; changing it rebuilds the iframe |
|
|
409
418
|
|
|
410
|
-
**Events** (`CustomEvent`, `composed`, `detail` = protocol payload): `xc-ready` · `xc-progress` · `xc-state` · `xc-stt` · `xc-utterance` · `xc-outfit-changed` · `xc-scene-changed` · `xc-move` · `xc-resize` · `xc-error`.
|
|
419
|
+
**Events** (`CustomEvent`, `composed`, `detail` = protocol payload): `xc-ready` · `xc-progress` · `xc-state` · `xc-stt` · `xc-utterance` · `xc-outfit-changed` · `xc-scene-changed` · `xc-lang-changed` · `xc-move` · `xc-resize` · `xc-error`.
|
|
411
420
|
**Methods**: `say` · `speakAudio` · `speakAudioStream` · `motion` · `expression` · `setOutfit` · `setScene` · `getOutfits` · `getScenes` · `prefetch` · `destroy`; `el.client` gives you the full SDK instance.
|
|
412
421
|
|
|
413
422
|
---
|
|
@@ -433,7 +442,8 @@ xc.setSize(240, 360); // programmatic resize (also live)
|
|
|
433
442
|
* Transparent scene + pass-through: only the character and (when `resizable`) the four 40 px corner zones take over the pointer; everywhere else still falls through to your page. Built-in buttons are never a drag / resize start.
|
|
434
443
|
* The blue text-selection box during a drag is suppressed on both sides (`user-select: none`, `selectstart` / `dragstart` blocked, pointer capture while resizing).
|
|
435
444
|
* Against an older `/embed` without `capabilities.gestures`, enabling inline `draggable` or `resizable` emits one `error { code: 'unsupported', command: 'gestures' }`.
|
|
436
|
-
*
|
|
445
|
+
* **Remember position & size (`persistBox`, off by default)**: with `draggable` / `resizable` on, `persistBox: true` (or a namespace string → key `xiaochun:box:<name>`) saves the box to **your page's** localStorage when a drag / resize **ends** (all reads / writes are try/catch'd — private mode or a full quota just means "not remembered") and restores it the next time the instance is created. Priority: explicit `width` / `height` > saved > default (600×1080); position has no explicit option (`position` is only an anchor preset), so a saved position always wins. **To get a user-resized size back, don't pass `width` / `height`.** Restoring is clamped to the *current* viewport (size within `[min, viewport]`, min 120×180 or your `resizable` limits; floating boxes pulled fully on-screen; inline boxes clamped horizontally and kept below the document top). A saved box whose `position` mode differs from the current one, or corrupt data, is ignored and removed. Only restored when a gesture is enabled. `xc.clearPersistedBox()` removes the saved value; `xc.clearPersistedBox({ reset: true })` also puts the box back to its initial place / size. Element: `persist-box` (`""` / `"true"` / a namespace), React: `persistBox` prop + `ref.clearPersistedBox()`.
|
|
446
|
+
* Try it: `examples/embed-host.html?draggable=1&resizable=1&persistBox=1`.
|
|
437
447
|
|
|
438
448
|
## 🧪 Try It Locally
|
|
439
449
|
|
package/dist/avatar-element.d.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* avatar-element.ts — `<xiaochun-avatar>` Web Component (Shadow DOM 内包 iframe)。
|
|
3
3
|
*
|
|
4
|
-
* 属性: src outfit scene model(deprecated) lang mic transparent draggable resizable min-size max-size border-radius position size lazy paused placeholder heavy ui controls allowed-origins cross-origin-isolated persist allow-custom-model
|
|
4
|
+
* 属性: src outfit scene model(deprecated) lang mic transparent draggable resizable min-size max-size border-radius position size lazy paused placeholder heavy ui ui-autohide controls allowed-origins cross-origin-isolated persist allow-custom-model
|
|
5
5
|
* - 布尔属性: 缺省取默认值; "" / "true" = true; "false" = false。
|
|
6
|
-
* - size="320x480" 或 size="320" (高 = 宽 × 1.5); 也可写 CSS 长度 "100%x480px"。改 size **热更新** (setSize, 不重建 iframe)。
|
|
6
|
+
* - size="320x480" 或 size="320" (高 = 宽 × 1.5); 也可写 CSS 长度 "100%x480px"。缺省 = 600x1080 (宽不超过容器、高不超过视口)。改 size **热更新** (setSize, 不重建 iframe)。
|
|
7
7
|
* - draggable: 手势拖动 (角色上按住拖, 内联 / 悬浮都行); resizable: 四角缩放热区 (对角固定, 不重建 iframe); 两者默认关, 改属性热更新。
|
|
8
8
|
* min-size / max-size="120x180": resizable 的最小 / 最大尺寸 (px, 缺省最小 120x180、最大只受视口限制)。
|
|
9
9
|
* - border-radius: 外壳圆角 (数字 = px 或 CSS 长度); 缺省: 非透明场景 20px (与 Tauri 桌宠窗口同值) / 透明 0; 改属性热更新 (setBorderRadius)。
|
|
10
10
|
* - outfit: 内置服装 id (xiaochun_maid); 改属性**热更新** (setOutfit, 不重建 iframe)。model 是 outfit 的旧别名 (deprecated)。
|
|
11
11
|
* - scene: light | dark | transparent; 改属性**热更新** (setScene, 不重建 iframe)。
|
|
12
|
-
* - ui: 要显示的 iframe 内置界面部件, 逗号分隔: chat,bubble,outfit,scene (例 ui="outfit,scene"); 缺省 = 都不显示; 改它会重建。
|
|
12
|
+
* - ui: 要显示的 iframe 内置界面部件, 逗号分隔: chat,bubble,outfit,scene,lang,github (例 ui="outfit,scene,lang,github"); 缺省 = 都不显示; 改它会重建。
|
|
13
13
|
* 旧写法 ui / ui="true" 已弃用 (= chat,bubble, 会 console.warn)。
|
|
14
|
+
* - ui-autohide: 内置界面的显示策略: 缺省 / "transparent" = 与 Tauri 一致 (只在透明场景点击出现, 亮暗场景常显); "true" = 所有场景点击出现; "false" = 一直显示。改它会重建。
|
|
15
|
+
* - persist-box: 记住用户拖动 / 缩放后的位置和大小 (宿主 localStorage): "" / "true" = 默认 key, 其它字符串 = 命名空间 (xiaochun:box:<名字>); 需配合 draggable / resizable; 显式 size 优先于已保存的大小。改它会重建。
|
|
14
16
|
* - persist: "host" 或自定义 localStorage key; 另把服装/场景偏好存在宿主页 (可选; 默认不存, iframe 自己的 localStorage 仍会记住)。
|
|
15
17
|
* - prefetch: "" / "true" = 预取全部内置服装 (婚纱除外), 或逗号分隔的 id 列表; 只在 heavy="eager" 时自动触发 (默认关)。
|
|
16
18
|
* - allow-custom-model: 允许 setModel({url}) 加载任意 https 模型 (默认关闭)。
|
|
17
19
|
* 样式 (CSS 自定义属性, 可写在 <xiaochun-avatar> 上或任意祖先上; 取值范围/效果见 client.ts 里的注释与 docs/EMBED.md §样式):
|
|
18
20
|
* --xc-radius --xc-shadow --xc-z-index --xc-offset-x --xc-offset-y --xc-bg
|
|
19
21
|
* 可用 ::part() 定制外壳: ::part(mount) ::part(wrapper) ::part(iframe) ::part(placeholder) (iframe 内部不可被宿主 CSS 影响)
|
|
20
|
-
* 事件 (CustomEvent, composed, detail = 协议 payload): xc-ready(模型加载完) xc-progress xc-state xc-stt xc-utterance xc-error xc-outfit-changed xc-scene-changed
|
|
22
|
+
* 事件 (CustomEvent, composed, detail = 协议 payload): xc-ready(模型加载完) xc-progress xc-state xc-stt xc-utterance xc-error xc-outfit-changed xc-scene-changed xc-lang-changed
|
|
21
23
|
* xc-move / xc-resize (用户拖动 / 缩放, detail = {phase, left, top, width, height})
|
|
22
24
|
* 方法: say(text) speakAudio(source, opts) speakAudioStream(opts) motion(m) expression(name) setOutfit(id) setScene(id) getOutfits() getScenes() destroy() (+ client 属性拿到完整 SDK 实例)
|
|
23
25
|
*/
|
|
@@ -41,6 +43,10 @@ export interface XiaochunAvatarElement extends HTMLElement {
|
|
|
41
43
|
setScene(id: string): Promise<void>;
|
|
42
44
|
getOutfits(): Promise<XcOutfitInfo[]>;
|
|
43
45
|
getScenes(): Promise<XcSceneInfo[]>;
|
|
46
|
+
/** 清除 persist-box 保存的位置 / 大小; { reset: true } 同时还原到初始位置和大小。 */
|
|
47
|
+
clearPersistedBox(opts?: {
|
|
48
|
+
reset?: boolean;
|
|
49
|
+
}): void;
|
|
44
50
|
/** 预取服装资源到 iframe 的 IndexedDB; ids 省略 = 全部 (婚纱除外)。 */
|
|
45
51
|
prefetch(ids?: string[]): Promise<XcPrefetchedPayload>;
|
|
46
52
|
destroy(): void;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.16 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
import {
|
|
3
3
|
createXiaochun
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UWL65W7D.js";
|
|
5
5
|
import {
|
|
6
|
-
isXcId
|
|
7
|
-
|
|
6
|
+
isXcId,
|
|
7
|
+
parseXcUiAutoHide
|
|
8
|
+
} from "./chunk-RHQW4QZH.js";
|
|
8
9
|
|
|
9
10
|
// src/avatar-element.ts
|
|
10
11
|
var OBSERVED = [
|
|
@@ -27,14 +28,20 @@ var OBSERVED = [
|
|
|
27
28
|
"placeholder",
|
|
28
29
|
"heavy",
|
|
29
30
|
"ui",
|
|
31
|
+
"ui-autohide",
|
|
32
|
+
"camera-fov",
|
|
33
|
+
"camera-distance",
|
|
34
|
+
"camera-height",
|
|
35
|
+
"camera-intro",
|
|
30
36
|
"controls",
|
|
31
37
|
"allowed-origins",
|
|
32
38
|
"cross-origin-isolated",
|
|
33
39
|
"persist",
|
|
40
|
+
"persist-box",
|
|
34
41
|
"allow-custom-model",
|
|
35
42
|
"prefetch"
|
|
36
43
|
];
|
|
37
|
-
var REBUILD = /* @__PURE__ */ new Set(["src", "position", "lazy", "placeholder", "heavy", "ui", "controls", "allowed-origins", "transparent", "cross-origin-isolated", "persist", "allow-custom-model", "prefetch"]);
|
|
44
|
+
var REBUILD = /* @__PURE__ */ new Set(["src", "position", "lazy", "placeholder", "heavy", "ui", "ui-autohide", "controls", "allowed-origins", "transparent", "cross-origin-isolated", "persist", "persist-box", "allow-custom-model", "prefetch"]);
|
|
38
45
|
function radiusAttr(v) {
|
|
39
46
|
if (v === null || v.trim() === "") return void 0;
|
|
40
47
|
const t = v.trim();
|
|
@@ -50,22 +57,37 @@ function prefetchAttr(v) {
|
|
|
50
57
|
if (v === "" || v === "true") return true;
|
|
51
58
|
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
52
59
|
}
|
|
60
|
+
function persistBoxAttr(v) {
|
|
61
|
+
if (v === null || v === "false" || v === "0") return void 0;
|
|
62
|
+
if (v === "" || v === "true" || v === "1") return true;
|
|
63
|
+
return v;
|
|
64
|
+
}
|
|
53
65
|
function uiAttr(v) {
|
|
54
66
|
if (v === null || v === "false" || v === "0") return void 0;
|
|
55
67
|
if (v === "" || v === "true" || v === "1") return true;
|
|
56
68
|
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
57
69
|
}
|
|
70
|
+
function cameraAttrs(el) {
|
|
71
|
+
const num = (name) => {
|
|
72
|
+
const v = el.getAttribute(name);
|
|
73
|
+
if (v === null || v.trim() === "") return null;
|
|
74
|
+
const n = Number(v);
|
|
75
|
+
return Number.isFinite(n) ? n : null;
|
|
76
|
+
};
|
|
77
|
+
const i = el.getAttribute("camera-intro");
|
|
78
|
+
return { fov: num("camera-fov"), distance: num("camera-distance"), height: num("camera-height"), intro: i === null ? null : i !== "false" && i !== "0" };
|
|
79
|
+
}
|
|
58
80
|
function parsePx(v) {
|
|
59
81
|
const m = v?.trim().match(/^(\d+(?:\.\d+)?)\s*x\s*(\d+(?:\.\d+)?)$/i);
|
|
60
82
|
return m ? { w: Number(m[1]), h: Number(m[2]) } : void 0;
|
|
61
83
|
}
|
|
62
84
|
function parseSize(v) {
|
|
63
|
-
if (!v) return { width:
|
|
85
|
+
if (!v) return { width: void 0, height: void 0 };
|
|
64
86
|
const m = v.trim().split(/x/i);
|
|
65
87
|
const px = (s) => /^\d+(\.\d+)?$/.test(s) ? `${s}px` : s;
|
|
66
88
|
if (m.length >= 2) return { width: px(m[0]), height: px(m[1]) };
|
|
67
89
|
const w = parseFloat(m[0]);
|
|
68
|
-
return Number.isFinite(w) && /^\d+(\.\d+)?$/.test(m[0]) ? { width: `${w}px`, height: `${Math.round(w * 1.5)}px` } : { width: m[0], height:
|
|
90
|
+
return Number.isFinite(w) && /^\d+(\.\d+)?$/.test(m[0]) ? { width: `${w}px`, height: `${Math.round(w * 1.5)}px` } : { width: m[0], height: void 0 };
|
|
69
91
|
}
|
|
70
92
|
function createElementClass() {
|
|
71
93
|
class XiaochunAvatarElementImpl extends HTMLElement {
|
|
@@ -120,6 +142,8 @@ function createElementClass() {
|
|
|
120
142
|
} else if (name === "border-radius") c.setBorderRadius(radiusAttr(newV));
|
|
121
143
|
else if (name === "draggable") c.setDraggable(boolAttr(this, "draggable", false));
|
|
122
144
|
else if (name === "resizable" || name === "min-size" || name === "max-size") c.setResizable(this.resizableOption());
|
|
145
|
+
else if (name.startsWith("camera-")) void c.setConfig({ camera: cameraAttrs(this) }).catch(() => {
|
|
146
|
+
});
|
|
123
147
|
else if (name === "mic") void c.mic(boolAttr(this, "mic", false)).catch(() => {
|
|
124
148
|
});
|
|
125
149
|
else if (name === "paused") {
|
|
@@ -152,10 +176,13 @@ function createElementClass() {
|
|
|
152
176
|
outfit: this.getAttribute("outfit") ?? this.getAttribute("model") ?? void 0,
|
|
153
177
|
scene: this.getAttribute("scene") ?? void 0,
|
|
154
178
|
persist: this.getAttribute("persist") || void 0,
|
|
179
|
+
persistBox: persistBoxAttr(this.getAttribute("persist-box")),
|
|
155
180
|
allowCustomModel: boolAttr(this, "allow-custom-model", false),
|
|
156
181
|
prefetch: prefetchAttr(this.getAttribute("prefetch")),
|
|
157
182
|
heavy: this.getAttribute("heavy") ?? void 0,
|
|
158
183
|
ui: uiAttr(this.getAttribute("ui")),
|
|
184
|
+
uiAutoHide: parseXcUiAutoHide(this.getAttribute("ui-autohide")),
|
|
185
|
+
camera: cameraAttrs(this),
|
|
159
186
|
controls: boolAttr(this, "controls", true),
|
|
160
187
|
crossOriginIsolated: boolAttr(this, "cross-origin-isolated", false)
|
|
161
188
|
});
|
|
@@ -169,6 +196,7 @@ function createElementClass() {
|
|
|
169
196
|
fwd("error", "xc-error");
|
|
170
197
|
fwd("outfit-changed", "xc-outfit-changed");
|
|
171
198
|
fwd("scene-changed", "xc-scene-changed");
|
|
199
|
+
fwd("lang-changed", "xc-lang-changed");
|
|
172
200
|
fwd("move", "xc-move");
|
|
173
201
|
fwd("resize", "xc-resize");
|
|
174
202
|
if (boolAttr(this, "paused", false)) c.pause();
|
|
@@ -208,6 +236,9 @@ function createElementClass() {
|
|
|
208
236
|
getScenes() {
|
|
209
237
|
return this.requireClient().getScenes();
|
|
210
238
|
}
|
|
239
|
+
clearPersistedBox(opts) {
|
|
240
|
+
this.client?.clearPersistedBox(opts);
|
|
241
|
+
}
|
|
211
242
|
prefetch(ids) {
|
|
212
243
|
return this.requireClient().prefetch(ids);
|
|
213
244
|
}
|
|
@@ -236,4 +267,4 @@ export {
|
|
|
236
267
|
XIAOCHUN_ELEMENT_TAG,
|
|
237
268
|
defineXiaochunElement
|
|
238
269
|
};
|
|
239
|
-
//# sourceMappingURL=chunk-
|
|
270
|
+
//# sourceMappingURL=chunk-HZ2ADKG7.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/avatar-element.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * avatar-element.ts \u2014 `<xiaochun-avatar>` Web Component (Shadow DOM \u5185\u5305 iframe)\u3002\n *\n * \u5C5E\u6027: src outfit scene model(deprecated) lang mic transparent draggable resizable min-size max-size border-radius position size lazy paused placeholder heavy ui ui-autohide controls allowed-origins cross-origin-isolated persist allow-custom-model\n * - \u5E03\u5C14\u5C5E\u6027: \u7F3A\u7701\u53D6\u9ED8\u8BA4\u503C; \"\" / \"true\" = true; \"false\" = false\u3002\n * - size=\"320x480\" \u6216 size=\"320\" (\u9AD8 = \u5BBD \u00D7 1.5); \u4E5F\u53EF\u5199 CSS \u957F\u5EA6 \"100%x480px\"\u3002\u7F3A\u7701 = 600x1080 (\u5BBD\u4E0D\u8D85\u8FC7\u5BB9\u5668\u3001\u9AD8\u4E0D\u8D85\u8FC7\u89C6\u53E3)\u3002\u6539 size **\u70ED\u66F4\u65B0** (setSize, \u4E0D\u91CD\u5EFA iframe)\u3002\n * - draggable: \u624B\u52BF\u62D6\u52A8 (\u89D2\u8272\u4E0A\u6309\u4F4F\u62D6, \u5185\u8054 / \u60AC\u6D6E\u90FD\u884C); resizable: \u56DB\u89D2\u7F29\u653E\u70ED\u533A (\u5BF9\u89D2\u56FA\u5B9A, \u4E0D\u91CD\u5EFA iframe); \u4E24\u8005\u9ED8\u8BA4\u5173, \u6539\u5C5E\u6027\u70ED\u66F4\u65B0\u3002\n * min-size / max-size=\"120x180\": resizable \u7684\u6700\u5C0F / \u6700\u5927\u5C3A\u5BF8 (px, \u7F3A\u7701\u6700\u5C0F 120x180\u3001\u6700\u5927\u53EA\u53D7\u89C6\u53E3\u9650\u5236)\u3002\n * - border-radius: \u5916\u58F3\u5706\u89D2 (\u6570\u5B57 = px \u6216 CSS \u957F\u5EA6); \u7F3A\u7701: \u975E\u900F\u660E\u573A\u666F 20px (\u4E0E Tauri \u684C\u5BA0\u7A97\u53E3\u540C\u503C) / \u900F\u660E 0; \u6539\u5C5E\u6027\u70ED\u66F4\u65B0 (setBorderRadius)\u3002\n * - outfit: \u5185\u7F6E\u670D\u88C5 id (xiaochun_maid); \u6539\u5C5E\u6027**\u70ED\u66F4\u65B0** (setOutfit, \u4E0D\u91CD\u5EFA iframe)\u3002model \u662F outfit \u7684\u65E7\u522B\u540D (deprecated)\u3002\n * - scene: light | dark | transparent; \u6539\u5C5E\u6027**\u70ED\u66F4\u65B0** (setScene, \u4E0D\u91CD\u5EFA iframe)\u3002\n * - ui: \u8981\u663E\u793A\u7684 iframe \u5185\u7F6E\u754C\u9762\u90E8\u4EF6, \u9017\u53F7\u5206\u9694: chat,bubble,outfit,scene,lang,github (\u4F8B ui=\"outfit,scene,lang,github\"); \u7F3A\u7701 = \u90FD\u4E0D\u663E\u793A; \u6539\u5B83\u4F1A\u91CD\u5EFA\u3002\n * \u65E7\u5199\u6CD5 ui / ui=\"true\" \u5DF2\u5F03\u7528 (= chat,bubble, \u4F1A console.warn)\u3002\n * - ui-autohide: \u5185\u7F6E\u754C\u9762\u7684\u663E\u793A\u7B56\u7565: \u7F3A\u7701 / \"transparent\" = \u4E0E Tauri \u4E00\u81F4 (\u53EA\u5728\u900F\u660E\u573A\u666F\u70B9\u51FB\u51FA\u73B0, \u4EAE\u6697\u573A\u666F\u5E38\u663E); \"true\" = \u6240\u6709\u573A\u666F\u70B9\u51FB\u51FA\u73B0; \"false\" = \u4E00\u76F4\u663E\u793A\u3002\u6539\u5B83\u4F1A\u91CD\u5EFA\u3002\n * - persist-box: \u8BB0\u4F4F\u7528\u6237\u62D6\u52A8 / \u7F29\u653E\u540E\u7684\u4F4D\u7F6E\u548C\u5927\u5C0F (\u5BBF\u4E3B localStorage): \"\" / \"true\" = \u9ED8\u8BA4 key, \u5176\u5B83\u5B57\u7B26\u4E32 = \u547D\u540D\u7A7A\u95F4 (xiaochun:box:<\u540D\u5B57>); \u9700\u914D\u5408 draggable / resizable; \u663E\u5F0F size \u4F18\u5148\u4E8E\u5DF2\u4FDD\u5B58\u7684\u5927\u5C0F\u3002\u6539\u5B83\u4F1A\u91CD\u5EFA\u3002\n * - persist: \"host\" \u6216\u81EA\u5B9A\u4E49 localStorage key; \u53E6\u628A\u670D\u88C5/\u573A\u666F\u504F\u597D\u5B58\u5728\u5BBF\u4E3B\u9875 (\u53EF\u9009; \u9ED8\u8BA4\u4E0D\u5B58, iframe \u81EA\u5DF1\u7684 localStorage \u4ECD\u4F1A\u8BB0\u4F4F)\u3002\n * - prefetch: \"\" / \"true\" = \u9884\u53D6\u5168\u90E8\u5185\u7F6E\u670D\u88C5 (\u5A5A\u7EB1\u9664\u5916), \u6216\u9017\u53F7\u5206\u9694\u7684 id \u5217\u8868; \u53EA\u5728 heavy=\"eager\" \u65F6\u81EA\u52A8\u89E6\u53D1 (\u9ED8\u8BA4\u5173)\u3002\n * - allow-custom-model: \u5141\u8BB8 setModel({url}) \u52A0\u8F7D\u4EFB\u610F https \u6A21\u578B (\u9ED8\u8BA4\u5173\u95ED)\u3002\n * \u6837\u5F0F (CSS \u81EA\u5B9A\u4E49\u5C5E\u6027, \u53EF\u5199\u5728 <xiaochun-avatar> \u4E0A\u6216\u4EFB\u610F\u7956\u5148\u4E0A; \u53D6\u503C\u8303\u56F4/\u6548\u679C\u89C1 client.ts \u91CC\u7684\u6CE8\u91CA\u4E0E docs/EMBED.md \u00A7\u6837\u5F0F):\n * --xc-radius --xc-shadow --xc-z-index --xc-offset-x --xc-offset-y --xc-bg\n * \u53EF\u7528 ::part() \u5B9A\u5236\u5916\u58F3: ::part(mount) ::part(wrapper) ::part(iframe) ::part(placeholder) (iframe \u5185\u90E8\u4E0D\u53EF\u88AB\u5BBF\u4E3B CSS \u5F71\u54CD)\n * \u4E8B\u4EF6 (CustomEvent, composed, detail = \u534F\u8BAE payload): xc-ready(\u6A21\u578B\u52A0\u8F7D\u5B8C) xc-progress xc-state xc-stt xc-utterance xc-error xc-outfit-changed xc-scene-changed xc-lang-changed\n * xc-move / xc-resize (\u7528\u6237\u62D6\u52A8 / \u7F29\u653E, detail = {phase, left, top, width, height})\n * \u65B9\u6CD5: say(text) speakAudio(source, opts) speakAudioStream(opts) motion(m) expression(name) setOutfit(id) setScene(id) getOutfits() getScenes() destroy() (+ client \u5C5E\u6027\u62FF\u5230\u5B8C\u6574 SDK \u5B9E\u4F8B)\n */\nimport {\n createXiaochun,\n type XiaochunAudioOptions,\n type XiaochunAudioSource,\n type XiaochunAudioStream,\n type XiaochunInstance,\n type XiaochunLazy,\n type XiaochunPosition,\n type XiaochunResizeLimits,\n} from './client';\nimport { isXcId, parseXcUiAutoHide } from './protocol';\nimport type { XcCamera, XcUiPart, XcExpressionPayload, XcHeavyMode, XcLang, XcMotionPayload, XcOutfitInfo, XcPrefetchedPayload, XcSceneInfo } from './protocol';\n\nconst OBSERVED = [\n 'src', 'outfit', 'scene', 'model', 'lang', 'mic', 'transparent', 'draggable', 'resizable', 'min-size', 'max-size', 'border-radius', 'position', 'size', 'lazy',\n 'paused', 'placeholder', 'heavy', 'ui', 'ui-autohide', 'camera-fov', 'camera-distance', 'camera-height', 'camera-intro', 'controls', 'allowed-origins', 'cross-origin-isolated', 'persist', 'persist-box', 'allow-custom-model', 'prefetch',\n] as const;\n/** \u6539\u4E86\u8FD9\u4E9B\u8981\u91CD\u5EFA iframe; \u5176\u4F59 (outfit / scene / model / lang / mic / paused / size / draggable / resizable / min-size / max-size / border-radius / camera-*) \u53EF\u4EE5\u70ED\u66F4\u65B0\u3002 */\nconst REBUILD = new Set(['src', 'position', 'lazy', 'placeholder', 'heavy', 'ui', 'ui-autohide', 'controls', 'allowed-origins', 'transparent', 'cross-origin-isolated', 'persist', 'persist-box', 'allow-custom-model', 'prefetch']);\n\n/** border-radius \u5C5E\u6027: \u7EAF\u6570\u5B57 = px, \u5176\u4F59\u5F53 CSS \u957F\u5EA6; \u7F3A\u7701 = undefined (\u8D70 SDK \u9ED8\u8BA4: \u975E\u900F\u660E 20px / \u900F\u660E 0)\u3002 */\nfunction radiusAttr(v: string | null): number | string | undefined {\n if (v === null || v.trim() === '') return undefined;\n const t = v.trim();\n return /^\\d+(\\.\\d+)?$/.test(t) ? Number(t) : t;\n}\n\nfunction boolAttr(el: Element, name: string, dflt: boolean): boolean {\n const v = el.getAttribute(name);\n if (v === null) return dflt;\n return v !== 'false';\n}\n\n/** prefetch \u5C5E\u6027: \u7F3A\u7701/false = \u5173; \"\" / \"true\" = \u5168\u90E8 (\u5A5A\u7EB1\u9664\u5916); \u5176\u5B83 = \u9017\u53F7\u5206\u9694\u7684 id \u5217\u8868\u3002 */\nfunction prefetchAttr(v: string | null): boolean | string[] {\n if (v === null || v === 'false') return false;\n if (v === '' || v === 'true') return true;\n return v.split(',').map((s) => s.trim()).filter(Boolean);\n}\n\n/** persist-box \u5C5E\u6027: \"\" / \"true\" \u2192 true; \"false\" / \u7F3A\u7701 \u2192 \u4E0D\u8BB0; \u5176\u5B83\u5B57\u7B26\u4E32 = \u547D\u540D\u7A7A\u95F4\u3002 */\nfunction persistBoxAttr(v: string | null): boolean | string | undefined {\n if (v === null || v === 'false' || v === '0') return undefined;\n if (v === '' || v === 'true' || v === '1') return true;\n return v;\n}\n\n/** ui \u5C5E\u6027: \u7F3A\u7701 / \"false\" / \"0\" = \u4E0D\u663E\u793A; \u9017\u53F7\u5206\u9694\u7684\u90E8\u4EF6\u540D (chat,bubble,outfit,scene,lang,github); \"\" / \"true\" / \"1\" = \u5F03\u7528\u7684\u65E7\u5199\u6CD5 (client \u91CC warn)\u3002 */\nfunction uiAttr(v: string | null): XcUiPart[] | boolean | undefined {\n if (v === null || v === 'false' || v === '0') return undefined;\n if (v === '' || v === 'true' || v === '1') return true;\n return v.split(',').map((s) => s.trim()).filter(Boolean) as XcUiPart[];\n}\n\n/** camera-fov / camera-distance / camera-height / camera-intro \u5C5E\u6027 \u2192 XcCamera; \u6CA1\u5199 / \u975E\u6CD5\u7684\u9879 = null (\u521B\u5EFA\u65F6\u5FFD\u7565, \u70ED\u66F4\u65B0\u65F6 = \u6062\u590D\u9ED8\u8BA4)\u3002 */\nfunction cameraAttrs(el: Element): XcCamera {\n const num = (name: string): number | null => {\n const v = el.getAttribute(name);\n if (v === null || v.trim() === '') return null;\n const n = Number(v);\n return Number.isFinite(n) ? n : null;\n };\n const i = el.getAttribute('camera-intro');\n return { fov: num('camera-fov'), distance: num('camera-distance'), height: num('camera-height'), intro: i === null ? null : i !== 'false' && i !== '0' };\n}\n\n/** \"120x180\" \u2192 {w,h} (px \u6570\u5B57); \u975E\u6CD5 \u2192 undefined\u3002 */\nfunction parsePx(v: string | null): { w: number; h: number } | undefined {\n const m = v?.trim().match(/^(\\d+(?:\\.\\d+)?)\\s*x\\s*(\\d+(?:\\.\\d+)?)$/i);\n return m ? { w: Number(m[1]), h: Number(m[2]) } : undefined;\n}\n\n/** size \u5C5E\u6027 \u2192 \u5BBD\u9AD8; \u6CA1\u5199\u7684\u7EF4\u5EA6\u8FD4\u56DE undefined = \u7528 SDK \u9ED8\u8BA4 (600x1080, \u53D7\u89C6\u53E3\u9650\u5236)\u3002 */\nfunction parseSize(v: string | null): { width: string | undefined; height: string | undefined } {\n if (!v) return { width: undefined, height: undefined };\n const m = v.trim().split(/x/i);\n const px = (s: string) => (/^\\d+(\\.\\d+)?$/.test(s) ? `${s}px` : s);\n if (m.length >= 2) return { width: px(m[0]), height: px(m[1]) };\n const w = parseFloat(m[0]);\n return Number.isFinite(w) && /^\\d+(\\.\\d+)?$/.test(m[0]) ? { width: `${w}px`, height: `${Math.round(w * 1.5)}px` } : { width: m[0], height: undefined };\n}\n\n/** \u516C\u5F00\u7C7B\u578B (\u7C7B\u672C\u8EAB\u5728\u6D4F\u89C8\u5668\u91CC\u624D\u521B\u5EFA, \u8FD9\u6837 SSR / Node \u4E0B import \u672C\u5305\u4E0D\u4F1A\u56E0 HTMLElement \u7F3A\u5931\u800C\u629B\u9519)\u3002 */\nexport interface XiaochunAvatarElement extends HTMLElement {\n client: XiaochunInstance | null;\n say(text: string, opts?: { mode?: 'speak' | 'chat' }): Promise<void>;\n /** \u76F4\u63A5\u64AD\u653E\u5BBF\u4E3B\u7ED9\u7684\u97F3\u9891 (\u4E0D\u8D70 TTS), \u540C\u65F6\u7531 EMAGE \u751F\u6210\u52A8\u4F5C + \u53E3\u578B\u3002 */\n speakAudio(source: XiaochunAudioSource, opts?: XiaochunAudioOptions): Promise<void>;\n speakAudioStream(opts: XiaochunAudioOptions & { sampleRate: number }): XiaochunAudioStream;\n motion(m: XcMotionPayload | string): Promise<void>;\n expression(name: XcExpressionPayload['name']): Promise<void>;\n /** \u6362\u5185\u7F6E\u670D\u88C5 (\u4E32\u884C + last-wins, \u89C1 XiaochunInstance.setOutfit)\u3002 */\n setOutfit(id: string): Promise<void>;\n setScene(id: string): Promise<void>;\n getOutfits(): Promise<XcOutfitInfo[]>;\n getScenes(): Promise<XcSceneInfo[]>;\n /** \u6E05\u9664 persist-box \u4FDD\u5B58\u7684\u4F4D\u7F6E / \u5927\u5C0F; { reset: true } \u540C\u65F6\u8FD8\u539F\u5230\u521D\u59CB\u4F4D\u7F6E\u548C\u5927\u5C0F\u3002 */\n clearPersistedBox(opts?: { reset?: boolean }): void;\n /** \u9884\u53D6\u670D\u88C5\u8D44\u6E90\u5230 iframe \u7684 IndexedDB; ids \u7701\u7565 = \u5168\u90E8 (\u5A5A\u7EB1\u9664\u5916)\u3002 */\n prefetch(ids?: string[]): Promise<XcPrefetchedPayload>;\n destroy(): void;\n}\n\nfunction createElementClass(): CustomElementConstructor {\n class XiaochunAvatarElementImpl extends HTMLElement {\n static get observedAttributes() { return [...OBSERVED]; }\n\n /** \u5E95\u5C42 SDK \u5B9E\u4F8B (\u8FDE\u63A5 DOM \u540E\u53EF\u7528)\u3002 */\n client: XiaochunInstance | null = null;\n private mountEl: HTMLDivElement | null = null;\n private rebuildScheduled = false;\n\n connectedCallback(): void {\n if (!this.shadowRoot) {\n const root = this.attachShadow({ mode: 'open' });\n // :host \u9ED8\u8BA4 inline-block + max-width:100% (\u914D\u5408 SDK \u7684\u56FA\u5B9A\u5BBD\u9AD8, \u4E0D\u4F1A\u6491\u7834\u7A84\u5C4F\u5BB9\u5668)\u3002\n // \u60AC\u6D6E (position=bottom-right|bottom-left) \u65F6 SDK \u5185\u90E8\u7528 position:fixed, \u4E0D\u53D7 :host \u76D2\u6A21\u578B\u5F71\u54CD\u3002\n root.innerHTML = '<style>:host{display:inline-block;max-width:100%}:host([hidden]){display:none}</style><div part=\"mount\"></div>';\n }\n this.mountEl = this.shadowRoot!.querySelector('div');\n this.build();\n }\n\n disconnectedCallback(): void {\n this.destroy();\n }\n\n attributeChangedCallback(name: string, oldV: string | null, newV: string | null): void {\n if (!this.client || oldV === newV) return;\n if (REBUILD.has(name)) {\n if (!this.rebuildScheduled) {\n this.rebuildScheduled = true;\n queueMicrotask(() => { this.rebuildScheduled = false; if (this.isConnected) { this.destroy(); this.build(); } });\n }\n return;\n }\n const c = this.client;\n // \u70ED\u66F4\u65B0: \u5931\u8D25 (busy / unknown_id / unsupported) \u90FD\u4F1A\u4F5C\u4E3A xc-error \u4E8B\u4EF6\u629B\u7ED9\u5BBF\u4E3B, \u8FD9\u91CC\u4E0D\u91CD\u590D\u5904\u7406\n if ((name === 'outfit' || name === 'model') && newV) {\n // outfit \u4E0E model(\u65E7\u522B\u540D) \u540C\u65F6\u5B58\u5728\u65F6 outfit \u4F18\u5148\n const want = name === 'model' && this.hasAttribute('outfit') ? null : newV;\n if (want) void (isXcId(want) ? c.setOutfit(want) : c.setModel(want)).catch(() => {}); // \u65E7\u7528\u6CD5 model=\"https://\u2026\" \u4ECD\u8D70 setModel\n }\n else if (name === 'scene' && newV) void c.setScene(newV).catch(() => {});\n else if (name === 'lang' && newV) void c.setConfig({ lang: newV as XcLang }).catch(() => {});\n else if (name === 'size') { const { width, height } = parseSize(newV); c.setSize(width, height); }\n else if (name === 'border-radius') c.setBorderRadius(radiusAttr(newV));\n else if (name === 'draggable') c.setDraggable(boolAttr(this, 'draggable', false));\n else if (name === 'resizable' || name === 'min-size' || name === 'max-size') c.setResizable(this.resizableOption());\n else if (name.startsWith('camera-')) void c.setConfig({ camera: cameraAttrs(this) }).catch(() => {});\n else if (name === 'mic') void c.mic(boolAttr(this, 'mic', false)).catch(() => {});\n else if (name === 'paused') { if (boolAttr(this, 'paused', false)) c.pause(); else c.resume(); }\n }\n\n private build(): void {\n if (!this.mountEl || this.client) return;\n const { width, height } = parseSize(this.getAttribute('size'));\n const placeholder = this.getAttribute('placeholder');\n const allowed = this.getAttribute('allowed-origins');\n const lazyAttr = this.getAttribute('lazy');\n const lazy: XiaochunLazy = lazyAttr === 'click' ? 'click' : lazyAttr === 'false' ? false : true;\n const c = createXiaochun({\n container: this.mountEl,\n src: this.getAttribute('src') || undefined,\n allowedOrigins: allowed ? allowed.split(',').map((s) => s.trim()).filter(Boolean) : undefined,\n lazy,\n placeholder: placeholder === 'none' ? false : placeholder || undefined,\n transparent: boolAttr(this, 'transparent', true), // \u60AC\u6D6E\u5934\u50CF\u9ED8\u8BA4\u900F\u660E\n width, height,\n position: (this.getAttribute('position') as XiaochunPosition | null) ?? 'inline',\n draggable: boolAttr(this, 'draggable', false),\n resizable: this.resizableOption(),\n borderRadius: radiusAttr(this.getAttribute('border-radius')),\n lang: (this.getAttribute('lang') as XcLang | null) ?? undefined,\n outfit: this.getAttribute('outfit') ?? this.getAttribute('model') ?? undefined,\n scene: this.getAttribute('scene') ?? undefined,\n persist: this.getAttribute('persist') || undefined,\n persistBox: persistBoxAttr(this.getAttribute('persist-box')),\n allowCustomModel: boolAttr(this, 'allow-custom-model', false),\n prefetch: prefetchAttr(this.getAttribute('prefetch')),\n heavy: (this.getAttribute('heavy') as XcHeavyMode | null) ?? undefined,\n ui: uiAttr(this.getAttribute('ui')),\n uiAutoHide: parseXcUiAutoHide(this.getAttribute('ui-autohide')),\n camera: cameraAttrs(this),\n controls: boolAttr(this, 'controls', true),\n crossOriginIsolated: boolAttr(this, 'cross-origin-isolated', false),\n });\n this.client = c;\n const fwd = (ev: string, name: string) => c.on(ev as 'ready', (detail) =>\n this.dispatchEvent(new CustomEvent(name, { detail, bubbles: true, composed: true })));\n fwd('ready', 'xc-ready');\n fwd('progress', 'xc-progress');\n fwd('state', 'xc-state');\n fwd('stt', 'xc-stt');\n fwd('utterance', 'xc-utterance');\n fwd('error', 'xc-error');\n fwd('outfit-changed', 'xc-outfit-changed');\n fwd('scene-changed', 'xc-scene-changed');\n fwd('lang-changed', 'xc-lang-changed');\n fwd('move', 'xc-move');\n fwd('resize', 'xc-resize');\n if (boolAttr(this, 'paused', false)) c.pause();\n if (boolAttr(this, 'mic', false)) void c.ready.then(() => c.mic(true)).catch(() => {});\n }\n\n /** resizable / min-size / max-size \u5C5E\u6027 \u2192 SDK \u7684 resizable \u9009\u9879 (\u7F3A\u7701 / \"false\" = \u5173)\u3002 */\n private resizableOption(): false | XiaochunResizeLimits {\n if (!boolAttr(this, 'resizable', false)) return false;\n const min = parsePx(this.getAttribute('min-size')), max = parsePx(this.getAttribute('max-size'));\n return { minWidth: min?.w, minHeight: min?.h, maxWidth: max?.w, maxHeight: max?.h };\n }\n\n say(text: string, opts?: { mode?: 'speak' | 'chat' }): Promise<void> {\n return this.requireClient().say(text, opts);\n }\n speakAudio(source: XiaochunAudioSource, opts?: XiaochunAudioOptions): Promise<void> {\n return this.requireClient().speakAudio(source, opts);\n }\n speakAudioStream(opts: XiaochunAudioOptions & { sampleRate: number }): XiaochunAudioStream {\n return this.requireClient().speakAudioStream(opts);\n }\n motion(m: XcMotionPayload | string): Promise<void> { return this.requireClient().motion(m); }\n expression(name: XcExpressionPayload['name']): Promise<void> { return this.requireClient().expression(name); }\n setOutfit(id: string): Promise<void> { return this.requireClient().setOutfit(id); }\n setScene(id: string): Promise<void> { return this.requireClient().setScene(id); }\n getOutfits(): Promise<XcOutfitInfo[]> { return this.requireClient().getOutfits(); }\n getScenes(): Promise<XcSceneInfo[]> { return this.requireClient().getScenes(); }\n clearPersistedBox(opts?: { reset?: boolean }): void { this.client?.clearPersistedBox(opts); }\n prefetch(ids?: string[]): Promise<XcPrefetchedPayload> { return this.requireClient().prefetch(ids); }\n\n destroy(): void {\n this.client?.destroy();\n this.client = null;\n }\n\n private requireClient(): XiaochunInstance {\n if (!this.client) throw new Error('<xiaochun-avatar> is not connected');\n return this.client;\n }\n }\n\n return XiaochunAvatarElementImpl;\n}\n\nexport const XIAOCHUN_ELEMENT_TAG = 'xiaochun-avatar';\n\n/** \u6CE8\u518C\u81EA\u5B9A\u4E49\u5143\u7D20 (\u5E42\u7B49)\u3002import '@firetable/project-xiaochun/element' \u4F1A\u81EA\u52A8\u8C03\u7528\u3002 */\nexport function defineXiaochunElement(tag: string = XIAOCHUN_ELEMENT_TAG): CustomElementConstructor | null {\n if (typeof customElements === 'undefined' || typeof HTMLElement === 'undefined') return null;\n const existing = customElements.get(tag);\n if (existing) return existing;\n const ctor = createElementClass();\n customElements.define(tag, ctor);\n return ctor;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;AAsCA,IAAM,WAAW;AAAA,EACf;AAAA,EAAO;AAAA,EAAU;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAe;AAAA,EAAa;AAAA,EAAa;AAAA,EAAY;AAAA,EAAY;AAAA,EAAiB;AAAA,EAAY;AAAA,EAAQ;AAAA,EACxJ;AAAA,EAAU;AAAA,EAAe;AAAA,EAAS;AAAA,EAAM;AAAA,EAAe;AAAA,EAAc;AAAA,EAAmB;AAAA,EAAiB;AAAA,EAAgB;AAAA,EAAY;AAAA,EAAmB;AAAA,EAAyB;AAAA,EAAW;AAAA,EAAe;AAAA,EAAsB;AACnO;AAEA,IAAM,UAAU,oBAAI,IAAI,CAAC,OAAO,YAAY,QAAQ,eAAe,SAAS,MAAM,eAAe,YAAY,mBAAmB,eAAe,yBAAyB,WAAW,eAAe,sBAAsB,UAAU,CAAC;AAGnO,SAAS,WAAW,GAA+C;AACjE,MAAI,MAAM,QAAQ,EAAE,KAAK,MAAM,GAAI,QAAO;AAC1C,QAAM,IAAI,EAAE,KAAK;AACjB,SAAO,gBAAgB,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI;AAC/C;AAEA,SAAS,SAAS,IAAa,MAAc,MAAwB;AACnE,QAAM,IAAI,GAAG,aAAa,IAAI;AAC9B,MAAI,MAAM,KAAM,QAAO;AACvB,SAAO,MAAM;AACf;AAGA,SAAS,aAAa,GAAsC;AAC1D,MAAI,MAAM,QAAQ,MAAM,QAAS,QAAO;AACxC,MAAI,MAAM,MAAM,MAAM,OAAQ,QAAO;AACrC,SAAO,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AACzD;AAGA,SAAS,eAAe,GAAgD;AACtE,MAAI,MAAM,QAAQ,MAAM,WAAW,MAAM,IAAK,QAAO;AACrD,MAAI,MAAM,MAAM,MAAM,UAAU,MAAM,IAAK,QAAO;AAClD,SAAO;AACT;AAGA,SAAS,OAAO,GAAoD;AAClE,MAAI,MAAM,QAAQ,MAAM,WAAW,MAAM,IAAK,QAAO;AACrD,MAAI,MAAM,MAAM,MAAM,UAAU,MAAM,IAAK,QAAO;AAClD,SAAO,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AACzD;AAGA,SAAS,YAAY,IAAuB;AAC1C,QAAM,MAAM,CAAC,SAAgC;AAC3C,UAAM,IAAI,GAAG,aAAa,IAAI;AAC9B,QAAI,MAAM,QAAQ,EAAE,KAAK,MAAM,GAAI,QAAO;AAC1C,UAAM,IAAI,OAAO,CAAC;AAClB,WAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAClC;AACA,QAAM,IAAI,GAAG,aAAa,cAAc;AACxC,SAAO,EAAE,KAAK,IAAI,YAAY,GAAG,UAAU,IAAI,iBAAiB,GAAG,QAAQ,IAAI,eAAe,GAAG,OAAO,MAAM,OAAO,OAAO,MAAM,WAAW,MAAM,IAAI;AACzJ;AAGA,SAAS,QAAQ,GAAwD;AACvE,QAAM,IAAI,GAAG,KAAK,EAAE,MAAM,0CAA0C;AACpE,SAAO,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,GAAG,GAAG,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI;AACpD;AAGA,SAAS,UAAU,GAA6E;AAC9F,MAAI,CAAC,EAAG,QAAO,EAAE,OAAO,QAAW,QAAQ,OAAU;AACrD,QAAM,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI;AAC7B,QAAM,KAAK,CAAC,MAAe,gBAAgB,KAAK,CAAC,IAAI,GAAG,CAAC,OAAO;AAChE,MAAI,EAAE,UAAU,EAAG,QAAO,EAAE,OAAO,GAAG,EAAE,CAAC,CAAC,GAAG,QAAQ,GAAG,EAAE,CAAC,CAAC,EAAE;AAC9D,QAAM,IAAI,WAAW,EAAE,CAAC,CAAC;AACzB,SAAO,OAAO,SAAS,CAAC,KAAK,gBAAgB,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,CAAC,MAAM,QAAQ,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,QAAQ,OAAU;AACvJ;AAuBA,SAAS,qBAA+C;AAAA,EACtD,MAAM,kCAAkC,YAAY;AAAA,IAApD;AAAA;AAIE;AAAA,oBAAkC;AAClC,WAAQ,UAAiC;AACzC,WAAQ,mBAAmB;AAAA;AAAA,IAL3B,WAAW,qBAAqB;AAAE,aAAO,CAAC,GAAG,QAAQ;AAAA,IAAG;AAAA,IAOxD,oBAA0B;AACxB,UAAI,CAAC,KAAK,YAAY;AACpB,cAAM,OAAO,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAG/C,aAAK,YAAY;AAAA,MACnB;AACA,WAAK,UAAU,KAAK,WAAY,cAAc,KAAK;AACnD,WAAK,MAAM;AAAA,IACb;AAAA,IAEA,uBAA6B;AAC3B,WAAK,QAAQ;AAAA,IACf;AAAA,IAEA,yBAAyB,MAAc,MAAqB,MAA2B;AACrF,UAAI,CAAC,KAAK,UAAU,SAAS,KAAM;AACnC,UAAI,QAAQ,IAAI,IAAI,GAAG;AACrB,YAAI,CAAC,KAAK,kBAAkB;AAC1B,eAAK,mBAAmB;AACxB,yBAAe,MAAM;AAAE,iBAAK,mBAAmB;AAAO,gBAAI,KAAK,aAAa;AAAE,mBAAK,QAAQ;AAAG,mBAAK,MAAM;AAAA,YAAG;AAAA,UAAE,CAAC;AAAA,QACjH;AACA;AAAA,MACF;AACA,YAAM,IAAI,KAAK;AAEf,WAAK,SAAS,YAAY,SAAS,YAAY,MAAM;AAEnD,cAAM,OAAO,SAAS,WAAW,KAAK,aAAa,QAAQ,IAAI,OAAO;AACtE,YAAI,KAAM,OAAM,OAAO,IAAI,IAAI,EAAE,UAAU,IAAI,IAAI,EAAE,SAAS,IAAI,GAAG,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACrF,WACS,SAAS,WAAW,KAAM,MAAK,EAAE,SAAS,IAAI,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,eAC9D,SAAS,UAAU,KAAM,MAAK,EAAE,UAAU,EAAE,MAAM,KAAe,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,eAClF,SAAS,QAAQ;AAAE,cAAM,EAAE,OAAO,OAAO,IAAI,UAAU,IAAI;AAAG,UAAE,QAAQ,OAAO,MAAM;AAAA,MAAG,WACxF,SAAS,gBAAiB,GAAE,gBAAgB,WAAW,IAAI,CAAC;AAAA,eAC5D,SAAS,YAAa,GAAE,aAAa,SAAS,MAAM,aAAa,KAAK,CAAC;AAAA,eACvE,SAAS,eAAe,SAAS,cAAc,SAAS,WAAY,GAAE,aAAa,KAAK,gBAAgB,CAAC;AAAA,eACzG,KAAK,WAAW,SAAS,EAAG,MAAK,EAAE,UAAU,EAAE,QAAQ,YAAY,IAAI,EAAE,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,eAC1F,SAAS,MAAO,MAAK,EAAE,IAAI,SAAS,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,eACvE,SAAS,UAAU;AAAE,YAAI,SAAS,MAAM,UAAU,KAAK,EAAG,GAAE,MAAM;AAAA,YAAQ,GAAE,OAAO;AAAA,MAAG;AAAA,IACjG;AAAA,IAEQ,QAAc;AACpB,UAAI,CAAC,KAAK,WAAW,KAAK,OAAQ;AAClC,YAAM,EAAE,OAAO,OAAO,IAAI,UAAU,KAAK,aAAa,MAAM,CAAC;AAC7D,YAAM,cAAc,KAAK,aAAa,aAAa;AACnD,YAAM,UAAU,KAAK,aAAa,iBAAiB;AACnD,YAAM,WAAW,KAAK,aAAa,MAAM;AACzC,YAAM,OAAqB,aAAa,UAAU,UAAU,aAAa,UAAU,QAAQ;AAC3F,YAAM,IAAI,eAAe;AAAA,QACvB,WAAW,KAAK;AAAA,QAChB,KAAK,KAAK,aAAa,KAAK,KAAK;AAAA,QACjC,gBAAgB,UAAU,QAAQ,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI;AAAA,QACpF;AAAA,QACA,aAAa,gBAAgB,SAAS,QAAQ,eAAe;AAAA,QAC7D,aAAa,SAAS,MAAM,eAAe,IAAI;AAAA;AAAA,QAC/C;AAAA,QAAO;AAAA,QACP,UAAW,KAAK,aAAa,UAAU,KAAiC;AAAA,QACxE,WAAW,SAAS,MAAM,aAAa,KAAK;AAAA,QAC5C,WAAW,KAAK,gBAAgB;AAAA,QAChC,cAAc,WAAW,KAAK,aAAa,eAAe,CAAC;AAAA,QAC3D,MAAO,KAAK,aAAa,MAAM,KAAuB;AAAA,QACtD,QAAQ,KAAK,aAAa,QAAQ,KAAK,KAAK,aAAa,OAAO,KAAK;AAAA,QACrE,OAAO,KAAK,aAAa,OAAO,KAAK;AAAA,QACrC,SAAS,KAAK,aAAa,SAAS,KAAK;AAAA,QACzC,YAAY,eAAe,KAAK,aAAa,aAAa,CAAC;AAAA,QAC3D,kBAAkB,SAAS,MAAM,sBAAsB,KAAK;AAAA,QAC5D,UAAU,aAAa,KAAK,aAAa,UAAU,CAAC;AAAA,QACpD,OAAQ,KAAK,aAAa,OAAO,KAA4B;AAAA,QAC7D,IAAI,OAAO,KAAK,aAAa,IAAI,CAAC;AAAA,QAClC,YAAY,kBAAkB,KAAK,aAAa,aAAa,CAAC;AAAA,QAC9D,QAAQ,YAAY,IAAI;AAAA,QACxB,UAAU,SAAS,MAAM,YAAY,IAAI;AAAA,QACzC,qBAAqB,SAAS,MAAM,yBAAyB,KAAK;AAAA,MACpE,CAAC;AACD,WAAK,SAAS;AACd,YAAM,MAAM,CAAC,IAAY,SAAiB,EAAE,GAAG,IAAe,CAAC,WAC7D,KAAK,cAAc,IAAI,YAAY,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,KAAK,CAAC,CAAC,CAAC;AACtF,UAAI,SAAS,UAAU;AACvB,UAAI,YAAY,aAAa;AAC7B,UAAI,SAAS,UAAU;AACvB,UAAI,OAAO,QAAQ;AACnB,UAAI,aAAa,cAAc;AAC/B,UAAI,SAAS,UAAU;AACvB,UAAI,kBAAkB,mBAAmB;AACzC,UAAI,iBAAiB,kBAAkB;AACvC,UAAI,gBAAgB,iBAAiB;AACrC,UAAI,QAAQ,SAAS;AACrB,UAAI,UAAU,WAAW;AACzB,UAAI,SAAS,MAAM,UAAU,KAAK,EAAG,GAAE,MAAM;AAC7C,UAAI,SAAS,MAAM,OAAO,KAAK,EAAG,MAAK,EAAE,MAAM,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACvF;AAAA;AAAA,IAGQ,kBAAgD;AACtD,UAAI,CAAC,SAAS,MAAM,aAAa,KAAK,EAAG,QAAO;AAChD,YAAM,MAAM,QAAQ,KAAK,aAAa,UAAU,CAAC,GAAG,MAAM,QAAQ,KAAK,aAAa,UAAU,CAAC;AAC/F,aAAO,EAAE,UAAU,KAAK,GAAG,WAAW,KAAK,GAAG,UAAU,KAAK,GAAG,WAAW,KAAK,EAAE;AAAA,IACpF;AAAA,IAEA,IAAI,MAAc,MAAmD;AACnE,aAAO,KAAK,cAAc,EAAE,IAAI,MAAM,IAAI;AAAA,IAC5C;AAAA,IACA,WAAW,QAA6B,MAA4C;AAClF,aAAO,KAAK,cAAc,EAAE,WAAW,QAAQ,IAAI;AAAA,IACrD;AAAA,IACA,iBAAiB,MAA0E;AACzF,aAAO,KAAK,cAAc,EAAE,iBAAiB,IAAI;AAAA,IACnD;AAAA,IACA,OAAO,GAA4C;AAAE,aAAO,KAAK,cAAc,EAAE,OAAO,CAAC;AAAA,IAAG;AAAA,IAC5F,WAAW,MAAkD;AAAE,aAAO,KAAK,cAAc,EAAE,WAAW,IAAI;AAAA,IAAG;AAAA,IAC7G,UAAU,IAA2B;AAAE,aAAO,KAAK,cAAc,EAAE,UAAU,EAAE;AAAA,IAAG;AAAA,IAClF,SAAS,IAA2B;AAAE,aAAO,KAAK,cAAc,EAAE,SAAS,EAAE;AAAA,IAAG;AAAA,IAChF,aAAsC;AAAE,aAAO,KAAK,cAAc,EAAE,WAAW;AAAA,IAAG;AAAA,IAClF,YAAoC;AAAE,aAAO,KAAK,cAAc,EAAE,UAAU;AAAA,IAAG;AAAA,IAC/E,kBAAkB,MAAkC;AAAE,WAAK,QAAQ,kBAAkB,IAAI;AAAA,IAAG;AAAA,IAC5F,SAAS,KAA8C;AAAE,aAAO,KAAK,cAAc,EAAE,SAAS,GAAG;AAAA,IAAG;AAAA,IAEpG,UAAgB;AACd,WAAK,QAAQ,QAAQ;AACrB,WAAK,SAAS;AAAA,IAChB;AAAA,IAEQ,gBAAkC;AACxC,UAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,oCAAoC;AACtE,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAEA,SAAO;AACT;AAEO,IAAM,uBAAuB;AAG7B,SAAS,sBAAsB,MAAc,sBAAuD;AACzG,MAAI,OAAO,mBAAmB,eAAe,OAAO,gBAAgB,YAAa,QAAO;AACxF,QAAM,WAAW,eAAe,IAAI,GAAG;AACvC,MAAI,SAAU,QAAO;AACrB,QAAM,OAAO,mBAAmB;AAChC,iBAAe,OAAO,KAAK,IAAI;AAC/B,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|