@lemoncat7/dsh-theme-xiaohei 0.3.0-alpha.23 → 0.3.2
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.md +39 -17
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/lib/appearance.js +26 -10
- package/lib/blink.js +3 -3
- package/lib/bloub-heixiu/ambient.d.ts +3 -0
- package/lib/bloub-heixiu/ambient.js +12 -0
- package/lib/bloub-heixiu/character.d.ts +33 -0
- package/lib/bloub-heixiu/character.js +124 -0
- package/lib/bloub-heixiu/effects.d.ts +4 -0
- package/lib/bloub-heixiu/effects.js +91 -0
- package/lib/bloub-heixiu/flight.d.ts +15 -0
- package/lib/bloub-heixiu/flight.js +66 -0
- package/lib/bloub-heixiu/follow.d.ts +13 -0
- package/lib/bloub-heixiu/follow.js +23 -0
- package/lib/bloub-heixiu/layout.d.ts +18 -0
- package/lib/bloub-heixiu/layout.js +12 -0
- package/lib/bloub-heixiu/renderer.d.ts +16 -0
- package/lib/bloub-heixiu/renderer.js +94 -0
- package/lib/bloub-heixiu/runtime.d.ts +6 -0
- package/lib/bloub-heixiu/runtime.js +395 -0
- package/lib/bloub-heixiu/signals.d.ts +17 -0
- package/lib/bloub-heixiu/signals.js +53 -0
- package/lib/bloub-heixiu/styles.d.ts +2 -0
- package/lib/bloub-heixiu/styles.js +98 -0
- package/lib/chrome/accessibility.d.ts +1 -1
- package/lib/chrome/accessibility.js +5 -13
- package/lib/chrome/composer-avatar.d.ts +10 -0
- package/lib/chrome/composer-avatar.js +599 -0
- package/lib/chrome/composer-send-heixiu.d.ts +3 -0
- package/lib/chrome/composer-send-heixiu.js +143 -0
- package/lib/chrome/composer.d.ts +2 -2
- package/lib/chrome/composer.js +84 -95
- package/lib/chrome/controls.d.ts +8 -0
- package/lib/chrome/controls.js +348 -0
- package/lib/chrome/conversation-messages.d.ts +1 -1
- package/lib/chrome/conversation-messages.js +207 -9
- package/lib/chrome/conversation.d.ts +1 -1
- package/lib/chrome/conversation.js +6 -0
- package/lib/chrome/identity.js +4 -18
- package/lib/chrome/portal.d.ts +1 -1
- package/lib/chrome/portal.js +1 -1
- package/lib/chrome/sidebar.d.ts +1 -1
- package/lib/chrome/sidebar.js +134 -153
- package/lib/chrome/tokens.d.ts +1 -1
- package/lib/chrome/tokens.js +121 -68
- package/lib/chrome/workspace.d.ts +2 -2
- package/lib/chrome/workspace.js +35 -254
- package/lib/chrome.js +4 -0
- package/lib/client.d.ts +5 -12
- package/lib/client.js +6262 -4455
- package/lib/composer-avatar.d.ts +18 -0
- package/lib/composer-avatar.js +441 -0
- package/lib/composer-send-heixiu.d.ts +8 -0
- package/lib/composer-send-heixiu.js +133 -0
- package/lib/gaze.js +3 -3
- package/lib/generated-backgrounds.d.ts +14 -0
- package/lib/generated-backgrounds.js +14 -0
- package/lib/generated-character-motion.d.ts +4 -0
- package/lib/generated-character-motion.js +2 -0
- package/lib/generated-identity.d.ts +1 -0
- package/lib/generated-identity.js +1 -0
- package/lib/generated-keyart.d.ts +18 -3
- package/lib/generated-keyart.js +18 -3
- package/lib/host-contract.d.ts +9 -0
- package/lib/host-contract.js +13 -0
- package/lib/host-dom.d.ts +11 -0
- package/lib/host-dom.js +60 -0
- package/lib/loading-heixiu.js +3 -3
- package/lib/metallic-brand-mark.d.ts +10 -0
- package/lib/metallic-brand-mark.js +323 -0
- package/lib/plugin.d.ts +1 -1
- package/lib/plugin.js +31 -16
- package/lib/portal.js +11 -10
- package/lib/reactions.js +3 -3
- package/lib/scene/avatar-model.d.ts +6 -0
- package/lib/scene/avatar-model.js +646 -0
- package/lib/scene/avatar-motion.d.ts +3 -0
- package/lib/scene/avatar-motion.js +799 -0
- package/lib/scene/character-idle.d.ts +17 -0
- package/lib/scene/character-idle.js +65 -0
- package/lib/scene/character-layout.d.ts +24 -0
- package/lib/scene/character-layout.js +51 -0
- package/lib/scene/character-motion-clock.d.ts +18 -0
- package/lib/scene/character-motion-clock.js +57 -0
- package/lib/scene/character-motion-types.d.ts +12 -0
- package/lib/scene/character-motion-types.js +2 -0
- package/lib/scene/character-motion.d.ts +6 -0
- package/lib/scene/character-motion.js +126 -0
- package/lib/scene/character-poses.d.ts +26 -0
- package/lib/scene/character-poses.js +11 -0
- package/lib/scene/character-rig-renderer.d.ts +4 -0
- package/lib/scene/character-rig-renderer.js +85 -0
- package/lib/scene/character-rig.d.ts +20 -0
- package/lib/scene/character-rig.js +53 -0
- package/lib/scene/heixiu-feedback.d.ts +1 -1
- package/lib/scene/heixiu-feedback.js +0 -17
- package/lib/scene/pointer-bridge.d.ts +21 -0
- package/lib/scene/pointer-bridge.js +155 -0
- package/lib/scene/runtime.d.ts +12 -0
- package/lib/scene/runtime.js +87 -0
- package/lib/scene/styles.d.ts +7 -0
- package/lib/scene/styles.js +75 -0
- package/lib/scene/sylva-performance.d.ts +11 -0
- package/lib/scene/sylva-performance.js +58 -0
- package/lib/scene/wallpaper-character-styles.d.ts +3 -0
- package/lib/scene/wallpaper-character-styles.js +53 -0
- package/lib/scene/wallpaper-character.d.ts +4 -0
- package/lib/scene/wallpaper-character.js +213 -0
- package/lib/scene.d.ts +7 -13
- package/lib/scene.js +7 -1023
- package/lib/sidebar-glass.js +99 -36
- package/lib/sidebar-heixiu-roaming.js +5 -5
- package/lib/sidebar-reveal.d.ts +8 -0
- package/lib/sidebar-reveal.js +51 -0
- package/lib/state.d.ts +14 -2
- package/lib/state.js +12 -4
- package/lib/theme.js +2 -2
- package/lib/vendor/bloub/decor.d.ts +114 -0
- package/lib/vendor/bloub/decor.js +198 -0
- package/lib/vendor/bloub/engine.d.ts +213 -0
- package/lib/vendor/bloub/engine.js +452 -0
- package/lib/vendor/bloub/expressions.d.ts +35 -0
- package/lib/vendor/bloub/expressions.js +143 -0
- package/lib/vendor/bloub/eyefit.d.ts +75 -0
- package/lib/vendor/bloub/eyefit.js +393 -0
- package/lib/vendor/bloub/face.d.ts +63 -0
- package/lib/vendor/bloub/face.js +106 -0
- package/lib/vendor/bloub/math.d.ts +22 -0
- package/lib/vendor/bloub/math.js +36 -0
- package/lib/vendor/bloub/profiles.d.ts +8 -0
- package/lib/vendor/bloub/profiles.js +20 -0
- package/lib/vendor/bloub/repere.d.ts +30 -0
- package/lib/vendor/bloub/repere.js +32 -0
- package/lib/vendor/bloub/shape.d.ts +80 -0
- package/lib/vendor/bloub/shape.js +268 -0
- package/lib/vendor/bloub/skins.d.ts +36 -0
- package/lib/vendor/bloub/skins.js +74 -0
- package/lib/vendor/bloub/states.d.ts +87 -0
- package/lib/vendor/bloub/states.js +468 -0
- package/package.json +23 -6
- package/lib/appearance.d.ts.map +0 -1
- package/lib/appearance.js.map +0 -1
- package/lib/blink.d.ts.map +0 -1
- package/lib/blink.js.map +0 -1
- package/lib/boot-appearance.d.ts.map +0 -1
- package/lib/boot-appearance.js.map +0 -1
- package/lib/boot-loader.d.ts.map +0 -1
- package/lib/boot-loader.js.map +0 -1
- package/lib/chrome/accessibility.d.ts.map +0 -1
- package/lib/chrome/accessibility.js.map +0 -1
- package/lib/chrome/blink.d.ts.map +0 -1
- package/lib/chrome/blink.js.map +0 -1
- package/lib/chrome/composer.d.ts.map +0 -1
- package/lib/chrome/composer.js.map +0 -1
- package/lib/chrome/conversation-messages.d.ts.map +0 -1
- package/lib/chrome/conversation-messages.js.map +0 -1
- package/lib/chrome/conversation.d.ts.map +0 -1
- package/lib/chrome/conversation.js.map +0 -1
- package/lib/chrome/frames.d.ts.map +0 -1
- package/lib/chrome/frames.js.map +0 -1
- package/lib/chrome/gaze.d.ts.map +0 -1
- package/lib/chrome/gaze.js.map +0 -1
- package/lib/chrome/identity.d.ts.map +0 -1
- package/lib/chrome/identity.js.map +0 -1
- package/lib/chrome/overlays.d.ts.map +0 -1
- package/lib/chrome/overlays.js.map +0 -1
- package/lib/chrome/portal.d.ts.map +0 -1
- package/lib/chrome/portal.js.map +0 -1
- package/lib/chrome/reactions.d.ts.map +0 -1
- package/lib/chrome/reactions.js.map +0 -1
- package/lib/chrome/sidebar.d.ts.map +0 -1
- package/lib/chrome/sidebar.js.map +0 -1
- package/lib/chrome/tokens.d.ts.map +0 -1
- package/lib/chrome/tokens.js.map +0 -1
- package/lib/chrome/workspace.d.ts.map +0 -1
- package/lib/chrome/workspace.js.map +0 -1
- package/lib/chrome.d.ts.map +0 -1
- package/lib/chrome.js.map +0 -1
- package/lib/client.d.ts.map +0 -1
- package/lib/client.js.map +0 -7
- package/lib/gaze.d.ts.map +0 -1
- package/lib/gaze.js.map +0 -1
- package/lib/generated-identity.d.ts.map +0 -1
- package/lib/generated-identity.js.map +0 -1
- package/lib/generated-keyart.d.ts.map +0 -1
- package/lib/generated-keyart.js.map +0 -1
- package/lib/generated-loading-assets.d.ts.map +0 -1
- package/lib/generated-loading-assets.js.map +0 -1
- package/lib/generated-sidebar-assets.d.ts +0 -4
- package/lib/generated-sidebar-assets.d.ts.map +0 -1
- package/lib/generated-sidebar-assets.js +0 -4
- package/lib/generated-sidebar-assets.js.map +0 -1
- package/lib/heixiu-interactions.d.ts.map +0 -1
- package/lib/heixiu-interactions.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/loading-heixiu.d.ts.map +0 -1
- package/lib/loading-heixiu.js.map +0 -1
- package/lib/plugin.d.ts.map +0 -1
- package/lib/plugin.js.map +0 -1
- package/lib/portal.d.ts.map +0 -1
- package/lib/portal.js.map +0 -1
- package/lib/reactions.d.ts.map +0 -1
- package/lib/reactions.js.map +0 -1
- package/lib/scene/heixiu-feedback.d.ts.map +0 -1
- package/lib/scene/heixiu-feedback.js.map +0 -1
- package/lib/scene/heixiu-interactions.d.ts.map +0 -1
- package/lib/scene/heixiu-interactions.js.map +0 -1
- package/lib/scene/state-transitions.d.ts.map +0 -1
- package/lib/scene/state-transitions.js.map +0 -1
- package/lib/scene.d.ts.map +0 -1
- package/lib/scene.js.map +0 -1
- package/lib/sidebar-glass.d.ts.map +0 -1
- package/lib/sidebar-glass.js.map +0 -1
- package/lib/sidebar-heixiu-escape.d.ts.map +0 -1
- package/lib/sidebar-heixiu-escape.js.map +0 -1
- package/lib/sidebar-heixiu-roaming.d.ts.map +0 -1
- package/lib/sidebar-heixiu-roaming.js.map +0 -1
- package/lib/state.d.ts.map +0 -1
- package/lib/state.js.map +0 -1
- package/lib/theme.d.ts.map +0 -1
- package/lib/theme.js.map +0 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const XIAOHEI_COMPOSER_AVATAR_CLASS = "xiaohei-composer-avatar";
|
|
2
|
+
export declare const XIAOHEI_COMPOSER_IDLE_MOTIONS: readonly ["glance-left", "glance-right", "nod", "perk", "peek", "double-blink", "ponder"];
|
|
3
|
+
export type XiaoheiComposerIdleMotion = typeof XIAOHEI_COMPOSER_IDLE_MOTIONS[number];
|
|
4
|
+
/** Pick a varied idle gesture without visibly repeating the previous one. */
|
|
5
|
+
export declare function resolveXiaoheiComposerIdleMotion(random?: () => number, previous?: XiaoheiComposerIdleMotion): XiaoheiComposerIdleMotion;
|
|
6
|
+
type ComposerPlacement = 'hero' | 'docked' | 'mobile';
|
|
7
|
+
interface ComposerAvatarPosition {
|
|
8
|
+
placement: ComposerPlacement;
|
|
9
|
+
scale: number;
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
}
|
|
13
|
+
/** Resolve the avatar target without coupling to generated Host class names. */
|
|
14
|
+
export declare function resolveXiaoheiComposerAvatarPosition(composer: Pick<DOMRect, 'bottom' | 'height' | 'left' | 'top' | 'width'>, viewportWidth: number, viewportHeight: number): ComposerAvatarPosition;
|
|
15
|
+
/** Mount one persistent avatar and let the native composer remain fully owned by DSH. */
|
|
16
|
+
export declare function installXiaoheiComposerAvatar(doc?: Document | undefined): () => void;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=composer-avatar.d.ts.map
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { XIAOHEI_BREAKFRAME_BLINK, XIAOHEI_BREAKFRAME_BRACE, XIAOHEI_BREAKFRAME_COMPLETE, XIAOHEI_BREAKFRAME_ERROR, XIAOHEI_BREAKFRAME_IDLE, XIAOHEI_BREAKFRAME_REACH, XIAOHEI_BREAKFRAME_THINKING, XIAOHEI_BREAKFRAME_WORKING, } from './generated-keyart.js';
|
|
2
|
+
import { XIAOHEI_HOST_SELECTORS } from './host-contract.js';
|
|
3
|
+
import { subscribeXiaoheiHostDom } from './host-dom.js';
|
|
4
|
+
export const XIAOHEI_COMPOSER_AVATAR_CLASS = 'xiaohei-composer-avatar';
|
|
5
|
+
export const XIAOHEI_COMPOSER_IDLE_MOTIONS = [
|
|
6
|
+
'glance-left',
|
|
7
|
+
'glance-right',
|
|
8
|
+
'nod',
|
|
9
|
+
'perk',
|
|
10
|
+
'peek',
|
|
11
|
+
'double-blink',
|
|
12
|
+
'ponder',
|
|
13
|
+
];
|
|
14
|
+
/** Pick a varied idle gesture without visibly repeating the previous one. */
|
|
15
|
+
export function resolveXiaoheiComposerIdleMotion(random = Math.random, previous) {
|
|
16
|
+
const candidates = previous === undefined
|
|
17
|
+
? XIAOHEI_COMPOSER_IDLE_MOTIONS
|
|
18
|
+
: XIAOHEI_COMPOSER_IDLE_MOTIONS.filter(motion => motion !== previous);
|
|
19
|
+
const index = Math.min(candidates.length - 1, Math.max(0, Math.floor(random() * candidates.length)));
|
|
20
|
+
return candidates[index] ?? XIAOHEI_COMPOSER_IDLE_MOTIONS[0];
|
|
21
|
+
}
|
|
22
|
+
const XIAOHEI_IDLE_MOTION_DURATION = {
|
|
23
|
+
'glance-left': 1_760,
|
|
24
|
+
'glance-right': 1_760,
|
|
25
|
+
'nod': 1_520,
|
|
26
|
+
'perk': 1_640,
|
|
27
|
+
'peek': 1_860,
|
|
28
|
+
'double-blink': 760,
|
|
29
|
+
'ponder': 2_260,
|
|
30
|
+
};
|
|
31
|
+
/** Resolve the avatar target without coupling to generated Host class names. */
|
|
32
|
+
export function resolveXiaoheiComposerAvatarPosition(composer, viewportWidth, viewportHeight) {
|
|
33
|
+
const avatarWidth = 172;
|
|
34
|
+
const avatarHeight = 184;
|
|
35
|
+
const mobile = viewportWidth <= 760;
|
|
36
|
+
const hero = composer.top < viewportHeight * 0.58;
|
|
37
|
+
let placement;
|
|
38
|
+
let scale;
|
|
39
|
+
let x;
|
|
40
|
+
let y;
|
|
41
|
+
if (mobile) {
|
|
42
|
+
placement = 'mobile';
|
|
43
|
+
scale = 0.72;
|
|
44
|
+
x = composer.left + 8;
|
|
45
|
+
y = composer.top - avatarHeight * scale - 8;
|
|
46
|
+
}
|
|
47
|
+
else if (hero) {
|
|
48
|
+
placement = 'hero';
|
|
49
|
+
scale = 0.92;
|
|
50
|
+
x = composer.left + composer.width / 2 - avatarWidth / 2;
|
|
51
|
+
y = composer.top - avatarHeight * scale - 12;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
placement = 'docked';
|
|
55
|
+
scale = 0.9;
|
|
56
|
+
x = composer.left - avatarWidth * scale - 16;
|
|
57
|
+
y = composer.bottom - avatarHeight * scale + 8;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
placement,
|
|
61
|
+
scale,
|
|
62
|
+
x: Math.round(Math.max(8, Math.min(viewportWidth - avatarWidth * scale - 8, x))),
|
|
63
|
+
y: Math.round(Math.max(8, Math.min(viewportHeight - avatarHeight * scale - 8, y))),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Mount one persistent avatar and let the native composer remain fully owned by DSH. */
|
|
67
|
+
export function installXiaoheiComposerAvatar(doc = typeof document === 'undefined' ? undefined : document) {
|
|
68
|
+
if (doc === undefined)
|
|
69
|
+
return () => { };
|
|
70
|
+
const win = doc.defaultView;
|
|
71
|
+
if (win === null)
|
|
72
|
+
return () => { };
|
|
73
|
+
doc.querySelector(`.${XIAOHEI_COMPOSER_AVATAR_CLASS}`)?.remove();
|
|
74
|
+
const avatar = doc.createElement('span');
|
|
75
|
+
avatar.className = XIAOHEI_COMPOSER_AVATAR_CLASS;
|
|
76
|
+
avatar.setAttribute('aria-hidden', 'true');
|
|
77
|
+
avatar.dataset.xiaoheiPortrait = 'idle';
|
|
78
|
+
const effectsBack = createAvatarLayer(doc, 'effects-back');
|
|
79
|
+
const ringBack = createAvatarLayer(doc, 'ring-back');
|
|
80
|
+
const circle = createAvatarLayer(doc, 'circle');
|
|
81
|
+
circle.append(...createPortraitImages(doc, 'inside'));
|
|
82
|
+
const overflow = createAvatarLayer(doc, 'overflow');
|
|
83
|
+
overflow.append(...createPortraitImages(doc, 'overflow'));
|
|
84
|
+
const ringFront = createAvatarLayer(doc, 'ring-front');
|
|
85
|
+
const breakoutRight = createAvatarLayer(doc, 'breakout-right');
|
|
86
|
+
breakoutRight.append(...createBreakoutRightImages(doc));
|
|
87
|
+
const effectsFront = createAvatarLayer(doc, 'effects-front');
|
|
88
|
+
avatar.append(effectsBack, ringBack, circle, overflow, ringFront, breakoutRight, effectsFront);
|
|
89
|
+
doc.body.append(avatar);
|
|
90
|
+
let disposed = false;
|
|
91
|
+
let composer;
|
|
92
|
+
let resizeObserver;
|
|
93
|
+
let frameRequest;
|
|
94
|
+
let pointerRequest;
|
|
95
|
+
let reactionTimer;
|
|
96
|
+
let reactionStageTimers = [];
|
|
97
|
+
let stateTimer;
|
|
98
|
+
let blinkTimer;
|
|
99
|
+
let blinkResetTimer;
|
|
100
|
+
let idleTimer;
|
|
101
|
+
let idleResetTimer;
|
|
102
|
+
let portraitRequest = 0;
|
|
103
|
+
let reactionRequest = 0;
|
|
104
|
+
let previousIdleMotion;
|
|
105
|
+
let desiredHostPortrait = resolveDisplayPortrait(doc.documentElement.dataset.xiaoheiState);
|
|
106
|
+
let layoutReady = false;
|
|
107
|
+
let idlePortraitReady = false;
|
|
108
|
+
let pointerX = 0;
|
|
109
|
+
let pointerY = 0;
|
|
110
|
+
const revealAvatar = () => {
|
|
111
|
+
if (layoutReady && idlePortraitReady)
|
|
112
|
+
avatar.dataset.xiaoheiAvatarReady = 'true';
|
|
113
|
+
};
|
|
114
|
+
const portraitImages = Array.from(avatar.querySelectorAll('[data-xiaohei-portrait-frame]'));
|
|
115
|
+
const imagesByState = new Map();
|
|
116
|
+
for (const image of portraitImages) {
|
|
117
|
+
const state = image.dataset.xiaoheiPortraitFrame;
|
|
118
|
+
const images = imagesByState.get(state) ?? [];
|
|
119
|
+
images.push(image);
|
|
120
|
+
imagesByState.set(state, images);
|
|
121
|
+
}
|
|
122
|
+
const decodedStates = new Set();
|
|
123
|
+
const decodeTasks = new Map();
|
|
124
|
+
const ensurePortraitDecoded = (state) => {
|
|
125
|
+
if (decodedStates.has(state))
|
|
126
|
+
return Promise.resolve(true);
|
|
127
|
+
const existing = decodeTasks.get(state);
|
|
128
|
+
if (existing !== undefined)
|
|
129
|
+
return existing;
|
|
130
|
+
const task = Promise.all((imagesByState.get(state) ?? []).map(decodePortraitImage))
|
|
131
|
+
.then((results) => {
|
|
132
|
+
const decoded = results.length > 0 && results.every(Boolean);
|
|
133
|
+
if (decoded)
|
|
134
|
+
decodedStates.add(state);
|
|
135
|
+
return decoded;
|
|
136
|
+
});
|
|
137
|
+
decodeTasks.set(state, task);
|
|
138
|
+
return task;
|
|
139
|
+
};
|
|
140
|
+
const showPortrait = async (state) => {
|
|
141
|
+
const request = ++portraitRequest;
|
|
142
|
+
const decoded = await ensurePortraitDecoded(state);
|
|
143
|
+
if (disposed || request !== portraitRequest || !decoded)
|
|
144
|
+
return;
|
|
145
|
+
avatar.dataset.xiaoheiPortrait = state;
|
|
146
|
+
};
|
|
147
|
+
const applyHostPortrait = () => {
|
|
148
|
+
if (avatar.dataset.xiaoheiAvatarReaction !== undefined)
|
|
149
|
+
return;
|
|
150
|
+
void showPortrait(desiredHostPortrait);
|
|
151
|
+
};
|
|
152
|
+
const scheduleHostPortrait = () => {
|
|
153
|
+
desiredHostPortrait = resolveDisplayPortrait(doc.documentElement.dataset.xiaoheiState);
|
|
154
|
+
if (stateTimer !== undefined)
|
|
155
|
+
clearTimeout(stateTimer);
|
|
156
|
+
stateTimer = setTimeout(() => {
|
|
157
|
+
stateTimer = undefined;
|
|
158
|
+
applyHostPortrait();
|
|
159
|
+
}, 150);
|
|
160
|
+
};
|
|
161
|
+
const measure = () => {
|
|
162
|
+
frameRequest = undefined;
|
|
163
|
+
if (disposed || composer === undefined || !composer.isConnected) {
|
|
164
|
+
avatar.removeAttribute('data-xiaohei-avatar-ready');
|
|
165
|
+
layoutReady = false;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const bounds = composer.getBoundingClientRect();
|
|
169
|
+
const viewport = win.visualViewport;
|
|
170
|
+
const viewportWidth = viewport?.width ?? win.innerWidth;
|
|
171
|
+
const viewportHeight = viewport?.height ?? win.innerHeight;
|
|
172
|
+
const position = resolveXiaoheiComposerAvatarPosition(bounds, viewportWidth, viewportHeight);
|
|
173
|
+
avatar.style.setProperty('--xiaohei-composer-avatar-x', `${position.x}px`);
|
|
174
|
+
avatar.style.setProperty('--xiaohei-composer-avatar-y', `${position.y}px`);
|
|
175
|
+
avatar.style.setProperty('--xiaohei-composer-avatar-scale', String(position.scale));
|
|
176
|
+
avatar.dataset.xiaoheiAvatarPlacement = position.placement;
|
|
177
|
+
if (!layoutReady) {
|
|
178
|
+
avatar.getBoundingClientRect();
|
|
179
|
+
layoutReady = true;
|
|
180
|
+
revealAvatar();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const scheduleMeasure = () => {
|
|
184
|
+
if (disposed || frameRequest !== undefined)
|
|
185
|
+
return;
|
|
186
|
+
frameRequest = win.requestAnimationFrame(measure);
|
|
187
|
+
};
|
|
188
|
+
const bindComposer = () => {
|
|
189
|
+
const next = doc.querySelector(XIAOHEI_HOST_SELECTORS.composerCard) ?? undefined;
|
|
190
|
+
if (next !== composer) {
|
|
191
|
+
resizeObserver?.disconnect();
|
|
192
|
+
resizeObserver = undefined;
|
|
193
|
+
composer = next;
|
|
194
|
+
if (composer !== undefined && typeof win.ResizeObserver === 'function') {
|
|
195
|
+
resizeObserver = new win.ResizeObserver(scheduleMeasure);
|
|
196
|
+
resizeObserver.observe(composer);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
scheduleMeasure();
|
|
200
|
+
};
|
|
201
|
+
const handleViewportChange = () => scheduleMeasure();
|
|
202
|
+
const renderPointerResponse = () => {
|
|
203
|
+
pointerRequest = undefined;
|
|
204
|
+
const bounds = avatar.getBoundingClientRect();
|
|
205
|
+
const centerX = bounds.left + bounds.width / 2;
|
|
206
|
+
const centerY = bounds.top + bounds.height / 2;
|
|
207
|
+
const deltaX = pointerX - centerX;
|
|
208
|
+
const deltaY = pointerY - centerY;
|
|
209
|
+
const distance = Math.hypot(deltaX, deltaY);
|
|
210
|
+
const proximity = Math.max(0, 1 - distance / 220);
|
|
211
|
+
avatar.style.setProperty('--xiaohei-avatar-look-x', `${Math.max(-2.5, Math.min(2.5, deltaX / 62)) * proximity}px`);
|
|
212
|
+
avatar.style.setProperty('--xiaohei-avatar-look-y', `${Math.max(-1.6, Math.min(1.6, deltaY / 88)) * proximity}px`);
|
|
213
|
+
avatar.style.setProperty('--xiaohei-avatar-look-rotation', `${Math.max(-1.2, Math.min(1.2, deltaX / 170)) * proximity}deg`);
|
|
214
|
+
avatar.toggleAttribute('data-xiaohei-avatar-aware', proximity > 0);
|
|
215
|
+
avatar.toggleAttribute('data-xiaohei-avatar-near', distance < 104);
|
|
216
|
+
};
|
|
217
|
+
const handlePointerMove = (event) => {
|
|
218
|
+
pointerX = event.clientX;
|
|
219
|
+
pointerY = event.clientY;
|
|
220
|
+
if (pointerRequest === undefined)
|
|
221
|
+
pointerRequest = win.requestAnimationFrame(renderPointerResponse);
|
|
222
|
+
};
|
|
223
|
+
const handleAvatarClick = () => {
|
|
224
|
+
if (avatar.dataset.xiaoheiAvatarReaction !== undefined)
|
|
225
|
+
return;
|
|
226
|
+
if (reactionTimer !== undefined)
|
|
227
|
+
clearTimeout(reactionTimer);
|
|
228
|
+
for (const timer of reactionStageTimers)
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
reactionStageTimers = [];
|
|
231
|
+
const request = ++reactionRequest;
|
|
232
|
+
if (idleResetTimer !== undefined)
|
|
233
|
+
clearTimeout(idleResetTimer);
|
|
234
|
+
idleResetTimer = undefined;
|
|
235
|
+
delete avatar.dataset.xiaoheiAvatarIdle;
|
|
236
|
+
avatar.dataset.xiaoheiAvatarReaction = 'touch';
|
|
237
|
+
avatar.dataset.xiaoheiAvatarReactionStage = 'anticipate';
|
|
238
|
+
reactionStageTimers.push(setTimeout(() => {
|
|
239
|
+
if (request !== reactionRequest)
|
|
240
|
+
return;
|
|
241
|
+
avatar.dataset.xiaoheiAvatarReactionStage = 'brace';
|
|
242
|
+
void showPortrait('brace');
|
|
243
|
+
}, 170), setTimeout(() => {
|
|
244
|
+
if (request !== reactionRequest)
|
|
245
|
+
return;
|
|
246
|
+
avatar.dataset.xiaoheiAvatarReactionStage = 'break';
|
|
247
|
+
void showPortrait('reach');
|
|
248
|
+
}, 430), setTimeout(() => {
|
|
249
|
+
if (request !== reactionRequest)
|
|
250
|
+
return;
|
|
251
|
+
avatar.dataset.xiaoheiAvatarReactionStage = 'recover';
|
|
252
|
+
void showPortrait('brace');
|
|
253
|
+
}, 820), setTimeout(() => {
|
|
254
|
+
if (request !== reactionRequest)
|
|
255
|
+
return;
|
|
256
|
+
avatar.dataset.xiaoheiAvatarReactionStage = 'settle';
|
|
257
|
+
void showPortrait(desiredHostPortrait);
|
|
258
|
+
}, 1_090));
|
|
259
|
+
reactionTimer = setTimeout(() => {
|
|
260
|
+
if (request !== reactionRequest)
|
|
261
|
+
return;
|
|
262
|
+
delete avatar.dataset.xiaoheiAvatarReaction;
|
|
263
|
+
delete avatar.dataset.xiaoheiAvatarReactionStage;
|
|
264
|
+
reactionTimer = undefined;
|
|
265
|
+
reactionStageTimers = [];
|
|
266
|
+
applyHostPortrait();
|
|
267
|
+
}, 1_340);
|
|
268
|
+
};
|
|
269
|
+
const scheduleBlink = () => {
|
|
270
|
+
if (disposed || blinkTimer !== undefined)
|
|
271
|
+
return;
|
|
272
|
+
blinkTimer = setTimeout(() => {
|
|
273
|
+
blinkTimer = undefined;
|
|
274
|
+
if (!doc.hidden
|
|
275
|
+
&& avatar.dataset.xiaoheiPortrait === 'idle'
|
|
276
|
+
&& avatar.dataset.xiaoheiAvatarReaction === undefined
|
|
277
|
+
&& avatar.dataset.xiaoheiAvatarIdle === undefined) {
|
|
278
|
+
void ensurePortraitDecoded('blink').then((decoded) => {
|
|
279
|
+
if (!decoded || disposed || avatar.dataset.xiaoheiPortrait !== 'idle')
|
|
280
|
+
return;
|
|
281
|
+
avatar.dataset.xiaoheiAvatarBlink = 'true';
|
|
282
|
+
if (blinkResetTimer !== undefined)
|
|
283
|
+
clearTimeout(blinkResetTimer);
|
|
284
|
+
blinkResetTimer = setTimeout(() => {
|
|
285
|
+
delete avatar.dataset.xiaoheiAvatarBlink;
|
|
286
|
+
blinkResetTimer = undefined;
|
|
287
|
+
}, 130);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
scheduleBlink();
|
|
291
|
+
}, 4200 + Math.random() * 3600);
|
|
292
|
+
};
|
|
293
|
+
const scheduleIdleMotion = () => {
|
|
294
|
+
if (disposed || idleTimer !== undefined)
|
|
295
|
+
return;
|
|
296
|
+
idleTimer = setTimeout(() => {
|
|
297
|
+
idleTimer = undefined;
|
|
298
|
+
if (!doc.hidden
|
|
299
|
+
&& desiredHostPortrait === 'idle'
|
|
300
|
+
&& avatar.dataset.xiaoheiAvatarReaction === undefined) {
|
|
301
|
+
const motion = resolveXiaoheiComposerIdleMotion(Math.random, previousIdleMotion);
|
|
302
|
+
previousIdleMotion = motion;
|
|
303
|
+
avatar.dataset.xiaoheiAvatarIdle = motion;
|
|
304
|
+
if (motion === 'ponder')
|
|
305
|
+
void showPortrait('thinking');
|
|
306
|
+
idleResetTimer = setTimeout(() => {
|
|
307
|
+
delete avatar.dataset.xiaoheiAvatarIdle;
|
|
308
|
+
idleResetTimer = undefined;
|
|
309
|
+
if (motion === 'ponder')
|
|
310
|
+
applyHostPortrait();
|
|
311
|
+
}, XIAOHEI_IDLE_MOTION_DURATION[motion]);
|
|
312
|
+
}
|
|
313
|
+
scheduleIdleMotion();
|
|
314
|
+
}, 4_800 + Math.random() * 5_600);
|
|
315
|
+
};
|
|
316
|
+
const handleTransitionEnd = (event) => {
|
|
317
|
+
if (composer === undefined)
|
|
318
|
+
return;
|
|
319
|
+
const target = event.target;
|
|
320
|
+
if (target instanceof win.Node && (target === composer || composer.contains(target)))
|
|
321
|
+
scheduleMeasure();
|
|
322
|
+
};
|
|
323
|
+
const unsubscribeHostDom = subscribeXiaoheiHostDom(doc, bindComposer);
|
|
324
|
+
const stateObserver = new win.MutationObserver(scheduleHostPortrait);
|
|
325
|
+
stateObserver.observe(doc.documentElement, {
|
|
326
|
+
attributes: true,
|
|
327
|
+
attributeFilter: ['data-xiaohei-state'],
|
|
328
|
+
});
|
|
329
|
+
win.addEventListener('resize', handleViewportChange, { passive: true });
|
|
330
|
+
win.addEventListener('pointermove', handlePointerMove, { passive: true });
|
|
331
|
+
win.visualViewport?.addEventListener('resize', handleViewportChange, { passive: true });
|
|
332
|
+
win.visualViewport?.addEventListener('scroll', handleViewportChange, { passive: true });
|
|
333
|
+
doc.addEventListener('transitionend', handleTransitionEnd, true);
|
|
334
|
+
avatar.addEventListener('click', handleAvatarClick);
|
|
335
|
+
bindComposer();
|
|
336
|
+
void ensurePortraitDecoded('idle').then((decoded) => {
|
|
337
|
+
if (!decoded || disposed)
|
|
338
|
+
return;
|
|
339
|
+
idlePortraitReady = true;
|
|
340
|
+
revealAvatar();
|
|
341
|
+
});
|
|
342
|
+
for (const [state] of PORTRAIT_SOURCES) {
|
|
343
|
+
if (state !== 'idle')
|
|
344
|
+
void ensurePortraitDecoded(state);
|
|
345
|
+
}
|
|
346
|
+
scheduleHostPortrait();
|
|
347
|
+
scheduleBlink();
|
|
348
|
+
scheduleIdleMotion();
|
|
349
|
+
return () => {
|
|
350
|
+
disposed = true;
|
|
351
|
+
unsubscribeHostDom();
|
|
352
|
+
stateObserver.disconnect();
|
|
353
|
+
resizeObserver?.disconnect();
|
|
354
|
+
if (frameRequest !== undefined)
|
|
355
|
+
win.cancelAnimationFrame(frameRequest);
|
|
356
|
+
if (pointerRequest !== undefined)
|
|
357
|
+
win.cancelAnimationFrame(pointerRequest);
|
|
358
|
+
if (reactionTimer !== undefined)
|
|
359
|
+
clearTimeout(reactionTimer);
|
|
360
|
+
for (const timer of reactionStageTimers)
|
|
361
|
+
clearTimeout(timer);
|
|
362
|
+
if (stateTimer !== undefined)
|
|
363
|
+
clearTimeout(stateTimer);
|
|
364
|
+
if (blinkTimer !== undefined)
|
|
365
|
+
clearTimeout(blinkTimer);
|
|
366
|
+
if (blinkResetTimer !== undefined)
|
|
367
|
+
clearTimeout(blinkResetTimer);
|
|
368
|
+
if (idleTimer !== undefined)
|
|
369
|
+
clearTimeout(idleTimer);
|
|
370
|
+
if (idleResetTimer !== undefined)
|
|
371
|
+
clearTimeout(idleResetTimer);
|
|
372
|
+
win.removeEventListener('resize', handleViewportChange);
|
|
373
|
+
win.removeEventListener('pointermove', handlePointerMove);
|
|
374
|
+
win.visualViewport?.removeEventListener('resize', handleViewportChange);
|
|
375
|
+
win.visualViewport?.removeEventListener('scroll', handleViewportChange);
|
|
376
|
+
doc.removeEventListener('transitionend', handleTransitionEnd, true);
|
|
377
|
+
avatar.removeEventListener('click', handleAvatarClick);
|
|
378
|
+
avatar.remove();
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function createAvatarImage(doc, source, state) {
|
|
382
|
+
const image = doc.createElement('img');
|
|
383
|
+
image.className = `${XIAOHEI_COMPOSER_AVATAR_CLASS}__image ${XIAOHEI_COMPOSER_AVATAR_CLASS}__image--${state}`;
|
|
384
|
+
image.alt = '';
|
|
385
|
+
image.decoding = 'async';
|
|
386
|
+
const portraitState = state.slice(state.lastIndexOf('-') + 1);
|
|
387
|
+
image.dataset.xiaoheiPortraitFrame = portraitState;
|
|
388
|
+
image.fetchPriority = portraitState === 'idle' ? 'high' : 'low';
|
|
389
|
+
image.src = source;
|
|
390
|
+
return image;
|
|
391
|
+
}
|
|
392
|
+
function resolveDisplayPortrait(state) {
|
|
393
|
+
switch (state) {
|
|
394
|
+
case 'thinking':
|
|
395
|
+
case 'waiting':
|
|
396
|
+
return 'thinking';
|
|
397
|
+
case 'tool':
|
|
398
|
+
case 'streaming':
|
|
399
|
+
return 'working';
|
|
400
|
+
case 'complete':
|
|
401
|
+
return 'complete';
|
|
402
|
+
case 'error':
|
|
403
|
+
return 'error';
|
|
404
|
+
default:
|
|
405
|
+
return 'idle';
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
async function decodePortraitImage(image) {
|
|
409
|
+
try {
|
|
410
|
+
if (typeof image.decode === 'function')
|
|
411
|
+
await image.decode();
|
|
412
|
+
return image.naturalWidth > 0 || image.complete;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return image.complete && image.naturalWidth > 0;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const PORTRAIT_SOURCES = [
|
|
419
|
+
['idle', XIAOHEI_BREAKFRAME_IDLE],
|
|
420
|
+
['blink', XIAOHEI_BREAKFRAME_BLINK],
|
|
421
|
+
['brace', XIAOHEI_BREAKFRAME_BRACE],
|
|
422
|
+
['reach', XIAOHEI_BREAKFRAME_REACH],
|
|
423
|
+
['thinking', XIAOHEI_BREAKFRAME_THINKING],
|
|
424
|
+
['working', XIAOHEI_BREAKFRAME_WORKING],
|
|
425
|
+
['complete', XIAOHEI_BREAKFRAME_COMPLETE],
|
|
426
|
+
['error', XIAOHEI_BREAKFRAME_ERROR],
|
|
427
|
+
];
|
|
428
|
+
function createPortraitImages(doc, layer) {
|
|
429
|
+
return PORTRAIT_SOURCES.map(([state, source]) => createAvatarImage(doc, source, `${layer}-${state}`));
|
|
430
|
+
}
|
|
431
|
+
function createBreakoutRightImages(doc) {
|
|
432
|
+
return PORTRAIT_SOURCES
|
|
433
|
+
.filter(([state]) => state === 'brace' || state === 'reach')
|
|
434
|
+
.map(([state, source]) => createAvatarImage(doc, source, `breakout-right-${state}`));
|
|
435
|
+
}
|
|
436
|
+
function createAvatarLayer(doc, name) {
|
|
437
|
+
const layer = doc.createElement('span');
|
|
438
|
+
layer.className = `${XIAOHEI_COMPOSER_AVATAR_CLASS}__${name}`;
|
|
439
|
+
return layer;
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=composer-avatar.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Keep idle blinks sparse without making their rhythm mechanical. */
|
|
2
|
+
export declare function resolveXiaoheiSendBlinkDelay(random?: () => number): number;
|
|
3
|
+
/**
|
|
4
|
+
* Turn the Host's real primary button into Heixiu without replacing its click,
|
|
5
|
+
* keyboard, disabled, tooltip, or accessible-name behaviour.
|
|
6
|
+
*/
|
|
7
|
+
export declare function installXiaoheiComposerSendHeixiu(doc?: Document | undefined): () => void;
|
|
8
|
+
//# sourceMappingURL=composer-send-heixiu.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { XIAOHEI_HEIXIU_BLINK, XIAOHEI_HEIXIU_OPEN } from './generated-keyart.js';
|
|
2
|
+
import { XIAOHEI_HOST_SELECTORS } from './host-contract.js';
|
|
3
|
+
import { subscribeXiaoheiHostDom } from './host-dom.js';
|
|
4
|
+
const SEND_BUTTON_SELECTOR = ':scope > :last-child > :last-child > button:last-child';
|
|
5
|
+
const SEND_ATTRIBUTE = 'data-xiaohei-send-heixiu';
|
|
6
|
+
const BLINK_ATTRIBUTE = 'data-xiaohei-blink';
|
|
7
|
+
const HEIXIU_CLASS = 'xiaohei-composer-send-heixiu';
|
|
8
|
+
const BLINK_DELAY_MIN_MS = 4600;
|
|
9
|
+
const BLINK_DELAY_MAX_MS = 8200;
|
|
10
|
+
const BLINK_CLOSED_MS = 128;
|
|
11
|
+
/** Keep idle blinks sparse without making their rhythm mechanical. */
|
|
12
|
+
export function resolveXiaoheiSendBlinkDelay(random = Math.random) {
|
|
13
|
+
const sample = Math.min(1, Math.max(0, random()));
|
|
14
|
+
return Math.round(BLINK_DELAY_MIN_MS + sample * (BLINK_DELAY_MAX_MS - BLINK_DELAY_MIN_MS));
|
|
15
|
+
}
|
|
16
|
+
function createFrame(doc, source, className) {
|
|
17
|
+
const image = doc.createElement('img');
|
|
18
|
+
image.className = className;
|
|
19
|
+
image.src = source;
|
|
20
|
+
image.alt = '';
|
|
21
|
+
image.draggable = false;
|
|
22
|
+
return image;
|
|
23
|
+
}
|
|
24
|
+
function createHeixiu(doc) {
|
|
25
|
+
const element = doc.createElement('span');
|
|
26
|
+
element.className = HEIXIU_CLASS;
|
|
27
|
+
element.setAttribute('aria-hidden', 'true');
|
|
28
|
+
element.append(createFrame(doc, XIAOHEI_HEIXIU_OPEN, `${HEIXIU_CLASS}__open`), createFrame(doc, XIAOHEI_HEIXIU_BLINK, `${HEIXIU_CLASS}__blink`));
|
|
29
|
+
return element;
|
|
30
|
+
}
|
|
31
|
+
/** The Host renders a rect only when the primary action has become Stop. */
|
|
32
|
+
function isSendAction(button) {
|
|
33
|
+
const hostGlyph = [...button.children].find(child => child.localName === 'svg');
|
|
34
|
+
return hostGlyph !== undefined && hostGlyph.querySelector('rect') === null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Turn the Host's real primary button into Heixiu without replacing its click,
|
|
38
|
+
* keyboard, disabled, tooltip, or accessible-name behaviour.
|
|
39
|
+
*/
|
|
40
|
+
export function installXiaoheiComposerSendHeixiu(doc = typeof document === 'undefined' ? undefined : document) {
|
|
41
|
+
if (doc?.body === undefined || doc.defaultView === null)
|
|
42
|
+
return () => { };
|
|
43
|
+
const win = doc.defaultView;
|
|
44
|
+
const reducedMotion = win.matchMedia('(prefers-reduced-motion: reduce)');
|
|
45
|
+
const decorations = new Map();
|
|
46
|
+
let blinkTimer = 0;
|
|
47
|
+
let reopenTimer = 0;
|
|
48
|
+
const canBlink = () => (decorations.size > 0
|
|
49
|
+
&& doc.visibilityState !== 'hidden'
|
|
50
|
+
&& !reducedMotion.matches);
|
|
51
|
+
const setClosed = (closed) => {
|
|
52
|
+
for (const decoration of decorations.values()) {
|
|
53
|
+
if (closed)
|
|
54
|
+
decoration.setAttribute(BLINK_ATTRIBUTE, 'closed');
|
|
55
|
+
else
|
|
56
|
+
decoration.removeAttribute(BLINK_ATTRIBUTE);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const clearTimers = () => {
|
|
60
|
+
if (blinkTimer !== 0)
|
|
61
|
+
win.clearTimeout(blinkTimer);
|
|
62
|
+
if (reopenTimer !== 0)
|
|
63
|
+
win.clearTimeout(reopenTimer);
|
|
64
|
+
blinkTimer = 0;
|
|
65
|
+
reopenTimer = 0;
|
|
66
|
+
};
|
|
67
|
+
const scheduleBlink = () => {
|
|
68
|
+
if (!canBlink() || blinkTimer !== 0 || reopenTimer !== 0)
|
|
69
|
+
return;
|
|
70
|
+
blinkTimer = win.setTimeout(() => {
|
|
71
|
+
blinkTimer = 0;
|
|
72
|
+
if (!canBlink())
|
|
73
|
+
return;
|
|
74
|
+
setClosed(true);
|
|
75
|
+
reopenTimer = win.setTimeout(() => {
|
|
76
|
+
reopenTimer = 0;
|
|
77
|
+
setClosed(false);
|
|
78
|
+
scheduleBlink();
|
|
79
|
+
}, BLINK_CLOSED_MS);
|
|
80
|
+
}, resolveXiaoheiSendBlinkDelay());
|
|
81
|
+
};
|
|
82
|
+
const restartBlink = () => {
|
|
83
|
+
clearTimers();
|
|
84
|
+
setClosed(false);
|
|
85
|
+
scheduleBlink();
|
|
86
|
+
};
|
|
87
|
+
const detach = (button, decoration) => {
|
|
88
|
+
decoration.remove();
|
|
89
|
+
button.removeAttribute(SEND_ATTRIBUTE);
|
|
90
|
+
decorations.delete(button);
|
|
91
|
+
};
|
|
92
|
+
const reconcile = () => {
|
|
93
|
+
const candidates = new Set();
|
|
94
|
+
for (const card of doc.querySelectorAll(XIAOHEI_HOST_SELECTORS.composerCard)) {
|
|
95
|
+
const button = card.querySelector(SEND_BUTTON_SELECTOR);
|
|
96
|
+
if (button !== null && isSendAction(button))
|
|
97
|
+
candidates.add(button);
|
|
98
|
+
}
|
|
99
|
+
const hadDecorations = decorations.size > 0;
|
|
100
|
+
for (const [button, decoration] of decorations) {
|
|
101
|
+
if (candidates.has(button) && decoration.parentElement === button)
|
|
102
|
+
continue;
|
|
103
|
+
detach(button, decoration);
|
|
104
|
+
}
|
|
105
|
+
for (const button of candidates) {
|
|
106
|
+
if (decorations.has(button))
|
|
107
|
+
continue;
|
|
108
|
+
const decoration = createHeixiu(doc);
|
|
109
|
+
button.setAttribute(SEND_ATTRIBUTE, 'true');
|
|
110
|
+
button.insertBefore(decoration, button.firstChild);
|
|
111
|
+
decorations.set(button, decoration);
|
|
112
|
+
}
|
|
113
|
+
if (!hadDecorations && decorations.size > 0)
|
|
114
|
+
scheduleBlink();
|
|
115
|
+
else if (hadDecorations && decorations.size === 0)
|
|
116
|
+
restartBlink();
|
|
117
|
+
};
|
|
118
|
+
const onEnvironmentChange = () => restartBlink();
|
|
119
|
+
reconcile();
|
|
120
|
+
const unsubscribeHostDom = subscribeXiaoheiHostDom(doc, reconcile);
|
|
121
|
+
doc.addEventListener('visibilitychange', onEnvironmentChange);
|
|
122
|
+
reducedMotion.addEventListener('change', onEnvironmentChange);
|
|
123
|
+
return () => {
|
|
124
|
+
clearTimers();
|
|
125
|
+
unsubscribeHostDom();
|
|
126
|
+
doc.removeEventListener('visibilitychange', onEnvironmentChange);
|
|
127
|
+
reducedMotion.removeEventListener('change', onEnvironmentChange);
|
|
128
|
+
for (const [button, decoration] of decorations)
|
|
129
|
+
detach(button, decoration);
|
|
130
|
+
decorations.clear();
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=composer-send-heixiu.js.map
|
package/lib/gaze.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { XIAOHEI_IDLE_EYE_BASE } from './generated-keyart.js';
|
|
2
2
|
import { XIAOHEI_GAZE_CSS } from './chrome/gaze.js';
|
|
3
|
+
import { subscribeXiaoheiHostDom } from './host-dom.js';
|
|
3
4
|
import { XIAOHEI_PORTAL_ACTIVITY_EVENT, XIAOHEI_PORTAL_LAYER_ID, } from './portal.js';
|
|
4
5
|
export const XIAOHEI_GAZE_STYLE_ID = 'dsh-theme-xiaohei/gaze-style';
|
|
5
6
|
const POINTER_FULL_GAZE_RADIUS_PX = 360;
|
|
@@ -106,8 +107,7 @@ export function installXiaoheiGaze(doc = typeof document === 'undefined' ? undef
|
|
|
106
107
|
};
|
|
107
108
|
const onPreferenceChange = () => wake();
|
|
108
109
|
const onStateChange = () => wake();
|
|
109
|
-
const
|
|
110
|
-
mountObserver.observe(doc.body, { childList: true, subtree: true });
|
|
110
|
+
const unsubscribeHostDom = subscribeXiaoheiHostDom(doc, attach);
|
|
111
111
|
const stateObserver = new win.MutationObserver(onStateChange);
|
|
112
112
|
stateObserver.observe(doc.documentElement, {
|
|
113
113
|
attributes: true,
|
|
@@ -124,7 +124,7 @@ export function installXiaoheiGaze(doc = typeof document === 'undefined' ? undef
|
|
|
124
124
|
disposed = true;
|
|
125
125
|
if (frameId !== 0)
|
|
126
126
|
win.cancelAnimationFrame(frameId);
|
|
127
|
-
|
|
127
|
+
unsubscribeHostDom();
|
|
128
128
|
stateObserver.disconnect();
|
|
129
129
|
doc.removeEventListener('pointermove', onPointerMove);
|
|
130
130
|
doc.removeEventListener('pointerleave', clearPointer);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Generated by scripts/embed-assets.mjs for atmosphere and independent lossless character poses. */
|
|
2
|
+
export declare const XIAOHEI_WALLPAPER_LIGHT: string;
|
|
3
|
+
export declare const XIAOHEI_WALLPAPER_DARK: string;
|
|
4
|
+
export declare const XIAOHEI_SEATED_LIGHT: string;
|
|
5
|
+
export declare const XIAOHEI_SEATED_DARK: string;
|
|
6
|
+
export declare const XIAOHEI_PEEK_LIGHT: string;
|
|
7
|
+
export declare const XIAOHEI_PEEK_DARK: string;
|
|
8
|
+
export declare const XIAOHEI_CHIN_LIGHT: string;
|
|
9
|
+
export declare const XIAOHEI_CHIN_DARK: string;
|
|
10
|
+
export declare const XIAOHEI_DOZE_LIGHT: string;
|
|
11
|
+
export declare const XIAOHEI_DOZE_DARK: string;
|
|
12
|
+
export declare const XIAOHEI_HALFPEEK_LIGHT: string;
|
|
13
|
+
export declare const XIAOHEI_HALFPEEK_DARK: string;
|
|
14
|
+
//# sourceMappingURL=generated-backgrounds.d.ts.map
|