@runtypelabs/persona 3.16.0 → 3.17.0
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/dist/animations/glyph-cycle.cjs +279 -0
- package/dist/animations/glyph-cycle.d.cts +5 -0
- package/dist/animations/glyph-cycle.d.ts +5 -0
- package/dist/animations/glyph-cycle.js +252 -0
- package/dist/animations/types-HPZY7oAI.d.cts +282 -0
- package/dist/animations/types-HPZY7oAI.d.ts +282 -0
- package/dist/animations/wipe.cjs +107 -0
- package/dist/animations/wipe.d.cts +5 -0
- package/dist/animations/wipe.d.ts +5 -0
- package/dist/animations/wipe.js +80 -0
- package/dist/index.cjs +48 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +205 -1
- package/dist/index.d.ts +205 -1
- package/dist/index.global.js +136 -81
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +48 -47
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +85 -0
- package/dist/testing.d.cts +39 -0
- package/dist/testing.d.ts +39 -0
- package/dist/testing.js +56 -0
- package/dist/theme-editor.cjs +714 -99
- package/dist/theme-editor.d.cts +214 -2
- package/dist/theme-editor.d.ts +214 -2
- package/dist/theme-editor.js +712 -99
- package/dist/widget.css +133 -0
- package/package.json +20 -3
- package/src/animations/glyph-cycle.ts +332 -0
- package/src/animations/wipe.ts +66 -0
- package/src/client.test.ts +141 -0
- package/src/client.ts +28 -0
- package/src/components/composer-builder.ts +61 -10
- package/src/components/message-bubble.test.ts +181 -2
- package/src/components/message-bubble.ts +209 -14
- package/src/components/panel.ts +4 -1
- package/src/defaults.ts +16 -0
- package/src/index-global.ts +31 -0
- package/src/index.ts +18 -0
- package/src/session.test.ts +93 -1
- package/src/session.ts +5 -0
- package/src/styles/widget.css +133 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/mock-stream.test.ts +80 -0
- package/src/testing/mock-stream.ts +94 -0
- package/src/testing.ts +2 -0
- package/src/theme-editor/index.ts +4 -0
- package/src/theme-editor/preview-utils.test.ts +60 -0
- package/src/theme-editor/preview-utils.ts +129 -0
- package/src/theme-editor/sections.test.ts +19 -0
- package/src/theme-editor/sections.ts +84 -1
- package/src/types.ts +210 -0
- package/src/ui.stop-button.test.ts +165 -0
- package/src/ui.ts +75 -6
- package/src/utils/message-fingerprint.ts +2 -0
- package/src/utils/morph.ts +7 -0
- package/src/utils/stream-animation.test.ts +417 -0
- package/src/utils/stream-animation.ts +449 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/animations/glyph-cycle.ts
|
|
21
|
+
var glyph_cycle_exports = {};
|
|
22
|
+
__export(glyph_cycle_exports, {
|
|
23
|
+
default: () => glyph_cycle_default,
|
|
24
|
+
glyphCycle: () => glyphCycle
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(glyph_cycle_exports);
|
|
27
|
+
|
|
28
|
+
// src/utils/stream-animation.ts
|
|
29
|
+
var BUILTIN_PLUGINS = [
|
|
30
|
+
{
|
|
31
|
+
name: "typewriter",
|
|
32
|
+
containerClass: "persona-stream-typewriter",
|
|
33
|
+
wrap: "char",
|
|
34
|
+
useCaret: true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "pop-bubble",
|
|
38
|
+
bubbleClass: "persona-stream-pop",
|
|
39
|
+
wrap: "none"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "letter-rise",
|
|
43
|
+
containerClass: "persona-stream-letter-rise",
|
|
44
|
+
wrap: "char"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "word-fade",
|
|
48
|
+
containerClass: "persona-stream-word-fade",
|
|
49
|
+
wrap: "word"
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
var globalRegistry = /* @__PURE__ */ new Map();
|
|
53
|
+
for (const plugin of BUILTIN_PLUGINS) globalRegistry.set(plugin.name, plugin);
|
|
54
|
+
var registerStreamAnimationPlugin = (plugin) => {
|
|
55
|
+
globalRegistry.set(plugin.name, plugin);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/animations/glyph-cycle.ts
|
|
59
|
+
var STYLES = `
|
|
60
|
+
[data-persona-root] .persona-stream-glyph-cycle .persona-stream-char {
|
|
61
|
+
animation: persona-stream-glyph-cycle-fade
|
|
62
|
+
calc(var(--persona-stream-step, 120ms) * 1.5) ease-out both;
|
|
63
|
+
}
|
|
64
|
+
[data-persona-root] .persona-stream-glyph-cycle .persona-stream-char[data-glyph-cycle-final] {
|
|
65
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
66
|
+
}
|
|
67
|
+
@keyframes persona-stream-glyph-cycle-fade {
|
|
68
|
+
from { opacity: 0.35; }
|
|
69
|
+
to { opacity: 1; }
|
|
70
|
+
}
|
|
71
|
+
@media (prefers-reduced-motion: reduce) {
|
|
72
|
+
[data-persona-root] .persona-stream-glyph-cycle .persona-stream-char {
|
|
73
|
+
animation: none !important;
|
|
74
|
+
opacity: 1 !important;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`.trim();
|
|
78
|
+
var GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$%&@";
|
|
79
|
+
var TICK_COUNT = 10;
|
|
80
|
+
var BASE_TICK_MS = 120;
|
|
81
|
+
var DEFAULT_STEP_MS = 120;
|
|
82
|
+
var CROSS_FLICKER_PROBABILITY = 0.4;
|
|
83
|
+
var BUFFER_THRESHOLD = 50;
|
|
84
|
+
var getStepMs = (container) => {
|
|
85
|
+
var _a;
|
|
86
|
+
if (!container) return DEFAULT_STEP_MS;
|
|
87
|
+
const raw = (_a = container.style.getPropertyValue("--persona-stream-step")) == null ? void 0 : _a.trim();
|
|
88
|
+
const match = raw.match(/([\d.]+)\s*ms/);
|
|
89
|
+
return match ? parseFloat(match[1]) : DEFAULT_STEP_MS;
|
|
90
|
+
};
|
|
91
|
+
var getTickMs = (span) => {
|
|
92
|
+
const container = span.closest(".persona-stream-glyph-cycle");
|
|
93
|
+
const step = getStepMs(container);
|
|
94
|
+
return BASE_TICK_MS * step / DEFAULT_STEP_MS;
|
|
95
|
+
};
|
|
96
|
+
var randomGlyph = (avoid) => {
|
|
97
|
+
let ch = GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
|
|
98
|
+
if (avoid && ch === avoid) {
|
|
99
|
+
ch = GLYPHS[(GLYPHS.indexOf(ch) + 1) % GLYPHS.length];
|
|
100
|
+
}
|
|
101
|
+
return ch;
|
|
102
|
+
};
|
|
103
|
+
var flickerLaterSiblings = (span) => {
|
|
104
|
+
const container = span.closest(".persona-stream-glyph-cycle");
|
|
105
|
+
if (!container) return;
|
|
106
|
+
const unsettled = container.querySelectorAll(
|
|
107
|
+
".persona-stream-char[data-glyph-cycle-final]"
|
|
108
|
+
);
|
|
109
|
+
let seenSelf = false;
|
|
110
|
+
for (const other of Array.from(unsettled)) {
|
|
111
|
+
if (other === span) {
|
|
112
|
+
seenSelf = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!seenSelf) continue;
|
|
116
|
+
if (Math.random() < CROSS_FLICKER_PROBABILITY) {
|
|
117
|
+
other.textContent = randomGlyph();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var containerNextSettleAt = /* @__PURE__ */ new WeakMap();
|
|
122
|
+
var DEFAULT_ARRIVAL_MS = 25;
|
|
123
|
+
var STAGGER_DRAIN_FACTOR = 0.5;
|
|
124
|
+
var MIN_STAGGER_MS = 6;
|
|
125
|
+
var EMA_ALPHA = 0.25;
|
|
126
|
+
var containerLastScheduleAt = /* @__PURE__ */ new WeakMap();
|
|
127
|
+
var containerArrivalMs = /* @__PURE__ */ new WeakMap();
|
|
128
|
+
var observeStagger = (container, now) => {
|
|
129
|
+
var _a;
|
|
130
|
+
if (!container) return DEFAULT_ARRIVAL_MS * STAGGER_DRAIN_FACTOR;
|
|
131
|
+
const last = containerLastScheduleAt.get(container);
|
|
132
|
+
containerLastScheduleAt.set(container, now);
|
|
133
|
+
let observed = (_a = containerArrivalMs.get(container)) != null ? _a : DEFAULT_ARRIVAL_MS;
|
|
134
|
+
if (last !== void 0) {
|
|
135
|
+
const interval = now - last;
|
|
136
|
+
if (interval > 1) {
|
|
137
|
+
observed = observed * (1 - EMA_ALPHA) + interval * EMA_ALPHA;
|
|
138
|
+
containerArrivalMs.set(container, observed);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Math.max(MIN_STAGGER_MS, observed * STAGGER_DRAIN_FACTOR);
|
|
142
|
+
};
|
|
143
|
+
var activeCyclesByMessage = /* @__PURE__ */ new Map();
|
|
144
|
+
var getMessageId = (span) => {
|
|
145
|
+
var _a;
|
|
146
|
+
const bubble = span.closest("[data-message-id]");
|
|
147
|
+
return (_a = bubble == null ? void 0 : bubble.dataset.messageId) != null ? _a : null;
|
|
148
|
+
};
|
|
149
|
+
var incrementActive = (messageId) => {
|
|
150
|
+
var _a;
|
|
151
|
+
if (!messageId) return;
|
|
152
|
+
activeCyclesByMessage.set(messageId, ((_a = activeCyclesByMessage.get(messageId)) != null ? _a : 0) + 1);
|
|
153
|
+
};
|
|
154
|
+
var decrementActive = (messageId) => {
|
|
155
|
+
var _a;
|
|
156
|
+
if (!messageId) return;
|
|
157
|
+
const count = (_a = activeCyclesByMessage.get(messageId)) != null ? _a : 0;
|
|
158
|
+
if (count <= 1) activeCyclesByMessage.delete(messageId);
|
|
159
|
+
else activeCyclesByMessage.set(messageId, count - 1);
|
|
160
|
+
};
|
|
161
|
+
var scheduleCycle = (span) => {
|
|
162
|
+
var _a;
|
|
163
|
+
if (span.dataset.glyphCycleScheduled === "true") return;
|
|
164
|
+
const finalChar = (_a = span.textContent) != null ? _a : "";
|
|
165
|
+
if (!finalChar || /\s/.test(finalChar)) return;
|
|
166
|
+
span.dataset.glyphCycleScheduled = "true";
|
|
167
|
+
span.dataset.glyphCycleFinal = finalChar;
|
|
168
|
+
span.setAttribute("data-preserve-runtime", "stream-glyph-cycle");
|
|
169
|
+
span.textContent = randomGlyph();
|
|
170
|
+
const messageId = getMessageId(span);
|
|
171
|
+
if (messageId) span.dataset.glyphCycleMessageId = messageId;
|
|
172
|
+
incrementActive(messageId);
|
|
173
|
+
const container = span.closest(".persona-stream-glyph-cycle");
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
const staggerMs = observeStagger(container, now);
|
|
176
|
+
const tickMs = getTickMs(span);
|
|
177
|
+
const baseDurationMs = TICK_COUNT * tickMs;
|
|
178
|
+
let settleAt = now + baseDurationMs;
|
|
179
|
+
if (container) {
|
|
180
|
+
const prev = containerNextSettleAt.get(container);
|
|
181
|
+
if (prev !== void 0) settleAt = Math.max(settleAt, prev);
|
|
182
|
+
containerNextSettleAt.set(container, settleAt + staggerMs);
|
|
183
|
+
}
|
|
184
|
+
startCycle(span, finalChar, settleAt);
|
|
185
|
+
};
|
|
186
|
+
var startCycle = (span, finalChar, settleAt) => {
|
|
187
|
+
var _a;
|
|
188
|
+
if (span.dataset.glyphCycleStarted === "true") return;
|
|
189
|
+
span.dataset.glyphCycleStarted = "true";
|
|
190
|
+
const tickMs = getTickMs(span);
|
|
191
|
+
let lastGlyph = (_a = span.textContent) != null ? _a : void 0;
|
|
192
|
+
const step = () => {
|
|
193
|
+
var _a2;
|
|
194
|
+
if (!span.isConnected) return;
|
|
195
|
+
if (Date.now() >= settleAt) {
|
|
196
|
+
span.textContent = finalChar;
|
|
197
|
+
span.removeAttribute("data-preserve-runtime");
|
|
198
|
+
delete span.dataset.glyphCycleStarted;
|
|
199
|
+
delete span.dataset.glyphCycleFinal;
|
|
200
|
+
decrementActive((_a2 = span.dataset.glyphCycleMessageId) != null ? _a2 : null);
|
|
201
|
+
delete span.dataset.glyphCycleMessageId;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const glyph = randomGlyph(lastGlyph);
|
|
205
|
+
span.textContent = glyph;
|
|
206
|
+
lastGlyph = glyph;
|
|
207
|
+
flickerLaterSiblings(span);
|
|
208
|
+
setTimeout(step, tickMs);
|
|
209
|
+
};
|
|
210
|
+
setTimeout(step, tickMs);
|
|
211
|
+
};
|
|
212
|
+
var processCharSpans = (root) => {
|
|
213
|
+
var _a;
|
|
214
|
+
const spans = (_a = root.querySelectorAll) == null ? void 0 : _a.call(
|
|
215
|
+
root,
|
|
216
|
+
".persona-stream-glyph-cycle .persona-stream-char:not([data-glyph-cycle-scheduled])"
|
|
217
|
+
);
|
|
218
|
+
if (!spans) return;
|
|
219
|
+
for (const span of Array.from(spans)) {
|
|
220
|
+
scheduleCycle(span);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var isElement = (node) => node.nodeType === 1;
|
|
224
|
+
var glyphCycle = {
|
|
225
|
+
name: "glyph-cycle",
|
|
226
|
+
containerClass: "persona-stream-glyph-cycle",
|
|
227
|
+
wrap: "char",
|
|
228
|
+
// Narrow the default skip list so inline `<code>` and fenced `<pre>`
|
|
229
|
+
// code blocks both render as cycling glyphs along with everything else.
|
|
230
|
+
// Links stay clickable; <script>/<style> stay untouched.
|
|
231
|
+
skipTags: ["a", "script", "style"],
|
|
232
|
+
styles: STYLES,
|
|
233
|
+
bufferContent(content) {
|
|
234
|
+
if (content.length < BUFFER_THRESHOLD) return "";
|
|
235
|
+
let boldPairs = 0;
|
|
236
|
+
let lastSafe = -1;
|
|
237
|
+
let i = 0;
|
|
238
|
+
while (i < content.length) {
|
|
239
|
+
if (content[i] === "*" && content[i + 1] === "*") {
|
|
240
|
+
boldPairs += 1;
|
|
241
|
+
i += 2;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (/\s/.test(content[i]) && boldPairs % 2 === 0) {
|
|
245
|
+
lastSafe = i;
|
|
246
|
+
}
|
|
247
|
+
i += 1;
|
|
248
|
+
}
|
|
249
|
+
if (lastSafe < 0) return "";
|
|
250
|
+
return content.slice(0, lastSafe);
|
|
251
|
+
},
|
|
252
|
+
isAnimating(message) {
|
|
253
|
+
var _a;
|
|
254
|
+
return ((_a = activeCyclesByMessage.get(message.id)) != null ? _a : 0) > 0;
|
|
255
|
+
},
|
|
256
|
+
onAttach(root) {
|
|
257
|
+
processCharSpans(root);
|
|
258
|
+
const observer = new MutationObserver((mutations) => {
|
|
259
|
+
for (const mutation of mutations) {
|
|
260
|
+
for (const node of Array.from(mutation.addedNodes)) {
|
|
261
|
+
if (!isElement(node)) continue;
|
|
262
|
+
if (node.classList.contains("persona-stream-char") && node.closest(".persona-stream-glyph-cycle")) {
|
|
263
|
+
scheduleCycle(node);
|
|
264
|
+
} else {
|
|
265
|
+
processCharSpans(node);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
271
|
+
return () => observer.disconnect();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
registerStreamAnimationPlugin(glyphCycle);
|
|
275
|
+
var glyph_cycle_default = glyphCycle;
|
|
276
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
277
|
+
0 && (module.exports = {
|
|
278
|
+
glyphCycle
|
|
279
|
+
});
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// src/utils/stream-animation.ts
|
|
2
|
+
var BUILTIN_PLUGINS = [
|
|
3
|
+
{
|
|
4
|
+
name: "typewriter",
|
|
5
|
+
containerClass: "persona-stream-typewriter",
|
|
6
|
+
wrap: "char",
|
|
7
|
+
useCaret: true
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: "pop-bubble",
|
|
11
|
+
bubbleClass: "persona-stream-pop",
|
|
12
|
+
wrap: "none"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: "letter-rise",
|
|
16
|
+
containerClass: "persona-stream-letter-rise",
|
|
17
|
+
wrap: "char"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "word-fade",
|
|
21
|
+
containerClass: "persona-stream-word-fade",
|
|
22
|
+
wrap: "word"
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
var globalRegistry = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const plugin of BUILTIN_PLUGINS) globalRegistry.set(plugin.name, plugin);
|
|
27
|
+
var registerStreamAnimationPlugin = (plugin) => {
|
|
28
|
+
globalRegistry.set(plugin.name, plugin);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/animations/glyph-cycle.ts
|
|
32
|
+
var STYLES = `
|
|
33
|
+
[data-persona-root] .persona-stream-glyph-cycle .persona-stream-char {
|
|
34
|
+
animation: persona-stream-glyph-cycle-fade
|
|
35
|
+
calc(var(--persona-stream-step, 120ms) * 1.5) ease-out both;
|
|
36
|
+
}
|
|
37
|
+
[data-persona-root] .persona-stream-glyph-cycle .persona-stream-char[data-glyph-cycle-final] {
|
|
38
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
39
|
+
}
|
|
40
|
+
@keyframes persona-stream-glyph-cycle-fade {
|
|
41
|
+
from { opacity: 0.35; }
|
|
42
|
+
to { opacity: 1; }
|
|
43
|
+
}
|
|
44
|
+
@media (prefers-reduced-motion: reduce) {
|
|
45
|
+
[data-persona-root] .persona-stream-glyph-cycle .persona-stream-char {
|
|
46
|
+
animation: none !important;
|
|
47
|
+
opacity: 1 !important;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
`.trim();
|
|
51
|
+
var GLYPHS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$%&@";
|
|
52
|
+
var TICK_COUNT = 10;
|
|
53
|
+
var BASE_TICK_MS = 120;
|
|
54
|
+
var DEFAULT_STEP_MS = 120;
|
|
55
|
+
var CROSS_FLICKER_PROBABILITY = 0.4;
|
|
56
|
+
var BUFFER_THRESHOLD = 50;
|
|
57
|
+
var getStepMs = (container) => {
|
|
58
|
+
var _a;
|
|
59
|
+
if (!container) return DEFAULT_STEP_MS;
|
|
60
|
+
const raw = (_a = container.style.getPropertyValue("--persona-stream-step")) == null ? void 0 : _a.trim();
|
|
61
|
+
const match = raw.match(/([\d.]+)\s*ms/);
|
|
62
|
+
return match ? parseFloat(match[1]) : DEFAULT_STEP_MS;
|
|
63
|
+
};
|
|
64
|
+
var getTickMs = (span) => {
|
|
65
|
+
const container = span.closest(".persona-stream-glyph-cycle");
|
|
66
|
+
const step = getStepMs(container);
|
|
67
|
+
return BASE_TICK_MS * step / DEFAULT_STEP_MS;
|
|
68
|
+
};
|
|
69
|
+
var randomGlyph = (avoid) => {
|
|
70
|
+
let ch = GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
|
|
71
|
+
if (avoid && ch === avoid) {
|
|
72
|
+
ch = GLYPHS[(GLYPHS.indexOf(ch) + 1) % GLYPHS.length];
|
|
73
|
+
}
|
|
74
|
+
return ch;
|
|
75
|
+
};
|
|
76
|
+
var flickerLaterSiblings = (span) => {
|
|
77
|
+
const container = span.closest(".persona-stream-glyph-cycle");
|
|
78
|
+
if (!container) return;
|
|
79
|
+
const unsettled = container.querySelectorAll(
|
|
80
|
+
".persona-stream-char[data-glyph-cycle-final]"
|
|
81
|
+
);
|
|
82
|
+
let seenSelf = false;
|
|
83
|
+
for (const other of Array.from(unsettled)) {
|
|
84
|
+
if (other === span) {
|
|
85
|
+
seenSelf = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!seenSelf) continue;
|
|
89
|
+
if (Math.random() < CROSS_FLICKER_PROBABILITY) {
|
|
90
|
+
other.textContent = randomGlyph();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var containerNextSettleAt = /* @__PURE__ */ new WeakMap();
|
|
95
|
+
var DEFAULT_ARRIVAL_MS = 25;
|
|
96
|
+
var STAGGER_DRAIN_FACTOR = 0.5;
|
|
97
|
+
var MIN_STAGGER_MS = 6;
|
|
98
|
+
var EMA_ALPHA = 0.25;
|
|
99
|
+
var containerLastScheduleAt = /* @__PURE__ */ new WeakMap();
|
|
100
|
+
var containerArrivalMs = /* @__PURE__ */ new WeakMap();
|
|
101
|
+
var observeStagger = (container, now) => {
|
|
102
|
+
var _a;
|
|
103
|
+
if (!container) return DEFAULT_ARRIVAL_MS * STAGGER_DRAIN_FACTOR;
|
|
104
|
+
const last = containerLastScheduleAt.get(container);
|
|
105
|
+
containerLastScheduleAt.set(container, now);
|
|
106
|
+
let observed = (_a = containerArrivalMs.get(container)) != null ? _a : DEFAULT_ARRIVAL_MS;
|
|
107
|
+
if (last !== void 0) {
|
|
108
|
+
const interval = now - last;
|
|
109
|
+
if (interval > 1) {
|
|
110
|
+
observed = observed * (1 - EMA_ALPHA) + interval * EMA_ALPHA;
|
|
111
|
+
containerArrivalMs.set(container, observed);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return Math.max(MIN_STAGGER_MS, observed * STAGGER_DRAIN_FACTOR);
|
|
115
|
+
};
|
|
116
|
+
var activeCyclesByMessage = /* @__PURE__ */ new Map();
|
|
117
|
+
var getMessageId = (span) => {
|
|
118
|
+
var _a;
|
|
119
|
+
const bubble = span.closest("[data-message-id]");
|
|
120
|
+
return (_a = bubble == null ? void 0 : bubble.dataset.messageId) != null ? _a : null;
|
|
121
|
+
};
|
|
122
|
+
var incrementActive = (messageId) => {
|
|
123
|
+
var _a;
|
|
124
|
+
if (!messageId) return;
|
|
125
|
+
activeCyclesByMessage.set(messageId, ((_a = activeCyclesByMessage.get(messageId)) != null ? _a : 0) + 1);
|
|
126
|
+
};
|
|
127
|
+
var decrementActive = (messageId) => {
|
|
128
|
+
var _a;
|
|
129
|
+
if (!messageId) return;
|
|
130
|
+
const count = (_a = activeCyclesByMessage.get(messageId)) != null ? _a : 0;
|
|
131
|
+
if (count <= 1) activeCyclesByMessage.delete(messageId);
|
|
132
|
+
else activeCyclesByMessage.set(messageId, count - 1);
|
|
133
|
+
};
|
|
134
|
+
var scheduleCycle = (span) => {
|
|
135
|
+
var _a;
|
|
136
|
+
if (span.dataset.glyphCycleScheduled === "true") return;
|
|
137
|
+
const finalChar = (_a = span.textContent) != null ? _a : "";
|
|
138
|
+
if (!finalChar || /\s/.test(finalChar)) return;
|
|
139
|
+
span.dataset.glyphCycleScheduled = "true";
|
|
140
|
+
span.dataset.glyphCycleFinal = finalChar;
|
|
141
|
+
span.setAttribute("data-preserve-runtime", "stream-glyph-cycle");
|
|
142
|
+
span.textContent = randomGlyph();
|
|
143
|
+
const messageId = getMessageId(span);
|
|
144
|
+
if (messageId) span.dataset.glyphCycleMessageId = messageId;
|
|
145
|
+
incrementActive(messageId);
|
|
146
|
+
const container = span.closest(".persona-stream-glyph-cycle");
|
|
147
|
+
const now = Date.now();
|
|
148
|
+
const staggerMs = observeStagger(container, now);
|
|
149
|
+
const tickMs = getTickMs(span);
|
|
150
|
+
const baseDurationMs = TICK_COUNT * tickMs;
|
|
151
|
+
let settleAt = now + baseDurationMs;
|
|
152
|
+
if (container) {
|
|
153
|
+
const prev = containerNextSettleAt.get(container);
|
|
154
|
+
if (prev !== void 0) settleAt = Math.max(settleAt, prev);
|
|
155
|
+
containerNextSettleAt.set(container, settleAt + staggerMs);
|
|
156
|
+
}
|
|
157
|
+
startCycle(span, finalChar, settleAt);
|
|
158
|
+
};
|
|
159
|
+
var startCycle = (span, finalChar, settleAt) => {
|
|
160
|
+
var _a;
|
|
161
|
+
if (span.dataset.glyphCycleStarted === "true") return;
|
|
162
|
+
span.dataset.glyphCycleStarted = "true";
|
|
163
|
+
const tickMs = getTickMs(span);
|
|
164
|
+
let lastGlyph = (_a = span.textContent) != null ? _a : void 0;
|
|
165
|
+
const step = () => {
|
|
166
|
+
var _a2;
|
|
167
|
+
if (!span.isConnected) return;
|
|
168
|
+
if (Date.now() >= settleAt) {
|
|
169
|
+
span.textContent = finalChar;
|
|
170
|
+
span.removeAttribute("data-preserve-runtime");
|
|
171
|
+
delete span.dataset.glyphCycleStarted;
|
|
172
|
+
delete span.dataset.glyphCycleFinal;
|
|
173
|
+
decrementActive((_a2 = span.dataset.glyphCycleMessageId) != null ? _a2 : null);
|
|
174
|
+
delete span.dataset.glyphCycleMessageId;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const glyph = randomGlyph(lastGlyph);
|
|
178
|
+
span.textContent = glyph;
|
|
179
|
+
lastGlyph = glyph;
|
|
180
|
+
flickerLaterSiblings(span);
|
|
181
|
+
setTimeout(step, tickMs);
|
|
182
|
+
};
|
|
183
|
+
setTimeout(step, tickMs);
|
|
184
|
+
};
|
|
185
|
+
var processCharSpans = (root) => {
|
|
186
|
+
var _a;
|
|
187
|
+
const spans = (_a = root.querySelectorAll) == null ? void 0 : _a.call(
|
|
188
|
+
root,
|
|
189
|
+
".persona-stream-glyph-cycle .persona-stream-char:not([data-glyph-cycle-scheduled])"
|
|
190
|
+
);
|
|
191
|
+
if (!spans) return;
|
|
192
|
+
for (const span of Array.from(spans)) {
|
|
193
|
+
scheduleCycle(span);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var isElement = (node) => node.nodeType === 1;
|
|
197
|
+
var glyphCycle = {
|
|
198
|
+
name: "glyph-cycle",
|
|
199
|
+
containerClass: "persona-stream-glyph-cycle",
|
|
200
|
+
wrap: "char",
|
|
201
|
+
// Narrow the default skip list so inline `<code>` and fenced `<pre>`
|
|
202
|
+
// code blocks both render as cycling glyphs along with everything else.
|
|
203
|
+
// Links stay clickable; <script>/<style> stay untouched.
|
|
204
|
+
skipTags: ["a", "script", "style"],
|
|
205
|
+
styles: STYLES,
|
|
206
|
+
bufferContent(content) {
|
|
207
|
+
if (content.length < BUFFER_THRESHOLD) return "";
|
|
208
|
+
let boldPairs = 0;
|
|
209
|
+
let lastSafe = -1;
|
|
210
|
+
let i = 0;
|
|
211
|
+
while (i < content.length) {
|
|
212
|
+
if (content[i] === "*" && content[i + 1] === "*") {
|
|
213
|
+
boldPairs += 1;
|
|
214
|
+
i += 2;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (/\s/.test(content[i]) && boldPairs % 2 === 0) {
|
|
218
|
+
lastSafe = i;
|
|
219
|
+
}
|
|
220
|
+
i += 1;
|
|
221
|
+
}
|
|
222
|
+
if (lastSafe < 0) return "";
|
|
223
|
+
return content.slice(0, lastSafe);
|
|
224
|
+
},
|
|
225
|
+
isAnimating(message) {
|
|
226
|
+
var _a;
|
|
227
|
+
return ((_a = activeCyclesByMessage.get(message.id)) != null ? _a : 0) > 0;
|
|
228
|
+
},
|
|
229
|
+
onAttach(root) {
|
|
230
|
+
processCharSpans(root);
|
|
231
|
+
const observer = new MutationObserver((mutations) => {
|
|
232
|
+
for (const mutation of mutations) {
|
|
233
|
+
for (const node of Array.from(mutation.addedNodes)) {
|
|
234
|
+
if (!isElement(node)) continue;
|
|
235
|
+
if (node.classList.contains("persona-stream-char") && node.closest(".persona-stream-glyph-cycle")) {
|
|
236
|
+
scheduleCycle(node);
|
|
237
|
+
} else {
|
|
238
|
+
processCharSpans(node);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
244
|
+
return () => observer.disconnect();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
registerStreamAnimationPlugin(glyphCycle);
|
|
248
|
+
var glyph_cycle_default = glyphCycle;
|
|
249
|
+
export {
|
|
250
|
+
glyph_cycle_default as default,
|
|
251
|
+
glyphCycle
|
|
252
|
+
};
|