@firetable/project-xiaochun 0.1.13 → 0.1.15
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 +144 -18
- package/README.md +144 -18
- package/dist/avatar-element.d.ts +23 -6
- package/dist/chunks/{chunk-UB2HFUBS.js → chunk-KKRZ3SUD.js} +50 -2
- package/dist/chunks/chunk-KKRZ3SUD.js.map +7 -0
- package/dist/chunks/chunk-KQSKHPVF.js +957 -0
- package/dist/chunks/chunk-KQSKHPVF.js.map +7 -0
- package/dist/chunks/{chunk-NKR4JG3C.js → chunk-T6EHFEEZ.js} +83 -9
- package/dist/chunks/chunk-T6EHFEEZ.js.map +7 -0
- package/dist/client.d.ts +134 -6
- package/dist/element.cjs +572 -71
- package/dist/element.cjs.map +4 -4
- package/dist/element.js +4 -4
- package/dist/gesture-box.d.ts +72 -0
- package/dist/index.cjs +599 -71
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -4
- package/dist/index.js.map +1 -1
- package/dist/loader.global.js +2 -2
- package/dist/loader.global.js.map +4 -4
- package/dist/protocol.cjs +49 -1
- package/dist/protocol.cjs.map +2 -2
- package/dist/protocol.d.ts +180 -9
- package/dist/protocol.js +20 -2
- package/dist/react.cjs +540 -77
- package/dist/react.cjs.map +4 -4
- package/dist/react.d.ts +17 -1
- package/dist/react.js +50 -15
- package/dist/react.js.map +2 -2
- package/package.json +5 -3
- package/dist/chunks/chunk-NKR4JG3C.js.map +0 -7
- package/dist/chunks/chunk-UB2HFUBS.js.map +0 -7
- package/dist/chunks/chunk-XE2YKXRA.js +0 -544
- package/dist/chunks/chunk-XE2YKXRA.js.map +0 -7
package/dist/react.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.15 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
"use strict";
|
|
3
3
|
"use client";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -32,6 +32,27 @@ var import_react = require("react");
|
|
|
32
32
|
var XC_PROTOCOL_VERSION = 1;
|
|
33
33
|
var XC_DEFAULT_ORIGIN = "https://xiaochun.firetable.tech";
|
|
34
34
|
var XC_DEFAULT_SRC = `${XC_DEFAULT_ORIGIN}/embed`;
|
|
35
|
+
var XC_WINDOW_CORNER_RADIUS = 20;
|
|
36
|
+
var XC_UI_PARTS = ["chat", "bubble", "outfit", "scene"];
|
|
37
|
+
var XC_UI_PART_SET = new Set(XC_UI_PARTS);
|
|
38
|
+
var XC_UI_LEGACY_PARTS = ["chat", "bubble"];
|
|
39
|
+
function parseXcUiList(items) {
|
|
40
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41
|
+
const unknown = [];
|
|
42
|
+
for (const it of items) {
|
|
43
|
+
const name = typeof it === "string" ? it.trim().toLowerCase() : "";
|
|
44
|
+
if (XC_UI_PART_SET.has(name)) seen.add(name);
|
|
45
|
+
else unknown.push(typeof it === "string" ? it.slice(0, 40) : String(typeof it));
|
|
46
|
+
}
|
|
47
|
+
return { parts: XC_UI_PARTS.filter((p) => seen.has(p)), unknown, legacy: false };
|
|
48
|
+
}
|
|
49
|
+
function normalizeXcUiOption(v) {
|
|
50
|
+
if (v === true) return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };
|
|
51
|
+
if (Array.isArray(v)) return parseXcUiList(v);
|
|
52
|
+
return { parts: [], unknown: [], legacy: false };
|
|
53
|
+
}
|
|
54
|
+
var XC_ID_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
55
|
+
var isXcId = (v) => typeof v === "string" && XC_ID_RE.test(v);
|
|
35
56
|
function xcMessage(type, payload, id) {
|
|
36
57
|
const m = { type, v: XC_PROTOCOL_VERSION, payload };
|
|
37
58
|
if (id !== void 0) m.id = id;
|
|
@@ -53,6 +74,95 @@ function normalizeOrigin(input) {
|
|
|
53
74
|
}
|
|
54
75
|
}
|
|
55
76
|
|
|
77
|
+
// src/gesture-box.ts
|
|
78
|
+
var DEFAULT_RESIZE_LIMITS = Object.freeze({
|
|
79
|
+
minWidth: 120,
|
|
80
|
+
minHeight: 180,
|
|
81
|
+
maxWidth: Infinity,
|
|
82
|
+
maxHeight: Infinity
|
|
83
|
+
});
|
|
84
|
+
var finitePositive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
85
|
+
function normalizeResizable(v) {
|
|
86
|
+
if (!v) return null;
|
|
87
|
+
const o = v === true ? {} : v;
|
|
88
|
+
const minWidth = finitePositive(o.minWidth) ? o.minWidth : DEFAULT_RESIZE_LIMITS.minWidth;
|
|
89
|
+
const minHeight = finitePositive(o.minHeight) ? o.minHeight : DEFAULT_RESIZE_LIMITS.minHeight;
|
|
90
|
+
const maxWidth = finitePositive(o.maxWidth) ? Math.max(o.maxWidth, minWidth) : Infinity;
|
|
91
|
+
const maxHeight = finitePositive(o.maxHeight) ? Math.max(o.maxHeight, minHeight) : Infinity;
|
|
92
|
+
return { minWidth, minHeight, maxWidth, maxHeight };
|
|
93
|
+
}
|
|
94
|
+
var clamp = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
|
|
95
|
+
function moveBox(start, totalDx, totalDy, vp) {
|
|
96
|
+
return {
|
|
97
|
+
left: clamp(start.left + totalDx, 0, vp.width - start.width),
|
|
98
|
+
top: clamp(start.top + totalDy, 0, vp.height - start.height),
|
|
99
|
+
width: start.width,
|
|
100
|
+
height: start.height
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function resizeBox(start, corner, totalDx, totalDy, limits, vp) {
|
|
104
|
+
const west = corner === "NW" || corner === "SW";
|
|
105
|
+
const north = corner === "NW" || corner === "NE";
|
|
106
|
+
const right = start.left + start.width, bottom = start.top + start.height;
|
|
107
|
+
const wantW = west ? start.width - totalDx : start.width + totalDx;
|
|
108
|
+
const wantH = north ? start.height - totalDy : start.height + totalDy;
|
|
109
|
+
const roomW = west ? right : vp.width - start.left;
|
|
110
|
+
const roomH = north ? bottom : vp.height - start.top;
|
|
111
|
+
const width = clamp(wantW, limits.minWidth, Math.min(limits.maxWidth, roomW));
|
|
112
|
+
const height = clamp(wantH, limits.minHeight, Math.min(limits.maxHeight, roomH));
|
|
113
|
+
return { left: west ? right - width : start.left, top: north ? bottom - height : start.top, width, height };
|
|
114
|
+
}
|
|
115
|
+
function fitBox(b, vp) {
|
|
116
|
+
const width = Math.min(b.width, Math.max(1, vp.width)), height = Math.min(b.height, Math.max(1, vp.height));
|
|
117
|
+
return { left: clamp(b.left, 0, vp.width - width), top: clamp(b.top, 0, vp.height - height), width, height };
|
|
118
|
+
}
|
|
119
|
+
var CORNERS = /* @__PURE__ */ new Set(["NW", "NE", "SW", "SE"]);
|
|
120
|
+
var PHASES = /* @__PURE__ */ new Set(["start", "move", "end"]);
|
|
121
|
+
var REASONS = /* @__PURE__ */ new Set(["up", "cancel", "blur"]);
|
|
122
|
+
var MAX_GESTURE_DELTA = 2e4;
|
|
123
|
+
var delta = (v) => typeof v === "number" && Number.isFinite(v) && Math.abs(v) <= MAX_GESTURE_DELTA;
|
|
124
|
+
var counter = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
125
|
+
function parseGesturePayload(kind, raw) {
|
|
126
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
127
|
+
const p = raw;
|
|
128
|
+
if (!counter(p.gesture) || !counter(p.seq) || typeof p.phase !== "string" || !PHASES.has(p.phase)) return null;
|
|
129
|
+
if (!delta(p.dx) || !delta(p.dy) || !delta(p.totalDx) || !delta(p.totalDy)) return null;
|
|
130
|
+
if (p.reason !== void 0 && (typeof p.reason !== "string" || !REASONS.has(p.reason))) return null;
|
|
131
|
+
const out = { gesture: p.gesture, seq: p.seq, phase: p.phase, totalDx: p.totalDx, totalDy: p.totalDy };
|
|
132
|
+
if (kind === "resize") {
|
|
133
|
+
if (typeof p.corner !== "string" || !CORNERS.has(p.corner)) return null;
|
|
134
|
+
out.corner = p.corner;
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
var GestureGate = class {
|
|
139
|
+
constructor() {
|
|
140
|
+
this.lastGesture = 0;
|
|
141
|
+
this.lastSeq = -1;
|
|
142
|
+
this.open = false;
|
|
143
|
+
}
|
|
144
|
+
get active() {
|
|
145
|
+
return this.open;
|
|
146
|
+
}
|
|
147
|
+
accept(p) {
|
|
148
|
+
if (p.phase === "start") {
|
|
149
|
+
if (p.gesture <= this.lastGesture || p.seq !== 0) return false;
|
|
150
|
+
this.lastGesture = p.gesture;
|
|
151
|
+
this.lastSeq = 0;
|
|
152
|
+
this.open = true;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (!this.open || p.gesture !== this.lastGesture || p.seq <= this.lastSeq) return false;
|
|
156
|
+
this.lastSeq = p.seq;
|
|
157
|
+
if (p.phase === "end") this.open = false;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
/** 宿主主动结束 (关闭手势 / 销毁): 之后同一手势的迟到消息被拒绝。 */
|
|
161
|
+
close() {
|
|
162
|
+
this.open = false;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
56
166
|
// src/client.ts
|
|
57
167
|
var FALLBACK_PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
58
168
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 180"><defs><linearGradient id="g" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#f6b8ae"/><stop offset="1" stop-color="#ea8377"/></linearGradient></defs><ellipse cx="60" cy="170" rx="34" ry="6" fill="#000" opacity=".12"/><circle cx="60" cy="48" r="26" fill="url(#g)" opacity=".75"/><path d="M26 160c2-40 18-62 34-62s32 22 34 62z" fill="url(#g)" opacity=".6"/></svg>'
|
|
@@ -79,8 +189,46 @@ function createXiaochun(options) {
|
|
|
79
189
|
if (!n) throw new Error(`[project-xiaochun] invalid allowedOrigins entry: ${o} (wildcards are not allowed)`);
|
|
80
190
|
trusted.add(n);
|
|
81
191
|
}
|
|
82
|
-
const
|
|
83
|
-
const
|
|
192
|
+
const prefsKey = options.persist === "host" ? "xiaochun:prefs" : typeof options.persist === "string" && options.persist ? options.persist : null;
|
|
193
|
+
const readPrefs = () => {
|
|
194
|
+
if (!prefsKey) return {};
|
|
195
|
+
try {
|
|
196
|
+
const raw = JSON.parse(window.localStorage.getItem(prefsKey) ?? "null");
|
|
197
|
+
const out = {};
|
|
198
|
+
if (raw && typeof raw === "object") {
|
|
199
|
+
if (isXcId(raw.outfit)) out.outfit = raw.outfit;
|
|
200
|
+
if (isXcId(raw.scene)) out.scene = raw.scene;
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
} catch {
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const writePrefs = (patch) => {
|
|
208
|
+
if (!prefsKey) return;
|
|
209
|
+
try {
|
|
210
|
+
const next = { ...readPrefs() };
|
|
211
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
212
|
+
if (v) next[k] = v;
|
|
213
|
+
else delete next[k];
|
|
214
|
+
}
|
|
215
|
+
window.localStorage.setItem(prefsKey, JSON.stringify(next));
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const prefs = readPrefs();
|
|
220
|
+
if (options.model !== void 0 && options.outfit === void 0) {
|
|
221
|
+
console.warn('[project-xiaochun] option "model" is deprecated, use "outfit" (built-in outfit id).');
|
|
222
|
+
}
|
|
223
|
+
const uiParse = normalizeXcUiOption(options.ui);
|
|
224
|
+
const uiParts = uiParse.parts;
|
|
225
|
+
if (uiParse.legacy) console.warn(`[project-xiaochun] option "ui: true" is deprecated, use an array of parts, e.g. ui: ['chat', 'bubble', 'outfit', 'scene'].`);
|
|
226
|
+
if (uiParse.unknown.length) console.warn(`[project-xiaochun] ignoring unknown ui part(s): ${uiParse.unknown.join(", ")} (known: ${XC_UI_PARTS.join(", ")})`);
|
|
227
|
+
const outfitOpt = options.outfit ?? (isXcId(options.model) ? options.model : void 0);
|
|
228
|
+
const initialOutfit = outfitOpt ?? prefs.outfit;
|
|
229
|
+
const initialScene = options.scene ?? prefs.scene ?? (options.transparent ? "transparent" : void 0);
|
|
230
|
+
let transparent = initialScene === "transparent" || initialScene !== "light" && initialScene !== "dark" && (options.transparent ?? false);
|
|
231
|
+
let passthrough = options.passthrough ?? transparent;
|
|
84
232
|
const lazy = options.lazy ?? true;
|
|
85
233
|
const position = options.position ?? "inline";
|
|
86
234
|
const handshakeTimeout = options.handshakeTimeout ?? 2e4;
|
|
@@ -93,9 +241,20 @@ function createXiaochun(options) {
|
|
|
93
241
|
ws.maxWidth = "100%";
|
|
94
242
|
ws.overflow = "visible";
|
|
95
243
|
ws.contain = "layout style";
|
|
96
|
-
ws.borderRadius = "var(--xc-radius, 0)";
|
|
97
244
|
ws.boxShadow = "var(--xc-shadow, none)";
|
|
98
|
-
|
|
245
|
+
const applyWrapperBg = () => {
|
|
246
|
+
ws.background = transparent ? "" : "var(--xc-bg, transparent)";
|
|
247
|
+
};
|
|
248
|
+
applyWrapperBg();
|
|
249
|
+
let radiusOpt = options.borderRadius;
|
|
250
|
+
const radiusValue = () => radiusOpt !== void 0 ? css(radiusOpt, "0px") : transparent ? "0px" : `${XC_WINDOW_CORNER_RADIUS}px`;
|
|
251
|
+
const applyRadius = () => {
|
|
252
|
+
const v = `var(--xc-radius, ${radiusValue()})`;
|
|
253
|
+
ws.borderRadius = v;
|
|
254
|
+
if (placeholderEl) placeholderEl.style.borderRadius = v;
|
|
255
|
+
if (iframe) iframe.style.borderRadius = v;
|
|
256
|
+
};
|
|
257
|
+
ws.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
99
258
|
if (position === "bottom-right") {
|
|
100
259
|
ws.right = "var(--xc-offset-x, 16px)";
|
|
101
260
|
ws.bottom = "var(--xc-offset-y, 16px)";
|
|
@@ -122,7 +281,7 @@ function createXiaochun(options) {
|
|
|
122
281
|
}
|
|
123
282
|
const ps = placeholderEl.style;
|
|
124
283
|
placeholderEl.setAttribute("part", "placeholder");
|
|
125
|
-
ps.borderRadius =
|
|
284
|
+
ps.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
126
285
|
ps.position = "absolute";
|
|
127
286
|
ps.inset = "0";
|
|
128
287
|
ps.width = "100%";
|
|
@@ -159,25 +318,45 @@ function createXiaochun(options) {
|
|
|
159
318
|
let autoPaused = false;
|
|
160
319
|
let loaded = false;
|
|
161
320
|
let lastHit = false;
|
|
321
|
+
let caps = null;
|
|
322
|
+
let currentOutfit = null;
|
|
323
|
+
let currentScene = null;
|
|
162
324
|
const queue = [];
|
|
163
325
|
const pending = /* @__PURE__ */ new Map();
|
|
164
326
|
let idSeq = 0;
|
|
165
327
|
let handshakeTimer = null;
|
|
166
328
|
let io = null;
|
|
167
329
|
let idleHandle = null;
|
|
168
|
-
|
|
330
|
+
let resolveHandshake;
|
|
331
|
+
const handshaked = new Promise((res) => {
|
|
332
|
+
resolveHandshake = res;
|
|
333
|
+
});
|
|
334
|
+
const failLocal = (type, id, code, message) => {
|
|
335
|
+
emit("error", { code, message, command: type });
|
|
336
|
+
const p = id ? pending.get(id) : void 0;
|
|
337
|
+
if (p && id) {
|
|
338
|
+
pending.delete(id);
|
|
339
|
+
p.reject(new Error(`[${code}] ${message}`));
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const unsupportedByFrame = (type, id) => failLocal(type, id, "unsupported", `${type} is not supported by this /embed deployment (older version; its xc.ready has no capabilities for it)`);
|
|
343
|
+
const postToFrame = (type, payload, id, transfer, requires) => {
|
|
169
344
|
if (!port) {
|
|
170
|
-
queue.push({ type, payload, id, transfer });
|
|
345
|
+
queue.push({ type, payload, id, transfer, requires });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (requires && !caps?.[requires]) {
|
|
349
|
+
unsupportedByFrame(type, id);
|
|
171
350
|
return;
|
|
172
351
|
}
|
|
173
352
|
port.postMessage(xcMessage(type, payload, id), transfer ?? []);
|
|
174
353
|
};
|
|
175
|
-
const send = (type, payload, wait = false, transfer, fixedId) => {
|
|
354
|
+
const send = (type, payload, wait = false, transfer, fixedId, requires) => {
|
|
176
355
|
if (destroyed) return Promise.reject(new Error("[project-xiaochun] instance destroyed"));
|
|
177
356
|
if (!iframe) activate();
|
|
178
357
|
const id = fixedId ?? `c${++idSeq}`;
|
|
179
358
|
const p = wait ? new Promise((resolve, reject) => pending.set(id, { resolve, reject, sawEnd: false })) : Promise.resolve();
|
|
180
|
-
postToFrame(type, payload, id, transfer);
|
|
359
|
+
postToFrame(type, payload, id, transfer, requires);
|
|
181
360
|
return p;
|
|
182
361
|
};
|
|
183
362
|
const toBuffer = (d) => d instanceof ArrayBuffer ? d : d.buffer.slice(d.byteOffset, d.byteOffset + d.byteLength);
|
|
@@ -227,7 +406,7 @@ function createXiaochun(options) {
|
|
|
227
406
|
let ended = false;
|
|
228
407
|
let fmt = rest.format === "pcm16" || rest.format === "float32" ? rest.format : null;
|
|
229
408
|
let done = Promise.resolve();
|
|
230
|
-
const
|
|
409
|
+
const handle = {
|
|
231
410
|
write(data) {
|
|
232
411
|
if (ended) throw new Error("[project-xiaochun] audio stream already ended");
|
|
233
412
|
if (destroyed) throw new Error("[project-xiaochun] instance destroyed");
|
|
@@ -261,17 +440,18 @@ function createXiaochun(options) {
|
|
|
261
440
|
return done;
|
|
262
441
|
}
|
|
263
442
|
};
|
|
264
|
-
return
|
|
443
|
+
return handle;
|
|
265
444
|
};
|
|
266
445
|
function buildUrl() {
|
|
267
446
|
const u = new URL(srcUrl.href);
|
|
268
447
|
u.searchParams.set("host", window.location.origin);
|
|
269
448
|
if (transparent) u.searchParams.set("transparent", "1");
|
|
270
|
-
if (
|
|
449
|
+
if (uiParts.length) u.searchParams.set("ui", uiParts.join(","));
|
|
271
450
|
if (options.lang) u.searchParams.set("lang", options.lang);
|
|
272
451
|
if (options.heavy) u.searchParams.set("heavy", options.heavy);
|
|
273
|
-
if (options.controls) u.searchParams.set("controls", "
|
|
274
|
-
if (
|
|
452
|
+
if (options.controls === false) u.searchParams.set("controls", "0");
|
|
453
|
+
if (initialScene && isXcId(initialScene)) u.searchParams.set("scene", initialScene);
|
|
454
|
+
if (initialOutfit) u.searchParams.set("outfit", initialOutfit);
|
|
275
455
|
return u.href;
|
|
276
456
|
}
|
|
277
457
|
function activate() {
|
|
@@ -282,7 +462,7 @@ function createXiaochun(options) {
|
|
|
282
462
|
const f = document.createElement("iframe");
|
|
283
463
|
f.title = "Project XiaoChun";
|
|
284
464
|
f.loading = "lazy";
|
|
285
|
-
f.allow = "microphone; autoplay";
|
|
465
|
+
f.allow = options.crossOriginIsolated === true ? "microphone; autoplay; cross-origin-isolated" : "microphone; autoplay";
|
|
286
466
|
f.referrerPolicy = "strict-origin-when-cross-origin";
|
|
287
467
|
if (options.sandbox !== false) {
|
|
288
468
|
f.setAttribute("sandbox", options.sandbox ?? "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox");
|
|
@@ -297,7 +477,7 @@ function createXiaochun(options) {
|
|
|
297
477
|
s.border = "0";
|
|
298
478
|
s.background = "transparent";
|
|
299
479
|
s.colorScheme = "normal";
|
|
300
|
-
s.borderRadius =
|
|
480
|
+
s.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
301
481
|
f.setAttribute("part", "iframe");
|
|
302
482
|
s.opacity = "0";
|
|
303
483
|
s.transition = "opacity .25s ease";
|
|
@@ -310,7 +490,7 @@ function createXiaochun(options) {
|
|
|
310
490
|
if (!port) emit("error", { code: "timeout", message: `no xc.ready from ${frameOrigin} within ${handshakeTimeout}ms` });
|
|
311
491
|
}, handshakeTimeout);
|
|
312
492
|
setupAutoPause();
|
|
313
|
-
|
|
493
|
+
if (passthrough) enablePassthrough();
|
|
314
494
|
}
|
|
315
495
|
const onWindowMessage = (ev) => {
|
|
316
496
|
if (destroyed || !iframe || ev.source !== iframe.contentWindow) return;
|
|
@@ -323,20 +503,39 @@ function createXiaochun(options) {
|
|
|
323
503
|
const ch = new MessageChannel();
|
|
324
504
|
port = ch.port1;
|
|
325
505
|
port.onmessage = onPortMessage;
|
|
506
|
+
caps = data.payload?.capabilities ?? null;
|
|
507
|
+
const gWant = wantGestures();
|
|
508
|
+
sentGestures = gWant;
|
|
509
|
+
const initConfig = {
|
|
510
|
+
...options.allowCustomModel ? { allowCustomModel: true } : {},
|
|
511
|
+
...gWant.move || gWant.resize ? { gestures: gWant } : {}
|
|
512
|
+
};
|
|
326
513
|
iframe.contentWindow.postMessage(
|
|
327
|
-
xcMessage("xc.init", { hostOrigin: window.location.origin }),
|
|
514
|
+
xcMessage("xc.init", { hostOrigin: window.location.origin, ...Object.keys(initConfig).length ? { config: initConfig } : {} }),
|
|
328
515
|
ev.origin,
|
|
329
516
|
[ch.port2]
|
|
330
517
|
);
|
|
518
|
+
warnGestureUnsupported();
|
|
331
519
|
if (handshakeTimer) {
|
|
332
520
|
clearTimeout(handshakeTimer);
|
|
333
521
|
handshakeTimer = null;
|
|
334
522
|
}
|
|
335
523
|
emit("handshake", data.payload);
|
|
336
|
-
|
|
524
|
+
resolveHandshake();
|
|
525
|
+
for (const m of queue.splice(0)) {
|
|
526
|
+
if (m.requires && !caps?.[m.requires]) unsupportedByFrame(m.type, m.id);
|
|
527
|
+
else port.postMessage(xcMessage(m.type, m.payload, m.id), m.transfer ?? []);
|
|
528
|
+
}
|
|
337
529
|
syncPause();
|
|
338
530
|
};
|
|
339
531
|
window.addEventListener("message", onWindowMessage);
|
|
532
|
+
function ack(id, value) {
|
|
533
|
+
const p = id ? pending.get(id) : void 0;
|
|
534
|
+
if (p && id) {
|
|
535
|
+
pending.delete(id);
|
|
536
|
+
p.resolve(value);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
340
539
|
function onPortMessage(ev) {
|
|
341
540
|
const data = ev.data;
|
|
342
541
|
if (!isXcEnvelope(data)) return;
|
|
@@ -347,6 +546,11 @@ function createXiaochun(options) {
|
|
|
347
546
|
case "xc.loaded":
|
|
348
547
|
if (!loaded) {
|
|
349
548
|
loaded = true;
|
|
549
|
+
if (options.prefetch && options.heavy === "eager" && caps?.prefetch) {
|
|
550
|
+
const ids = Array.isArray(options.prefetch) ? options.prefetch : void 0;
|
|
551
|
+
void api.prefetch(ids).catch(() => {
|
|
552
|
+
});
|
|
553
|
+
}
|
|
350
554
|
if (iframe) iframe.style.opacity = "1";
|
|
351
555
|
if (placeholderEl) {
|
|
352
556
|
const el = placeholderEl;
|
|
@@ -359,6 +563,30 @@ function createXiaochun(options) {
|
|
|
359
563
|
}
|
|
360
564
|
emit("ready", data.payload);
|
|
361
565
|
break;
|
|
566
|
+
case "xc.outfit-changed": {
|
|
567
|
+
const pl = data.payload;
|
|
568
|
+
if (!pl.noop) {
|
|
569
|
+
currentOutfit = pl.id;
|
|
570
|
+
emit("outfit-changed", pl);
|
|
571
|
+
if (!pl.initial && pl.id) writePrefs({ outfit: pl.id });
|
|
572
|
+
}
|
|
573
|
+
ack(data.id);
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
case "xc.scene-changed": {
|
|
577
|
+
const pl = data.payload;
|
|
578
|
+
currentScene = pl.id;
|
|
579
|
+
if (pl.transparent !== transparent) applyTransparency(pl.transparent);
|
|
580
|
+
if (!pl.noop) {
|
|
581
|
+
emit("scene-changed", pl);
|
|
582
|
+
if (!pl.initial) writePrefs({ scene: pl.id });
|
|
583
|
+
}
|
|
584
|
+
ack(data.id);
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case "xc.prefetched":
|
|
588
|
+
ack(data.id, data.payload);
|
|
589
|
+
break;
|
|
362
590
|
case "xc.state":
|
|
363
591
|
emit("state", data.payload);
|
|
364
592
|
break;
|
|
@@ -383,9 +611,16 @@ function createXiaochun(options) {
|
|
|
383
611
|
}
|
|
384
612
|
break;
|
|
385
613
|
}
|
|
614
|
+
case "xc.gesture-move":
|
|
615
|
+
onGesture("move", data.payload);
|
|
616
|
+
break;
|
|
617
|
+
case "xc.gesture-resize":
|
|
618
|
+
onGesture("resize", data.payload);
|
|
619
|
+
break;
|
|
386
620
|
case "xc.error": {
|
|
387
621
|
const { code, message, command } = data.payload;
|
|
388
622
|
emit("error", { code, message, command });
|
|
623
|
+
if (code === "unknown_id" && (command === "outfit" || command === "scene")) writePrefs({ [command]: null });
|
|
389
624
|
const p = data.id ? pending.get(data.id) : void 0;
|
|
390
625
|
if (p && data.id) {
|
|
391
626
|
pending.delete(data.id);
|
|
@@ -397,8 +632,26 @@ function createXiaochun(options) {
|
|
|
397
632
|
break;
|
|
398
633
|
}
|
|
399
634
|
}
|
|
635
|
+
const POINTER_MIN_INTERVAL_MS = 32;
|
|
636
|
+
const POINTER_MIN_MOVE_PX = 2;
|
|
400
637
|
let pointerRaf = 0;
|
|
401
638
|
let lastPtr = null;
|
|
639
|
+
let lastPosted = null;
|
|
640
|
+
let rectCache = null;
|
|
641
|
+
let rectRo = null;
|
|
642
|
+
let rectAt = 0;
|
|
643
|
+
const invalidateRect = () => {
|
|
644
|
+
rectCache = null;
|
|
645
|
+
};
|
|
646
|
+
const getRect = () => {
|
|
647
|
+
if (!iframe) return null;
|
|
648
|
+
const now = performance.now();
|
|
649
|
+
if (!rectCache || now - rectAt > 400) {
|
|
650
|
+
rectCache = iframe.getBoundingClientRect();
|
|
651
|
+
rectAt = now;
|
|
652
|
+
}
|
|
653
|
+
return rectCache;
|
|
654
|
+
};
|
|
402
655
|
const onHostPointer = (e) => {
|
|
403
656
|
if (!iframe || !port || iframe.style.pointerEvents === "auto") return;
|
|
404
657
|
lastPtr = { x: e.clientX, y: e.clientY };
|
|
@@ -406,20 +659,63 @@ function createXiaochun(options) {
|
|
|
406
659
|
pointerRaf = requestAnimationFrame(() => {
|
|
407
660
|
pointerRaf = 0;
|
|
408
661
|
if (!iframe || !port || !lastPtr) return;
|
|
409
|
-
const r =
|
|
662
|
+
const r = getRect();
|
|
663
|
+
if (!r) return;
|
|
410
664
|
const x = lastPtr.x - r.left, y = lastPtr.y - r.top;
|
|
411
665
|
if (x < 0 || y < 0 || x > r.width || y > r.height) {
|
|
666
|
+
lastPosted = null;
|
|
412
667
|
if (lastHit) {
|
|
413
668
|
lastHit = false;
|
|
414
669
|
iframe.style.pointerEvents = "none";
|
|
415
670
|
}
|
|
416
671
|
return;
|
|
417
672
|
}
|
|
673
|
+
const now = performance.now();
|
|
674
|
+
if (lastPosted && now - lastPosted.t < POINTER_MIN_INTERVAL_MS) return;
|
|
675
|
+
if (lastPosted && Math.abs(x - lastPosted.x) < POINTER_MIN_MOVE_PX && Math.abs(y - lastPosted.y) < POINTER_MIN_MOVE_PX) return;
|
|
676
|
+
lastPosted = { x, y, t: now };
|
|
418
677
|
port.postMessage(xcMessage("xc.pointer", { x, y }));
|
|
419
678
|
});
|
|
420
679
|
};
|
|
421
|
-
|
|
422
|
-
|
|
680
|
+
let passthroughActive = false;
|
|
681
|
+
function enablePassthrough() {
|
|
682
|
+
if (passthroughActive || !iframe) return;
|
|
683
|
+
passthroughActive = true;
|
|
684
|
+
window.addEventListener("pointermove", onHostPointer, { passive: true });
|
|
685
|
+
window.addEventListener("pointerdown", onHostPointer, { passive: true });
|
|
686
|
+
window.addEventListener("scroll", invalidateRect, { passive: true, capture: true });
|
|
687
|
+
window.addEventListener("resize", invalidateRect, { passive: true });
|
|
688
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
689
|
+
rectRo = new ResizeObserver(invalidateRect);
|
|
690
|
+
rectRo.observe(iframe);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function disablePassthrough() {
|
|
694
|
+
if (!passthroughActive) return;
|
|
695
|
+
passthroughActive = false;
|
|
696
|
+
window.removeEventListener("pointermove", onHostPointer);
|
|
697
|
+
window.removeEventListener("pointerdown", onHostPointer);
|
|
698
|
+
window.removeEventListener("scroll", invalidateRect, true);
|
|
699
|
+
window.removeEventListener("resize", invalidateRect);
|
|
700
|
+
rectRo?.disconnect();
|
|
701
|
+
rectRo = null;
|
|
702
|
+
if (pointerRaf) {
|
|
703
|
+
cancelAnimationFrame(pointerRaf);
|
|
704
|
+
pointerRaf = 0;
|
|
705
|
+
}
|
|
706
|
+
lastPtr = null;
|
|
707
|
+
}
|
|
708
|
+
function applyTransparency(next) {
|
|
709
|
+
transparent = next;
|
|
710
|
+
applyWrapperBg();
|
|
711
|
+
applyRadius();
|
|
712
|
+
passthrough = next ? options.passthrough ?? true : false;
|
|
713
|
+
lastHit = false;
|
|
714
|
+
lastPosted = null;
|
|
715
|
+
rectCache = null;
|
|
716
|
+
if (passthrough) enablePassthrough();
|
|
717
|
+
else disablePassthrough();
|
|
718
|
+
if (iframe) iframe.style.pointerEvents = passthrough ? "none" : "auto";
|
|
423
719
|
}
|
|
424
720
|
let pauseIo = null;
|
|
425
721
|
function setupAutoPause() {
|
|
@@ -472,49 +768,123 @@ function createXiaochun(options) {
|
|
|
472
768
|
}, { rootMargin: `${options.lazyMargin ?? 200}px` });
|
|
473
769
|
io.observe(wrapper);
|
|
474
770
|
}
|
|
475
|
-
let
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
if (!drag) return;
|
|
506
|
-
ws.left = `${Math.min(Math.max(0, e.clientX - drag.dx), window.innerWidth - 40)}px`;
|
|
507
|
-
ws.top = `${Math.min(Math.max(0, e.clientY - drag.dy), window.innerHeight - 40)}px`;
|
|
771
|
+
let draggableOn = options.draggable === true;
|
|
772
|
+
let resizeLimits = normalizeResizable(options.resizable);
|
|
773
|
+
const gates = { move: new GestureGate(), resize: new GestureGate() };
|
|
774
|
+
let activeGesture = null;
|
|
775
|
+
let inlineShift = { x: 0, y: 0 };
|
|
776
|
+
let userMoved = false;
|
|
777
|
+
let sentGestures = null;
|
|
778
|
+
let warnedGesture = false;
|
|
779
|
+
const viewport = () => ({
|
|
780
|
+
width: document.documentElement.clientWidth || window.innerWidth,
|
|
781
|
+
height: document.documentElement.clientHeight || window.innerHeight
|
|
782
|
+
});
|
|
783
|
+
const readBox = () => {
|
|
784
|
+
const r = wrapper.getBoundingClientRect();
|
|
785
|
+
return { left: r.left, top: r.top, width: r.width, height: r.height };
|
|
786
|
+
};
|
|
787
|
+
const wantGestures = () => ({ move: draggableOn && caps?.gestures?.move === true, resize: resizeLimits !== null && caps?.gestures?.resize === true });
|
|
788
|
+
function applyBox(b, inlineBase, withSize) {
|
|
789
|
+
if (withSize) {
|
|
790
|
+
ws.width = `${b.width}px`;
|
|
791
|
+
ws.height = `${b.height}px`;
|
|
792
|
+
ws.maxWidth = "none";
|
|
793
|
+
}
|
|
794
|
+
if (position === "inline") {
|
|
795
|
+
const base = inlineBase ?? { left: b.left - inlineShift.x, top: b.top - inlineShift.y };
|
|
796
|
+
inlineShift = { x: b.left - base.left, y: b.top - base.top };
|
|
797
|
+
ws.setProperty("translate", `${inlineShift.x}px ${inlineShift.y}px`);
|
|
798
|
+
} else {
|
|
799
|
+
ws.left = `${b.left}px`;
|
|
800
|
+
ws.top = `${b.top}px`;
|
|
508
801
|
ws.right = "auto";
|
|
509
802
|
ws.bottom = "auto";
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
803
|
+
}
|
|
804
|
+
userMoved = true;
|
|
805
|
+
invalidateRect();
|
|
806
|
+
}
|
|
807
|
+
let selectionLock = null;
|
|
808
|
+
function lockSelection(on) {
|
|
809
|
+
const de = document.documentElement;
|
|
810
|
+
if (on && !selectionLock) {
|
|
811
|
+
selectionLock = { us: de.style.getPropertyValue("user-select"), wus: de.style.getPropertyValue("-webkit-user-select") };
|
|
812
|
+
de.style.setProperty("user-select", "none");
|
|
813
|
+
de.style.setProperty("-webkit-user-select", "none");
|
|
814
|
+
try {
|
|
815
|
+
window.getSelection()?.removeAllRanges();
|
|
816
|
+
} catch {
|
|
817
|
+
}
|
|
818
|
+
} else if (!on && selectionLock) {
|
|
819
|
+
de.style.setProperty("user-select", selectionLock.us);
|
|
820
|
+
de.style.setProperty("-webkit-user-select", selectionLock.wus);
|
|
821
|
+
selectionLock = null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function endActiveGesture(kind) {
|
|
825
|
+
if (!activeGesture || kind && activeGesture.kind !== kind) return;
|
|
826
|
+
gates[activeGesture.kind].close();
|
|
827
|
+
const b = readBox();
|
|
828
|
+
emit(activeGesture.kind, { phase: "end", ...b });
|
|
829
|
+
activeGesture = null;
|
|
830
|
+
lockSelection(false);
|
|
831
|
+
}
|
|
832
|
+
function onGesture(kind, raw) {
|
|
833
|
+
if (destroyed || !caps?.gestures) return;
|
|
834
|
+
if (kind === "move" ? !draggableOn : resizeLimits === null) return;
|
|
835
|
+
const p = parseGesturePayload(kind, raw);
|
|
836
|
+
if (!p) return;
|
|
837
|
+
if (p.phase === "start" ? activeGesture !== null : activeGesture?.kind !== kind) return;
|
|
838
|
+
if (!gates[kind].accept(p)) return;
|
|
839
|
+
if (p.phase === "start") {
|
|
840
|
+
const start = readBox();
|
|
841
|
+
activeGesture = {
|
|
842
|
+
kind,
|
|
843
|
+
start,
|
|
844
|
+
corner: p.corner,
|
|
845
|
+
inlineBase: position === "inline" ? { left: start.left - inlineShift.x, top: start.top - inlineShift.y } : null
|
|
846
|
+
};
|
|
847
|
+
lockSelection(true);
|
|
848
|
+
emit(kind, { phase: "start", ...start });
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
const a = activeGesture;
|
|
852
|
+
const vp = viewport();
|
|
853
|
+
const next = kind === "move" ? moveBox(a.start, p.totalDx, p.totalDy, vp) : resizeBox(a.start, a.corner, p.totalDx, p.totalDy, resizeLimits, vp);
|
|
854
|
+
applyBox(next, a.inlineBase, kind === "resize");
|
|
855
|
+
emit(kind, { phase: p.phase, ...next });
|
|
856
|
+
if (p.phase === "end") {
|
|
857
|
+
activeGesture = null;
|
|
858
|
+
lockSelection(false);
|
|
859
|
+
}
|
|
517
860
|
}
|
|
861
|
+
function syncGestures() {
|
|
862
|
+
if (destroyed) return;
|
|
863
|
+
const want = wantGestures();
|
|
864
|
+
if (!want.move) endActiveGesture("move");
|
|
865
|
+
if (!want.resize) endActiveGesture("resize");
|
|
866
|
+
if (!port || !caps) return;
|
|
867
|
+
warnGestureUnsupported();
|
|
868
|
+
if (sentGestures && sentGestures.move === want.move && sentGestures.resize === want.resize) return;
|
|
869
|
+
sentGestures = want;
|
|
870
|
+
if (caps.gestures) port.postMessage(xcMessage("xc.setConfig", { gestures: want }));
|
|
871
|
+
}
|
|
872
|
+
function warnGestureUnsupported() {
|
|
873
|
+
if (warnedGesture || !caps || caps.gestures) return;
|
|
874
|
+
const needMove = draggableOn;
|
|
875
|
+
if (!needMove && resizeLimits === null) return;
|
|
876
|
+
warnedGesture = true;
|
|
877
|
+
const what = [needMove ? "draggable" : "", resizeLimits !== null ? "resizable" : ""].filter(Boolean).join(" / ");
|
|
878
|
+
emit("error", { code: "unsupported", message: `${what} needs gesture support (capabilities.gestures), which this /embed deployment does not advertise (older version)`, command: "gestures" });
|
|
879
|
+
}
|
|
880
|
+
const onWindowResize = () => {
|
|
881
|
+
if (!userMoved || activeGesture || destroyed) return;
|
|
882
|
+
const cur = readBox();
|
|
883
|
+
const fit = fitBox(cur, viewport());
|
|
884
|
+
if (fit.left === cur.left && fit.top === cur.top && fit.width === cur.width && fit.height === cur.height) return;
|
|
885
|
+
applyBox(fit, position === "inline" ? { left: cur.left - inlineShift.x, top: cur.top - inlineShift.y } : null, fit.width !== cur.width || fit.height !== cur.height);
|
|
886
|
+
};
|
|
887
|
+
window.addEventListener("resize", onWindowResize, { passive: true });
|
|
518
888
|
scheduleLazy();
|
|
519
889
|
const api = {
|
|
520
890
|
element: wrapper,
|
|
@@ -529,8 +899,65 @@ function createXiaochun(options) {
|
|
|
529
899
|
motion: (m) => send("xc.motion", typeof m === "string" ? m.startsWith("http") || m.startsWith("/") ? { url: m } : { name: m } : m),
|
|
530
900
|
expression: (name) => send("xc.expression", { name }),
|
|
531
901
|
lookAt: (x, y) => send("xc.lookAt", { x, y }),
|
|
532
|
-
|
|
902
|
+
setOutfit: (id) => {
|
|
903
|
+
if (!isXcId(id)) return Promise.reject(new Error("[bad_request] invalid outfit id"));
|
|
904
|
+
return send("xc.setOutfit", { id }, true, void 0, void 0, "outfits");
|
|
905
|
+
},
|
|
906
|
+
setScene: (id) => {
|
|
907
|
+
if (!isXcId(id)) return Promise.reject(new Error("[bad_request] invalid scene id"));
|
|
908
|
+
return send("xc.setScene", { id }, true, void 0, void 0, "scenes");
|
|
909
|
+
},
|
|
910
|
+
prefetch: (ids) => {
|
|
911
|
+
if (ids !== void 0 && (!Array.isArray(ids) || ids.some((x) => !isXcId(x)))) {
|
|
912
|
+
return Promise.reject(new Error("[bad_request] prefetch ids must be an array of outfit ids"));
|
|
913
|
+
}
|
|
914
|
+
return send("xc.prefetch", ids ? { ids } : {}, true, void 0, void 0, "prefetch");
|
|
915
|
+
},
|
|
916
|
+
getOutfits: async () => {
|
|
917
|
+
if (!iframe) activate();
|
|
918
|
+
await handshaked;
|
|
919
|
+
return caps?.outfits ? caps.outfits.slice() : [];
|
|
920
|
+
},
|
|
921
|
+
getScenes: async () => {
|
|
922
|
+
if (!iframe) activate();
|
|
923
|
+
await handshaked;
|
|
924
|
+
return caps?.scenes ? caps.scenes.slice() : [];
|
|
925
|
+
},
|
|
926
|
+
get outfit() {
|
|
927
|
+
return currentOutfit;
|
|
928
|
+
},
|
|
929
|
+
get scene() {
|
|
930
|
+
return currentScene;
|
|
931
|
+
},
|
|
932
|
+
setModel: (m) => {
|
|
933
|
+
const msg = typeof m === "string" ? /^(https?:)?\/|\.vrm/i.test(m) ? { url: m } : { outfit: m } : m;
|
|
934
|
+
if (msg.url !== void 0 && msg.outfit === void 0 && !options.allowCustomModel) {
|
|
935
|
+
const message = "custom model URLs are disabled; pass allowCustomModel: true to createXiaochun() (only for URLs you trust) or use setOutfit() with a built-in id";
|
|
936
|
+
emit("error", { code: "unsupported", message, command: "xc.setModel" });
|
|
937
|
+
return Promise.reject(new Error(`[unsupported] ${message}`));
|
|
938
|
+
}
|
|
939
|
+
return send("xc.setModel", msg);
|
|
940
|
+
},
|
|
533
941
|
setConfig: (cfg) => send("xc.setConfig", cfg),
|
|
942
|
+
setSize(width, height) {
|
|
943
|
+
if (destroyed) return;
|
|
944
|
+
ws.width = css(width, "320px");
|
|
945
|
+
ws.height = css(height, "480px");
|
|
946
|
+
invalidateRect();
|
|
947
|
+
},
|
|
948
|
+
getBox: () => readBox(),
|
|
949
|
+
setBorderRadius(radius) {
|
|
950
|
+
radiusOpt = radius;
|
|
951
|
+
applyRadius();
|
|
952
|
+
},
|
|
953
|
+
setDraggable(on) {
|
|
954
|
+
draggableOn = on === true;
|
|
955
|
+
syncGestures();
|
|
956
|
+
},
|
|
957
|
+
setResizable(on) {
|
|
958
|
+
resizeLimits = normalizeResizable(on);
|
|
959
|
+
syncGestures();
|
|
960
|
+
},
|
|
534
961
|
startListening: () => send("xc.mic", { enabled: true }),
|
|
535
962
|
stopListening: () => send("xc.mic", { enabled: false }),
|
|
536
963
|
mic: (enabled) => send("xc.mic", { enabled }),
|
|
@@ -555,7 +982,10 @@ function createXiaochun(options) {
|
|
|
555
982
|
if (handshakeTimer) clearTimeout(handshakeTimer);
|
|
556
983
|
if (pointerRaf) cancelAnimationFrame(pointerRaf);
|
|
557
984
|
window.removeEventListener("message", onWindowMessage);
|
|
558
|
-
window.removeEventListener("
|
|
985
|
+
window.removeEventListener("resize", onWindowResize);
|
|
986
|
+
lockSelection(false);
|
|
987
|
+
disablePassthrough();
|
|
988
|
+
resolveHandshake();
|
|
559
989
|
try {
|
|
560
990
|
port?.close();
|
|
561
991
|
} catch {
|
|
@@ -594,7 +1024,11 @@ var EVENT_MAP = [
|
|
|
594
1024
|
["stt", "onStt"],
|
|
595
1025
|
["utterance", "onUtterance"],
|
|
596
1026
|
["hit-region", "onHitRegion"],
|
|
1027
|
+
["outfit-changed", "onOutfitChanged"],
|
|
1028
|
+
["scene-changed", "onSceneChanged"],
|
|
597
1029
|
["error", "onError"],
|
|
1030
|
+
["move", "onMove"],
|
|
1031
|
+
["resize", "onResize"],
|
|
598
1032
|
["destroy", "onDestroy"]
|
|
599
1033
|
];
|
|
600
1034
|
function useXiaochun(options = {}) {
|
|
@@ -609,8 +1043,8 @@ function useXiaochun(options = {}) {
|
|
|
609
1043
|
(0, import_react.useEffect)(() => {
|
|
610
1044
|
if (!el) return;
|
|
611
1045
|
const cur = live.current;
|
|
612
|
-
const { onHandshake: _a, onReady: _b, onProgress: _c, onState: _d, onStt: _e, onUtterance: _f, onHitRegion: _g, onError: _h, onDestroy: _i, paused: _p, mic: _m, allowedOrigins, ...create } = cur;
|
|
613
|
-
void [_a, _b, _c, _d, _e, _f, _g, _h, _i, _p, _m];
|
|
1046
|
+
const { onHandshake: _a, onReady: _b, onProgress: _c, onState: _d, onStt: _e, onUtterance: _f, onHitRegion: _g, onError: _h, onDestroy: _i, onOutfitChanged: _j, onSceneChanged: _k, onMove: _q, onResize: _r, paused: _p, mic: _m, allowedOrigins, ...create } = cur;
|
|
1047
|
+
void [_a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _p, _m, _q, _r];
|
|
614
1048
|
const inst = createXiaochun({ ...create, allowedOrigins: allowedOrigins?.slice(), container: el });
|
|
615
1049
|
const offs = [];
|
|
616
1050
|
for (const [ev, cbName] of EVENT_MAP) {
|
|
@@ -640,20 +1074,23 @@ function useXiaochun(options = {}) {
|
|
|
640
1074
|
o.lazyMargin,
|
|
641
1075
|
o.placeholder,
|
|
642
1076
|
o.transparent,
|
|
643
|
-
o.width,
|
|
644
|
-
o.height,
|
|
645
1077
|
o.position,
|
|
646
|
-
o.
|
|
647
|
-
o.ui,
|
|
1078
|
+
JSON.stringify(o.ui ?? null),
|
|
648
1079
|
o.heavy,
|
|
649
1080
|
o.controls,
|
|
650
1081
|
o.autoPause,
|
|
651
1082
|
o.passthrough,
|
|
652
1083
|
o.sandbox,
|
|
653
1084
|
o.handshakeTimeout,
|
|
654
|
-
o.
|
|
1085
|
+
o.crossOriginIsolated,
|
|
1086
|
+
o.zIndex,
|
|
1087
|
+
o.allowCustomModel,
|
|
1088
|
+
o.persist,
|
|
1089
|
+
Array.isArray(o.prefetch) ? o.prefetch.join(",") : o.prefetch
|
|
655
1090
|
]);
|
|
656
|
-
const
|
|
1091
|
+
const wantOutfit = o.outfit ?? o.model;
|
|
1092
|
+
const first = (0, import_react.useRef)({ lang: o.lang, outfit: wantOutfit, scene: o.scene, width: o.width, height: o.height });
|
|
1093
|
+
const resizableKey = JSON.stringify(o.resizable ?? false);
|
|
657
1094
|
(0, import_react.useEffect)(() => {
|
|
658
1095
|
if (!client || first.current.lang === o.lang) return;
|
|
659
1096
|
first.current.lang = o.lang;
|
|
@@ -661,11 +1098,32 @@ function useXiaochun(options = {}) {
|
|
|
661
1098
|
});
|
|
662
1099
|
}, [client, o.lang]);
|
|
663
1100
|
(0, import_react.useEffect)(() => {
|
|
664
|
-
if (!client || first.current.
|
|
665
|
-
first.current.
|
|
666
|
-
if (
|
|
1101
|
+
if (!client || first.current.outfit === wantOutfit) return;
|
|
1102
|
+
first.current.outfit = wantOutfit;
|
|
1103
|
+
if (wantOutfit) void (isXcId(wantOutfit) ? client.setOutfit(wantOutfit) : client.setModel(wantOutfit)).catch(() => {
|
|
667
1104
|
});
|
|
668
|
-
}, [client,
|
|
1105
|
+
}, [client, wantOutfit]);
|
|
1106
|
+
(0, import_react.useEffect)(() => {
|
|
1107
|
+
if (!client || first.current.scene === o.scene) return;
|
|
1108
|
+
first.current.scene = o.scene;
|
|
1109
|
+
if (o.scene) void client.setScene(o.scene).catch(() => {
|
|
1110
|
+
});
|
|
1111
|
+
}, [client, o.scene]);
|
|
1112
|
+
(0, import_react.useEffect)(() => {
|
|
1113
|
+
if (!client || first.current.width === o.width && first.current.height === o.height) return;
|
|
1114
|
+
first.current.width = o.width;
|
|
1115
|
+
first.current.height = o.height;
|
|
1116
|
+
client.setSize(o.width ?? 320, o.height ?? 480);
|
|
1117
|
+
}, [client, o.width, o.height]);
|
|
1118
|
+
(0, import_react.useEffect)(() => {
|
|
1119
|
+
client?.setDraggable(o.draggable === true);
|
|
1120
|
+
}, [client, o.draggable]);
|
|
1121
|
+
(0, import_react.useEffect)(() => {
|
|
1122
|
+
client?.setBorderRadius(o.borderRadius);
|
|
1123
|
+
}, [client, o.borderRadius]);
|
|
1124
|
+
(0, import_react.useEffect)(() => {
|
|
1125
|
+
client?.setResizable(o.resizable ?? false);
|
|
1126
|
+
}, [client, resizableKey]);
|
|
669
1127
|
(0, import_react.useEffect)(() => {
|
|
670
1128
|
if (!client || o.paused === void 0) return;
|
|
671
1129
|
if (o.paused) client.pause();
|
|
@@ -705,6 +1163,11 @@ function XiaochunInner(props, ref) {
|
|
|
705
1163
|
motion: (m) => c()?.motion(m) ?? reject(),
|
|
706
1164
|
expression: (n) => c()?.expression(n) ?? reject(),
|
|
707
1165
|
lookAt: (x, y) => c()?.lookAt(x, y) ?? reject(),
|
|
1166
|
+
setOutfit: (id) => c()?.setOutfit(id) ?? reject(),
|
|
1167
|
+
setScene: (id) => c()?.setScene(id) ?? reject(),
|
|
1168
|
+
getOutfits: () => c()?.getOutfits() ?? reject(),
|
|
1169
|
+
getScenes: () => c()?.getScenes() ?? reject(),
|
|
1170
|
+
prefetch: (ids) => c()?.prefetch(ids) ?? reject(),
|
|
708
1171
|
setModel: (m) => c()?.setModel(m) ?? reject(),
|
|
709
1172
|
setConfig: (cfg) => c()?.setConfig(cfg) ?? reject(),
|
|
710
1173
|
startListening: () => c()?.startListening() ?? reject(),
|