@firetable/project-xiaochun 0.1.14 → 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 +117 -19
- package/README.md +117 -19
- package/dist/avatar-element.d.ts +29 -6
- package/dist/chunks/{chunk-HLVD6HQM.js → chunk-HZ2ADKG7.js} +114 -11
- package/dist/chunks/chunk-HZ2ADKG7.js.map +7 -0
- package/dist/chunks/chunk-RHQW4QZH.js +222 -0
- package/dist/chunks/chunk-RHQW4QZH.js.map +7 -0
- package/dist/chunks/chunk-UWL65W7D.js +1083 -0
- package/dist/chunks/chunk-UWL65W7D.js.map +7 -0
- package/dist/client.d.ts +187 -7
- package/dist/element.cjs +738 -80
- 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 +806 -81
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +38 -6
- 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 +133 -2
- package/dist/protocol.cjs.map +2 -2
- package/dist/protocol.d.ts +279 -9
- package/dist/protocol.js +36 -4
- package/dist/react.cjs +689 -86
- package/dist/react.cjs.map +4 -4
- package/dist/react.d.ts +23 -1
- package/dist/react.js +66 -18
- package/dist/react.js.map +2 -2
- package/package.json +5 -3
- package/dist/chunks/chunk-DUU5PTKT.js +0 -91
- package/dist/chunks/chunk-DUU5PTKT.js.map +0 -7
- package/dist/chunks/chunk-HLVD6HQM.js.map +0 -7
- package/dist/chunks/chunk-OLVSOJML.js +0 -579
- package/dist/chunks/chunk-OLVSOJML.js.map +0 -7
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @firetable/project-xiaochun v0.1.
|
|
1
|
+
/*! @firetable/project-xiaochun v0.1.16 | MIT | https://github.com/FireTable/project-xiaochun */
|
|
2
2
|
"use strict";
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -21,23 +21,39 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
|
+
XC_CAMERA_RANGES: () => XC_CAMERA_RANGES,
|
|
24
25
|
XC_DEFAULT_ORIGIN: () => XC_DEFAULT_ORIGIN,
|
|
25
26
|
XC_DEFAULT_SRC: () => XC_DEFAULT_SRC,
|
|
26
27
|
XC_FRAME_TO_HOST: () => XC_FRAME_TO_HOST,
|
|
27
28
|
XC_HOST_TO_FRAME: () => XC_HOST_TO_FRAME,
|
|
29
|
+
XC_ID_RE: () => XC_ID_RE,
|
|
28
30
|
XC_IMPLEMENTED_COMMANDS: () => XC_IMPLEMENTED_COMMANDS,
|
|
31
|
+
XC_LANGS: () => XC_LANGS,
|
|
29
32
|
XC_PROTOCOL_MAPPING: () => XC_PROTOCOL_MAPPING,
|
|
30
33
|
XC_PROTOCOL_VERSION: () => XC_PROTOCOL_VERSION,
|
|
34
|
+
XC_UI_AUTOHIDE_DEFAULT: () => XC_UI_AUTOHIDE_DEFAULT,
|
|
35
|
+
XC_UI_LEGACY_PARTS: () => XC_UI_LEGACY_PARTS,
|
|
36
|
+
XC_UI_PARTS: () => XC_UI_PARTS,
|
|
31
37
|
XC_UNSUPPORTED_COMMANDS: () => XC_UNSUPPORTED_COMMANDS,
|
|
38
|
+
XC_WINDOW_CORNER_RADIUS: () => XC_WINDOW_CORNER_RADIUS,
|
|
32
39
|
XIAOCHUN_ELEMENT_TAG: () => XIAOCHUN_ELEMENT_TAG,
|
|
33
40
|
XIAOCHUN_PROTOCOL_SCHEME: () => XIAOCHUN_PROTOCOL_SCHEME,
|
|
34
41
|
createXiaochun: () => createXiaochun,
|
|
35
42
|
defineXiaochunElement: () => defineXiaochunElement,
|
|
36
43
|
isXcEnvelope: () => isXcEnvelope,
|
|
44
|
+
isXcId: () => isXcId,
|
|
37
45
|
normalizeOrigin: () => normalizeOrigin,
|
|
46
|
+
normalizeXcCamera: () => normalizeXcCamera,
|
|
47
|
+
normalizeXcUiOption: () => normalizeXcUiOption,
|
|
38
48
|
parseProtocolUrl: () => parseProtocolUrl,
|
|
49
|
+
parseXcCameraParams: () => parseXcCameraParams,
|
|
50
|
+
parseXcUiAutoHide: () => parseXcUiAutoHide,
|
|
51
|
+
parseXcUiList: () => parseXcUiList,
|
|
52
|
+
parseXcUiParam: () => parseXcUiParam,
|
|
39
53
|
toProtocolUrl: () => toProtocolUrl,
|
|
40
|
-
|
|
54
|
+
xcHasOwn: () => xcHasOwn,
|
|
55
|
+
xcMessage: () => xcMessage,
|
|
56
|
+
xcUiAutoHideActive: () => xcUiAutoHideActive
|
|
41
57
|
});
|
|
42
58
|
module.exports = __toCommonJS(src_exports);
|
|
43
59
|
|
|
@@ -56,6 +72,9 @@ var XC_HOST_TO_FRAME = [
|
|
|
56
72
|
"xc.lookAt",
|
|
57
73
|
"xc.pointer",
|
|
58
74
|
"xc.setModel",
|
|
75
|
+
"xc.setOutfit",
|
|
76
|
+
"xc.setScene",
|
|
77
|
+
"xc.prefetch",
|
|
59
78
|
"xc.setConfig",
|
|
60
79
|
"xc.mic",
|
|
61
80
|
"xc.pause",
|
|
@@ -66,10 +85,16 @@ var XC_FRAME_TO_HOST = [
|
|
|
66
85
|
"xc.ready",
|
|
67
86
|
"xc.load.progress",
|
|
68
87
|
"xc.loaded",
|
|
88
|
+
"xc.outfit-changed",
|
|
89
|
+
"xc.scene-changed",
|
|
90
|
+
"xc.lang-changed",
|
|
91
|
+
"xc.prefetched",
|
|
69
92
|
"xc.state",
|
|
70
93
|
"xc.stt",
|
|
71
94
|
"xc.utterance",
|
|
72
95
|
"xc.hit-region",
|
|
96
|
+
"xc.gesture-move",
|
|
97
|
+
"xc.gesture-resize",
|
|
73
98
|
"xc.error"
|
|
74
99
|
];
|
|
75
100
|
var XC_IMPLEMENTED_COMMANDS = [
|
|
@@ -81,6 +106,9 @@ var XC_IMPLEMENTED_COMMANDS = [
|
|
|
81
106
|
"xc.expression",
|
|
82
107
|
"xc.pointer",
|
|
83
108
|
"xc.setModel",
|
|
109
|
+
"xc.setOutfit",
|
|
110
|
+
"xc.setScene",
|
|
111
|
+
"xc.prefetch",
|
|
84
112
|
"xc.setConfig",
|
|
85
113
|
"xc.mic",
|
|
86
114
|
"xc.pause",
|
|
@@ -95,6 +123,109 @@ var XC_PROTOCOL_MAPPING = [
|
|
|
95
123
|
{ xc: "xc.audio", note: "source \u4E3A ArrayBuffer / Blob", action: "audio", url: null },
|
|
96
124
|
{ xc: "xc.audio.chunk / xc.audio.end", note: "\u6D41\u5F0F PCM", action: "audio", url: null }
|
|
97
125
|
];
|
|
126
|
+
var XC_WINDOW_CORNER_RADIUS = 20;
|
|
127
|
+
var XC_UI_PARTS = ["chat", "bubble", "outfit", "scene", "lang", "github"];
|
|
128
|
+
var XC_UI_PART_SET = new Set(XC_UI_PARTS);
|
|
129
|
+
var XC_UI_LEGACY_PARTS = ["chat", "bubble"];
|
|
130
|
+
function parseXcUiList(items) {
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
const unknown = [];
|
|
133
|
+
for (const it of items) {
|
|
134
|
+
const name = typeof it === "string" ? it.trim().toLowerCase() : "";
|
|
135
|
+
if (XC_UI_PART_SET.has(name)) seen.add(name);
|
|
136
|
+
else unknown.push(typeof it === "string" ? it.slice(0, 40) : String(typeof it));
|
|
137
|
+
}
|
|
138
|
+
return { parts: XC_UI_PARTS.filter((p) => seen.has(p)), unknown, legacy: false };
|
|
139
|
+
}
|
|
140
|
+
function parseXcUiParam(raw) {
|
|
141
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
142
|
+
if (v === "" || v === "0" || v === "false") return { parts: [], unknown: [], legacy: false };
|
|
143
|
+
if (v === "1" || v === "true") return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };
|
|
144
|
+
return parseXcUiList(v.split(",").filter((x) => x.trim() !== ""));
|
|
145
|
+
}
|
|
146
|
+
function normalizeXcUiOption(v) {
|
|
147
|
+
if (v === true) return { parts: [...XC_UI_LEGACY_PARTS], unknown: [], legacy: true };
|
|
148
|
+
if (Array.isArray(v)) return parseXcUiList(v);
|
|
149
|
+
return { parts: [], unknown: [], legacy: false };
|
|
150
|
+
}
|
|
151
|
+
var XC_UI_AUTOHIDE_DEFAULT = "transparent";
|
|
152
|
+
function parseXcUiAutoHide(raw) {
|
|
153
|
+
if (raw === true || raw === false || raw === "transparent") return raw;
|
|
154
|
+
if (typeof raw !== "string") return void 0;
|
|
155
|
+
const v = raw.trim().toLowerCase();
|
|
156
|
+
if (v === "1" || v === "true") return true;
|
|
157
|
+
if (v === "0" || v === "false") return false;
|
|
158
|
+
if (v === "transparent") return "transparent";
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function xcUiAutoHideActive(mode, sceneTransparent) {
|
|
162
|
+
return mode === "transparent" ? sceneTransparent : mode;
|
|
163
|
+
}
|
|
164
|
+
var XC_CAMERA_RANGES = {
|
|
165
|
+
fov: [15, 60],
|
|
166
|
+
distance: [1, 15],
|
|
167
|
+
height: [-1, 1]
|
|
168
|
+
};
|
|
169
|
+
var XC_CAMERA_NUM_KEYS = ["fov", "distance", "height"];
|
|
170
|
+
var XC_CAMERA_KEYS = ["fov", "distance", "height", "intro"];
|
|
171
|
+
function normalizeXcCamera(raw) {
|
|
172
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { ok: false, error: "camera must be an object" };
|
|
173
|
+
const src = raw;
|
|
174
|
+
for (const k of Object.keys(src)) {
|
|
175
|
+
if (!XC_CAMERA_KEYS.includes(k)) return { ok: false, error: `unknown camera key: ${k} (expected ${XC_CAMERA_KEYS.join(" | ")})` };
|
|
176
|
+
}
|
|
177
|
+
const out = {};
|
|
178
|
+
const clamped = [];
|
|
179
|
+
for (const k of XC_CAMERA_NUM_KEYS) {
|
|
180
|
+
const v = src[k];
|
|
181
|
+
if (v === void 0) continue;
|
|
182
|
+
if (v === null) {
|
|
183
|
+
out[k] = null;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return { ok: false, error: `camera.${k} must be a finite number or null` };
|
|
187
|
+
const [lo, hi] = XC_CAMERA_RANGES[k];
|
|
188
|
+
const c = Math.min(hi, Math.max(lo, v));
|
|
189
|
+
if (c !== v) clamped.push(k);
|
|
190
|
+
out[k] = c;
|
|
191
|
+
}
|
|
192
|
+
if (src.intro !== void 0) {
|
|
193
|
+
if (src.intro !== null && typeof src.intro !== "boolean") return { ok: false, error: "camera.intro must be a boolean or null" };
|
|
194
|
+
out.intro = src.intro;
|
|
195
|
+
}
|
|
196
|
+
return { ok: true, camera: out, clamped };
|
|
197
|
+
}
|
|
198
|
+
function parseXcCameraParams(get) {
|
|
199
|
+
const camera = {};
|
|
200
|
+
const warnings = [];
|
|
201
|
+
const names = { fov: "cameraFov", distance: "cameraDistance", height: "cameraHeight" };
|
|
202
|
+
for (const k of XC_CAMERA_NUM_KEYS) {
|
|
203
|
+
const raw = get(names[k]);
|
|
204
|
+
if (raw === null) continue;
|
|
205
|
+
const t = raw.trim();
|
|
206
|
+
const n = t === "" ? NaN : Number(t);
|
|
207
|
+
if (!Number.isFinite(n)) {
|
|
208
|
+
warnings.push(`ignoring invalid ?${names[k]}=${raw}`);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const [lo, hi] = XC_CAMERA_RANGES[k];
|
|
212
|
+
const c = Math.min(hi, Math.max(lo, n));
|
|
213
|
+
if (c !== n) warnings.push(`?${names[k]}=${raw} is out of range [${lo}, ${hi}], clamped to ${c}`);
|
|
214
|
+
camera[k] = c;
|
|
215
|
+
}
|
|
216
|
+
const intro = get("cameraIntro");
|
|
217
|
+
if (intro !== null) {
|
|
218
|
+
const v = intro.trim().toLowerCase();
|
|
219
|
+
if (v === "1" || v === "true") camera.intro = true;
|
|
220
|
+
else if (v === "0" || v === "false") camera.intro = false;
|
|
221
|
+
else warnings.push(`ignoring invalid ?cameraIntro=${intro}`);
|
|
222
|
+
}
|
|
223
|
+
return { camera, warnings };
|
|
224
|
+
}
|
|
225
|
+
var XC_ID_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
226
|
+
var isXcId = (v) => typeof v === "string" && XC_ID_RE.test(v);
|
|
227
|
+
var xcHasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
228
|
+
var XC_LANGS = ["zh-CN", "en", "ja"];
|
|
98
229
|
function xcMessage(type, payload, id) {
|
|
99
230
|
const m = { type, v: XC_PROTOCOL_VERSION, payload };
|
|
100
231
|
if (id !== void 0) m.id = id;
|
|
@@ -116,11 +247,113 @@ function normalizeOrigin(input) {
|
|
|
116
247
|
}
|
|
117
248
|
}
|
|
118
249
|
|
|
250
|
+
// src/gesture-box.ts
|
|
251
|
+
var DEFAULT_RESIZE_LIMITS = Object.freeze({
|
|
252
|
+
minWidth: 120,
|
|
253
|
+
minHeight: 180,
|
|
254
|
+
maxWidth: Infinity,
|
|
255
|
+
maxHeight: Infinity
|
|
256
|
+
});
|
|
257
|
+
var finitePositive = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;
|
|
258
|
+
function normalizeResizable(v) {
|
|
259
|
+
if (!v) return null;
|
|
260
|
+
const o = v === true ? {} : v;
|
|
261
|
+
const minWidth = finitePositive(o.minWidth) ? o.minWidth : DEFAULT_RESIZE_LIMITS.minWidth;
|
|
262
|
+
const minHeight = finitePositive(o.minHeight) ? o.minHeight : DEFAULT_RESIZE_LIMITS.minHeight;
|
|
263
|
+
const maxWidth = finitePositive(o.maxWidth) ? Math.max(o.maxWidth, minWidth) : Infinity;
|
|
264
|
+
const maxHeight = finitePositive(o.maxHeight) ? Math.max(o.maxHeight, minHeight) : Infinity;
|
|
265
|
+
return { minWidth, minHeight, maxWidth, maxHeight };
|
|
266
|
+
}
|
|
267
|
+
var clamp = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
|
|
268
|
+
function moveBox(start, totalDx, totalDy, vp) {
|
|
269
|
+
return {
|
|
270
|
+
left: clamp(start.left + totalDx, 0, vp.width - start.width),
|
|
271
|
+
top: clamp(start.top + totalDy, 0, vp.height - start.height),
|
|
272
|
+
width: start.width,
|
|
273
|
+
height: start.height
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function resizeBox(start, corner, totalDx, totalDy, limits, vp) {
|
|
277
|
+
const west = corner === "NW" || corner === "SW";
|
|
278
|
+
const north = corner === "NW" || corner === "NE";
|
|
279
|
+
const right = start.left + start.width, bottom = start.top + start.height;
|
|
280
|
+
const wantW = west ? start.width - totalDx : start.width + totalDx;
|
|
281
|
+
const wantH = north ? start.height - totalDy : start.height + totalDy;
|
|
282
|
+
const roomW = west ? right : vp.width - start.left;
|
|
283
|
+
const roomH = north ? bottom : vp.height - start.top;
|
|
284
|
+
const width = clamp(wantW, limits.minWidth, Math.min(limits.maxWidth, roomW));
|
|
285
|
+
const height = clamp(wantH, limits.minHeight, Math.min(limits.maxHeight, roomH));
|
|
286
|
+
return { left: west ? right - width : start.left, top: north ? bottom - height : start.top, width, height };
|
|
287
|
+
}
|
|
288
|
+
function fitBox(b, vp) {
|
|
289
|
+
const width = Math.min(b.width, Math.max(1, vp.width)), height = Math.min(b.height, Math.max(1, vp.height));
|
|
290
|
+
return { left: clamp(b.left, 0, vp.width - width), top: clamp(b.top, 0, vp.height - height), width, height };
|
|
291
|
+
}
|
|
292
|
+
var CORNERS = /* @__PURE__ */ new Set(["NW", "NE", "SW", "SE"]);
|
|
293
|
+
var PHASES = /* @__PURE__ */ new Set(["start", "move", "end"]);
|
|
294
|
+
var REASONS = /* @__PURE__ */ new Set(["up", "cancel", "blur"]);
|
|
295
|
+
var MAX_GESTURE_DELTA = 2e4;
|
|
296
|
+
var delta = (v) => typeof v === "number" && Number.isFinite(v) && Math.abs(v) <= MAX_GESTURE_DELTA;
|
|
297
|
+
var counter = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0;
|
|
298
|
+
function parseGesturePayload(kind, raw) {
|
|
299
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
300
|
+
const p = raw;
|
|
301
|
+
if (!counter(p.gesture) || !counter(p.seq) || typeof p.phase !== "string" || !PHASES.has(p.phase)) return null;
|
|
302
|
+
if (!delta(p.dx) || !delta(p.dy) || !delta(p.totalDx) || !delta(p.totalDy)) return null;
|
|
303
|
+
if (p.reason !== void 0 && (typeof p.reason !== "string" || !REASONS.has(p.reason))) return null;
|
|
304
|
+
const out = { gesture: p.gesture, seq: p.seq, phase: p.phase, totalDx: p.totalDx, totalDy: p.totalDy };
|
|
305
|
+
if (kind === "resize") {
|
|
306
|
+
if (typeof p.corner !== "string" || !CORNERS.has(p.corner)) return null;
|
|
307
|
+
out.corner = p.corner;
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
var GestureGate = class {
|
|
312
|
+
constructor() {
|
|
313
|
+
this.lastGesture = 0;
|
|
314
|
+
this.lastSeq = -1;
|
|
315
|
+
this.open = false;
|
|
316
|
+
}
|
|
317
|
+
get active() {
|
|
318
|
+
return this.open;
|
|
319
|
+
}
|
|
320
|
+
accept(p) {
|
|
321
|
+
if (p.phase === "start") {
|
|
322
|
+
if (p.gesture <= this.lastGesture || p.seq !== 0) return false;
|
|
323
|
+
this.lastGesture = p.gesture;
|
|
324
|
+
this.lastSeq = 0;
|
|
325
|
+
this.open = true;
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
if (!this.open || p.gesture !== this.lastGesture || p.seq <= this.lastSeq) return false;
|
|
329
|
+
this.lastSeq = p.seq;
|
|
330
|
+
if (p.phase === "end") this.open = false;
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
/** 宿主主动结束 (关闭手势 / 销毁): 之后同一手势的迟到消息被拒绝。 */
|
|
334
|
+
close() {
|
|
335
|
+
this.open = false;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
119
339
|
// src/client.ts
|
|
120
340
|
var FALLBACK_PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
|
|
121
341
|
'<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>'
|
|
122
342
|
);
|
|
343
|
+
var noSelect = (st) => {
|
|
344
|
+
st.setProperty("user-select", "none");
|
|
345
|
+
st.setProperty("-webkit-user-select", "none");
|
|
346
|
+
st.setProperty("-webkit-touch-callout", "none");
|
|
347
|
+
st.setProperty("-webkit-tap-highlight-color", "transparent");
|
|
348
|
+
};
|
|
123
349
|
var css = (v, d) => v === void 0 ? d : typeof v === "number" ? `${v}px` : v;
|
|
350
|
+
var XC_DEFAULT_WIDTH = 600;
|
|
351
|
+
var XC_DEFAULT_HEIGHT = 1080;
|
|
352
|
+
var defaultWidthCss = (floating = false) => floating ? `min(${XC_DEFAULT_WIDTH}px, calc(100vw - 2 * var(--xc-offset-x, 16px)))` : `min(${XC_DEFAULT_WIDTH}px, 100vw)`;
|
|
353
|
+
var defaultHeightCss = (floating = false) => {
|
|
354
|
+
const unit = typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports("height", "1svh") ? "svh" : "vh";
|
|
355
|
+
return floating ? `min(${XC_DEFAULT_HEIGHT}px, calc(100${unit} - 2 * var(--xc-offset-y, 16px)))` : `min(${XC_DEFAULT_HEIGHT}px, 100${unit})`;
|
|
356
|
+
};
|
|
124
357
|
function createXiaochun(options) {
|
|
125
358
|
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
126
359
|
throw new Error("[project-xiaochun] createXiaochun must run in a browser");
|
|
@@ -142,8 +375,56 @@ function createXiaochun(options) {
|
|
|
142
375
|
if (!n) throw new Error(`[project-xiaochun] invalid allowedOrigins entry: ${o} (wildcards are not allowed)`);
|
|
143
376
|
trusted.add(n);
|
|
144
377
|
}
|
|
145
|
-
const
|
|
146
|
-
const
|
|
378
|
+
const prefsKey = options.persist === "host" ? "xiaochun:prefs" : typeof options.persist === "string" && options.persist ? options.persist : null;
|
|
379
|
+
const readPrefs = () => {
|
|
380
|
+
if (!prefsKey) return {};
|
|
381
|
+
try {
|
|
382
|
+
const raw = JSON.parse(window.localStorage.getItem(prefsKey) ?? "null");
|
|
383
|
+
const out = {};
|
|
384
|
+
if (raw && typeof raw === "object") {
|
|
385
|
+
if (isXcId(raw.outfit)) out.outfit = raw.outfit;
|
|
386
|
+
if (isXcId(raw.scene)) out.scene = raw.scene;
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
} catch {
|
|
390
|
+
return {};
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const writePrefs = (patch) => {
|
|
394
|
+
if (!prefsKey) return;
|
|
395
|
+
try {
|
|
396
|
+
const next = { ...readPrefs() };
|
|
397
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
398
|
+
if (v) next[k] = v;
|
|
399
|
+
else delete next[k];
|
|
400
|
+
}
|
|
401
|
+
window.localStorage.setItem(prefsKey, JSON.stringify(next));
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const prefs = readPrefs();
|
|
406
|
+
if (options.model !== void 0 && options.outfit === void 0) {
|
|
407
|
+
console.warn('[project-xiaochun] option "model" is deprecated, use "outfit" (built-in outfit id).');
|
|
408
|
+
}
|
|
409
|
+
const uiParse = normalizeXcUiOption(options.ui);
|
|
410
|
+
const uiParts = uiParse.parts;
|
|
411
|
+
const uiAutoHide = parseXcUiAutoHide(options.uiAutoHide);
|
|
412
|
+
if (options.uiAutoHide !== void 0 && uiAutoHide === void 0) console.warn(`[project-xiaochun] ignoring invalid uiAutoHide: ${String(options.uiAutoHide)} (use true | false | 'transparent')`);
|
|
413
|
+
let camera = {};
|
|
414
|
+
if (options.camera !== void 0) {
|
|
415
|
+
const r = normalizeXcCamera(options.camera);
|
|
416
|
+
if (r.ok) {
|
|
417
|
+
camera = r.camera;
|
|
418
|
+
if (r.clamped.length) console.warn(`[project-xiaochun] camera.${r.clamped.join(", camera.")} out of range, clamped (fov ${XC_CAMERA_RANGES.fov.join("\u2013")}, distance ${XC_CAMERA_RANGES.distance.join("\u2013")}, height ${XC_CAMERA_RANGES.height.join("\u2013")})`);
|
|
419
|
+
} else console.warn(`[project-xiaochun] ignoring invalid camera option: ${r.error}`);
|
|
420
|
+
}
|
|
421
|
+
if (uiParse.legacy) console.warn(`[project-xiaochun] option "ui: true" is deprecated, use an array of parts, e.g. ui: ['chat', 'bubble', 'outfit', 'scene'].`);
|
|
422
|
+
if (uiParse.unknown.length) console.warn(`[project-xiaochun] ignoring unknown ui part(s): ${uiParse.unknown.join(", ")} (known: ${XC_UI_PARTS.join(", ")})`);
|
|
423
|
+
const outfitOpt = options.outfit ?? (isXcId(options.model) ? options.model : void 0);
|
|
424
|
+
const initialOutfit = outfitOpt ?? prefs.outfit;
|
|
425
|
+
const initialScene = options.scene ?? prefs.scene ?? (options.transparent ? "transparent" : void 0);
|
|
426
|
+
let transparent = initialScene === "transparent" || initialScene !== "light" && initialScene !== "dark" && (options.transparent ?? false);
|
|
427
|
+
let passthrough = options.passthrough ?? transparent;
|
|
147
428
|
const lazy = options.lazy ?? true;
|
|
148
429
|
const position = options.position ?? "inline";
|
|
149
430
|
const handshakeTimeout = options.handshakeTimeout ?? 2e4;
|
|
@@ -151,14 +432,26 @@ function createXiaochun(options) {
|
|
|
151
432
|
wrapper.setAttribute("data-xiaochun", "");
|
|
152
433
|
const ws = wrapper.style;
|
|
153
434
|
ws.position = position === "inline" ? "relative" : "fixed";
|
|
154
|
-
ws.width = css(options.width, "
|
|
155
|
-
ws.height = css(options.height, "
|
|
435
|
+
ws.width = css(options.width, defaultWidthCss(position !== "inline"));
|
|
436
|
+
ws.height = css(options.height, defaultHeightCss(position !== "inline"));
|
|
156
437
|
ws.maxWidth = "100%";
|
|
157
438
|
ws.overflow = "visible";
|
|
158
439
|
ws.contain = "layout style";
|
|
159
|
-
ws
|
|
440
|
+
noSelect(ws);
|
|
160
441
|
ws.boxShadow = "var(--xc-shadow, none)";
|
|
161
|
-
|
|
442
|
+
const applyWrapperBg = () => {
|
|
443
|
+
ws.background = transparent ? "" : "var(--xc-bg, transparent)";
|
|
444
|
+
};
|
|
445
|
+
applyWrapperBg();
|
|
446
|
+
let radiusOpt = options.borderRadius;
|
|
447
|
+
const radiusValue = () => radiusOpt !== void 0 ? css(radiusOpt, "0px") : transparent ? "0px" : `${XC_WINDOW_CORNER_RADIUS}px`;
|
|
448
|
+
const applyRadius = () => {
|
|
449
|
+
const v = `var(--xc-radius, ${radiusValue()})`;
|
|
450
|
+
ws.borderRadius = v;
|
|
451
|
+
if (placeholderEl) placeholderEl.style.borderRadius = v;
|
|
452
|
+
if (iframe) iframe.style.borderRadius = v;
|
|
453
|
+
};
|
|
454
|
+
ws.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
162
455
|
if (position === "bottom-right") {
|
|
163
456
|
ws.right = "var(--xc-offset-x, 16px)";
|
|
164
457
|
ws.bottom = "var(--xc-offset-y, 16px)";
|
|
@@ -179,13 +472,13 @@ function createXiaochun(options) {
|
|
|
179
472
|
img.src = options.placeholder || FALLBACK_PLACEHOLDER;
|
|
180
473
|
img.alt = "Project XiaoChun";
|
|
181
474
|
img.decoding = "async";
|
|
182
|
-
img.width =
|
|
183
|
-
img.height =
|
|
475
|
+
img.width = XC_DEFAULT_WIDTH;
|
|
476
|
+
img.height = XC_DEFAULT_HEIGHT;
|
|
184
477
|
placeholderEl = img;
|
|
185
478
|
}
|
|
186
479
|
const ps = placeholderEl.style;
|
|
187
480
|
placeholderEl.setAttribute("part", "placeholder");
|
|
188
|
-
ps.borderRadius =
|
|
481
|
+
ps.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
189
482
|
ps.position = "absolute";
|
|
190
483
|
ps.inset = "0";
|
|
191
484
|
ps.width = "100%";
|
|
@@ -194,6 +487,7 @@ function createXiaochun(options) {
|
|
|
194
487
|
ps.transition = "opacity .25s ease";
|
|
195
488
|
ps.pointerEvents = "auto";
|
|
196
489
|
ps.cursor = lazy === false ? "default" : "pointer";
|
|
490
|
+
noSelect(ps);
|
|
197
491
|
placeholderEl.addEventListener("click", () => activate());
|
|
198
492
|
wrapper.appendChild(placeholderEl);
|
|
199
493
|
}
|
|
@@ -222,25 +516,45 @@ function createXiaochun(options) {
|
|
|
222
516
|
let autoPaused = false;
|
|
223
517
|
let loaded = false;
|
|
224
518
|
let lastHit = false;
|
|
519
|
+
let caps = null;
|
|
520
|
+
let currentOutfit = null;
|
|
521
|
+
let currentScene = null;
|
|
225
522
|
const queue = [];
|
|
226
523
|
const pending = /* @__PURE__ */ new Map();
|
|
227
524
|
let idSeq = 0;
|
|
228
525
|
let handshakeTimer = null;
|
|
229
526
|
let io = null;
|
|
230
527
|
let idleHandle = null;
|
|
231
|
-
|
|
528
|
+
let resolveHandshake;
|
|
529
|
+
const handshaked = new Promise((res) => {
|
|
530
|
+
resolveHandshake = res;
|
|
531
|
+
});
|
|
532
|
+
const failLocal = (type, id, code, message) => {
|
|
533
|
+
emit("error", { code, message, command: type });
|
|
534
|
+
const p = id ? pending.get(id) : void 0;
|
|
535
|
+
if (p && id) {
|
|
536
|
+
pending.delete(id);
|
|
537
|
+
p.reject(new Error(`[${code}] ${message}`));
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
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)`);
|
|
541
|
+
const postToFrame = (type, payload, id, transfer, requires) => {
|
|
232
542
|
if (!port) {
|
|
233
|
-
queue.push({ type, payload, id, transfer });
|
|
543
|
+
queue.push({ type, payload, id, transfer, requires });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (requires && !caps?.[requires]) {
|
|
547
|
+
unsupportedByFrame(type, id);
|
|
234
548
|
return;
|
|
235
549
|
}
|
|
236
550
|
port.postMessage(xcMessage(type, payload, id), transfer ?? []);
|
|
237
551
|
};
|
|
238
|
-
const send = (type, payload, wait = false, transfer, fixedId) => {
|
|
552
|
+
const send = (type, payload, wait = false, transfer, fixedId, requires) => {
|
|
239
553
|
if (destroyed) return Promise.reject(new Error("[project-xiaochun] instance destroyed"));
|
|
240
554
|
if (!iframe) activate();
|
|
241
555
|
const id = fixedId ?? `c${++idSeq}`;
|
|
242
556
|
const p = wait ? new Promise((resolve, reject) => pending.set(id, { resolve, reject, sawEnd: false })) : Promise.resolve();
|
|
243
|
-
postToFrame(type, payload, id, transfer);
|
|
557
|
+
postToFrame(type, payload, id, transfer, requires);
|
|
244
558
|
return p;
|
|
245
559
|
};
|
|
246
560
|
const toBuffer = (d) => d instanceof ArrayBuffer ? d : d.buffer.slice(d.byteOffset, d.byteOffset + d.byteLength);
|
|
@@ -290,7 +604,7 @@ function createXiaochun(options) {
|
|
|
290
604
|
let ended = false;
|
|
291
605
|
let fmt = rest.format === "pcm16" || rest.format === "float32" ? rest.format : null;
|
|
292
606
|
let done = Promise.resolve();
|
|
293
|
-
const
|
|
607
|
+
const handle = {
|
|
294
608
|
write(data) {
|
|
295
609
|
if (ended) throw new Error("[project-xiaochun] audio stream already ended");
|
|
296
610
|
if (destroyed) throw new Error("[project-xiaochun] instance destroyed");
|
|
@@ -324,17 +638,23 @@ function createXiaochun(options) {
|
|
|
324
638
|
return done;
|
|
325
639
|
}
|
|
326
640
|
};
|
|
327
|
-
return
|
|
641
|
+
return handle;
|
|
328
642
|
};
|
|
329
643
|
function buildUrl() {
|
|
330
644
|
const u = new URL(srcUrl.href);
|
|
331
645
|
u.searchParams.set("host", window.location.origin);
|
|
332
646
|
if (transparent) u.searchParams.set("transparent", "1");
|
|
333
|
-
if (
|
|
647
|
+
if (uiParts.length) u.searchParams.set("ui", uiParts.join(","));
|
|
334
648
|
if (options.lang) u.searchParams.set("lang", options.lang);
|
|
649
|
+
if (uiAutoHide !== void 0) u.searchParams.set("uiAutoHide", String(uiAutoHide));
|
|
650
|
+
if (typeof camera.fov === "number") u.searchParams.set("cameraFov", String(camera.fov));
|
|
651
|
+
if (typeof camera.distance === "number") u.searchParams.set("cameraDistance", String(camera.distance));
|
|
652
|
+
if (typeof camera.height === "number") u.searchParams.set("cameraHeight", String(camera.height));
|
|
653
|
+
if (typeof camera.intro === "boolean") u.searchParams.set("cameraIntro", camera.intro ? "1" : "0");
|
|
335
654
|
if (options.heavy) u.searchParams.set("heavy", options.heavy);
|
|
336
|
-
if (options.controls) u.searchParams.set("controls", "
|
|
337
|
-
if (
|
|
655
|
+
if (options.controls === false) u.searchParams.set("controls", "0");
|
|
656
|
+
if (initialScene && isXcId(initialScene)) u.searchParams.set("scene", initialScene);
|
|
657
|
+
if (initialOutfit) u.searchParams.set("outfit", initialOutfit);
|
|
338
658
|
return u.href;
|
|
339
659
|
}
|
|
340
660
|
function activate() {
|
|
@@ -360,8 +680,9 @@ function createXiaochun(options) {
|
|
|
360
680
|
s.border = "0";
|
|
361
681
|
s.background = "transparent";
|
|
362
682
|
s.colorScheme = "normal";
|
|
363
|
-
s.borderRadius =
|
|
683
|
+
s.borderRadius = `var(--xc-radius, ${radiusValue()})`;
|
|
364
684
|
f.setAttribute("part", "iframe");
|
|
685
|
+
noSelect(s);
|
|
365
686
|
s.opacity = "0";
|
|
366
687
|
s.transition = "opacity .25s ease";
|
|
367
688
|
s.pointerEvents = passthrough ? "none" : "auto";
|
|
@@ -373,7 +694,7 @@ function createXiaochun(options) {
|
|
|
373
694
|
if (!port) emit("error", { code: "timeout", message: `no xc.ready from ${frameOrigin} within ${handshakeTimeout}ms` });
|
|
374
695
|
}, handshakeTimeout);
|
|
375
696
|
setupAutoPause();
|
|
376
|
-
|
|
697
|
+
if (passthrough) enablePassthrough();
|
|
377
698
|
}
|
|
378
699
|
const onWindowMessage = (ev) => {
|
|
379
700
|
if (destroyed || !iframe || ev.source !== iframe.contentWindow) return;
|
|
@@ -386,20 +707,39 @@ function createXiaochun(options) {
|
|
|
386
707
|
const ch = new MessageChannel();
|
|
387
708
|
port = ch.port1;
|
|
388
709
|
port.onmessage = onPortMessage;
|
|
710
|
+
caps = data.payload?.capabilities ?? null;
|
|
711
|
+
const gWant = wantGestures();
|
|
712
|
+
sentGestures = gWant;
|
|
713
|
+
const initConfig = {
|
|
714
|
+
...options.allowCustomModel ? { allowCustomModel: true } : {},
|
|
715
|
+
...gWant.move || gWant.resize ? { gestures: gWant } : {}
|
|
716
|
+
};
|
|
389
717
|
iframe.contentWindow.postMessage(
|
|
390
|
-
xcMessage("xc.init", { hostOrigin: window.location.origin }),
|
|
718
|
+
xcMessage("xc.init", { hostOrigin: window.location.origin, ...Object.keys(initConfig).length ? { config: initConfig } : {} }),
|
|
391
719
|
ev.origin,
|
|
392
720
|
[ch.port2]
|
|
393
721
|
);
|
|
722
|
+
warnGestureUnsupported();
|
|
394
723
|
if (handshakeTimer) {
|
|
395
724
|
clearTimeout(handshakeTimer);
|
|
396
725
|
handshakeTimer = null;
|
|
397
726
|
}
|
|
398
727
|
emit("handshake", data.payload);
|
|
399
|
-
|
|
728
|
+
resolveHandshake();
|
|
729
|
+
for (const m of queue.splice(0)) {
|
|
730
|
+
if (m.requires && !caps?.[m.requires]) unsupportedByFrame(m.type, m.id);
|
|
731
|
+
else port.postMessage(xcMessage(m.type, m.payload, m.id), m.transfer ?? []);
|
|
732
|
+
}
|
|
400
733
|
syncPause();
|
|
401
734
|
};
|
|
402
735
|
window.addEventListener("message", onWindowMessage);
|
|
736
|
+
function ack(id, value) {
|
|
737
|
+
const p = id ? pending.get(id) : void 0;
|
|
738
|
+
if (p && id) {
|
|
739
|
+
pending.delete(id);
|
|
740
|
+
p.resolve(value);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
403
743
|
function onPortMessage(ev) {
|
|
404
744
|
const data = ev.data;
|
|
405
745
|
if (!isXcEnvelope(data)) return;
|
|
@@ -410,6 +750,11 @@ function createXiaochun(options) {
|
|
|
410
750
|
case "xc.loaded":
|
|
411
751
|
if (!loaded) {
|
|
412
752
|
loaded = true;
|
|
753
|
+
if (options.prefetch && options.heavy === "eager" && caps?.prefetch) {
|
|
754
|
+
const ids = Array.isArray(options.prefetch) ? options.prefetch : void 0;
|
|
755
|
+
void api.prefetch(ids).catch(() => {
|
|
756
|
+
});
|
|
757
|
+
}
|
|
413
758
|
if (iframe) iframe.style.opacity = "1";
|
|
414
759
|
if (placeholderEl) {
|
|
415
760
|
const el = placeholderEl;
|
|
@@ -422,6 +767,33 @@ function createXiaochun(options) {
|
|
|
422
767
|
}
|
|
423
768
|
emit("ready", data.payload);
|
|
424
769
|
break;
|
|
770
|
+
case "xc.outfit-changed": {
|
|
771
|
+
const pl = data.payload;
|
|
772
|
+
if (!pl.noop) {
|
|
773
|
+
currentOutfit = pl.id;
|
|
774
|
+
emit("outfit-changed", pl);
|
|
775
|
+
if (!pl.initial && pl.id) writePrefs({ outfit: pl.id });
|
|
776
|
+
}
|
|
777
|
+
ack(data.id);
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case "xc.scene-changed": {
|
|
781
|
+
const pl = data.payload;
|
|
782
|
+
currentScene = pl.id;
|
|
783
|
+
if (pl.transparent !== transparent) applyTransparency(pl.transparent);
|
|
784
|
+
if (!pl.noop) {
|
|
785
|
+
emit("scene-changed", pl);
|
|
786
|
+
if (!pl.initial) writePrefs({ scene: pl.id });
|
|
787
|
+
}
|
|
788
|
+
ack(data.id);
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
case "xc.lang-changed":
|
|
792
|
+
emit("lang-changed", data.payload);
|
|
793
|
+
break;
|
|
794
|
+
case "xc.prefetched":
|
|
795
|
+
ack(data.id, data.payload);
|
|
796
|
+
break;
|
|
425
797
|
case "xc.state":
|
|
426
798
|
emit("state", data.payload);
|
|
427
799
|
break;
|
|
@@ -446,9 +818,16 @@ function createXiaochun(options) {
|
|
|
446
818
|
}
|
|
447
819
|
break;
|
|
448
820
|
}
|
|
821
|
+
case "xc.gesture-move":
|
|
822
|
+
onGesture("move", data.payload);
|
|
823
|
+
break;
|
|
824
|
+
case "xc.gesture-resize":
|
|
825
|
+
onGesture("resize", data.payload);
|
|
826
|
+
break;
|
|
449
827
|
case "xc.error": {
|
|
450
828
|
const { code, message, command } = data.payload;
|
|
451
829
|
emit("error", { code, message, command });
|
|
830
|
+
if (code === "unknown_id" && (command === "outfit" || command === "scene")) writePrefs({ [command]: null });
|
|
452
831
|
const p = data.id ? pending.get(data.id) : void 0;
|
|
453
832
|
if (p && data.id) {
|
|
454
833
|
pending.delete(data.id);
|
|
@@ -505,16 +884,46 @@ function createXiaochun(options) {
|
|
|
505
884
|
port.postMessage(xcMessage("xc.pointer", { x, y }));
|
|
506
885
|
});
|
|
507
886
|
};
|
|
508
|
-
|
|
509
|
-
|
|
887
|
+
let passthroughActive = false;
|
|
888
|
+
function enablePassthrough() {
|
|
889
|
+
if (passthroughActive || !iframe) return;
|
|
890
|
+
passthroughActive = true;
|
|
510
891
|
window.addEventListener("pointermove", onHostPointer, { passive: true });
|
|
892
|
+
window.addEventListener("pointerdown", onHostPointer, { passive: true });
|
|
511
893
|
window.addEventListener("scroll", invalidateRect, { passive: true, capture: true });
|
|
512
894
|
window.addEventListener("resize", invalidateRect, { passive: true });
|
|
513
|
-
if (typeof ResizeObserver !== "undefined"
|
|
895
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
514
896
|
rectRo = new ResizeObserver(invalidateRect);
|
|
515
897
|
rectRo.observe(iframe);
|
|
516
898
|
}
|
|
517
899
|
}
|
|
900
|
+
function disablePassthrough() {
|
|
901
|
+
if (!passthroughActive) return;
|
|
902
|
+
passthroughActive = false;
|
|
903
|
+
window.removeEventListener("pointermove", onHostPointer);
|
|
904
|
+
window.removeEventListener("pointerdown", onHostPointer);
|
|
905
|
+
window.removeEventListener("scroll", invalidateRect, true);
|
|
906
|
+
window.removeEventListener("resize", invalidateRect);
|
|
907
|
+
rectRo?.disconnect();
|
|
908
|
+
rectRo = null;
|
|
909
|
+
if (pointerRaf) {
|
|
910
|
+
cancelAnimationFrame(pointerRaf);
|
|
911
|
+
pointerRaf = 0;
|
|
912
|
+
}
|
|
913
|
+
lastPtr = null;
|
|
914
|
+
}
|
|
915
|
+
function applyTransparency(next) {
|
|
916
|
+
transparent = next;
|
|
917
|
+
applyWrapperBg();
|
|
918
|
+
applyRadius();
|
|
919
|
+
passthrough = next ? options.passthrough ?? true : false;
|
|
920
|
+
lastHit = false;
|
|
921
|
+
lastPosted = null;
|
|
922
|
+
rectCache = null;
|
|
923
|
+
if (passthrough) enablePassthrough();
|
|
924
|
+
else disablePassthrough();
|
|
925
|
+
if (iframe) iframe.style.pointerEvents = passthrough ? "none" : "auto";
|
|
926
|
+
}
|
|
518
927
|
let pauseIo = null;
|
|
519
928
|
function setupAutoPause() {
|
|
520
929
|
if (options.autoPause === false || typeof IntersectionObserver === "undefined") return;
|
|
@@ -566,50 +975,177 @@ function createXiaochun(options) {
|
|
|
566
975
|
}, { rootMargin: `${options.lazyMargin ?? 200}px` });
|
|
567
976
|
io.observe(wrapper);
|
|
568
977
|
}
|
|
569
|
-
let
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
if (!drag) return;
|
|
600
|
-
ws.left = `${Math.min(Math.max(0, e.clientX - drag.dx), window.innerWidth - 40)}px`;
|
|
601
|
-
ws.top = `${Math.min(Math.max(0, e.clientY - drag.dy), window.innerHeight - 40)}px`;
|
|
978
|
+
let draggableOn = options.draggable === true;
|
|
979
|
+
let resizeLimits = normalizeResizable(options.resizable);
|
|
980
|
+
const gates = { move: new GestureGate(), resize: new GestureGate() };
|
|
981
|
+
let activeGesture = null;
|
|
982
|
+
let inlineShift = { x: 0, y: 0 };
|
|
983
|
+
let userMoved = false;
|
|
984
|
+
let sentGestures = null;
|
|
985
|
+
let warnedGesture = false;
|
|
986
|
+
const viewport = () => ({
|
|
987
|
+
width: document.documentElement.clientWidth || window.innerWidth,
|
|
988
|
+
height: document.documentElement.clientHeight || window.innerHeight
|
|
989
|
+
});
|
|
990
|
+
const readBox = () => {
|
|
991
|
+
const r = wrapper.getBoundingClientRect();
|
|
992
|
+
return { left: r.left, top: r.top, width: r.width, height: r.height };
|
|
993
|
+
};
|
|
994
|
+
const wantGestures = () => ({ move: draggableOn && caps?.gestures?.move === true, resize: resizeLimits !== null && caps?.gestures?.resize === true });
|
|
995
|
+
function applyBox(b, inlineBase, withSize) {
|
|
996
|
+
if (withSize) {
|
|
997
|
+
ws.width = `${b.width}px`;
|
|
998
|
+
ws.height = `${b.height}px`;
|
|
999
|
+
ws.maxWidth = "none";
|
|
1000
|
+
}
|
|
1001
|
+
if (position === "inline") {
|
|
1002
|
+
const base = inlineBase ?? { left: b.left - inlineShift.x, top: b.top - inlineShift.y };
|
|
1003
|
+
inlineShift = { x: b.left - base.left, y: b.top - base.top };
|
|
1004
|
+
ws.setProperty("translate", `${inlineShift.x}px ${inlineShift.y}px`);
|
|
1005
|
+
} else {
|
|
1006
|
+
ws.left = `${b.left}px`;
|
|
1007
|
+
ws.top = `${b.top}px`;
|
|
602
1008
|
ws.right = "auto";
|
|
603
1009
|
ws.bottom = "auto";
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1010
|
+
}
|
|
1011
|
+
userMoved = true;
|
|
1012
|
+
invalidateRect();
|
|
1013
|
+
}
|
|
1014
|
+
let selectionLock = null;
|
|
1015
|
+
function lockSelection(on) {
|
|
1016
|
+
const de = document.documentElement;
|
|
1017
|
+
if (on && !selectionLock) {
|
|
1018
|
+
selectionLock = { us: de.style.getPropertyValue("user-select"), wus: de.style.getPropertyValue("-webkit-user-select") };
|
|
1019
|
+
de.style.setProperty("user-select", "none");
|
|
1020
|
+
de.style.setProperty("-webkit-user-select", "none");
|
|
1021
|
+
try {
|
|
1022
|
+
window.getSelection()?.removeAllRanges();
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
} else if (!on && selectionLock) {
|
|
1026
|
+
de.style.setProperty("user-select", selectionLock.us);
|
|
1027
|
+
de.style.setProperty("-webkit-user-select", selectionLock.wus);
|
|
1028
|
+
selectionLock = null;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
function endActiveGesture(kind) {
|
|
1032
|
+
if (!activeGesture || kind && activeGesture.kind !== kind) return;
|
|
1033
|
+
gates[activeGesture.kind].close();
|
|
1034
|
+
const b = readBox();
|
|
1035
|
+
emit(activeGesture.kind, { phase: "end", ...b });
|
|
1036
|
+
activeGesture = null;
|
|
1037
|
+
lockSelection(false);
|
|
1038
|
+
saveBox();
|
|
1039
|
+
}
|
|
1040
|
+
function onGesture(kind, raw) {
|
|
1041
|
+
if (destroyed || !caps?.gestures) return;
|
|
1042
|
+
if (kind === "move" ? !draggableOn : resizeLimits === null) return;
|
|
1043
|
+
const p = parseGesturePayload(kind, raw);
|
|
1044
|
+
if (!p) return;
|
|
1045
|
+
if (p.phase === "start" ? activeGesture !== null : activeGesture?.kind !== kind) return;
|
|
1046
|
+
if (!gates[kind].accept(p)) return;
|
|
1047
|
+
if (p.phase === "start") {
|
|
1048
|
+
const start = readBox();
|
|
1049
|
+
activeGesture = {
|
|
1050
|
+
kind,
|
|
1051
|
+
start,
|
|
1052
|
+
corner: p.corner,
|
|
1053
|
+
inlineBase: position === "inline" ? { left: start.left - inlineShift.x, top: start.top - inlineShift.y } : null
|
|
1054
|
+
};
|
|
1055
|
+
lockSelection(true);
|
|
1056
|
+
emit(kind, { phase: "start", ...start });
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const a = activeGesture;
|
|
1060
|
+
const vp = viewport();
|
|
1061
|
+
const next = kind === "move" ? moveBox(a.start, p.totalDx, p.totalDy, vp) : resizeBox(a.start, a.corner, p.totalDx, p.totalDy, resizeLimits, vp);
|
|
1062
|
+
applyBox(next, a.inlineBase, kind === "resize");
|
|
1063
|
+
emit(kind, { phase: p.phase, ...next });
|
|
1064
|
+
if (p.phase === "end") {
|
|
1065
|
+
activeGesture = null;
|
|
1066
|
+
lockSelection(false);
|
|
1067
|
+
saveBox();
|
|
1068
|
+
}
|
|
612
1069
|
}
|
|
1070
|
+
function syncGestures() {
|
|
1071
|
+
if (destroyed) return;
|
|
1072
|
+
const want = wantGestures();
|
|
1073
|
+
if (!want.move) endActiveGesture("move");
|
|
1074
|
+
if (!want.resize) endActiveGesture("resize");
|
|
1075
|
+
if (!port || !caps) return;
|
|
1076
|
+
warnGestureUnsupported();
|
|
1077
|
+
if (sentGestures && sentGestures.move === want.move && sentGestures.resize === want.resize) return;
|
|
1078
|
+
sentGestures = want;
|
|
1079
|
+
if (caps.gestures) port.postMessage(xcMessage("xc.setConfig", { gestures: want }));
|
|
1080
|
+
}
|
|
1081
|
+
function warnGestureUnsupported() {
|
|
1082
|
+
if (warnedGesture || !caps || caps.gestures) return;
|
|
1083
|
+
const needMove = draggableOn;
|
|
1084
|
+
if (!needMove && resizeLimits === null) return;
|
|
1085
|
+
warnedGesture = true;
|
|
1086
|
+
const what = [needMove ? "draggable" : "", resizeLimits !== null ? "resizable" : ""].filter(Boolean).join(" / ");
|
|
1087
|
+
emit("error", { code: "unsupported", message: `${what} needs gesture support (capabilities.gestures), which this /embed deployment does not advertise (older version)`, command: "gestures" });
|
|
1088
|
+
}
|
|
1089
|
+
const onWindowResize = () => {
|
|
1090
|
+
if (!userMoved || activeGesture || destroyed) return;
|
|
1091
|
+
const cur = readBox();
|
|
1092
|
+
const vp = viewport();
|
|
1093
|
+
const fit = position === "inline" ? { ...fitBox({ ...cur, top: 0 }, { width: vp.width, height: Number.MAX_SAFE_INTEGER }), top: Math.max(cur.top, -window.scrollY) } : fitBox(cur, vp);
|
|
1094
|
+
if (fit.left === cur.left && fit.top === cur.top && fit.width === cur.width && fit.height === cur.height) return;
|
|
1095
|
+
applyBox(fit, position === "inline" ? { left: cur.left - inlineShift.x, top: cur.top - inlineShift.y } : null, fit.width !== cur.width || fit.height !== cur.height);
|
|
1096
|
+
};
|
|
1097
|
+
window.addEventListener("resize", onWindowResize, { passive: true });
|
|
1098
|
+
const boxKey = options.persistBox === true ? "xiaochun:box" : typeof options.persistBox === "string" && options.persistBox ? `xiaochun:box:${options.persistBox}` : null;
|
|
1099
|
+
const saveBox = () => {
|
|
1100
|
+
if (!boxKey) return;
|
|
1101
|
+
try {
|
|
1102
|
+
const b = readBox();
|
|
1103
|
+
const at = position === "inline" ? inlineShift : { x: b.left, y: b.top };
|
|
1104
|
+
window.localStorage.setItem(boxKey, JSON.stringify({ v: 1, mode: position, x: Math.round(at.x), y: Math.round(at.y), width: Math.round(b.width), height: Math.round(b.height) }));
|
|
1105
|
+
} catch {
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
const loadBox = () => {
|
|
1109
|
+
if (!boxKey) return null;
|
|
1110
|
+
try {
|
|
1111
|
+
const raw = window.localStorage.getItem(boxKey);
|
|
1112
|
+
if (raw === null) return null;
|
|
1113
|
+
let o = null;
|
|
1114
|
+
try {
|
|
1115
|
+
o = JSON.parse(raw);
|
|
1116
|
+
} catch {
|
|
1117
|
+
}
|
|
1118
|
+
const ok = (v, lo, hi) => typeof v === "number" && Number.isFinite(v) && v >= lo && v <= hi;
|
|
1119
|
+
if (o && o.v === 1 && o.mode === position && ok(o.x, -1e5, 1e5) && ok(o.y, -1e5, 1e5) && ok(o.width, 1, 1e5) && ok(o.height, 1, 1e5)) {
|
|
1120
|
+
return { x: o.x, y: o.y, width: o.width, height: o.height };
|
|
1121
|
+
}
|
|
1122
|
+
window.localStorage.removeItem(boxKey);
|
|
1123
|
+
} catch {
|
|
1124
|
+
}
|
|
1125
|
+
return null;
|
|
1126
|
+
};
|
|
1127
|
+
function restoreBox() {
|
|
1128
|
+
if (!boxKey || !draggableOn && resizeLimits === null) return;
|
|
1129
|
+
const st = loadBox();
|
|
1130
|
+
if (!st) return;
|
|
1131
|
+
const vp = viewport();
|
|
1132
|
+
const lim = resizeLimits ?? DEFAULT_RESIZE_LIMITS;
|
|
1133
|
+
const cur = readBox();
|
|
1134
|
+
const useW = resizeLimits !== null && options.width === void 0;
|
|
1135
|
+
const useH = resizeLimits !== null && options.height === void 0;
|
|
1136
|
+
const clampN = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
|
|
1137
|
+
const width = useW ? clampN(st.width, lim.minWidth, Math.min(lim.maxWidth, vp.width)) : cur.width;
|
|
1138
|
+
const height = useH ? clampN(st.height, lim.minHeight, Math.min(lim.maxHeight, vp.height)) : cur.height;
|
|
1139
|
+
if (position === "inline") {
|
|
1140
|
+
const base = { left: cur.left - inlineShift.x, top: cur.top - inlineShift.y };
|
|
1141
|
+
const left = clampN(base.left + st.x, 0, vp.width - width);
|
|
1142
|
+
const top = Math.max(base.top + st.y, -window.scrollY);
|
|
1143
|
+
applyBox({ left, top, width, height }, base, useW || useH);
|
|
1144
|
+
} else {
|
|
1145
|
+
applyBox(fitBox({ left: st.x, top: st.y, width, height }, vp), null, useW || useH);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
restoreBox();
|
|
613
1149
|
scheduleLazy();
|
|
614
1150
|
const api = {
|
|
615
1151
|
element: wrapper,
|
|
@@ -624,8 +1160,98 @@ function createXiaochun(options) {
|
|
|
624
1160
|
motion: (m) => send("xc.motion", typeof m === "string" ? m.startsWith("http") || m.startsWith("/") ? { url: m } : { name: m } : m),
|
|
625
1161
|
expression: (name) => send("xc.expression", { name }),
|
|
626
1162
|
lookAt: (x, y) => send("xc.lookAt", { x, y }),
|
|
627
|
-
|
|
1163
|
+
setOutfit: (id) => {
|
|
1164
|
+
if (!isXcId(id)) return Promise.reject(new Error("[bad_request] invalid outfit id"));
|
|
1165
|
+
return send("xc.setOutfit", { id }, true, void 0, void 0, "outfits");
|
|
1166
|
+
},
|
|
1167
|
+
setScene: (id) => {
|
|
1168
|
+
if (!isXcId(id)) return Promise.reject(new Error("[bad_request] invalid scene id"));
|
|
1169
|
+
return send("xc.setScene", { id }, true, void 0, void 0, "scenes");
|
|
1170
|
+
},
|
|
1171
|
+
prefetch: (ids) => {
|
|
1172
|
+
if (ids !== void 0 && (!Array.isArray(ids) || ids.some((x) => !isXcId(x)))) {
|
|
1173
|
+
return Promise.reject(new Error("[bad_request] prefetch ids must be an array of outfit ids"));
|
|
1174
|
+
}
|
|
1175
|
+
return send("xc.prefetch", ids ? { ids } : {}, true, void 0, void 0, "prefetch");
|
|
1176
|
+
},
|
|
1177
|
+
getOutfits: async () => {
|
|
1178
|
+
if (!iframe) activate();
|
|
1179
|
+
await handshaked;
|
|
1180
|
+
return caps?.outfits ? caps.outfits.slice() : [];
|
|
1181
|
+
},
|
|
1182
|
+
getScenes: async () => {
|
|
1183
|
+
if (!iframe) activate();
|
|
1184
|
+
await handshaked;
|
|
1185
|
+
return caps?.scenes ? caps.scenes.slice() : [];
|
|
1186
|
+
},
|
|
1187
|
+
get outfit() {
|
|
1188
|
+
return currentOutfit;
|
|
1189
|
+
},
|
|
1190
|
+
get scene() {
|
|
1191
|
+
return currentScene;
|
|
1192
|
+
},
|
|
1193
|
+
setModel: (m) => {
|
|
1194
|
+
const msg = typeof m === "string" ? /^(https?:)?\/|\.vrm/i.test(m) ? { url: m } : { outfit: m } : m;
|
|
1195
|
+
if (msg.url !== void 0 && msg.outfit === void 0 && !options.allowCustomModel) {
|
|
1196
|
+
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";
|
|
1197
|
+
emit("error", { code: "unsupported", message, command: "xc.setModel" });
|
|
1198
|
+
return Promise.reject(new Error(`[unsupported] ${message}`));
|
|
1199
|
+
}
|
|
1200
|
+
return send("xc.setModel", msg);
|
|
1201
|
+
},
|
|
628
1202
|
setConfig: (cfg) => send("xc.setConfig", cfg),
|
|
1203
|
+
setSize(width, height) {
|
|
1204
|
+
if (destroyed) return;
|
|
1205
|
+
ws.width = css(width, defaultWidthCss(position !== "inline"));
|
|
1206
|
+
ws.height = css(height, defaultHeightCss(position !== "inline"));
|
|
1207
|
+
if (ws.maxWidth === "none") ws.maxWidth = "100%";
|
|
1208
|
+
invalidateRect();
|
|
1209
|
+
},
|
|
1210
|
+
getBox: () => readBox(),
|
|
1211
|
+
clearPersistedBox(opts) {
|
|
1212
|
+
if (boxKey) {
|
|
1213
|
+
try {
|
|
1214
|
+
window.localStorage.removeItem(boxKey);
|
|
1215
|
+
} catch {
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (destroyed || opts?.reset !== true) return;
|
|
1219
|
+
endActiveGesture();
|
|
1220
|
+
ws.width = css(options.width, defaultWidthCss(position !== "inline"));
|
|
1221
|
+
ws.height = css(options.height, defaultHeightCss(position !== "inline"));
|
|
1222
|
+
ws.maxWidth = "100%";
|
|
1223
|
+
if (position === "inline") {
|
|
1224
|
+
inlineShift = { x: 0, y: 0 };
|
|
1225
|
+
ws.removeProperty("translate");
|
|
1226
|
+
} else {
|
|
1227
|
+
ws.left = "";
|
|
1228
|
+
ws.top = "";
|
|
1229
|
+
ws.right = "";
|
|
1230
|
+
ws.bottom = "";
|
|
1231
|
+
if (position === "bottom-right") {
|
|
1232
|
+
ws.right = "var(--xc-offset-x, 16px)";
|
|
1233
|
+
ws.bottom = "var(--xc-offset-y, 16px)";
|
|
1234
|
+
}
|
|
1235
|
+
if (position === "bottom-left") {
|
|
1236
|
+
ws.left = "var(--xc-offset-x, 16px)";
|
|
1237
|
+
ws.bottom = "var(--xc-offset-y, 16px)";
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
userMoved = false;
|
|
1241
|
+
invalidateRect();
|
|
1242
|
+
},
|
|
1243
|
+
setBorderRadius(radius) {
|
|
1244
|
+
radiusOpt = radius;
|
|
1245
|
+
applyRadius();
|
|
1246
|
+
},
|
|
1247
|
+
setDraggable(on) {
|
|
1248
|
+
draggableOn = on === true;
|
|
1249
|
+
syncGestures();
|
|
1250
|
+
},
|
|
1251
|
+
setResizable(on) {
|
|
1252
|
+
resizeLimits = normalizeResizable(on);
|
|
1253
|
+
syncGestures();
|
|
1254
|
+
},
|
|
629
1255
|
startListening: () => send("xc.mic", { enabled: true }),
|
|
630
1256
|
stopListening: () => send("xc.mic", { enabled: false }),
|
|
631
1257
|
mic: (enabled) => send("xc.mic", { enabled }),
|
|
@@ -650,10 +1276,10 @@ function createXiaochun(options) {
|
|
|
650
1276
|
if (handshakeTimer) clearTimeout(handshakeTimer);
|
|
651
1277
|
if (pointerRaf) cancelAnimationFrame(pointerRaf);
|
|
652
1278
|
window.removeEventListener("message", onWindowMessage);
|
|
653
|
-
window.removeEventListener("
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1279
|
+
window.removeEventListener("resize", onWindowResize);
|
|
1280
|
+
lockSelection(false);
|
|
1281
|
+
disablePassthrough();
|
|
1282
|
+
resolveHandshake();
|
|
657
1283
|
try {
|
|
658
1284
|
port?.close();
|
|
659
1285
|
} catch {
|
|
@@ -686,11 +1312,17 @@ function createXiaochun(options) {
|
|
|
686
1312
|
// src/avatar-element.ts
|
|
687
1313
|
var OBSERVED = [
|
|
688
1314
|
"src",
|
|
1315
|
+
"outfit",
|
|
1316
|
+
"scene",
|
|
689
1317
|
"model",
|
|
690
1318
|
"lang",
|
|
691
1319
|
"mic",
|
|
692
1320
|
"transparent",
|
|
693
1321
|
"draggable",
|
|
1322
|
+
"resizable",
|
|
1323
|
+
"min-size",
|
|
1324
|
+
"max-size",
|
|
1325
|
+
"border-radius",
|
|
694
1326
|
"position",
|
|
695
1327
|
"size",
|
|
696
1328
|
"lazy",
|
|
@@ -698,23 +1330,66 @@ var OBSERVED = [
|
|
|
698
1330
|
"placeholder",
|
|
699
1331
|
"heavy",
|
|
700
1332
|
"ui",
|
|
1333
|
+
"ui-autohide",
|
|
1334
|
+
"camera-fov",
|
|
1335
|
+
"camera-distance",
|
|
1336
|
+
"camera-height",
|
|
1337
|
+
"camera-intro",
|
|
701
1338
|
"controls",
|
|
702
1339
|
"allowed-origins",
|
|
703
|
-
"cross-origin-isolated"
|
|
1340
|
+
"cross-origin-isolated",
|
|
1341
|
+
"persist",
|
|
1342
|
+
"persist-box",
|
|
1343
|
+
"allow-custom-model",
|
|
1344
|
+
"prefetch"
|
|
704
1345
|
];
|
|
705
|
-
var REBUILD = /* @__PURE__ */ new Set(["src", "
|
|
1346
|
+
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"]);
|
|
1347
|
+
function radiusAttr(v) {
|
|
1348
|
+
if (v === null || v.trim() === "") return void 0;
|
|
1349
|
+
const t = v.trim();
|
|
1350
|
+
return /^\d+(\.\d+)?$/.test(t) ? Number(t) : t;
|
|
1351
|
+
}
|
|
706
1352
|
function boolAttr(el, name, dflt) {
|
|
707
1353
|
const v = el.getAttribute(name);
|
|
708
1354
|
if (v === null) return dflt;
|
|
709
1355
|
return v !== "false";
|
|
710
1356
|
}
|
|
1357
|
+
function prefetchAttr(v) {
|
|
1358
|
+
if (v === null || v === "false") return false;
|
|
1359
|
+
if (v === "" || v === "true") return true;
|
|
1360
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1361
|
+
}
|
|
1362
|
+
function persistBoxAttr(v) {
|
|
1363
|
+
if (v === null || v === "false" || v === "0") return void 0;
|
|
1364
|
+
if (v === "" || v === "true" || v === "1") return true;
|
|
1365
|
+
return v;
|
|
1366
|
+
}
|
|
1367
|
+
function uiAttr(v) {
|
|
1368
|
+
if (v === null || v === "false" || v === "0") return void 0;
|
|
1369
|
+
if (v === "" || v === "true" || v === "1") return true;
|
|
1370
|
+
return v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1371
|
+
}
|
|
1372
|
+
function cameraAttrs(el) {
|
|
1373
|
+
const num = (name) => {
|
|
1374
|
+
const v = el.getAttribute(name);
|
|
1375
|
+
if (v === null || v.trim() === "") return null;
|
|
1376
|
+
const n = Number(v);
|
|
1377
|
+
return Number.isFinite(n) ? n : null;
|
|
1378
|
+
};
|
|
1379
|
+
const i = el.getAttribute("camera-intro");
|
|
1380
|
+
return { fov: num("camera-fov"), distance: num("camera-distance"), height: num("camera-height"), intro: i === null ? null : i !== "false" && i !== "0" };
|
|
1381
|
+
}
|
|
1382
|
+
function parsePx(v) {
|
|
1383
|
+
const m = v?.trim().match(/^(\d+(?:\.\d+)?)\s*x\s*(\d+(?:\.\d+)?)$/i);
|
|
1384
|
+
return m ? { w: Number(m[1]), h: Number(m[2]) } : void 0;
|
|
1385
|
+
}
|
|
711
1386
|
function parseSize(v) {
|
|
712
|
-
if (!v) return { width:
|
|
1387
|
+
if (!v) return { width: void 0, height: void 0 };
|
|
713
1388
|
const m = v.trim().split(/x/i);
|
|
714
1389
|
const px = (s) => /^\d+(\.\d+)?$/.test(s) ? `${s}px` : s;
|
|
715
1390
|
if (m.length >= 2) return { width: px(m[0]), height: px(m[1]) };
|
|
716
1391
|
const w = parseFloat(m[0]);
|
|
717
|
-
return Number.isFinite(w) && /^\d+(\.\d+)?$/.test(m[0]) ? { width: `${w}px`, height: `${Math.round(w * 1.5)}px` } : { width: m[0], height:
|
|
1392
|
+
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 };
|
|
718
1393
|
}
|
|
719
1394
|
function createElementClass() {
|
|
720
1395
|
class XiaochunAvatarElementImpl extends HTMLElement {
|
|
@@ -755,10 +1430,22 @@ function createElementClass() {
|
|
|
755
1430
|
return;
|
|
756
1431
|
}
|
|
757
1432
|
const c = this.client;
|
|
758
|
-
if (name === "
|
|
1433
|
+
if ((name === "outfit" || name === "model") && newV) {
|
|
1434
|
+
const want = name === "model" && this.hasAttribute("outfit") ? null : newV;
|
|
1435
|
+
if (want) void (isXcId(want) ? c.setOutfit(want) : c.setModel(want)).catch(() => {
|
|
1436
|
+
});
|
|
1437
|
+
} else if (name === "scene" && newV) void c.setScene(newV).catch(() => {
|
|
759
1438
|
});
|
|
760
1439
|
else if (name === "lang" && newV) void c.setConfig({ lang: newV }).catch(() => {
|
|
761
1440
|
});
|
|
1441
|
+
else if (name === "size") {
|
|
1442
|
+
const { width, height } = parseSize(newV);
|
|
1443
|
+
c.setSize(width, height);
|
|
1444
|
+
} else if (name === "border-radius") c.setBorderRadius(radiusAttr(newV));
|
|
1445
|
+
else if (name === "draggable") c.setDraggable(boolAttr(this, "draggable", false));
|
|
1446
|
+
else if (name === "resizable" || name === "min-size" || name === "max-size") c.setResizable(this.resizableOption());
|
|
1447
|
+
else if (name.startsWith("camera-")) void c.setConfig({ camera: cameraAttrs(this) }).catch(() => {
|
|
1448
|
+
});
|
|
762
1449
|
else if (name === "mic") void c.mic(boolAttr(this, "mic", false)).catch(() => {
|
|
763
1450
|
});
|
|
764
1451
|
else if (name === "paused") {
|
|
@@ -785,11 +1472,20 @@ function createElementClass() {
|
|
|
785
1472
|
height,
|
|
786
1473
|
position: this.getAttribute("position") ?? "inline",
|
|
787
1474
|
draggable: boolAttr(this, "draggable", false),
|
|
1475
|
+
resizable: this.resizableOption(),
|
|
1476
|
+
borderRadius: radiusAttr(this.getAttribute("border-radius")),
|
|
788
1477
|
lang: this.getAttribute("lang") ?? void 0,
|
|
789
|
-
|
|
1478
|
+
outfit: this.getAttribute("outfit") ?? this.getAttribute("model") ?? void 0,
|
|
1479
|
+
scene: this.getAttribute("scene") ?? void 0,
|
|
1480
|
+
persist: this.getAttribute("persist") || void 0,
|
|
1481
|
+
persistBox: persistBoxAttr(this.getAttribute("persist-box")),
|
|
1482
|
+
allowCustomModel: boolAttr(this, "allow-custom-model", false),
|
|
1483
|
+
prefetch: prefetchAttr(this.getAttribute("prefetch")),
|
|
790
1484
|
heavy: this.getAttribute("heavy") ?? void 0,
|
|
791
|
-
ui:
|
|
792
|
-
|
|
1485
|
+
ui: uiAttr(this.getAttribute("ui")),
|
|
1486
|
+
uiAutoHide: parseXcUiAutoHide(this.getAttribute("ui-autohide")),
|
|
1487
|
+
camera: cameraAttrs(this),
|
|
1488
|
+
controls: boolAttr(this, "controls", true),
|
|
793
1489
|
crossOriginIsolated: boolAttr(this, "cross-origin-isolated", false)
|
|
794
1490
|
});
|
|
795
1491
|
this.client = c;
|
|
@@ -800,10 +1496,21 @@ function createElementClass() {
|
|
|
800
1496
|
fwd("stt", "xc-stt");
|
|
801
1497
|
fwd("utterance", "xc-utterance");
|
|
802
1498
|
fwd("error", "xc-error");
|
|
1499
|
+
fwd("outfit-changed", "xc-outfit-changed");
|
|
1500
|
+
fwd("scene-changed", "xc-scene-changed");
|
|
1501
|
+
fwd("lang-changed", "xc-lang-changed");
|
|
1502
|
+
fwd("move", "xc-move");
|
|
1503
|
+
fwd("resize", "xc-resize");
|
|
803
1504
|
if (boolAttr(this, "paused", false)) c.pause();
|
|
804
1505
|
if (boolAttr(this, "mic", false)) void c.ready.then(() => c.mic(true)).catch(() => {
|
|
805
1506
|
});
|
|
806
1507
|
}
|
|
1508
|
+
/** resizable / min-size / max-size 属性 → SDK 的 resizable 选项 (缺省 / "false" = 关)。 */
|
|
1509
|
+
resizableOption() {
|
|
1510
|
+
if (!boolAttr(this, "resizable", false)) return false;
|
|
1511
|
+
const min = parsePx(this.getAttribute("min-size")), max = parsePx(this.getAttribute("max-size"));
|
|
1512
|
+
return { minWidth: min?.w, minHeight: min?.h, maxWidth: max?.w, maxHeight: max?.h };
|
|
1513
|
+
}
|
|
807
1514
|
say(text, opts) {
|
|
808
1515
|
return this.requireClient().say(text, opts);
|
|
809
1516
|
}
|
|
@@ -819,6 +1526,24 @@ function createElementClass() {
|
|
|
819
1526
|
expression(name) {
|
|
820
1527
|
return this.requireClient().expression(name);
|
|
821
1528
|
}
|
|
1529
|
+
setOutfit(id) {
|
|
1530
|
+
return this.requireClient().setOutfit(id);
|
|
1531
|
+
}
|
|
1532
|
+
setScene(id) {
|
|
1533
|
+
return this.requireClient().setScene(id);
|
|
1534
|
+
}
|
|
1535
|
+
getOutfits() {
|
|
1536
|
+
return this.requireClient().getOutfits();
|
|
1537
|
+
}
|
|
1538
|
+
getScenes() {
|
|
1539
|
+
return this.requireClient().getScenes();
|
|
1540
|
+
}
|
|
1541
|
+
clearPersistedBox(opts) {
|
|
1542
|
+
this.client?.clearPersistedBox(opts);
|
|
1543
|
+
}
|
|
1544
|
+
prefetch(ids) {
|
|
1545
|
+
return this.requireClient().prefetch(ids);
|
|
1546
|
+
}
|
|
822
1547
|
destroy() {
|
|
823
1548
|
this.client?.destroy();
|
|
824
1549
|
this.client = null;
|