agent-robot-avatar 0.3.0 → 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/CHANGELOG.md +25 -0
- package/README.md +9 -9
- package/agent-robot-avatar.js +0 -1
- package/docs/README.de.md +3 -3
- package/docs/README.es.md +3 -3
- package/docs/README.fr.md +3 -3
- package/docs/README.ja.md +3 -3
- package/docs/README.ko.md +3 -3
- package/docs/README.pt.md +3 -3
- package/docs/README.zh-CN.md +9 -9
- package/docs/README.zh-TW.md +3 -3
- package/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/agent-robot-avatar-core.js +57 -24
- package/src/agent-robot-avatar-runtime.js +192 -76
- package/src/agent-robot-avatar-version.js +1 -1
- package/src/agent-robot-avatar-runtime-fixes.js +0 -178
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ The project follows Semantic Versioning for public releases. Internal developmen
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.3.2] - 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Unified synchronous `size` attribute parsing so numeric and `px` values use the same validation immediately, without a transient fallback before runtime observers run.
|
|
14
|
+
- Added the existing public `sleep()`, `wake()`, and `input()` convenience methods to the TypeScript declaration surface and type checks.
|
|
15
|
+
- Stabilized request-replacement race tests by ordering competing clicks in the same browser task instead of depending on an 80 ms wall-clock window.
|
|
16
|
+
- Expanded package integrity coverage to require the runtime and geometry modules explicitly.
|
|
17
|
+
|
|
18
|
+
- Prevented the accessible request lifecycle example's delayed replacement step from starting after cancellation, a newer user request, or a newer replacement flow has invalidated it.
|
|
19
|
+
- Made the example's business status the only automatic live region; the visible request debug log remains available for deliberate reading without `role="log"` live semantics or whole-log text rewrites.
|
|
20
|
+
|
|
21
|
+
## [0.3.1] - 2026-09-10
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Prevented disconnect cleanup from recreating per-instance media-query listeners and observers; reattaching now initializes connected runtime resources once without emitting cleanup-only lifecycle events.
|
|
26
|
+
- Decoupled automatic sleep timing from frame rendering so `motion="reduce"` can pause continuous drawing without disabling `auto-sleep`, while active waiting/input continue to take priority.
|
|
27
|
+
- Ensured reduced-motion state changes commit their final static SVG before rendering pauses, including the fully closed sleep pose.
|
|
28
|
+
- Made `stopWaiting()` terminate the action it actually resets: waiting ends normally, other active program actions are cancelled once, and idle calls emit no false terminal event.
|
|
29
|
+
- Separated explicit `noteActivity()` calls from ambient DOM activity so host calls inside real event handlers keep API semantics while automatic wakeups continue to obey `wake-on`.
|
|
30
|
+
- Restricted action alias lookup to declared keys so prototype-like names such as `constructor` and `__proto__` fail before changing waiting/action state.
|
|
31
|
+
|
|
9
32
|
## [0.3.0] - 2026-09-10
|
|
10
33
|
|
|
11
34
|
### Added
|
|
@@ -93,6 +116,8 @@ First public release.
|
|
|
93
116
|
- npm-compatible package metadata for later package publication
|
|
94
117
|
- Contribution guide, issue templates, pull request template, MIT License, and Ko-fi funding metadata
|
|
95
118
|
|
|
119
|
+
[0.3.2]: https://github.com/CX-ArtLab/agent-robot-avatar/releases/tag/v0.3.2
|
|
120
|
+
[0.3.1]: https://github.com/CX-ArtLab/agent-robot-avatar/releases/tag/v0.3.1
|
|
96
121
|
[0.3.0]: https://github.com/CX-ArtLab/agent-robot-avatar/releases/tag/v0.3.0
|
|
97
122
|
[0.2.1]: https://github.com/CX-ArtLab/agent-robot-avatar/releases/tag/v0.2.1
|
|
98
123
|
[0.2.0]: https://github.com/CX-ARTLab/agent-robot-avatar/releases/tag/v0.2.0
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](./LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar can also be used as a visual feedback layer for AG-UI-style a
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar is built with SVG and vanilla JavaScript. It has no third-party animation framework dependency, works as a native custom element, and exposes a compact API for expressions, interaction states, pointer behavior, waiting feedback, and head-shape customization.
|
|
22
22
|
|
|
23
|
-
**Current public version: v0.3.
|
|
23
|
+
**Current public version: v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="./assets/demo/agent-robot-avatar-demo.gif" alt="Agent Robot Avatar interactive animation demo" width="560">
|
|
@@ -98,7 +98,7 @@ Return to the default idle state:
|
|
|
98
98
|
avatar.reset();
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
`reset()` cancels the current program action, pending waits, head animations, drag/rebound state, and delayed drag reactions, then returns to idle. Cancelled animation promises continue to resolve normally rather than rejecting. Removing the element performs the same internal cleanup without emitting cleanup-only `face-state` or `action-state` notifications. Reattaching starts from idle
|
|
101
|
+
`reset()` cancels the current program action, pending waits, head animations, drag/rebound state, and delayed drag reactions, then returns to idle. Cancelled animation promises continue to resolve normally rather than rejecting. Removing the element performs the same internal cleanup without emitting cleanup-only `face-state` or `action-state` notifications, and also tears down its per-instance media-query listener and observers without restarting rendering as a cleanup side effect. Reattaching starts from idle, preserves appearance and behavior attributes/settings, and installs connected runtime resources once.
|
|
102
102
|
|
|
103
103
|
A valid new action replaces the previous active action. The old semantic action receives `cancel`; it cannot resume later and overwrite the replacement. An unknown action throws before destructive cleanup. A no-op action such as `wake` while already awake leaves the current action unchanged.
|
|
104
104
|
|
|
@@ -147,7 +147,7 @@ avatar.startWaiting();
|
|
|
147
147
|
avatar.stopWaiting();
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
`startWaiting()` remains active until `stopWaiting()`, `reset()`, or another program action replaces it. A replacement reports `cancel`; `stopWaiting()` reports a normal `end`. Drag deformation remains available while waiting, but decorative drag expressions are suppressed and discarded rather than replayed after waiting ends.
|
|
150
|
+
`startWaiting()` remains active until `stopWaiting()`, `reset()`, or another program action replaces it. A replacement reports `cancel`; `stopWaiting()` reports a normal `end` when waiting is active. For backward compatibility, calling `stopWaiting()` during another active program action still returns the avatar to idle; that action now receives exactly one `cancel`. Calling it with no active action does not manufacture a lifecycle event. Drag deformation remains available while waiting, but decorative drag expressions are suppressed and discarded rather than replayed after waiting ends.
|
|
151
151
|
|
|
152
152
|
A typical request lifecycle can be written as:
|
|
153
153
|
|
|
@@ -182,7 +182,7 @@ avatar.addEventListener('action-state', (event) => {
|
|
|
182
182
|
- `phase`: `start`, followed by exactly one `end` or `cancel` for each action that actually starts.
|
|
183
183
|
- `source`: `api`, `interaction`, or `automatic`.
|
|
184
184
|
- Continuous actions such as `startWaiting()` and active input do not emit `end` just because one animation cycle completes.
|
|
185
|
-
- Replacement or `reset()` emits `cancel`; `stopWaiting()` emits `end` for waiting.
|
|
185
|
+
- Replacement or `reset()` emits `cancel`; `stopWaiting()` emits `end` for active waiting, or `cancel` for a different active action that it resets to idle.
|
|
186
186
|
- No-op calls emit no lifecycle event.
|
|
187
187
|
- Decorative drag feedback uses `action: "reaction"` and `source: "interaction"`, so it cannot be confused with an API-level task success/failure.
|
|
188
188
|
- The event bubbles and is `composed`, so it can be observed above the custom element/Shadow DOM boundary.
|
|
@@ -216,7 +216,7 @@ avatar.addEventListener('action-state', ({ detail }) => {
|
|
|
216
216
|
|
|
217
217
|
Use a normal `role="status"` for routine updates. Do not announce blinking, gaze changes, or decorative drag reactions, and do not automatically make every error an interruptive alert. If the host already announces the same status elsewhere, the avatar can remain decorative to avoid duplicate screen-reader output.
|
|
218
218
|
|
|
219
|
-
See [`examples/accessibility.html`](./examples/accessibility.html) for a runnable example.
|
|
219
|
+
See [`examples/accessibility.html`](./examples/accessibility.html) for a runnable request-lifecycle example covering waiting, success, failure, connection errors, host cancellation, request replacement, and stale-result protection. Its live-region text follows host request state rather than low-level face changes.
|
|
220
220
|
|
|
221
221
|
## Wake policy
|
|
222
222
|
|
|
@@ -232,7 +232,7 @@ The `wake-on` attribute controls automatic wake behavior:
|
|
|
232
232
|
| `interaction` | Only direct interaction with this avatar wakes it automatically. Activity elsewhere does not. |
|
|
233
233
|
| `manual` | Environment activity, including direct avatar interaction, does not automatically wake it. |
|
|
234
234
|
|
|
235
|
-
Explicit API intent remains explicit: `avatar.play('wake')` wakes the avatar in every mode, and `avatar.noteActivity()`
|
|
235
|
+
Explicit API intent remains explicit: `avatar.play('wake')` wakes the avatar in every mode, and `avatar.noteActivity()` records activity and wakes by default even when the host calls it from inside a real `keydown`, `pointerdown`, or `click` handler. Pass `false` to `noteActivity(false)` to record activity without waking. Automatic page/direct-interaction wakeups are handled separately and still obey `wake-on`; their `action-state` source is `automatic`, while an explicit host wake remains `api`. Other program actions may leave sleep when their action semantics require a new visible state. `reset()` always returns to idle.
|
|
236
236
|
|
|
237
237
|
Different avatar instances may use different `wake-on` values.
|
|
238
238
|
|
|
@@ -250,7 +250,7 @@ Use the `motion` attribute to control animation intensity:
|
|
|
250
250
|
| `reduce` | Keeps recognizable static states while suppressing continuous or elastic decorative motion. |
|
|
251
251
|
| `full` | Uses the normal animation style regardless of the system reduced-motion preference. |
|
|
252
252
|
|
|
253
|
-
Reduced mode suppresses continuous waiting orbits, idle wandering/blinking motion, head-follow inertia, elastic rebound, antenna spring motion, and status flashing where appropriate. Waiting/input lifecycles remain active until the host ends or replaces them, even when their visual state is static. Switching modes does not revive cancelled work or change `action-state` lifecycle semantics.
|
|
253
|
+
Reduced mode suppresses continuous waiting orbits, idle wandering/blinking motion, head-follow inertia, elastic rebound, antenna spring motion, and status flashing where appropriate. Waiting/input lifecycles remain active until the host ends or replaces them, even when their visual state is static. Automatic sleep uses an independent cancellable timer rather than the draw loop, so it still works while reduced motion has paused continuous rendering; activity and runtime `auto-sleep` changes reschedule it, and active waiting/input are not overwritten. Static state changes are committed before reduced rendering pauses, including the final closed-eye sleep pose. Switching modes does not revive cancelled work or change `action-state` lifecycle semantics.
|
|
254
254
|
|
|
255
255
|
## Pointer following
|
|
256
256
|
|
|
@@ -425,7 +425,7 @@ Issues and pull requests are welcome. See [`CONTRIBUTING.md`](./CONTRIBUTING.md)
|
|
|
425
425
|
|
|
426
426
|
## Project status
|
|
427
427
|
|
|
428
|
-
v0.3.
|
|
428
|
+
v0.3.2 is the current public release. v0.1.0 remains available as the first public version. The public API is intentionally small so it can evolve carefully before a future `1.0.0` stability commitment.
|
|
429
429
|
|
|
430
430
|
Agent Robot Avatar is independently developed and is not affiliated with, endorsed by, or representative of any AI platform or brand.
|
|
431
431
|
|
package/agent-robot-avatar.js
CHANGED
|
@@ -7,7 +7,6 @@ import './src/agent-robot-avatar-inspect.js';
|
|
|
7
7
|
import './src/agent-robot-avatar-failure.js';
|
|
8
8
|
import './src/agent-robot-avatar-head-roundness.js';
|
|
9
9
|
import './src/agent-robot-avatar-runtime.js';
|
|
10
|
-
import './src/agent-robot-avatar-runtime-fixes.js';
|
|
11
10
|
import { VERSION } from './src/agent-robot-avatar-version.js';
|
|
12
11
|
|
|
13
12
|
// Elements already present in markup can upgrade while the core module is
|
package/docs/README.de.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar kann außerdem als visuelle Feedback-Ebene für Agent-Oberfl
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar basiert auf SVG und Vanilla JavaScript und benötigt kein externes Animationsframework. Die Komponente funktioniert als natives Custom Element und stellt eine kompakte API für Ausdrücke, Interaktionszustände, Pointer-Following, Wartefeedback und Kopfform bereit.
|
|
22
22
|
|
|
23
|
-
**Aktuelle öffentliche Version: v0.3.
|
|
23
|
+
**Aktuelle öffentliche Version: v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Interaktive animierte Demo von Agent Robot Avatar" width="560">
|
|
@@ -153,7 +153,7 @@ Issues und Pull Requests sind willkommen. Bitte vor Änderungen [`CONTRIBUTING.m
|
|
|
153
153
|
|
|
154
154
|
## Projektstatus
|
|
155
155
|
|
|
156
|
-
v0.3.
|
|
156
|
+
v0.3.2 ist die aktuelle öffentliche Version. v0.1.0 bleibt als erste öffentliche Version verfügbar. Die öffentliche API bleibt bewusst klein, damit sie vor einer späteren Stabilitätszusage mit `1.0.0` sorgfältig weiterentwickelt werden kann.
|
|
157
157
|
|
|
158
158
|
Agent Robot Avatar ist ein unabhängig entwickeltes Open-Source-Projekt und steht in keiner offiziellen Verbindung zu einer KI-Plattform oder Marke.
|
|
159
159
|
|
package/docs/README.es.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar también puede funcionar como una capa de feedback visual par
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar está construido con SVG y JavaScript nativo, sin depender de frameworks de animación de terceros. Funciona como un Custom Element nativo y ofrece una API compacta para expresiones, estados de interacción, seguimiento del puntero, espera y forma de la cabeza.
|
|
22
22
|
|
|
23
|
-
**Versión pública actual: v0.3.
|
|
23
|
+
**Versión pública actual: v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Demostración animada interactiva de Agent Robot Avatar" width="560">
|
|
@@ -212,7 +212,7 @@ Se aceptan Issues y Pull Requests. Consulta [`CONTRIBUTING.md`](../CONTRIBUTING.
|
|
|
212
212
|
|
|
213
213
|
## Estado del proyecto
|
|
214
214
|
|
|
215
|
-
v0.3.
|
|
215
|
+
v0.3.2 es la versión pública actual. v0.1.0 continúa disponible como la primera versión pública. La API pública se mantiene deliberadamente pequeña para poder evolucionar con cuidado antes de un futuro compromiso de estabilidad `1.0.0`.
|
|
216
216
|
|
|
217
217
|
Agent Robot Avatar es un proyecto independiente de código abierto y no está afiliado, respaldado ni representa a ninguna plataforma o marca de IA.
|
|
218
218
|
|
package/docs/README.fr.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar peut également servir de couche de retour visuel pour les in
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar est construit avec SVG et JavaScript natif, sans framework d’animation tiers. Il fonctionne comme un Custom Element natif et propose une API compacte pour les expressions, les états d’interaction, le suivi du pointeur, l’attente et la forme de la tête.
|
|
22
22
|
|
|
23
|
-
**Version publique actuelle : v0.3.
|
|
23
|
+
**Version publique actuelle : v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Démonstration animée interactive d’Agent Robot Avatar" width="560">
|
|
@@ -212,7 +212,7 @@ Issues et Pull Requests sont les bienvenus. Consultez [`CONTRIBUTING.md`](../CON
|
|
|
212
212
|
|
|
213
213
|
## État du projet
|
|
214
214
|
|
|
215
|
-
v0.3.
|
|
215
|
+
v0.3.2 est la version publique actuelle. v0.1.0 reste disponible comme première version publique. L’API publique reste volontairement limitée afin d’évoluer avec prudence avant un futur engagement de stabilité `1.0.0`.
|
|
216
216
|
|
|
217
217
|
Agent Robot Avatar est un projet open source développé indépendamment ; il n’est affilié, approuvé ni représentatif d’aucune plateforme ou marque d’IA.
|
|
218
218
|
|
package/docs/README.ja.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar は、AG-UI スタイルの Agent インターフェースに
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar は SVG と Vanilla JavaScript だけで構築され、サードパーティー製アニメーションフレームワークに依存しません。ネイティブ Custom Element として動作し、表情、状態、ポインター追従、待機フィードバック、頭部形状をシンプルな API で制御できます。
|
|
22
22
|
|
|
23
|
-
**現在の公開バージョン:v0.3.
|
|
23
|
+
**現在の公開バージョン:v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Agent Robot Avatar インタラクティブアニメーションデモ" width="560">
|
|
@@ -212,7 +212,7 @@ Issue と Pull Request を歓迎します。提出前に [`CONTRIBUTING.md`](../
|
|
|
212
212
|
|
|
213
213
|
## プロジェクトの状態
|
|
214
214
|
|
|
215
|
-
v0.3.
|
|
215
|
+
v0.3.2 は現在の公開バージョンです。v0.1.0 は最初の公開バージョンとして引き続き利用できます。将来の `1.0.0` に向けて慎重に進化できるよう、公開 API は意図的に小さく保っています。
|
|
216
216
|
|
|
217
217
|
Agent Robot Avatar は独立して開発されたオープンソースプロジェクトであり、特定の AI プラットフォームやブランドとは提携・公式承認関係にありません。
|
|
218
218
|
|
package/docs/README.ko.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar는 AG-UI 스타일 Agent 인터페이스의 시각적 피드
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar는 SVG와 Vanilla JavaScript로만 구성되어 있으며 타사 애니메이션 프레임워크에 의존하지 않습니다. 네이티브 Custom Element로 동작하고 표정, 상호작용 상태, 포인터 추적, 대기 피드백, 머리 형태를 제어하는 간결한 API를 제공합니다.
|
|
22
22
|
|
|
23
|
-
**현재 공개 버전: v0.3.
|
|
23
|
+
**현재 공개 버전: v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Agent Robot Avatar 인터랙티브 애니메이션 데모" width="560">
|
|
@@ -212,7 +212,7 @@ Issue와 Pull Request를 환영합니다. 제출 전 [`CONTRIBUTING.md`](../CONT
|
|
|
212
212
|
|
|
213
213
|
## 프로젝트 상태
|
|
214
214
|
|
|
215
|
-
v0.3.
|
|
215
|
+
v0.3.2은 현재 공개 버전입니다. v0.1.0은 첫 공개 버전으로 계속 제공됩니다. 향후 `1.0.0` 안정성 약속 이전에 신중하게 발전시킬 수 있도록 공개 API를 의도적으로 작게 유지하고 있습니다.
|
|
216
216
|
|
|
217
217
|
Agent Robot Avatar는 독립적으로 개발된 오픈 소스 프로젝트이며 특정 AI 플랫폼이나 브랜드와 제휴하거나 공식 승인을 받은 프로젝트가 아닙니다.
|
|
218
218
|
|
package/docs/README.pt.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar também pode funcionar como uma camada de feedback visual par
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar é construído com SVG e JavaScript puro, sem dependência de frameworks de animação de terceiros. Funciona como um Custom Element nativo e oferece uma API compacta para expressões, estados de interação, seguimento do ponteiro, feedback de espera e formato da cabeça.
|
|
22
22
|
|
|
23
|
-
**Versão pública atual: v0.3.
|
|
23
|
+
**Versão pública atual: v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Demonstração animada interativa do Agent Robot Avatar" width="560">
|
|
@@ -212,7 +212,7 @@ Issues e Pull Requests são bem-vindos. Leia [`CONTRIBUTING.md`](../CONTRIBUTING
|
|
|
212
212
|
|
|
213
213
|
## Status do projeto
|
|
214
214
|
|
|
215
|
-
v0.3.
|
|
215
|
+
v0.3.2 é a versão pública atual. v0.1.0 continua disponível como a primeira versão pública. A API pública é mantida deliberadamente pequena para evoluir com cuidado antes de um futuro compromisso de estabilidade `1.0.0`.
|
|
216
216
|
|
|
217
217
|
Agent Robot Avatar é um projeto independente de código aberto e não é afiliado, endossado nem representa nenhuma plataforma ou marca de IA.
|
|
218
218
|
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
Agent Robot Avatar 基于 SVG 与原生 JavaScript 构建,不依赖第三方动画框架,作为原生自定义元素工作,并提供尽量精简的公开 API。
|
|
18
18
|
|
|
19
|
-
**当前公开版本:v0.3.
|
|
19
|
+
**当前公开版本:v0.3.2**
|
|
20
20
|
|
|
21
21
|
<p align="center">
|
|
22
22
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Agent Robot Avatar 交互动画演示" width="560">
|
|
@@ -83,7 +83,7 @@ avatar.play('error');
|
|
|
83
83
|
avatar.reset();
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
`reset()` 会统一取消当前程序动作、待完成计时、头部动画、拖拽/回弹和尚未触发的拖拽表情,然后回到 `idle`。被取消的动画 Promise 仍按原约定正常结束,不会因为取消而产生未处理拒绝。移除组件时会执行同类内部清理,但不会额外发送用于清理的 `face-state` 或 `action-state`
|
|
86
|
+
`reset()` 会统一取消当前程序动作、待完成计时、头部动画、拖拽/回弹和尚未触发的拖拽表情,然后回到 `idle`。被取消的动画 Promise 仍按原约定正常结束,不会因为取消而产生未处理拒绝。移除组件时会执行同类内部清理,但不会额外发送用于清理的 `face-state` 或 `action-state` 通知;同时会释放该实例的媒体偏好监听和观察器,清理过程不会反向重启渲染。重新挂载后从 `idle` 开始、保留配置,并只初始化一次连接期运行资源。
|
|
87
87
|
|
|
88
88
|
真正开始的新动作会替换旧动作;旧动作不能在之后恢复并覆盖新动作。未知动作会在破坏性清理前抛错。已经醒着时调用 `wake`、已经处于持续输入时再次调用 `input` 等无操作调用不会破坏当前动作,也不会产生虚假的语义生命周期事件。
|
|
89
89
|
|
|
@@ -130,7 +130,7 @@ avatar.startWaiting();
|
|
|
130
130
|
avatar.stopWaiting();
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
`startWaiting()` 会一直保持到 `stopWaiting()`、`reset()`
|
|
133
|
+
`startWaiting()` 会一直保持到 `stopWaiting()`、`reset()` 或新的程序动作将其替换。当前确实处于 waiting 时,`stopWaiting()` 对 waiting 的语义生命周期是正常 `end`。为保持既有行为,如果在其他活跃程序动作期间调用 `stopWaiting()`,头像仍会回到 `idle`,但该动作现在会且只会记录一次 `cancel`;没有活跃动作时不会制造虚假的终止事件。
|
|
134
134
|
|
|
135
135
|
等待、输入或其他尚未结束的程序动作期间仍可以拖拽产生形变,但拖拽表情不会覆盖程序动作;被抑制的拖拽表情会直接丢弃,不会在动作结束后补播。
|
|
136
136
|
|
|
@@ -167,7 +167,7 @@ avatar.addEventListener('action-state', (event) => {
|
|
|
167
167
|
- `phase`:`start`,之后且仅之后一个 `end` 或 `cancel`。
|
|
168
168
|
- `source`:`api`、`interaction`、`automatic`。
|
|
169
169
|
- 持续 waiting / input 不会因为单次视觉循环完成而产生 `end`。
|
|
170
|
-
- `stopWaiting()`
|
|
170
|
+
- `stopWaiting()` 在 waiting 活跃时正常记录 `end`;若它把其他活跃程序动作重置为 idle,则该动作记录 `cancel`。
|
|
171
171
|
- 无操作调用不产生虚假事件。
|
|
172
172
|
- 普通拖拽反馈使用 `action: "reaction"`、`source: "interaction"`,不会被误认为 API 发出的任务成功/失败。
|
|
173
173
|
- 事件会冒泡,并设置为 `composed`,可跨越组件的 Shadow DOM 边界监听。
|
|
@@ -199,7 +199,7 @@ avatar.addEventListener('action-state', ({ detail }) => {
|
|
|
199
199
|
|
|
200
200
|
不要播报眨眼、视线变化或普通拖拽反应;也不要默认把所有错误都做成打断式警报。如果宿主本身已经播报同一状态,可以把头像视为装饰,避免重复通知。
|
|
201
201
|
|
|
202
|
-
完整示例见 [`examples/accessibility.html`](../examples/accessibility.html)
|
|
202
|
+
完整示例见 [`examples/accessibility.html`](../examples/accessibility.html),其中覆盖等待、成功、失败、连接错误、宿主取消、新请求替换旧请求,以及旧请求迟到结果不能覆盖新状态;无障碍文字由宿主请求状态驱动,不重复播报底层表情变化。
|
|
203
203
|
|
|
204
204
|
## 睡眠唤醒策略
|
|
205
205
|
|
|
@@ -215,7 +215,7 @@ avatar.addEventListener('action-state', ({ detail }) => {
|
|
|
215
215
|
| `interaction` | 只有直接与这个头像交互时自动唤醒;页面其他区域活动不唤醒。 |
|
|
216
216
|
| `manual` | 页面活动和头像交互都不会自动唤醒,由宿主显式控制。 |
|
|
217
217
|
|
|
218
|
-
显式调用仍保持显式语义:`avatar.play('wake')`
|
|
218
|
+
显式调用仍保持显式语义:`avatar.play('wake')` 在所有模式下都可唤醒;`avatar.noteActivity()` 默认记录活动并唤醒,即使宿主是在真实 `keydown`、`pointerdown` 或 `click` 事件处理函数中调用它也一样。`noteActivity(false)` 只记录活动、不唤醒。页面环境活动与头像直接交互的自动唤醒会走独立来源并继续遵守 `wake-on`;自动唤醒的 `action-state` source 为 `automatic`,宿主显式唤醒仍为 `api`。其他程序动作可以按照动作语义离开睡眠;`reset()` 始终回到 `idle`。不同实例可以使用不同策略。
|
|
219
219
|
|
|
220
220
|
## 减少动态效果
|
|
221
221
|
|
|
@@ -231,7 +231,7 @@ avatar.addEventListener('action-state', ({ detail }) => {
|
|
|
231
231
|
| `reduce` | 保留可辨认的静态状态,减少持续、弹性和闪烁类动画。 |
|
|
232
232
|
| `full` | 始终使用普通动画,不受系统减少动态效果偏好影响。 |
|
|
233
233
|
|
|
234
|
-
reduce 模式会简化等待转圈、待机游走/眨眼、惯性跟随、弹性回弹、天线弹簧和闪烁等装饰动态,但 waiting / input
|
|
234
|
+
reduce 模式会简化等待转圈、待机游走/眨眼、惯性跟随、弹性回弹、天线弹簧和闪烁等装饰动态,但 waiting / input 等语义生命周期仍持续存在,直到宿主结束或替换。自动睡眠使用独立、可取消的计时调度,不依赖持续绘制,因此 reduce 已暂停绘制时仍能按 `auto-sleep` 进入睡眠;用户活动和运行时修改超时会重新调度,waiting/input 不会被自动睡眠覆盖。影响画面的静态状态会先提交最终 SVG 再暂停,包括最终闭眼的 sleep 姿态。切换 motion 不会恢复已经被取消的旧动作,也不会改变 `action-state` 的语义。
|
|
235
235
|
|
|
236
236
|
## 鼠标 / 指针跟随
|
|
237
237
|
|
|
@@ -379,7 +379,7 @@ LICENSE MIT License
|
|
|
379
379
|
|
|
380
380
|
## 项目状态
|
|
381
381
|
|
|
382
|
-
v0.3.
|
|
382
|
+
v0.3.2 是当前公开版本。当前刻意保持较小的公开 API,以便在未来 `1.0.0` 稳定性承诺之前继续谨慎演进。
|
|
383
383
|
|
|
384
384
|
Agent Robot Avatar 为独立开发的开源项目,不隶属于、代表或获得任何 AI 平台或品牌的官方背书。
|
|
385
385
|
|
package/docs/README.zh-TW.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
[English](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/README.md) | [简体中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-CN.md) | [繁體中文](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.zh-TW.md) | [日本語](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ja.md) | [한국어](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.ko.md) | [Español](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.es.md) | [Português](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.pt.md) | [Deutsch](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.de.md) | [Français](https://github.com/CX-ArtLab/agent-robot-avatar/blob/main/docs/README.fr.md)
|
|
8
8
|
|
|
9
|
-
 [](../LICENSE) [](https://github.com/CX-ArtLab/agent-robot-avatar/actions/workflows/validate.yml) [](https://ko-fi.com/P0E625WIOI)
|
|
10
10
|
|
|
11
11
|
      
|
|
12
12
|
|
|
@@ -20,7 +20,7 @@ Agent Robot Avatar 也可作為 AG-UI 類 Agent 介面的視覺回饋層。
|
|
|
20
20
|
|
|
21
21
|
Agent Robot Avatar 以 SVG 與原生 JavaScript 建構,不依賴第三方動畫框架。它作為原生自訂元素運作,並提供精簡 API 來控制表情、互動狀態、指標跟隨、等待回饋與頭部造型。
|
|
22
22
|
|
|
23
|
-
**目前公開版本:v0.3.
|
|
23
|
+
**目前公開版本:v0.3.2**
|
|
24
24
|
|
|
25
25
|
<p align="center">
|
|
26
26
|
<img src="../assets/demo/agent-robot-avatar-demo.gif" alt="Agent Robot Avatar 互動動畫展示" width="560">
|
|
@@ -214,7 +214,7 @@ LICENSE MIT License
|
|
|
214
214
|
|
|
215
215
|
## 專案狀態
|
|
216
216
|
|
|
217
|
-
v0.3.
|
|
217
|
+
v0.3.2 是目前的公開版本。v0.1.0 作為第一個公開版本繼續保留。公開 API 目前刻意保持精簡,讓專案能在未來 `1.0.0` 穩定性承諾前持續謹慎演進。
|
|
218
218
|
|
|
219
219
|
Agent Robot Avatar 是獨立開發的開源專案,不隸屬、代表或獲得任何 AI 平台或品牌的官方背書。
|
|
220
220
|
|
package/index.d.ts
CHANGED
|
@@ -76,6 +76,9 @@ export interface AgentRobotAvatarEventMap extends HTMLElementEventMap {
|
|
|
76
76
|
export declare class AgentRobotAvatar extends HTMLElement {
|
|
77
77
|
play(action: AgentRobotAvatarAction): this | Promise<void>;
|
|
78
78
|
reset(): this;
|
|
79
|
+
sleep(): this | Promise<void>;
|
|
80
|
+
wake(): this | Promise<void>;
|
|
81
|
+
input(active?: boolean): this | Promise<void>;
|
|
79
82
|
startWaiting(): Promise<this>;
|
|
80
83
|
stopWaiting(): this;
|
|
81
84
|
setPointerFollow(enabled?: boolean): this;
|
package/package.json
CHANGED
|
@@ -59,6 +59,13 @@ const AgentRobotAvatar = (() => {
|
|
|
59
59
|
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
60
60
|
const lerp = (a, b, t) => a + (b - a) * t;
|
|
61
61
|
const clonePose = p => ({...p});
|
|
62
|
+
const DEFAULT_SIZE = 112;
|
|
63
|
+
const parseSize = value => {
|
|
64
|
+
const text = String(value ?? '').trim();
|
|
65
|
+
if (!/^(?:\d+(?:\.\d+)?|\.\d+)(?:px)?$/i.test(text)) return DEFAULT_SIZE;
|
|
66
|
+
const numeric = Number.parseFloat(text);
|
|
67
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : DEFAULT_SIZE;
|
|
68
|
+
};
|
|
62
69
|
const connectedFaces = new Set();
|
|
63
70
|
|
|
64
71
|
const forEachConnectedFace = callback => {
|
|
@@ -73,8 +80,14 @@ const AgentRobotAvatar = (() => {
|
|
|
73
80
|
if (face._dragJelly.active) face._onDragMove(event);
|
|
74
81
|
});
|
|
75
82
|
};
|
|
76
|
-
const handleGlobalPointerDown =
|
|
77
|
-
|
|
83
|
+
const handleGlobalPointerDown = event => forEachConnectedFace(face => {
|
|
84
|
+
if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
|
|
85
|
+
else face.noteActivity();
|
|
86
|
+
});
|
|
87
|
+
const handleGlobalKeyDown = event => forEachConnectedFace(face => {
|
|
88
|
+
if (typeof face._noteEnvironmentActivity === 'function') face._noteEnvironmentActivity(event);
|
|
89
|
+
else face.noteActivity();
|
|
90
|
+
});
|
|
78
91
|
const handleGlobalPointerEnd = event => {
|
|
79
92
|
forEachConnectedFace(face => {
|
|
80
93
|
if (face._dragJelly.active) face._onDragEnd(event);
|
|
@@ -175,34 +188,55 @@ const AgentRobotAvatar = (() => {
|
|
|
175
188
|
}
|
|
176
189
|
|
|
177
190
|
disconnectedCallback() {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
// This callback is captured when the custom element is defined, so the
|
|
192
|
+
// runtime teardown must be entered from the registered core lifecycle.
|
|
193
|
+
this._runtimeDisconnecting = true;
|
|
194
|
+
try {
|
|
195
|
+
unregisterConnectedFace(this);
|
|
196
|
+
this.removeEventListener('pointerdown', this._boundDragStart);
|
|
197
|
+
this.removeEventListener('click', this._boundClickCapture, true);
|
|
198
|
+
// Removal cancels work; reattachment starts idle with appearance settings intact.
|
|
199
|
+
this._resetToIdle(false);
|
|
200
|
+
this._blinkAnim = null;
|
|
201
|
+
this._blink = 1;
|
|
202
|
+
this._pointer.active = false;
|
|
203
|
+
this._pointer.influence = 0;
|
|
204
|
+
const drag = this._dragJelly;
|
|
205
|
+
if (drag.pointerId != null) {
|
|
206
|
+
try { this.releasePointerCapture(drag.pointerId); } catch (_) {}
|
|
207
|
+
}
|
|
208
|
+
drag.active = false;
|
|
209
|
+
drag.pointerId = null;
|
|
210
|
+
this._finishDragReturn(null);
|
|
211
|
+
this._suppressClick = false;
|
|
212
|
+
this._running = false;
|
|
213
|
+
cancelAnimationFrame(this._raf);
|
|
214
|
+
this._raf = 0;
|
|
215
|
+
} finally {
|
|
216
|
+
this._teardownRuntime?.();
|
|
217
|
+
this._runtimeDisconnecting = false;
|
|
190
218
|
}
|
|
191
|
-
drag.active = false;
|
|
192
|
-
drag.pointerId = null;
|
|
193
|
-
this._finishDragReturn(null);
|
|
194
|
-
this._suppressClick = false;
|
|
195
|
-
this._running = false;
|
|
196
|
-
cancelAnimationFrame(this._raf);
|
|
197
|
-
this._raf = 0;
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
static get observedAttributes() { return ['color','size','auto-sleep']; }
|
|
201
222
|
attributeChangedCallback(name, oldV, newV) {
|
|
202
223
|
if (oldV === newV) return;
|
|
203
224
|
if (name === 'color') this._applyColor(newV);
|
|
204
|
-
if (name === 'size') this.
|
|
205
|
-
if (name === 'auto-sleep')
|
|
225
|
+
if (name === 'size') this._syncSizeAttribute();
|
|
226
|
+
if (name === 'auto-sleep') {
|
|
227
|
+
this._autoSleepMs = Number(newV) || 0;
|
|
228
|
+
this._scheduleAutoSleep?.();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_syncSizeAttribute() {
|
|
233
|
+
if (!this.hasAttribute('size')) {
|
|
234
|
+
this.style.removeProperty('--face-size');
|
|
235
|
+
return DEFAULT_SIZE;
|
|
236
|
+
}
|
|
237
|
+
const size = parseSize(this.getAttribute('size'));
|
|
238
|
+
this.style.setProperty('--face-size', `${size}px`);
|
|
239
|
+
return size;
|
|
206
240
|
}
|
|
207
241
|
|
|
208
242
|
_applyColor(value) {
|
|
@@ -1096,7 +1130,6 @@ const AgentRobotAvatar = (() => {
|
|
|
1096
1130
|
this._updateLook(ts, dt);
|
|
1097
1131
|
this._updateHeadFollow(ts, dt);
|
|
1098
1132
|
this._updateDragJelly(dt);
|
|
1099
|
-
this._autoSleep(ts);
|
|
1100
1133
|
this._draw(ts);
|
|
1101
1134
|
}
|
|
1102
1135
|
if (this._canPauseFrames(ts)) {
|
|
@@ -5,7 +5,6 @@ const basePlay = proto.play;
|
|
|
5
5
|
const baseNoteActivity = proto.noteActivity;
|
|
6
6
|
const baseSleep = proto.sleep;
|
|
7
7
|
const baseWake = proto.wake;
|
|
8
|
-
const baseAutoSleep = proto._autoSleep;
|
|
9
8
|
const baseResetToIdle = proto._resetToIdle;
|
|
10
9
|
const baseStartMorph = proto._startMorph;
|
|
11
10
|
const baseAnimateHead = proto._animateHead;
|
|
@@ -22,6 +21,7 @@ const baseCanPauseFrames = proto._canPauseFrames;
|
|
|
22
21
|
const baseInput = proto.input;
|
|
23
22
|
const baseStartWaiting = proto.startWaiting;
|
|
24
23
|
const baseStopWaiting = proto.stopWaiting;
|
|
24
|
+
const baseDraw = proto._draw;
|
|
25
25
|
|
|
26
26
|
const DEFAULT_SIZE = 112;
|
|
27
27
|
const ACTION_ALIASES = Object.freeze({
|
|
@@ -50,30 +50,10 @@ const ACTION_ALIASES = Object.freeze({
|
|
|
50
50
|
wake: 'wake',
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
let environmentEvent = null;
|
|
54
|
-
let environmentClearQueued = false;
|
|
55
|
-
|
|
56
|
-
function markEnvironmentEvent(event) {
|
|
57
|
-
environmentEvent = event;
|
|
58
|
-
if (environmentClearQueued) return;
|
|
59
|
-
environmentClearQueued = true;
|
|
60
|
-
queueMicrotask(() => {
|
|
61
|
-
environmentEvent = null;
|
|
62
|
-
environmentClearQueued = false;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Core already owns the single shared window listeners. Capture on document so
|
|
67
|
-
// policy context is available before the core's bubbling window handlers run,
|
|
68
|
-
// without adding a second global window listener set per runtime feature.
|
|
69
|
-
if (typeof document !== 'undefined') {
|
|
70
|
-
document.addEventListener('pointerdown', markEnvironmentEvent, true);
|
|
71
|
-
document.addEventListener('keydown', markEnvironmentEvent, true);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
53
|
function normalizeAction(name) {
|
|
75
54
|
const raw = String(name || '').trim().toLowerCase();
|
|
76
|
-
|
|
55
|
+
const canonical = Object.hasOwn(ACTION_ALIASES, raw) ? ACTION_ALIASES[raw] : null;
|
|
56
|
+
return { raw, canonical };
|
|
77
57
|
}
|
|
78
58
|
|
|
79
59
|
function wakePolicy(instance) {
|
|
@@ -90,10 +70,12 @@ function reducedMotion(instance) {
|
|
|
90
70
|
const requested = requestedMotion(instance);
|
|
91
71
|
if (requested === 'reduce') return true;
|
|
92
72
|
if (requested === 'full') return false;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
:
|
|
73
|
+
// A fresh query preserves the existing WebKit runtime-preference hardening
|
|
74
|
+
// without installing additional listeners.
|
|
75
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
76
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
77
|
+
}
|
|
78
|
+
return instance._runtimeMotionQuery?.matches === true;
|
|
97
79
|
}
|
|
98
80
|
|
|
99
81
|
function emitActionState(instance, record, phase, silent = false) {
|
|
@@ -115,6 +97,7 @@ function cancelActiveAction(instance, { silent = false } = {}) {
|
|
|
115
97
|
}
|
|
116
98
|
|
|
117
99
|
function beginAction(instance, action, source, continuous = false) {
|
|
100
|
+
clearAutoSleep(instance);
|
|
118
101
|
cancelActiveAction(instance);
|
|
119
102
|
const record = {
|
|
120
103
|
id: (instance._actionStateSequence || 0) + 1,
|
|
@@ -134,6 +117,7 @@ function finishAction(instance, record, phase = 'end') {
|
|
|
134
117
|
if (instance._activeActionState !== record) return;
|
|
135
118
|
instance._activeActionState = null;
|
|
136
119
|
emitActionState(instance, record, phase);
|
|
120
|
+
scheduleAutoSleep(instance);
|
|
137
121
|
}
|
|
138
122
|
|
|
139
123
|
function trackFiniteAction(instance, record, result) {
|
|
@@ -148,24 +132,9 @@ function trackFiniteAction(instance, record, result) {
|
|
|
148
132
|
}
|
|
149
133
|
}
|
|
150
134
|
|
|
151
|
-
function parseSize(value) {
|
|
152
|
-
const text = String(value ?? '').trim();
|
|
153
|
-
const match = text.match(/^(?:\d+(?:\.\d+)?|\.\d+)(?:px)?$/i);
|
|
154
|
-
if (!match) return DEFAULT_SIZE;
|
|
155
|
-
const numeric = Number.parseFloat(text);
|
|
156
|
-
if (!Number.isFinite(numeric) || numeric <= 0) return DEFAULT_SIZE;
|
|
157
|
-
return numeric;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
135
|
function applySize(instance) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
instance._runtimeSizeApplied = false;
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const size = parseSize(instance.getAttribute('size'));
|
|
167
|
-
instance.style.setProperty('--face-size', `${size}px`);
|
|
168
|
-
instance._runtimeSizeApplied = true;
|
|
136
|
+
instance._syncSizeAttribute();
|
|
137
|
+
instance._runtimeSizeApplied = instance.hasAttribute('size');
|
|
169
138
|
}
|
|
170
139
|
|
|
171
140
|
function hasVisibleLayout(instance) {
|
|
@@ -182,6 +151,98 @@ function cancelHeadAnimations(instance) {
|
|
|
182
151
|
if (instance._headMotion) instance._headMotion.style.transform = '';
|
|
183
152
|
}
|
|
184
153
|
|
|
154
|
+
|
|
155
|
+
function cancelContinuousMotion(instance) {
|
|
156
|
+
cancelHeadAnimations(instance);
|
|
157
|
+
instance._blinkAnim = null;
|
|
158
|
+
instance._blink = 1;
|
|
159
|
+
instance._boredRoutine = null;
|
|
160
|
+
instance._boredLookSpeed = null;
|
|
161
|
+
if (instance._wanderTarget) instance._wanderTarget.x = instance._wanderTarget.y = 0;
|
|
162
|
+
if (instance._wander) instance._wander.x = instance._wander.y = 0;
|
|
163
|
+
if (instance._look) instance._look.x = instance._look.y = 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function dragAtRest(drag) {
|
|
167
|
+
if (!drag || drag.active || drag.returning || drag.pendingReaction) return false;
|
|
168
|
+
return Math.abs(drag.x) < 0.0001 && Math.abs(drag.y) < 0.0001 &&
|
|
169
|
+
Math.abs(drag.vx) < 0.0001 && Math.abs(drag.vy) < 0.0001 &&
|
|
170
|
+
Math.abs(drag.stretch) < 0.0001 && Math.abs(drag.shear) < 0.0001 &&
|
|
171
|
+
Math.abs(drag.pullX) < 0.0001 && Math.abs(drag.pullY) < 0.0001;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function clearIdleDragVisuals(instance) {
|
|
175
|
+
if (!dragAtRest(instance._dragJelly)) return;
|
|
176
|
+
if (instance._dragMotion) {
|
|
177
|
+
instance._dragMotion.style.transform = '';
|
|
178
|
+
instance._dragMotion.style.transformOrigin = '';
|
|
179
|
+
}
|
|
180
|
+
if (instance._headShape && instance._baseHeadPathD) {
|
|
181
|
+
instance._headShape.setAttribute('d', instance._baseHeadPathD);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function clearAutoSleep(instance) {
|
|
186
|
+
if (!instance._runtimeAutoSleepTimer) return;
|
|
187
|
+
clearTimeout(instance._runtimeAutoSleepTimer);
|
|
188
|
+
instance._runtimeAutoSleepTimer = 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function autoSleepBlocked(instance) {
|
|
192
|
+
const record = instance._activeActionState;
|
|
193
|
+
return Boolean(
|
|
194
|
+
!instance.isConnected ||
|
|
195
|
+
instance._runtimeDisconnecting ||
|
|
196
|
+
instance._sleeping ||
|
|
197
|
+
(record && !record.terminal && record.source !== 'interaction') ||
|
|
198
|
+
instance._waitingRequested ||
|
|
199
|
+
instance._waitingFx ||
|
|
200
|
+
instance._inputWanted ||
|
|
201
|
+
instance._state === 'input' ||
|
|
202
|
+
instance._expressionLock
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function scheduleAutoSleep(instance) {
|
|
207
|
+
clearAutoSleep(instance);
|
|
208
|
+
const delayMs = Number(instance._autoSleepMs);
|
|
209
|
+
if (!Number.isFinite(delayMs) || delayMs <= 0 || autoSleepBlocked(instance)) return;
|
|
210
|
+
const remaining = Math.max(0, (instance._lastActivity + delayMs) - performance.now());
|
|
211
|
+
instance._runtimeAutoSleepTimer = setTimeout(() => {
|
|
212
|
+
instance._runtimeAutoSleepTimer = 0;
|
|
213
|
+
if (autoSleepBlocked(instance)) return;
|
|
214
|
+
const currentDelay = Number(instance._autoSleepMs);
|
|
215
|
+
if (!Number.isFinite(currentDelay) || currentDelay <= 0) return;
|
|
216
|
+
const nextRemaining = (instance._lastActivity + currentDelay) - performance.now();
|
|
217
|
+
if (nextRemaining > 1) {
|
|
218
|
+
scheduleAutoSleep(instance);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const previousSource = instance._runtimeActionSource;
|
|
222
|
+
instance._runtimeActionSource = 'automatic';
|
|
223
|
+
try {
|
|
224
|
+
instance.sleep();
|
|
225
|
+
} finally {
|
|
226
|
+
instance._runtimeActionSource = previousSource;
|
|
227
|
+
}
|
|
228
|
+
}, remaining);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function requestVisualCommit(instance) {
|
|
232
|
+
if (!instance.isConnected || instance._runtimeDisconnecting) return;
|
|
233
|
+
instance._runtimeVisualDirty = true;
|
|
234
|
+
instance._resumeFrames?.();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function syncMotionPreference(instance) {
|
|
238
|
+
const reduced = reducedMotion(instance);
|
|
239
|
+
if (instance._runtimeLastReducedMotion === reduced) return reduced;
|
|
240
|
+
instance._runtimeLastReducedMotion = reduced;
|
|
241
|
+
instance._runtimeVisualDirty = true;
|
|
242
|
+
if (reduced) cancelContinuousMotion(instance);
|
|
243
|
+
return reduced;
|
|
244
|
+
}
|
|
245
|
+
|
|
185
246
|
function clearDragGeometry(instance, { preserveClick = false, releaseCapture = true } = {}) {
|
|
186
247
|
const drag = instance._dragJelly;
|
|
187
248
|
if (!drag) return;
|
|
@@ -298,26 +359,20 @@ function setRuntimeVisible(instance, visible) {
|
|
|
298
359
|
}
|
|
299
360
|
|
|
300
361
|
function handleMotionChange(instance) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
instance
|
|
304
|
-
instance._blink = 1;
|
|
305
|
-
instance._boredRoutine = null;
|
|
306
|
-
instance._boredLookSpeed = null;
|
|
307
|
-
instance._wanderTarget.x = 0;
|
|
308
|
-
instance._wanderTarget.y = 0;
|
|
309
|
-
instance._look.x = 0;
|
|
310
|
-
instance._look.y = 0;
|
|
362
|
+
const reduced = syncMotionPreference(instance);
|
|
363
|
+
if (reduced) {
|
|
364
|
+
cancelContinuousMotion(instance);
|
|
311
365
|
instance._pose = { ...instance._toPose };
|
|
312
366
|
instance._fromPose = { ...instance._toPose };
|
|
313
367
|
instance._morphDuration = 0;
|
|
314
368
|
if (instance._dragJelly?.returning) finishDragReturn(instance, instance._dragJelly.pendingReaction);
|
|
315
369
|
}
|
|
316
|
-
instance
|
|
370
|
+
requestVisualCommit(instance);
|
|
371
|
+
scheduleAutoSleep(instance);
|
|
317
372
|
}
|
|
318
373
|
|
|
319
374
|
function ensureRuntime(instance) {
|
|
320
|
-
if (instance._runtimePolicyReady) return;
|
|
375
|
+
if (instance._runtimePolicyReady || !instance.isConnected || instance._runtimeDisconnecting) return;
|
|
321
376
|
instance._runtimePolicyReady = true;
|
|
322
377
|
if (!instance.style.touchAction) instance.style.touchAction = 'pinch-zoom';
|
|
323
378
|
applySize(instance);
|
|
@@ -334,15 +389,18 @@ function ensureRuntime(instance) {
|
|
|
334
389
|
if (typeof MutationObserver !== 'undefined') {
|
|
335
390
|
instance._runtimeAttributeObserver = new MutationObserver(records => {
|
|
336
391
|
let motionChanged = false;
|
|
392
|
+
let autoSleepChanged = false;
|
|
337
393
|
for (const record of records) {
|
|
338
394
|
if (record.attributeName === 'size') applySize(instance);
|
|
339
395
|
if (record.attributeName === 'motion') motionChanged = true;
|
|
396
|
+
if (record.attributeName === 'auto-sleep') autoSleepChanged = true;
|
|
340
397
|
}
|
|
341
398
|
if (motionChanged) handleMotionChange(instance);
|
|
399
|
+
if (autoSleepChanged) scheduleAutoSleep(instance);
|
|
342
400
|
});
|
|
343
401
|
instance._runtimeAttributeObserver.observe(instance, {
|
|
344
402
|
attributes: true,
|
|
345
|
-
attributeFilter: ['size', 'motion', 'wake-on'],
|
|
403
|
+
attributeFilter: ['size', 'motion', 'wake-on', 'auto-sleep'],
|
|
346
404
|
});
|
|
347
405
|
}
|
|
348
406
|
|
|
@@ -380,9 +438,12 @@ function ensureRuntime(instance) {
|
|
|
380
438
|
};
|
|
381
439
|
instance._runtimeLoopWrapped = true;
|
|
382
440
|
}
|
|
441
|
+
scheduleAutoSleep(instance);
|
|
383
442
|
}
|
|
384
443
|
|
|
385
444
|
function teardownRuntime(instance) {
|
|
445
|
+
clearAutoSleep(instance);
|
|
446
|
+
cancelContinuousMotion(instance);
|
|
386
447
|
instance._runtimeIntersectionObserver?.disconnect();
|
|
387
448
|
instance._runtimeResizeObserver?.disconnect();
|
|
388
449
|
instance._runtimeAttributeObserver?.disconnect();
|
|
@@ -404,6 +465,10 @@ function teardownRuntime(instance) {
|
|
|
404
465
|
instance._runtimeLostCapture = null;
|
|
405
466
|
instance._runtimePolicyReady = false;
|
|
406
467
|
instance._runtimeVisible = false;
|
|
468
|
+
instance._runtimeLastReducedMotion = undefined;
|
|
469
|
+
instance._runtimeVisualDirty = false;
|
|
470
|
+
instance._pendingPointerEvent = null;
|
|
471
|
+
instance._pendingDragEvent = null;
|
|
407
472
|
}
|
|
408
473
|
|
|
409
474
|
function eventTargetsAvatar(event, instance) {
|
|
@@ -418,19 +483,28 @@ proto._isReducedMotion = function() {
|
|
|
418
483
|
|
|
419
484
|
proto.noteActivity = function(wake = true) {
|
|
420
485
|
ensureRuntime(this);
|
|
421
|
-
|
|
486
|
+
const shouldWake = wake !== false && (this._sleeping || this._state === 'sleep');
|
|
487
|
+
const result = baseNoteActivity.call(this, false);
|
|
488
|
+
scheduleAutoSleep(this);
|
|
489
|
+
if (shouldWake) this.wake();
|
|
490
|
+
return result;
|
|
491
|
+
};
|
|
422
492
|
|
|
423
|
-
|
|
424
|
-
|
|
493
|
+
proto._noteEnvironmentActivity = function(event) {
|
|
494
|
+
ensureRuntime(this);
|
|
495
|
+
const shouldConsiderWake = this._sleeping || this._state === 'sleep';
|
|
496
|
+
const result = baseNoteActivity.call(this, false);
|
|
497
|
+
scheduleAutoSleep(this);
|
|
498
|
+
if (!shouldConsiderWake) return result;
|
|
425
499
|
|
|
426
500
|
const policy = wakePolicy(this);
|
|
427
501
|
const shouldWake = policy === 'activity' || (policy === 'interaction' && eventTargetsAvatar(event, this));
|
|
428
|
-
if (!shouldWake) return
|
|
502
|
+
if (!shouldWake) return result;
|
|
429
503
|
|
|
430
504
|
const previousSource = this._runtimeActionSource;
|
|
431
505
|
this._runtimeActionSource = 'automatic';
|
|
432
506
|
try {
|
|
433
|
-
return
|
|
507
|
+
return this.wake();
|
|
434
508
|
} finally {
|
|
435
509
|
this._runtimeActionSource = previousSource;
|
|
436
510
|
}
|
|
@@ -443,11 +517,15 @@ proto._resetToIdle = function(notify = true) {
|
|
|
443
517
|
return baseResetToIdle.call(this, notify);
|
|
444
518
|
} finally {
|
|
445
519
|
this._runtimeResetNotify = previous;
|
|
520
|
+
scheduleAutoSleep(this);
|
|
446
521
|
}
|
|
447
522
|
};
|
|
448
523
|
|
|
449
524
|
proto._startMorph = function(target, duration, ease) {
|
|
450
|
-
|
|
525
|
+
const reduced = reducedMotion(this);
|
|
526
|
+
const result = baseStartMorph.call(this, target, reduced ? 0 : duration, ease);
|
|
527
|
+
if (reduced) requestVisualCommit(this);
|
|
528
|
+
return result;
|
|
451
529
|
};
|
|
452
530
|
|
|
453
531
|
proto._animateHead = function(frames, duration) {
|
|
@@ -481,8 +559,12 @@ proto._updateHeadFollow = function(now, dt) {
|
|
|
481
559
|
};
|
|
482
560
|
|
|
483
561
|
proto._updateDragJelly = function(dt) {
|
|
484
|
-
if (!reducedMotion(this)) return baseUpdateDragJelly.call(this, dt);
|
|
485
562
|
const drag = this._dragJelly;
|
|
563
|
+
if (!reducedMotion(this)) {
|
|
564
|
+
const result = baseUpdateDragJelly.call(this, dt);
|
|
565
|
+
clearIdleDragVisuals(this);
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
486
568
|
if (!drag) return;
|
|
487
569
|
if (drag.active) {
|
|
488
570
|
drag.x = drag.targetX;
|
|
@@ -495,6 +577,7 @@ proto._updateDragJelly = function(dt) {
|
|
|
495
577
|
} else if (drag.returning) {
|
|
496
578
|
finishDragReturn(this, drag.pendingReaction);
|
|
497
579
|
}
|
|
580
|
+
clearIdleDragVisuals(this);
|
|
498
581
|
};
|
|
499
582
|
|
|
500
583
|
proto._onPointerMove = function(event) {
|
|
@@ -542,15 +625,25 @@ proto._finishDragReturn = function(reaction) {
|
|
|
542
625
|
};
|
|
543
626
|
|
|
544
627
|
proto._resumeFrames = function() {
|
|
628
|
+
if (!this.isConnected || this._runtimeDisconnecting) return;
|
|
545
629
|
ensureRuntime(this);
|
|
546
630
|
if (this._runtimeVisible === false) return;
|
|
631
|
+
if (syncMotionPreference(this)) this._runtimeVisualDirty = true;
|
|
547
632
|
return baseResumeFrames.call(this);
|
|
548
633
|
};
|
|
549
634
|
|
|
635
|
+
proto._draw = function(now) {
|
|
636
|
+
syncMotionPreference(this);
|
|
637
|
+
const result = baseDraw.call(this, now);
|
|
638
|
+
this._runtimeVisualDirty = false;
|
|
639
|
+
return result;
|
|
640
|
+
};
|
|
641
|
+
|
|
550
642
|
proto._canPauseFrames = function(now) {
|
|
551
643
|
if (this._runtimeVisible === false) return true;
|
|
552
|
-
|
|
553
|
-
if (
|
|
644
|
+
const reduced = syncMotionPreference(this);
|
|
645
|
+
if (!reduced) return baseCanPauseFrames.call(this, now);
|
|
646
|
+
if (this._pendingPointerEvent || this._pendingDragEvent || this._runtimeVisualDirty) return false;
|
|
554
647
|
return true;
|
|
555
648
|
};
|
|
556
649
|
|
|
@@ -568,6 +661,7 @@ function leaveSleepForAction(instance) {
|
|
|
568
661
|
}
|
|
569
662
|
|
|
570
663
|
function prepareReplacement(instance, canonical) {
|
|
664
|
+
clearAutoSleep(instance);
|
|
571
665
|
cancelActiveAction(instance);
|
|
572
666
|
clearDragGeometry(instance, { preserveClick: true });
|
|
573
667
|
instance._cancelPendingWaits?.();
|
|
@@ -618,6 +712,7 @@ proto.input = function(active = true) {
|
|
|
618
712
|
|
|
619
713
|
proto.startWaiting = async function() {
|
|
620
714
|
ensureRuntime(this);
|
|
715
|
+
clearAutoSleep(this);
|
|
621
716
|
const result = await baseStartWaiting.call(this);
|
|
622
717
|
if (this._waitingRequested && this._waitingFx) beginAction(this, 'waiting', 'api', true);
|
|
623
718
|
return result;
|
|
@@ -626,7 +721,7 @@ proto.startWaiting = async function() {
|
|
|
626
721
|
proto.stopWaiting = function() {
|
|
627
722
|
ensureRuntime(this);
|
|
628
723
|
const record = this._activeActionState?.action === 'waiting' ? this._activeActionState : null;
|
|
629
|
-
this._runtimeEndingWaiting =
|
|
724
|
+
this._runtimeEndingWaiting = Boolean(record);
|
|
630
725
|
let result;
|
|
631
726
|
try {
|
|
632
727
|
result = baseStopWaiting.call(this);
|
|
@@ -661,14 +756,16 @@ proto.wake = function() {
|
|
|
661
756
|
return result;
|
|
662
757
|
};
|
|
663
758
|
|
|
664
|
-
proto._autoSleep = function(
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
759
|
+
proto._autoSleep = function() {
|
|
760
|
+
// Auto-sleep is scheduled independently from drawing by scheduleAutoSleep().
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
proto._scheduleAutoSleep = function() {
|
|
764
|
+
scheduleAutoSleep(this);
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
proto._teardownRuntime = function() {
|
|
768
|
+
teardownRuntime(this);
|
|
672
769
|
};
|
|
673
770
|
|
|
674
771
|
function setMask(top, bottom, topY, bottomY, topAngle = 0, bottomAngle = 0) {
|
|
@@ -721,6 +818,26 @@ function drawReducedState(instance) {
|
|
|
721
818
|
}
|
|
722
819
|
}
|
|
723
820
|
|
|
821
|
+
for (const property of ['_waitingFx', '_inspectFx', '_failureFx', '_warningFx', '_systemErrorShake']) {
|
|
822
|
+
const slot = Symbol(property);
|
|
823
|
+
Object.defineProperty(proto, property, {
|
|
824
|
+
configurable: true,
|
|
825
|
+
get() { return this[slot]; },
|
|
826
|
+
set(value) {
|
|
827
|
+
this[slot] = value;
|
|
828
|
+
this._runtimeVisualDirty = true;
|
|
829
|
+
if (this.isConnected && !this._runtimeDisconnecting && syncMotionPreference(this)) {
|
|
830
|
+
requestVisualCommit(this);
|
|
831
|
+
queueMicrotask(() => {
|
|
832
|
+
if (this.isConnected && !this._runtimeDisconnecting && syncMotionPreference(this)) {
|
|
833
|
+
cancelContinuousMotion(this);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
724
841
|
registerAvatarExtension({
|
|
725
842
|
name: 'runtime-policy',
|
|
726
843
|
reset() {
|
|
@@ -728,7 +845,6 @@ registerAvatarExtension({
|
|
|
728
845
|
if (!this._runtimeEndingWaiting) cancelActiveAction(this, { silent });
|
|
729
846
|
clearDragGeometry(this, { preserveClick: !silent });
|
|
730
847
|
this._pendingPointerEvent = null;
|
|
731
|
-
if (!this.isConnected) teardownRuntime(this);
|
|
732
848
|
},
|
|
733
849
|
draw() {
|
|
734
850
|
ensureRuntime(this);
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import AgentRobotAvatar from './agent-robot-avatar-extension-host.js';
|
|
2
|
-
|
|
3
|
-
const proto = AgentRobotAvatar.prototype;
|
|
4
|
-
const baseUpdateDragJelly = proto._updateDragJelly;
|
|
5
|
-
const baseResumeFrames = proto._resumeFrames;
|
|
6
|
-
const baseCanPauseFrames = proto._canPauseFrames;
|
|
7
|
-
const baseDraw = proto._draw;
|
|
8
|
-
const baseIsReducedMotion = proto._isReducedMotion;
|
|
9
|
-
|
|
10
|
-
let currentEnvironmentEvent = null;
|
|
11
|
-
let clearEnvironmentTimer = 0;
|
|
12
|
-
|
|
13
|
-
function rememberEnvironmentEvent(event) {
|
|
14
|
-
currentEnvironmentEvent = event;
|
|
15
|
-
if (clearEnvironmentTimer) clearTimeout(clearEnvironmentTimer);
|
|
16
|
-
clearEnvironmentTimer = setTimeout(() => {
|
|
17
|
-
currentEnvironmentEvent = null;
|
|
18
|
-
clearEnvironmentTimer = 0;
|
|
19
|
-
}, 0);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof document !== 'undefined') {
|
|
23
|
-
document.addEventListener('pointerdown', rememberEnvironmentEvent, true);
|
|
24
|
-
document.addEventListener('keydown', rememberEnvironmentEvent, true);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function wakePolicy(instance) {
|
|
28
|
-
const value = String(instance.getAttribute('wake-on') || 'activity').trim().toLowerCase();
|
|
29
|
-
return value === 'manual' || value === 'interaction' ? value : 'activity';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function eventTargetsAvatar(event, instance) {
|
|
33
|
-
const path = event?.composedPath?.();
|
|
34
|
-
if (Array.isArray(path)) return path.includes(instance);
|
|
35
|
-
return event?.target === instance || instance.contains?.(event?.target);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Keep the public noteActivity(wake?) contract, but distinguish calls made by
|
|
39
|
-
// the shared environment listeners from explicit API calls. Environment input
|
|
40
|
-
// follows wake-on; an explicit noteActivity() remains an intentional wake.
|
|
41
|
-
proto.noteActivity = function(wake = true) {
|
|
42
|
-
this._lastActivity = performance.now();
|
|
43
|
-
this._resumeFrames?.();
|
|
44
|
-
if (!wake || (!this._sleeping && this._state !== 'sleep')) return;
|
|
45
|
-
|
|
46
|
-
const event = currentEnvironmentEvent;
|
|
47
|
-
if (!event) {
|
|
48
|
-
this.wake();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const policy = wakePolicy(this);
|
|
53
|
-
const shouldWake = policy === 'activity' || (policy === 'interaction' && eventTargetsAvatar(event, this));
|
|
54
|
-
if (!shouldWake) return;
|
|
55
|
-
|
|
56
|
-
const previousSource = this._runtimeActionSource;
|
|
57
|
-
this._runtimeActionSource = 'automatic';
|
|
58
|
-
try {
|
|
59
|
-
this.wake();
|
|
60
|
-
} finally {
|
|
61
|
-
this._runtimeActionSource = previousSource;
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
function dragAtRest(drag) {
|
|
66
|
-
if (!drag || drag.active || drag.returning || drag.pendingReaction) return false;
|
|
67
|
-
return Math.abs(drag.x) < 0.0001 && Math.abs(drag.y) < 0.0001 &&
|
|
68
|
-
Math.abs(drag.vx) < 0.0001 && Math.abs(drag.vy) < 0.0001 &&
|
|
69
|
-
Math.abs(drag.stretch) < 0.0001 && Math.abs(drag.shear) < 0.0001 &&
|
|
70
|
-
Math.abs(drag.pullX) < 0.0001 && Math.abs(drag.pullY) < 0.0001;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Core's original jelly renderer writes an identity transform every frame even
|
|
74
|
-
// after reset. Clear that identity transform once the interaction is fully at
|
|
75
|
-
// rest so reset/disconnect leave no visual residue or unnecessary style churn.
|
|
76
|
-
proto._updateDragJelly = function(dt) {
|
|
77
|
-
const result = baseUpdateDragJelly.call(this, dt);
|
|
78
|
-
if (dragAtRest(this._dragJelly)) {
|
|
79
|
-
if (this._dragMotion) {
|
|
80
|
-
this._dragMotion.style.transform = '';
|
|
81
|
-
this._dragMotion.style.transformOrigin = '';
|
|
82
|
-
}
|
|
83
|
-
if (this._headShape && this._baseHeadPathD) {
|
|
84
|
-
this._headShape.setAttribute('d', this._baseHeadPathD);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return result;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
function cancelContinuousMotion(instance) {
|
|
91
|
-
const animations = instance._headMotion?.getAnimations?.() || [];
|
|
92
|
-
for (const animation of animations) animation.cancel();
|
|
93
|
-
if (instance._headMotion) instance._headMotion.style.transform = '';
|
|
94
|
-
instance._blinkAnim = null;
|
|
95
|
-
instance._blink = 1;
|
|
96
|
-
instance._boredRoutine = null;
|
|
97
|
-
instance._boredLookSpeed = null;
|
|
98
|
-
if (instance._wanderTarget) instance._wanderTarget.x = instance._wanderTarget.y = 0;
|
|
99
|
-
if (instance._look) instance._look.x = instance._look.y = 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function requestedMotion(instance) {
|
|
103
|
-
const value = String(instance.getAttribute('motion') || 'auto').trim().toLowerCase();
|
|
104
|
-
return value === 'reduce' || value === 'full' ? value : 'auto';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function effectiveReducedMotion(instance) {
|
|
108
|
-
const requested = requestedMotion(instance);
|
|
109
|
-
if (requested === 'reduce') return true;
|
|
110
|
-
if (requested === 'full') return false;
|
|
111
|
-
|
|
112
|
-
// A fresh MediaQueryList avoids engine-specific delays where an older
|
|
113
|
-
// MediaQueryList object's matches value lags behind a runtime preference
|
|
114
|
-
// update. Fall back to the coordinator's original resolver when unavailable.
|
|
115
|
-
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
116
|
-
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
117
|
-
}
|
|
118
|
-
return baseIsReducedMotion.call(instance);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function syncMotionPreference(instance) {
|
|
122
|
-
const reduced = effectiveReducedMotion(instance);
|
|
123
|
-
if (instance._runtimeLastReducedMotion === reduced) return reduced;
|
|
124
|
-
instance._runtimeLastReducedMotion = reduced;
|
|
125
|
-
instance._runtimeVisualDirty = true;
|
|
126
|
-
if (reduced) cancelContinuousMotion(instance);
|
|
127
|
-
return reduced;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Some engines deliver matchMedia change events later than the next animation
|
|
131
|
-
// frame. Also sample the effective preference while frames are active so a
|
|
132
|
-
// runtime switch cannot leave an old Web Animation running until that event.
|
|
133
|
-
proto._isReducedMotion = function() {
|
|
134
|
-
return syncMotionPreference(this);
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Reduced motion should render each newly reached static state once, then stop
|
|
138
|
-
// scheduling frames. Extension effects historically create some Web Animations
|
|
139
|
-
// directly instead of routing through _animateHead; cancel those in a microtask
|
|
140
|
-
// after an effect becomes active so ordinary-mode animation code stays intact.
|
|
141
|
-
for (const property of ['_waitingFx', '_inspectFx', '_failureFx', '_warningFx', '_systemErrorShake']) {
|
|
142
|
-
const slot = Symbol(property);
|
|
143
|
-
Object.defineProperty(proto, property, {
|
|
144
|
-
configurable: true,
|
|
145
|
-
get() { return this[slot]; },
|
|
146
|
-
set(value) {
|
|
147
|
-
this[slot] = value;
|
|
148
|
-
this._runtimeVisualDirty = true;
|
|
149
|
-
if (this.isConnected && this._isReducedMotion?.()) {
|
|
150
|
-
this._resumeFrames?.();
|
|
151
|
-
queueMicrotask(() => {
|
|
152
|
-
if (this.isConnected && this._isReducedMotion?.()) cancelContinuousMotion(this);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
proto._resumeFrames = function() {
|
|
160
|
-
if (this._isReducedMotion?.()) this._runtimeVisualDirty = true;
|
|
161
|
-
return baseResumeFrames.call(this);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
proto._draw = function(now) {
|
|
165
|
-
syncMotionPreference(this);
|
|
166
|
-
const result = baseDraw.call(this, now);
|
|
167
|
-
this._runtimeVisualDirty = false;
|
|
168
|
-
return result;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
proto._canPauseFrames = function(now) {
|
|
172
|
-
syncMotionPreference(this);
|
|
173
|
-
if (this._isReducedMotion?.() && this._runtimeVisualDirty) return false;
|
|
174
|
-
return baseCanPauseFrames.call(this, now);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
export { AgentRobotAvatar };
|
|
178
|
-
export default AgentRobotAvatar;
|