@staticcanvas/typeflow 0.34.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/LICENSE +21 -0
- package/README.md +637 -0
- package/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
- package/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
- package/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
- package/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
- package/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
- package/branding/dist/png/typeflow-logo-128x128.png +0 -0
- package/branding/dist/png/typeflow-logo-16x16.png +0 -0
- package/branding/dist/png/typeflow-logo-256x256.png +0 -0
- package/branding/dist/png/typeflow-logo-512x512.png +0 -0
- package/branding/dist/png/typeflow-logo-64x64.png +0 -0
- package/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
- package/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
- package/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
- package/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
- package/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
- package/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
- package/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
- package/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
- package/branding/gen-logo.ps1 +14 -0
- package/branding/typeflow-logo.svg +162 -0
- package/dist/typeflow/LICENSE +21 -0
- package/dist/typeflow/README.md +231 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-16x16.ico +0 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-256x256.ico +0 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-32x32.ico +0 -0
- package/dist/typeflow/branding/dist/ico/typeflow-logo-48x48.ico +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-1024x1024.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-128x128.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-16x16.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-256x256.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-512x512.png +0 -0
- package/dist/typeflow/branding/dist/png/typeflow-logo-64x64.png +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-1024x1024.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-128x128.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-16x16.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-256x256.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-32x32.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-48x48.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-512x512.webp +0 -0
- package/dist/typeflow/branding/dist/webp/typeflow-logo-64x64.webp +0 -0
- package/dist/typeflow/branding/gen-logo.ps1 +14 -0
- package/dist/typeflow/branding/typeflow-logo.svg +162 -0
- package/dist/typeflow/dist/typeflow-caret.cjs +47 -0
- package/dist/typeflow/dist/typeflow-caret.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.esm.js +47 -0
- package/dist/typeflow/dist/typeflow-caret.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-caret.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.js +47 -0
- package/dist/typeflow/dist/typeflow-caret.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.min.js +2 -0
- package/dist/typeflow/dist/typeflow-caret.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-caret.mjs +47 -0
- package/dist/typeflow/dist/typeflow-caret.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.cjs +109 -0
- package/dist/typeflow/dist/typeflow-debug.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.esm.js +109 -0
- package/dist/typeflow/dist/typeflow-debug.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-debug.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.js +109 -0
- package/dist/typeflow/dist/typeflow-debug.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.min.js +2 -0
- package/dist/typeflow/dist/typeflow-debug.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-debug.mjs +109 -0
- package/dist/typeflow/dist/typeflow-debug.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.cjs +66 -0
- package/dist/typeflow/dist/typeflow-extchars.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.js +66 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-extchars.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.js +66 -0
- package/dist/typeflow/dist/typeflow-extchars.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.min.js +2 -0
- package/dist/typeflow/dist/typeflow-extchars.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-extchars.mjs +66 -0
- package/dist/typeflow/dist/typeflow-extchars.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.cjs +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.js +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-keystroke.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.js +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.min.js +2 -0
- package/dist/typeflow/dist/typeflow-keystroke.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-keystroke.mjs +47 -0
- package/dist/typeflow/dist/typeflow-keystroke.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.cjs +76 -0
- package/dist/typeflow/dist/typeflow-metrics.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.js +76 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-metrics.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.js +76 -0
- package/dist/typeflow/dist/typeflow-metrics.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.min.js +2 -0
- package/dist/typeflow/dist/typeflow-metrics.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-metrics.mjs +76 -0
- package/dist/typeflow/dist/typeflow-metrics.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.cjs +3 -0
- package/dist/typeflow/dist/typeflow-parallel.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.js +3 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-parallel.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.js +3 -0
- package/dist/typeflow/dist/typeflow-parallel.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.min.js +2 -0
- package/dist/typeflow/dist/typeflow-parallel.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-parallel.mjs +3 -0
- package/dist/typeflow/dist/typeflow-parallel.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.cjs +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.js +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow-webaudio.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.js +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.min.js +2 -0
- package/dist/typeflow/dist/typeflow-webaudio.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow-webaudio.mjs +81 -0
- package/dist/typeflow/dist/typeflow-webaudio.mjs.map +1 -0
- package/dist/typeflow/dist/typeflow.cjs +277 -0
- package/dist/typeflow/dist/typeflow.cjs.map +1 -0
- package/dist/typeflow/dist/typeflow.esm.js +277 -0
- package/dist/typeflow/dist/typeflow.esm.js.map +1 -0
- package/dist/typeflow/dist/typeflow.esm.min.js +2 -0
- package/dist/typeflow/dist/typeflow.esm.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow.js +277 -0
- package/dist/typeflow/dist/typeflow.js.map +1 -0
- package/dist/typeflow/dist/typeflow.min.js +2 -0
- package/dist/typeflow/dist/typeflow.min.js.map +1 -0
- package/dist/typeflow/dist/typeflow.mjs +277 -0
- package/dist/typeflow/dist/typeflow.mjs.map +1 -0
- package/dist/typeflow/jsr.jsonc +27 -0
- package/dist/typeflow/package.json +79 -0
- package/dist/typeflow/src/typeflow-audio-presets.js +137 -0
- package/dist/typeflow/src/typeflow-caret.d.ts +65 -0
- package/dist/typeflow/src/typeflow-caret.js +303 -0
- package/dist/typeflow/src/typeflow-debug.d.ts +34 -0
- package/dist/typeflow/src/typeflow-debug.js +223 -0
- package/dist/typeflow/src/typeflow-extchars.d.ts +28 -0
- package/dist/typeflow/src/typeflow-extchars.js +132 -0
- package/dist/typeflow/src/typeflow-keystroke.d.ts +28 -0
- package/dist/typeflow/src/typeflow-keystroke.js +220 -0
- package/dist/typeflow/src/typeflow-metrics.d.ts +38 -0
- package/dist/typeflow/src/typeflow-metrics.js +556 -0
- package/dist/typeflow/src/typeflow-parallel.d.ts +36 -0
- package/dist/typeflow/src/typeflow-parallel.js +157 -0
- package/dist/typeflow/src/typeflow-webaudio.d.ts +83 -0
- package/dist/typeflow/src/typeflow-webaudio.js +517 -0
- package/dist/typeflow/src/typeflow.d.ts +328 -0
- package/dist/typeflow/src/typeflow.js +2119 -0
- package/jsr.jsonc +27 -0
- package/package.json +133 -0
- package/src/typeflow-audio-presets.js +137 -0
- package/src/typeflow-caret.d.ts +65 -0
- package/src/typeflow-caret.js +303 -0
- package/src/typeflow-debug.d.ts +34 -0
- package/src/typeflow-debug.js +223 -0
- package/src/typeflow-extchars.d.ts +28 -0
- package/src/typeflow-extchars.js +132 -0
- package/src/typeflow-keystroke.d.ts +28 -0
- package/src/typeflow-keystroke.js +220 -0
- package/src/typeflow-metrics.d.ts +38 -0
- package/src/typeflow-metrics.js +556 -0
- package/src/typeflow-parallel.d.ts +36 -0
- package/src/typeflow-parallel.js +157 -0
- package/src/typeflow-webaudio.d.ts +83 -0
- package/src/typeflow-webaudio.js +517 -0
- package/src/typeflow.d.ts +328 -0
- package/src/typeflow.js +2119 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
2
|
+
* @description TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.
|
|
3
|
+
* A persistent, editable cursor bound to an element's text: jump or move to any
|
|
4
|
+
* grapheme index, delete a character or whole word at that position, and type
|
|
5
|
+
* new text in with the usual character-by-character animation. Zero
|
|
6
|
+
* dependencies; opportunistically defers to a page-global TypeFlow so starting
|
|
7
|
+
* a `TypeFlow.type()`/`erase()` on the same element interrupts an in-flight
|
|
8
|
+
* caret edit, without requiring a hard import of the core engine.
|
|
9
|
+
*
|
|
10
|
+
* @module @staticcanvas/typeflow/caret
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const CURSOR_BLINK_INTERVAL = 530;
|
|
14
|
+
const WHITESPACE_UNIT = /^\s+$/u;
|
|
15
|
+
|
|
16
|
+
function segmentGraphemes(text) {
|
|
17
|
+
if (typeof Intl !== 'undefined' && Intl.Segmenter) {
|
|
18
|
+
return Array.from(
|
|
19
|
+
new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text),
|
|
20
|
+
(s) => s.segment
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return Array.from(text);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function prefersReducedMotion() {
|
|
27
|
+
return (
|
|
28
|
+
typeof window !== 'undefined' &&
|
|
29
|
+
typeof window.matchMedia === 'function' &&
|
|
30
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Stops any animation the page-global TypeFlow engine may have active on `el`, if present. */
|
|
35
|
+
function stopPageTypeFlow(el) {
|
|
36
|
+
if (typeof window !== 'undefined') window.TypeFlow?.stop?.(el);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clampCaretIndex(position, max) {
|
|
40
|
+
const value = position === undefined || position === null ? max : Number(position);
|
|
41
|
+
if (!Number.isFinite(value)) return max;
|
|
42
|
+
return Math.max(0, Math.min(Math.round(value), max));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createCursorSpan(cursorChar) {
|
|
46
|
+
const span = document.createElement('span');
|
|
47
|
+
span.className = 'tf-cursor';
|
|
48
|
+
span.setAttribute('aria-hidden', 'true');
|
|
49
|
+
span.textContent = cursorChar;
|
|
50
|
+
return span;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Interactive, stateful cursor bound to an element's text content.
|
|
55
|
+
*
|
|
56
|
+
* Unlike `TypeFlow.type()`/`erase()`, which run once from start to finish, a
|
|
57
|
+
* Caret is persistent: `jump()`, `move()`, `deleteChar()`, and `deleteWord()`
|
|
58
|
+
* edit `text`/`position` and re-render immediately; `insert()` is animated
|
|
59
|
+
* and returns a `{ stop, promise }` controller.
|
|
60
|
+
*/
|
|
61
|
+
export class TypeFlowCaret {
|
|
62
|
+
/**
|
|
63
|
+
* @param {Element | string} target - Element or CSS selector.
|
|
64
|
+
* @param {object} [config={}]
|
|
65
|
+
* @param {string} [config.text] - Initial text. Defaults to the target's current text content.
|
|
66
|
+
* @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.
|
|
67
|
+
* @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.
|
|
68
|
+
* @param {boolean} [config.cursorBlink=true]
|
|
69
|
+
* @param {number} [config.speed=40] - Default ms per character for `insert()`.
|
|
70
|
+
* @param {boolean} [config.respectReducedMotion=true]
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m "wip"' });
|
|
74
|
+
* caret.jump(3).deleteWord('forward').insert('push');
|
|
75
|
+
* // "git push -m "wip""
|
|
76
|
+
*/
|
|
77
|
+
constructor(target, config = {}) {
|
|
78
|
+
this.el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
79
|
+
if (!this.el) throw new Error('TypeFlowCaret: target element not found.');
|
|
80
|
+
stopPageTypeFlow(this.el);
|
|
81
|
+
|
|
82
|
+
this.cursor = config.cursor ?? '|';
|
|
83
|
+
this.cursorBlink = config.cursorBlink ?? true;
|
|
84
|
+
this.speed = config.speed ?? 40;
|
|
85
|
+
this.respectReducedMotion = config.respectReducedMotion ?? true;
|
|
86
|
+
this.units = segmentGraphemes(config.text ?? this.el.textContent ?? '');
|
|
87
|
+
this.index = clampCaretIndex(config.position ?? this.units.length, this.units.length);
|
|
88
|
+
this._blinkInterval = null;
|
|
89
|
+
this._activeInsert = null;
|
|
90
|
+
this._render();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Current plain-text content, cursor excluded. */
|
|
94
|
+
get text() {
|
|
95
|
+
return this.units.join('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Current caret index, in graphemes, from the start of the text. */
|
|
99
|
+
get position() {
|
|
100
|
+
return this.index;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Total number of graphemes in the current text. */
|
|
104
|
+
get length() {
|
|
105
|
+
return this.units.length;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Moves the caret to an absolute grapheme index, clamped to bounds. */
|
|
109
|
+
jump(position) {
|
|
110
|
+
this._stopInsert();
|
|
111
|
+
this.index = clampCaretIndex(position, this.units.length);
|
|
112
|
+
this._render();
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Moves the caret by a relative number of graphemes (positive = right, negative = left). */
|
|
117
|
+
move(delta) {
|
|
118
|
+
return this.jump(this.index + (Number(delta) || 0));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Deletes one grapheme at the caret.
|
|
123
|
+
* @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).
|
|
124
|
+
*/
|
|
125
|
+
deleteChar(direction = 'backward') {
|
|
126
|
+
this._stopInsert();
|
|
127
|
+
if (direction === 'forward') {
|
|
128
|
+
if (this.index < this.units.length) this.units.splice(this.index, 1);
|
|
129
|
+
} else if (this.index > 0) {
|
|
130
|
+
this.units.splice(this.index - 1, 1);
|
|
131
|
+
this.index -= 1;
|
|
132
|
+
}
|
|
133
|
+
this._render();
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Deletes the whitespace-delimited word adjacent to the caret.
|
|
139
|
+
* @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the word (and any whitespace immediately before it) that precedes the caret; `'forward'` removes the word (and any whitespace immediately after it) that follows.
|
|
140
|
+
*/
|
|
141
|
+
deleteWord(direction = 'backward') {
|
|
142
|
+
this._stopInsert();
|
|
143
|
+
if (direction === 'forward') {
|
|
144
|
+
let end = this.index;
|
|
145
|
+
let ateLeadingSpace = false;
|
|
146
|
+
while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) {
|
|
147
|
+
end += 1;
|
|
148
|
+
ateLeadingSpace = true;
|
|
149
|
+
}
|
|
150
|
+
while (end < this.units.length && !WHITESPACE_UNIT.test(this.units[end])) end += 1;
|
|
151
|
+
if (!ateLeadingSpace) {
|
|
152
|
+
while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) end += 1;
|
|
153
|
+
}
|
|
154
|
+
if (end === this.index) return this;
|
|
155
|
+
this.units.splice(this.index, end - this.index);
|
|
156
|
+
} else {
|
|
157
|
+
let start = this.index;
|
|
158
|
+
while (start > 0 && WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;
|
|
159
|
+
while (start > 0 && !WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;
|
|
160
|
+
if (start === this.index) return this;
|
|
161
|
+
this.units.splice(start, this.index - start);
|
|
162
|
+
this.index = start;
|
|
163
|
+
}
|
|
164
|
+
this._render();
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Types `text` at the current caret position, one grapheme at a time,
|
|
170
|
+
* shifting any following content to the right as it goes.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} text
|
|
173
|
+
* @param {object} [config={}]
|
|
174
|
+
* @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.
|
|
175
|
+
* @param {boolean} [config.respectReducedMotion]
|
|
176
|
+
* @param {() => void} [config.onStart]
|
|
177
|
+
* @param {(text: string) => void} [config.onComplete]
|
|
178
|
+
* @param {() => void} [config.onInterrupt]
|
|
179
|
+
* @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}
|
|
180
|
+
*/
|
|
181
|
+
insert(text, config = {}) {
|
|
182
|
+
this._stopInsert();
|
|
183
|
+
const insertUnits = segmentGraphemes(text === null || text === undefined ? '' : String(text));
|
|
184
|
+
const speed = Math.max(0, Number(config.speed ?? this.speed) || 0);
|
|
185
|
+
const startIndex = this.index;
|
|
186
|
+
|
|
187
|
+
let settled = false;
|
|
188
|
+
let timeoutId = null;
|
|
189
|
+
let resolveFn = null;
|
|
190
|
+
const settle = (completed) => {
|
|
191
|
+
if (settled) return;
|
|
192
|
+
settled = true;
|
|
193
|
+
if (timeoutId !== null) clearTimeout(timeoutId);
|
|
194
|
+
this._activeInsert = null;
|
|
195
|
+
if (completed) config.onComplete?.(this.text);
|
|
196
|
+
else config.onInterrupt?.();
|
|
197
|
+
resolveFn?.({ completed, text: this.text });
|
|
198
|
+
};
|
|
199
|
+
const controller = {
|
|
200
|
+
stop: () => settle(false),
|
|
201
|
+
promise: new Promise((resolve) => {
|
|
202
|
+
resolveFn = resolve;
|
|
203
|
+
}),
|
|
204
|
+
};
|
|
205
|
+
this._activeInsert = controller;
|
|
206
|
+
|
|
207
|
+
if (insertUnits.length === 0) {
|
|
208
|
+
settle(true);
|
|
209
|
+
return controller;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const respectReducedMotion = config.respectReducedMotion ?? this.respectReducedMotion;
|
|
213
|
+
if (respectReducedMotion === true && prefersReducedMotion()) {
|
|
214
|
+
this.units.splice(startIndex, 0, ...insertUnits);
|
|
215
|
+
this.index = startIndex + insertUnits.length;
|
|
216
|
+
this._render();
|
|
217
|
+
settle(true);
|
|
218
|
+
return controller;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
config.onStart?.();
|
|
222
|
+
let step = 0;
|
|
223
|
+
const tick = () => {
|
|
224
|
+
this.units.splice(startIndex + step, 0, insertUnits[step]);
|
|
225
|
+
this.index = startIndex + step + 1;
|
|
226
|
+
this._render();
|
|
227
|
+
step += 1;
|
|
228
|
+
if (step >= insertUnits.length) {
|
|
229
|
+
settle(true);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
timeoutId = setTimeout(tick, speed);
|
|
233
|
+
};
|
|
234
|
+
timeoutId = setTimeout(tick, speed);
|
|
235
|
+
return controller;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Stops an in-flight `insert()`, if any. */
|
|
239
|
+
stop() {
|
|
240
|
+
this._stopInsert();
|
|
241
|
+
return this;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */
|
|
245
|
+
destroy() {
|
|
246
|
+
this._stopInsert();
|
|
247
|
+
if (this._blinkInterval !== null) {
|
|
248
|
+
clearInterval(this._blinkInterval);
|
|
249
|
+
this._blinkInterval = null;
|
|
250
|
+
}
|
|
251
|
+
return this;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_stopInsert() {
|
|
255
|
+
this._activeInsert?.stop();
|
|
256
|
+
stopPageTypeFlow(this.el);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_render() {
|
|
260
|
+
if (this._blinkInterval !== null) {
|
|
261
|
+
clearInterval(this._blinkInterval);
|
|
262
|
+
this._blinkInterval = null;
|
|
263
|
+
}
|
|
264
|
+
const before = this.units.slice(0, this.index).join('');
|
|
265
|
+
const after = this.units.slice(this.index).join('');
|
|
266
|
+
this.el.replaceChildren(document.createTextNode(before));
|
|
267
|
+
|
|
268
|
+
if (this.cursor) {
|
|
269
|
+
const cursorSpan = createCursorSpan(this.cursor);
|
|
270
|
+
this.el.appendChild(cursorSpan);
|
|
271
|
+
if (this.cursorBlink) {
|
|
272
|
+
let visible = true;
|
|
273
|
+
this._blinkInterval = setInterval(() => {
|
|
274
|
+
visible = !visible;
|
|
275
|
+
cursorSpan.style.visibility = visible ? 'visible' : 'hidden';
|
|
276
|
+
}, CURSOR_BLINK_INTERVAL);
|
|
277
|
+
this._blinkInterval.unref?.();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
this.el.appendChild(document.createTextNode(after));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Factory form of `new TypeFlowCaret(target, config)`.
|
|
287
|
+
*
|
|
288
|
+
* @param {Element | string} target
|
|
289
|
+
* @param {object} [config]
|
|
290
|
+
* @returns {TypeFlowCaret}
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* const caret = Caret('#terminal', { text: 'git commit' });
|
|
294
|
+
* caret.move(-6).insert('push --force ');
|
|
295
|
+
*/
|
|
296
|
+
export function Caret(target, config) {
|
|
297
|
+
return new TypeFlowCaret(target, config);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (typeof window !== 'undefined') {
|
|
301
|
+
window.TypeFlowCaret = TypeFlowCaret;
|
|
302
|
+
window.Caret = Caret;
|
|
303
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeFlow Debug - Diagnostic Tracing and LogCAD Integration.
|
|
3
|
+
*/
|
|
4
|
+
export interface TypeFlowDebugOptions {
|
|
5
|
+
logcad?: boolean;
|
|
6
|
+
console?: boolean;
|
|
7
|
+
maxHistory?: number;
|
|
8
|
+
traceFrames?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TypeFlowDebugEntry {
|
|
12
|
+
timestamp: string;
|
|
13
|
+
level: 'info' | 'warn' | 'error' | 'debug' | 'trace';
|
|
14
|
+
message: string;
|
|
15
|
+
data: Record<string, any>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class TypeFlowDebug {
|
|
19
|
+
static enabled: boolean;
|
|
20
|
+
static options: TypeFlowDebugOptions;
|
|
21
|
+
static history: TypeFlowDebugEntry[];
|
|
22
|
+
|
|
23
|
+
static enable(options?: TypeFlowDebugOptions): void;
|
|
24
|
+
static disable(): void;
|
|
25
|
+
static log(
|
|
26
|
+
level: 'info' | 'warn' | 'error' | 'debug' | 'trace',
|
|
27
|
+
message: string,
|
|
28
|
+
data?: Record<string, any>
|
|
29
|
+
): void;
|
|
30
|
+
static trace<T>(controller: T, label?: string): T;
|
|
31
|
+
static getHistory(): TypeFlowDebugEntry[];
|
|
32
|
+
static clearHistory(): void;
|
|
33
|
+
static exportLog(): string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
2
|
+
* @description TypeFlow Debug - Diagnostic Tracing and LogCAD Integration.
|
|
3
|
+
* Provides lifecycle telemetry, animation step timings, event hooks, and structured
|
|
4
|
+
* logger integration for TypeFlow applications.
|
|
5
|
+
*
|
|
6
|
+
* @module @staticcanvas/typeflow/debug
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class TypeFlowDebug {
|
|
10
|
+
static enabled = false;
|
|
11
|
+
static options = {
|
|
12
|
+
logcad: true,
|
|
13
|
+
console: true,
|
|
14
|
+
maxHistory: 200,
|
|
15
|
+
traceFrames: false,
|
|
16
|
+
};
|
|
17
|
+
static history = [];
|
|
18
|
+
static originalType = null;
|
|
19
|
+
static originalErase = null;
|
|
20
|
+
|
|
21
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
22
|
+
* @description Enables diagnostic telemetry tracing across TypeFlow animation lifecycles.
|
|
23
|
+
* @param {Object} [opts={}] - Configuration options for the debug session.
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*
|
|
26
|
+
* -------------
|
|
27
|
+
*
|
|
28
|
+
* @Properties
|
|
29
|
+
* @param {boolean} [opts.logcad=true] - Whether to route logs through @staticcanvas/logcad if available.
|
|
30
|
+
* @param {boolean} [opts.console=true] - Whether to output styled colored logs to the browser console.
|
|
31
|
+
* @param {number} [opts.maxHistory=200] - Maximum number of recent log entries to retain in memory.
|
|
32
|
+
* @param {boolean} [opts.traceFrames=false] - Whether to log every individual frame render tick.
|
|
33
|
+
*
|
|
34
|
+
* -------------
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
*
|
|
38
|
+
* // Enable debugging with default settings
|
|
39
|
+
* TypeFlowDebug.enable();
|
|
40
|
+
*
|
|
41
|
+
* // Enable with custom history size and disabled console output
|
|
42
|
+
* TypeFlowDebug.enable({
|
|
43
|
+
* logcad: true,
|
|
44
|
+
* console: false,
|
|
45
|
+
* maxHistory: 500,
|
|
46
|
+
* });
|
|
47
|
+
*/
|
|
48
|
+
static enable(opts = {}) {
|
|
49
|
+
TypeFlowDebug.options = { ...TypeFlowDebug.options, ...opts };
|
|
50
|
+
TypeFlowDebug.enabled = true;
|
|
51
|
+
TypeFlowDebug.log('info', 'TypeFlowDebug enabled', { options: TypeFlowDebug.options });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
55
|
+
* @description Disables diagnostic tracing and halts active logging.
|
|
56
|
+
* @returns {void}
|
|
57
|
+
*
|
|
58
|
+
* -------------
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* TypeFlowDebug.disable();
|
|
62
|
+
*/
|
|
63
|
+
static disable() {
|
|
64
|
+
TypeFlowDebug.enabled = false;
|
|
65
|
+
TypeFlowDebug.log('info', 'TypeFlowDebug disabled');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
69
|
+
* @description Internal logging bridge supporting @staticcanvas/logcad and styled console messages.
|
|
70
|
+
* @param {'info' | 'warn' | 'error' | 'debug' | 'trace'} level - Severity level of the log entry.
|
|
71
|
+
* @param {string} message - Human-readable summary of the event.
|
|
72
|
+
* @param {Object} [data={}] - Contextual payload data to associate with the event.
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*
|
|
75
|
+
* -------------
|
|
76
|
+
*
|
|
77
|
+
* @Properties
|
|
78
|
+
* @param {string} level - Severity level ('info', 'warn', 'error', 'debug', 'trace').
|
|
79
|
+
* @param {string} message - The message text to record.
|
|
80
|
+
* @param {Object} [data={}] - Arbitrary metadata object (timings, diffs, controllers).
|
|
81
|
+
*
|
|
82
|
+
* -------------
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* TypeFlowDebug.log('info', 'Sequence step completed', { step: 1, duration: 45 });
|
|
86
|
+
* TypeFlowDebug.log('warn', 'Frame drop detected', { expectedMs: 16, actualMs: 34 });
|
|
87
|
+
*/
|
|
88
|
+
static log(level, message, data = {}) {
|
|
89
|
+
if (!TypeFlowDebug.enabled && level !== 'error') return;
|
|
90
|
+
|
|
91
|
+
const entry = {
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
level,
|
|
94
|
+
message,
|
|
95
|
+
data,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
TypeFlowDebug.history.push(entry);
|
|
99
|
+
if (TypeFlowDebug.history.length > TypeFlowDebug.options.maxHistory) {
|
|
100
|
+
TypeFlowDebug.history.shift();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 1. Integration with @staticcanvas/logcad if available globally or via module
|
|
104
|
+
if (TypeFlowDebug.options.logcad && typeof globalThis !== 'undefined' && globalThis.logcad) {
|
|
105
|
+
try {
|
|
106
|
+
const logger = globalThis.logcad.getLogger?.('TypeFlow') || globalThis.logcad;
|
|
107
|
+
if (typeof logger[level] === 'function') {
|
|
108
|
+
logger[level](message, data);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// Fallback to console
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 2. Structured fallback console output with CSS colors
|
|
117
|
+
if (TypeFlowDebug.options.console && typeof console !== 'undefined') {
|
|
118
|
+
const colors = {
|
|
119
|
+
info: 'color: #38bdf8; font-weight: bold;',
|
|
120
|
+
warn: 'color: #f59e0b; font-weight: bold;',
|
|
121
|
+
error: 'color: #ef4444; font-weight: bold;',
|
|
122
|
+
debug: 'color: #ff6b2b; font-weight: bold;',
|
|
123
|
+
trace: 'color: #94a3b8; font-weight: normal;',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const tag = `%c[TypeFlow:${level.toUpperCase()}]`;
|
|
127
|
+
const style = colors[level] || colors.info;
|
|
128
|
+
|
|
129
|
+
if (Object.keys(data).length > 0) {
|
|
130
|
+
console[level === 'trace' ? 'debug' : level] || console.log(tag, style, message, data);
|
|
131
|
+
} else {
|
|
132
|
+
console[level === 'trace' ? 'debug' : level] || console.log(tag, style, message);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
138
|
+
* @description Tracks a specific TypeFlow animation controller and logs lifecycle events upon resolution or rejection.
|
|
139
|
+
* @param {Object} controller - Active TypeFlow controller instance.
|
|
140
|
+
* @param {string} [label='Animation'] - Descriptive name for the tracked target.
|
|
141
|
+
* @returns {Object} The passed-in controller for chaining.
|
|
142
|
+
*
|
|
143
|
+
* -------------
|
|
144
|
+
*
|
|
145
|
+
* @Properties
|
|
146
|
+
* @param {Object} controller - The TypeFlow controller returned by TypeFlow.type() / TypeFlow.erase().
|
|
147
|
+
* @param {Promise} controller.promise - The underlying completion promise.
|
|
148
|
+
* @param {Function} controller.stop - Method to abort the animation.
|
|
149
|
+
* @param {string} [label='Animation'] - Label displayed in log output.
|
|
150
|
+
*
|
|
151
|
+
* -------------
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* const ctrl = TypeFlow.type('#headline', 'Hello World');
|
|
155
|
+
* TypeFlowDebug.trace(ctrl, 'Hero Headline');
|
|
156
|
+
*/
|
|
157
|
+
static trace(controller, label = 'Animation') {
|
|
158
|
+
if (!TypeFlowDebug.enabled || !controller) return controller;
|
|
159
|
+
|
|
160
|
+
const start = performance.now();
|
|
161
|
+
TypeFlowDebug.log('debug', `[${label}] Started animation`, { controller });
|
|
162
|
+
|
|
163
|
+
if (controller.promise) {
|
|
164
|
+
controller.promise.then(
|
|
165
|
+
(res) => {
|
|
166
|
+
const duration = (performance.now() - start).toFixed(2);
|
|
167
|
+
TypeFlowDebug.log('info', `[${label}] Resolved (${duration}ms)`, res);
|
|
168
|
+
},
|
|
169
|
+
(err) => {
|
|
170
|
+
const duration = (performance.now() - start).toFixed(2);
|
|
171
|
+
TypeFlowDebug.log('error', `[${label}] Rejected (${duration}ms)`, err);
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return controller;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
180
|
+
* @description Returns a shallow copy of the recorded event history.
|
|
181
|
+
* @returns {Array<Object>} List of history entry objects.
|
|
182
|
+
*
|
|
183
|
+
* -------------
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* const history = TypeFlowDebug.getHistory();
|
|
187
|
+
* console.log('Total recorded events:', history.length);
|
|
188
|
+
*/
|
|
189
|
+
static getHistory() {
|
|
190
|
+
return [...TypeFlowDebug.history];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
194
|
+
* @description Clears the current event history ring buffer.
|
|
195
|
+
* @returns {void}
|
|
196
|
+
*
|
|
197
|
+
* -------------
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* TypeFlowDebug.clearHistory();
|
|
201
|
+
*/
|
|
202
|
+
static clearHistory() {
|
|
203
|
+
TypeFlowDebug.history = [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** * * * * * * * * * * * * * * * * * * * * * * * * * * **
|
|
207
|
+
* @description Exports recorded telemetry event history as a formatted JSON string.
|
|
208
|
+
* @returns {string} Serialized JSON representation of debug history.
|
|
209
|
+
*
|
|
210
|
+
* -------------
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* const jsonReport = TypeFlowDebug.exportLog();
|
|
214
|
+
* navigator.clipboard.writeText(jsonReport);
|
|
215
|
+
*/
|
|
216
|
+
static exportLog() {
|
|
217
|
+
return JSON.stringify(TypeFlowDebug.history, null, 2);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (typeof window !== 'undefined') {
|
|
222
|
+
window.TypeFlowDebug = TypeFlowDebug;
|
|
223
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeFlow ExtChars - Extended Unicode Glyph Presets & Scramble Pools.
|
|
3
|
+
*/
|
|
4
|
+
export const EXT_CHARSETS: {
|
|
5
|
+
hieroglyphs: string;
|
|
6
|
+
runic: string;
|
|
7
|
+
ogham: string;
|
|
8
|
+
coptic: string;
|
|
9
|
+
katakana: string;
|
|
10
|
+
cyberMatrix: string;
|
|
11
|
+
blocks: string;
|
|
12
|
+
boxDrawing: string;
|
|
13
|
+
mathSymbols: string;
|
|
14
|
+
braille: string;
|
|
15
|
+
astronomical: string;
|
|
16
|
+
geometric: string;
|
|
17
|
+
glitch: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class TypeFlowExtChars {
|
|
21
|
+
static charsets: Record<string, string>;
|
|
22
|
+
static getCharset(name: string): string;
|
|
23
|
+
static registerCharset(name: string, glyphs: string): void;
|
|
24
|
+
static combineCharsets(...names: string[]): string;
|
|
25
|
+
static randomGlyph(charsetOrName: string): string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getExtendedCharset(name: string): string;
|