@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
package/jsr.jsonc
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
// URL to the schema. This gives you autocompletion and validation in VS Code.
|
|
3
|
+
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
|
4
|
+
// ---------------------------------------------------------
|
|
5
|
+
// PACKAGE IDENTITY
|
|
6
|
+
// ---------------------------------------------------------
|
|
7
|
+
// The name must be scoped with an '@' (e.g. @username/pkgname)
|
|
8
|
+
"name": "@staticcanvas/typeflow",
|
|
9
|
+
// Must be a valid SemVer version (e.g. 0.1.0, 1.0.0-rc.1)
|
|
10
|
+
"version": "0.34.0",
|
|
11
|
+
// ---------------------------------------------------------
|
|
12
|
+
// EXPORTS
|
|
13
|
+
// ---------------------------------------------------------
|
|
14
|
+
// Defines what files users can import.
|
|
15
|
+
// "." is the default import
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./src/typeflow.js"
|
|
18
|
+
},
|
|
19
|
+
// ---------------------------------------------------------
|
|
20
|
+
// PUBLISHING CONTROLS
|
|
21
|
+
// ---------------------------------------------------------
|
|
22
|
+
// Controls which files are uploaded to JSR.
|
|
23
|
+
"publish": {
|
|
24
|
+
"include": ["src/**", "dist/**", "package.json", "README.md", "LICENSE"],
|
|
25
|
+
"exclude": ["src/**/*.test.js"],
|
|
26
|
+
},
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@staticcanvas/typeflow",
|
|
3
|
+
"version": "0.34.0",
|
|
4
|
+
"description": "Typeflow library.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/typeflow.cjs",
|
|
7
|
+
"module": "./dist/typeflow.mjs",
|
|
8
|
+
"browser": "./dist/typeflow.js",
|
|
9
|
+
"types": "./src/typeflow.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/typeflow.mjs",
|
|
13
|
+
"require": "./dist/typeflow.cjs",
|
|
14
|
+
"default": "./dist/typeflow.js"
|
|
15
|
+
},
|
|
16
|
+
"./metrics": {
|
|
17
|
+
"import": "./dist/typeflow-metrics.mjs",
|
|
18
|
+
"require": "./dist/typeflow-metrics.cjs",
|
|
19
|
+
"default": "./dist/typeflow-metrics.js"
|
|
20
|
+
},
|
|
21
|
+
"./debug": {
|
|
22
|
+
"import": "./dist/typeflow-debug.mjs",
|
|
23
|
+
"require": "./dist/typeflow-debug.cjs",
|
|
24
|
+
"default": "./dist/typeflow-debug.js"
|
|
25
|
+
},
|
|
26
|
+
"./caret": {
|
|
27
|
+
"import": "./dist/typeflow-caret.mjs",
|
|
28
|
+
"require": "./dist/typeflow-caret.cjs",
|
|
29
|
+
"default": "./dist/typeflow-caret.js"
|
|
30
|
+
},
|
|
31
|
+
"./webaudio": {
|
|
32
|
+
"types": "./src/typeflow-webaudio.d.ts",
|
|
33
|
+
"import": "./dist/typeflow-webaudio.mjs",
|
|
34
|
+
"require": "./dist/typeflow-webaudio.cjs",
|
|
35
|
+
"default": "./dist/typeflow-webaudio.js"
|
|
36
|
+
},
|
|
37
|
+
"./parallel": {
|
|
38
|
+
"types": "./src/typeflow-parallel.d.ts",
|
|
39
|
+
"import": "./dist/typeflow-parallel.mjs",
|
|
40
|
+
"require": "./dist/typeflow-parallel.cjs",
|
|
41
|
+
"default": "./dist/typeflow-parallel.js"
|
|
42
|
+
},
|
|
43
|
+
"./keystroke": {
|
|
44
|
+
"types": "./src/typeflow-keystroke.d.ts",
|
|
45
|
+
"import": "./dist/typeflow-keystroke.mjs",
|
|
46
|
+
"require": "./dist/typeflow-keystroke.cjs",
|
|
47
|
+
"default": "./dist/typeflow-keystroke.js"
|
|
48
|
+
},
|
|
49
|
+
"./extchars": {
|
|
50
|
+
"types": "./src/typeflow-extchars.d.ts",
|
|
51
|
+
"import": "./dist/typeflow-extchars.mjs",
|
|
52
|
+
"require": "./dist/typeflow-extchars.cjs",
|
|
53
|
+
"default": "./dist/typeflow-extchars.js"
|
|
54
|
+
},
|
|
55
|
+
"./package.json": "./package.json"
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"dist",
|
|
59
|
+
"src",
|
|
60
|
+
"branding",
|
|
61
|
+
"README.md",
|
|
62
|
+
"LICENSE",
|
|
63
|
+
"jsr.jsonc"
|
|
64
|
+
],
|
|
65
|
+
"scripts": {
|
|
66
|
+
"dev": "vite",
|
|
67
|
+
"serve:demo": "vite --open docs/static/webutil/index.html",
|
|
68
|
+
"docs:build": "npm run build && hugo --source docs",
|
|
69
|
+
"docs:serve": "npm run build && hugo server --source docs --buildDrafts --disableFastRender --ignoreCache --noHTTPCache --gc",
|
|
70
|
+
"build": "vite build && npm_config_mode=metrics vite build --config vite.optional.config.js && npm_config_mode=debug vite build --config vite.optional.config.js && npm_config_mode=caret vite build --config vite.optional.config.js && npm_config_mode=webaudio vite build --config vite.optional.config.js && npm_config_mode=parallel vite build --config vite.optional.config.js && npm_config_mode=keystroke vite build --config vite.optional.config.js && npm_config_mode=extchars vite build --config vite.optional.config.js",
|
|
71
|
+
"lint": "eslint .",
|
|
72
|
+
"lint:fix": "eslint . --fix",
|
|
73
|
+
"format": "prettier --write .",
|
|
74
|
+
"test": "vitest run",
|
|
75
|
+
"test:coverage": "vitest run --coverage",
|
|
76
|
+
"test:bun": "bun test",
|
|
77
|
+
"test:coverage:bun": "bun test --coverage",
|
|
78
|
+
"test:e2e": "npm run build && playwright test",
|
|
79
|
+
"test:e2e:ui": "npm run build && playwright test --ui",
|
|
80
|
+
"benchmark": "node --expose-gc scripts/run-benchmark.mjs",
|
|
81
|
+
"benchmark:quick": "node --expose-gc scripts/run-benchmark.mjs --quick",
|
|
82
|
+
"benchmark:md": "node --expose-gc scripts/run-benchmark.mjs --markdown",
|
|
83
|
+
"benchmark:json": "node --expose-gc scripts/run-benchmark.mjs --json",
|
|
84
|
+
"benchmark:browser": "npm run build && hugo --source docs --baseURL=http://127.0.0.1/ --gc && node scripts/run-browser-benchmark.mjs",
|
|
85
|
+
"benchmark:browser:quick": "npm run build && hugo --source docs --baseURL=http://127.0.0.1/ --gc && node scripts/run-browser-benchmark.mjs --quick",
|
|
86
|
+
"benchmark:profile": "npm run build && hugo --source docs --baseURL=http://127.0.0.1/ --gc && node scripts/profile-runtime.mjs",
|
|
87
|
+
"check:size": "node scripts/check-bundle-size.mjs",
|
|
88
|
+
"validate:docs-data": "node scripts/validate-doc-data.mjs",
|
|
89
|
+
"validate:doc-links": "hugo --source docs --gc && node scripts/validate-doc-links.mjs",
|
|
90
|
+
"release:docs:preview": "pwsh -NoProfile -File scripts/update-release-docs.ps1",
|
|
91
|
+
"release:docs": "pwsh -NoProfile -File scripts/update-release-docs.ps1 -Apply",
|
|
92
|
+
"release:docs:rebuild:preview": "pwsh -NoProfile -File scripts/update-release-docs.ps1 -Rebuild",
|
|
93
|
+
"release:docs:rebuild": "pwsh -NoProfile -File scripts/update-release-docs.ps1 -Rebuild -Apply",
|
|
94
|
+
"validate:release": "node scripts/validate-release.mjs",
|
|
95
|
+
"check:quality": "npm run validate:docs-data && npm run build && npm run check:size && npm test"
|
|
96
|
+
},
|
|
97
|
+
"keywords": [
|
|
98
|
+
"staticcanvas",
|
|
99
|
+
"esm"
|
|
100
|
+
],
|
|
101
|
+
"author": "Garvey K. Snow",
|
|
102
|
+
"license": "MIT",
|
|
103
|
+
"peerDependencies": {
|
|
104
|
+
"@staticcanvas/logcad": "^0.6.0"
|
|
105
|
+
},
|
|
106
|
+
"peerDependenciesMeta": {
|
|
107
|
+
"@staticcanvas/logcad": {
|
|
108
|
+
"optional": true
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"repository": {
|
|
112
|
+
"type": "git",
|
|
113
|
+
"url": "https://gitlab.com/staticcanvas/typeflow.git"
|
|
114
|
+
},
|
|
115
|
+
"homepage": "https://staticcanvas.gitlab.io/typeflow",
|
|
116
|
+
"bugs": {
|
|
117
|
+
"url": "https://gitlab.com/staticcanvas/typeflow/-/issues"
|
|
118
|
+
},
|
|
119
|
+
"devDependencies": {
|
|
120
|
+
"@eslint/js": "^9.20.0",
|
|
121
|
+
"@playwright/test": "^1.62.1",
|
|
122
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
123
|
+
"@vitest/coverage-v8": "^3.2.7",
|
|
124
|
+
"eslint": "^9.20.0",
|
|
125
|
+
"eslint-config-prettier": "^10.0.1",
|
|
126
|
+
"globals": "^15.14.0",
|
|
127
|
+
"jsdom": "^26.0.0",
|
|
128
|
+
"prettier": "^3.5.0",
|
|
129
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
130
|
+
"vite": "^6.1.0",
|
|
131
|
+
"vitest": "^3.0.5"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export const AUDIO_PRESET_SCHEMA_VERSION = 1;
|
|
2
|
+
|
|
3
|
+
const freeze = (value) => {
|
|
4
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
5
|
+
Object.values(value).forEach(freeze);
|
|
6
|
+
Object.freeze(value);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const layer = (oscillator, start, end, gain, duration, extras = {}) => ({
|
|
12
|
+
oscillator,
|
|
13
|
+
frequency: { start, end },
|
|
14
|
+
gain: { start: gain, end: 0.001 },
|
|
15
|
+
duration,
|
|
16
|
+
filter: extras.filter ?? null,
|
|
17
|
+
detune: { base: extras.detune ?? 0, variance: extras.variance ?? 0.06 },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const preset = (type, erase) => ({
|
|
21
|
+
version: AUDIO_PRESET_SCHEMA_VERSION,
|
|
22
|
+
actions: { type, erase: erase ?? type },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const AUDIO_PRESETS = freeze({
|
|
26
|
+
'mechanical-blue': preset(
|
|
27
|
+
[layer('triangle', 3400, 900, 0.18, 0.008), layer('sine', 200, 50, 0.32, 0.035)],
|
|
28
|
+
[layer('triangle', 900, 180, 0.12, 0.025)]
|
|
29
|
+
),
|
|
30
|
+
'mechanical-red': preset(
|
|
31
|
+
[layer('sine', 220, 70, 0.26, 0.028)],
|
|
32
|
+
[layer('sine', 150, 55, 0.12, 0.022)]
|
|
33
|
+
),
|
|
34
|
+
'mechanical-brown': preset(
|
|
35
|
+
[layer('triangle', 240, 75, 0.27, 0.032)],
|
|
36
|
+
[layer('triangle', 170, 60, 0.13, 0.024)]
|
|
37
|
+
),
|
|
38
|
+
'soft-keyboard': preset(
|
|
39
|
+
[
|
|
40
|
+
layer('sine', 520, 280, 0.08, 0.018, {
|
|
41
|
+
filter: { type: 'lowpass', frequency: 1200, q: 0.7 },
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
[layer('sine', 320, 180, 0.06, 0.016)]
|
|
45
|
+
),
|
|
46
|
+
typewriter: preset(
|
|
47
|
+
[layer('square', 320, 90, 0.17, 0.022)],
|
|
48
|
+
[layer('square', 180, 70, 0.13, 0.026)]
|
|
49
|
+
),
|
|
50
|
+
'terminal-beep': preset(
|
|
51
|
+
[layer('square', 880, 880, 0.08, 0.025)],
|
|
52
|
+
[layer('square', 440, 330, 0.06, 0.03)]
|
|
53
|
+
),
|
|
54
|
+
synth: preset(
|
|
55
|
+
[layer('sawtooth', 950, 180, 0.12, 0.045)],
|
|
56
|
+
[layer('sawtooth', 420, 120, 0.09, 0.04)]
|
|
57
|
+
),
|
|
58
|
+
correction: preset(
|
|
59
|
+
[layer('triangle', 360, 110, 0.1, 0.035)],
|
|
60
|
+
[layer('triangle', 260, 80, 0.14, 0.045)]
|
|
61
|
+
),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
export const AUDIO_PRESET_ALIASES = freeze({
|
|
65
|
+
clicky: 'mechanical-blue',
|
|
66
|
+
mechanical: 'mechanical-red',
|
|
67
|
+
tactile: 'mechanical-brown',
|
|
68
|
+
teletype: 'typewriter',
|
|
69
|
+
vintage: 'typewriter',
|
|
70
|
+
'terminal-8bit': 'terminal-beep',
|
|
71
|
+
'8bit': 'terminal-beep',
|
|
72
|
+
beep: 'terminal-beep',
|
|
73
|
+
'cyber-synth': 'synth',
|
|
74
|
+
neon: 'synth',
|
|
75
|
+
bubble: 'soft-keyboard',
|
|
76
|
+
pop: 'soft-keyboard',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export function validateAudioPreset(config) {
|
|
80
|
+
if (
|
|
81
|
+
!config ||
|
|
82
|
+
config.version !== AUDIO_PRESET_SCHEMA_VERSION ||
|
|
83
|
+
!config.actions ||
|
|
84
|
+
typeof config.actions !== 'object'
|
|
85
|
+
) {
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
`TypeFlowWebAudio: preset must use schema version ${AUDIO_PRESET_SCHEMA_VERSION}.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const output = { version: AUDIO_PRESET_SCHEMA_VERSION, actions: {} };
|
|
91
|
+
for (const action of ['type', 'erase']) {
|
|
92
|
+
const layers = config.actions[action];
|
|
93
|
+
if (!Array.isArray(layers) || layers.length === 0 || layers.length > 4) {
|
|
94
|
+
throw new TypeError(
|
|
95
|
+
`TypeFlowWebAudio: actions.${action} must contain 1-4 oscillator layers.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
output.actions[action] = layers.map((candidate) => {
|
|
99
|
+
const oscillator = String(candidate.oscillator ?? 'sine');
|
|
100
|
+
if (!['sine', 'square', 'sawtooth', 'triangle'].includes(oscillator))
|
|
101
|
+
throw new RangeError('TypeFlowWebAudio: invalid oscillator type.');
|
|
102
|
+
const duration = Number(candidate.duration);
|
|
103
|
+
const start = Number(candidate.frequency?.start);
|
|
104
|
+
const end = Number(candidate.frequency?.end);
|
|
105
|
+
const gainStart = Number(candidate.gain?.start);
|
|
106
|
+
const gainEnd = Number(candidate.gain?.end);
|
|
107
|
+
if (!(duration >= 0.001 && duration <= 2))
|
|
108
|
+
throw new RangeError('TypeFlowWebAudio: duration must be 0.001-2 seconds.');
|
|
109
|
+
if (!(start >= 20 && start <= 20000 && end >= 20 && end <= 20000))
|
|
110
|
+
throw new RangeError('TypeFlowWebAudio: frequency must be 20-20000 Hz.');
|
|
111
|
+
if (!(gainStart >= 0 && gainStart <= 1 && gainEnd > 0 && gainEnd <= 1))
|
|
112
|
+
throw new RangeError('TypeFlowWebAudio: gain must be within safe bounds.');
|
|
113
|
+
const variance = Number(candidate.detune?.variance ?? 0);
|
|
114
|
+
if (!(variance >= 0 && variance <= 0.5))
|
|
115
|
+
throw new RangeError('TypeFlowWebAudio: detune variance must be 0-0.5.');
|
|
116
|
+
const filter =
|
|
117
|
+
candidate.filter === null || candidate.filter === undefined
|
|
118
|
+
? null
|
|
119
|
+
: {
|
|
120
|
+
type: String(candidate.filter.type ?? 'lowpass'),
|
|
121
|
+
frequency: Math.max(20, Math.min(20000, Number(candidate.filter.frequency) || 1000)),
|
|
122
|
+
q: Math.max(0.0001, Math.min(1000, Number(candidate.filter.q) || 1)),
|
|
123
|
+
};
|
|
124
|
+
if (filter && !['lowpass', 'highpass', 'bandpass', 'notch'].includes(filter.type))
|
|
125
|
+
throw new RangeError('TypeFlowWebAudio: invalid filter type.');
|
|
126
|
+
return {
|
|
127
|
+
oscillator,
|
|
128
|
+
frequency: { start, end },
|
|
129
|
+
gain: { start: gainStart, end: gainEnd },
|
|
130
|
+
duration,
|
|
131
|
+
filter,
|
|
132
|
+
detune: { base: Number(candidate.detune?.base) || 0, variance },
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return freeze(output);
|
|
137
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.
|
|
3
|
+
*/
|
|
4
|
+
export interface TypeFlowCaretConfig {
|
|
5
|
+
/** Initial text. Defaults to the target's current text content. */
|
|
6
|
+
text?: string;
|
|
7
|
+
/** Initial caret index in graphemes. Defaults to the end of the text. */
|
|
8
|
+
position?: number;
|
|
9
|
+
/** Cursor glyph. Empty string hides it. Default: '|' */
|
|
10
|
+
cursor?: string;
|
|
11
|
+
/** Default: true */
|
|
12
|
+
cursorBlink?: boolean;
|
|
13
|
+
/** Default ms per character for `insert()`. Default: 40 */
|
|
14
|
+
speed?: number;
|
|
15
|
+
/** Instantly complete `insert()` instead of animating if the user prefers reduced motion. Default: true */
|
|
16
|
+
respectReducedMotion?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TypeFlowCaretInsertConfig {
|
|
20
|
+
/** Ms per character. Defaults to the speed passed to the constructor. */
|
|
21
|
+
speed?: number;
|
|
22
|
+
respectReducedMotion?: boolean;
|
|
23
|
+
onStart?: () => void;
|
|
24
|
+
onComplete?: (text: string) => void;
|
|
25
|
+
onInterrupt?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TypeFlowCaretResult {
|
|
29
|
+
completed: boolean;
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface TypeFlowCaretController {
|
|
34
|
+
stop: () => void;
|
|
35
|
+
promise: Promise<TypeFlowCaretResult>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class TypeFlowCaret {
|
|
39
|
+
constructor(target: Element | string, config?: TypeFlowCaretConfig);
|
|
40
|
+
|
|
41
|
+
/** Current plain-text content, cursor excluded. */
|
|
42
|
+
readonly text: string;
|
|
43
|
+
/** Current caret index, in graphemes, from the start of the text. */
|
|
44
|
+
readonly position: number;
|
|
45
|
+
/** Total number of graphemes in the current text. */
|
|
46
|
+
readonly length: number;
|
|
47
|
+
|
|
48
|
+
/** Moves the caret to an absolute grapheme index, clamped to bounds. */
|
|
49
|
+
jump(position: number): this;
|
|
50
|
+
/** Moves the caret by a relative number of graphemes (positive = right, negative = left). */
|
|
51
|
+
move(delta: number): this;
|
|
52
|
+
/** Deletes one grapheme at the caret: 'backward' (Backspace, default) or 'forward' (Delete). */
|
|
53
|
+
deleteChar(direction?: "backward" | "forward"): this;
|
|
54
|
+
/** Deletes the whitespace-delimited word adjacent to the caret. */
|
|
55
|
+
deleteWord(direction?: "backward" | "forward"): this;
|
|
56
|
+
/** Types `text` at the current caret position, shifting following content to the right. */
|
|
57
|
+
insert(text: string, config?: TypeFlowCaretInsertConfig): TypeFlowCaretController;
|
|
58
|
+
/** Stops an in-flight `insert()`, if any. */
|
|
59
|
+
stop(): this;
|
|
60
|
+
/** Stops any in-flight `insert()` and clears the blink interval. */
|
|
61
|
+
destroy(): this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Factory form of `new TypeFlowCaret(target, config)`. */
|
|
65
|
+
export function Caret(target: Element | string, config?: TypeFlowCaretConfig): TypeFlowCaret;
|
|
@@ -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
|
+
}
|