@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,162 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
<!-- Author: Garvey K. Snow / StaticCanvas -->
|
|
4
|
+
<!-- License: Creative Commons Attribution (CC BY 4.0) -->
|
|
5
|
+
|
|
6
|
+
<svg
|
|
7
|
+
width="512"
|
|
8
|
+
height="512"
|
|
9
|
+
viewBox="0 0 135.46667 135.46667"
|
|
10
|
+
version="1.1"
|
|
11
|
+
id="svg1"
|
|
12
|
+
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
|
13
|
+
sodipodi:docname="typeflow-logosvg"
|
|
14
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
15
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
16
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
17
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
18
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
19
|
+
<sodipodi:namedview
|
|
20
|
+
id="namedview1"
|
|
21
|
+
pagecolor="#ffffff"
|
|
22
|
+
bordercolor="#000000"
|
|
23
|
+
borderopacity="0.25"
|
|
24
|
+
inkscape:showpageshadow="2"
|
|
25
|
+
inkscape:pageopacity="0.0"
|
|
26
|
+
inkscape:pagecheckerboard="0"
|
|
27
|
+
inkscape:deskcolor="#d1d1d1"
|
|
28
|
+
inkscape:document-units="mm"
|
|
29
|
+
inkscape:zoom="0.83276505"
|
|
30
|
+
inkscape:cx="4.2028661"
|
|
31
|
+
inkscape:cy="309.21086"
|
|
32
|
+
inkscape:window-width="1440"
|
|
33
|
+
inkscape:window-height="899"
|
|
34
|
+
inkscape:window-x="0"
|
|
35
|
+
inkscape:window-y="0"
|
|
36
|
+
inkscape:window-maximized="1"
|
|
37
|
+
inkscape:current-layer="layer1" />
|
|
38
|
+
<defs
|
|
39
|
+
id="defs1">
|
|
40
|
+
<linearGradient
|
|
41
|
+
id="linearGradient15"
|
|
42
|
+
inkscape:collect="always">
|
|
43
|
+
<stop
|
|
44
|
+
style="stop-color:#ff0c05;stop-opacity:1;"
|
|
45
|
+
offset="0"
|
|
46
|
+
id="stop15" />
|
|
47
|
+
<stop
|
|
48
|
+
style="stop-color:#fdbb4f;stop-opacity:1;"
|
|
49
|
+
offset="1"
|
|
50
|
+
id="stop16" />
|
|
51
|
+
</linearGradient>
|
|
52
|
+
<inkscape:path-effect
|
|
53
|
+
effect="powerclip"
|
|
54
|
+
message=""
|
|
55
|
+
id="path-effect14"
|
|
56
|
+
is_visible="true"
|
|
57
|
+
lpeversion="1"
|
|
58
|
+
inverse="true"
|
|
59
|
+
flatten="false"
|
|
60
|
+
hide_clip="false" />
|
|
61
|
+
<linearGradient
|
|
62
|
+
id="linearGradient9"
|
|
63
|
+
inkscape:collect="always">
|
|
64
|
+
<stop
|
|
65
|
+
style="stop-color:#ff0000;stop-opacity:1;"
|
|
66
|
+
offset="0"
|
|
67
|
+
id="stop9" />
|
|
68
|
+
<stop
|
|
69
|
+
style="stop-color:#fdcf58;stop-opacity:1;"
|
|
70
|
+
offset="1"
|
|
71
|
+
id="stop11" />
|
|
72
|
+
</linearGradient>
|
|
73
|
+
<inkscape:path-effect
|
|
74
|
+
effect="powerclip"
|
|
75
|
+
message=""
|
|
76
|
+
id="path-effect8"
|
|
77
|
+
is_visible="true"
|
|
78
|
+
lpeversion="1"
|
|
79
|
+
inverse="true"
|
|
80
|
+
flatten="false"
|
|
81
|
+
hide_clip="false" />
|
|
82
|
+
<linearGradient
|
|
83
|
+
id="linearGradient1"
|
|
84
|
+
inkscape:collect="always">
|
|
85
|
+
<stop
|
|
86
|
+
style="stop-color:#152331;stop-opacity:1;"
|
|
87
|
+
offset="0"
|
|
88
|
+
id="stop1" />
|
|
89
|
+
<stop
|
|
90
|
+
style="stop-color:#252b33;stop-opacity:1;"
|
|
91
|
+
offset="0.545663"
|
|
92
|
+
id="stop14" />
|
|
93
|
+
<stop
|
|
94
|
+
style="stop-color:#494949;stop-opacity:1;"
|
|
95
|
+
offset="0.97742236"
|
|
96
|
+
id="stop3" />
|
|
97
|
+
</linearGradient>
|
|
98
|
+
<linearGradient
|
|
99
|
+
inkscape:collect="always"
|
|
100
|
+
xlink:href="#linearGradient1"
|
|
101
|
+
id="linearGradient2"
|
|
102
|
+
x1="53.244328"
|
|
103
|
+
y1="67.510217"
|
|
104
|
+
x2="125.41869"
|
|
105
|
+
y2="63.438999"
|
|
106
|
+
gradientUnits="userSpaceOnUse"
|
|
107
|
+
gradientTransform="matrix(1.5316336,0,0,0.9502836,-73.067166,6.6824504)" />
|
|
108
|
+
<linearGradient
|
|
109
|
+
inkscape:collect="always"
|
|
110
|
+
xlink:href="#linearGradient9"
|
|
111
|
+
id="linearGradient10"
|
|
112
|
+
x1="2.2222646"
|
|
113
|
+
y1="67.526265"
|
|
114
|
+
x2="133.40492"
|
|
115
|
+
y2="67.526265"
|
|
116
|
+
gradientUnits="userSpaceOnUse"
|
|
117
|
+
gradientTransform="matrix(0.97562941,0,0,0.97562941,0.06430015,2.3289887)" />
|
|
118
|
+
<clipPath
|
|
119
|
+
clipPathUnits="userSpaceOnUse"
|
|
120
|
+
id="clipPath14">
|
|
121
|
+
<path
|
|
122
|
+
style="font-weight:900;font-size:85.9333px;font-family:Timmana;-inkscape-font-specification:'Timmana Heavy';display:none;fill:#4de276;stroke-width:36.7496;stroke-dasharray:881.991, 881.991"
|
|
123
|
+
d="M 62.121174,50.305851 H 51.580021 L 45.507402,93.387077 H 34.50794 L 40.465984,50.305851 H 29.810255 L 30.95603,41.368789 H 63.496105 Z M 86.417507,72.992242 H 73.011914 l -2.864443,20.394835 h -11.11404 l 7.332975,-52.018288 h 27.15492 l -1.260353,8.937062 H 76.22009 l -2.062398,14.207638 h 13.405593 z m 30.664703,-36.32114 q -1.83325,3.437332 -5.156,10.426573 -3.20818,6.874663 -7.44755,15.926305 -4.1248,9.051639 -8.937065,19.59279 -4.697688,10.541151 -9.395373,21.54061 h -9.509953 q 4.812265,-10.999459 9.509953,-21.54061 4.697685,-10.541151 8.822484,-19.59279 4.239376,-9.051642 7.447554,-15.926305 3.32275,-6.989241 5.156,-10.426573 z"
|
|
124
|
+
id="path14"
|
|
125
|
+
aria-label="TF/" />
|
|
126
|
+
<path
|
|
127
|
+
id="lpe_path-effect14"
|
|
128
|
+
style="font-weight:900;font-size:85.9333px;font-family:Timmana;-inkscape-font-specification:'Timmana Heavy';fill:#4de276;stroke-width:36.7496;stroke-dasharray:881.991, 881.991"
|
|
129
|
+
class="powerclip"
|
|
130
|
+
d="M -0.99145186,2.2282375 H 134.1298 V 138.60146 H -0.99145186 Z M 62.121174,50.305851 63.496105,41.368789 H 30.95603 l -1.145775,8.937062 H 40.465984 L 34.50794,93.387077 h 10.999462 l 6.072619,-43.081226 z m 24.296333,22.686391 1.145778,-8.478753 H 74.157692 L 76.22009,50.305851 h 16.040883 l 1.260353,-8.937062 h -27.15492 l -7.332975,52.018288 h 11.11404 l 2.864443,-20.394835 z m 30.664703,-36.32114 h -9.50995 q -1.83325,3.437332 -5.156,10.426573 -3.208178,6.874663 -7.447554,15.926305 -4.124799,9.051639 -8.822484,19.59279 -4.697688,10.541151 -9.509953,21.54061 h 9.509953 q 4.697685,-10.999459 9.395373,-21.54061 4.812265,-10.541151 8.937065,-19.59279 4.23937,-9.051642 7.44755,-15.926305 3.32275,-6.989241 5.156,-10.426573 z" />
|
|
131
|
+
</clipPath>
|
|
132
|
+
<linearGradient
|
|
133
|
+
inkscape:collect="always"
|
|
134
|
+
xlink:href="#linearGradient15"
|
|
135
|
+
id="linearGradient16"
|
|
136
|
+
x1="4.3812527"
|
|
137
|
+
y1="70.414794"
|
|
138
|
+
x2="129.1298"
|
|
139
|
+
y2="70.414794"
|
|
140
|
+
gradientUnits="userSpaceOnUse" />
|
|
141
|
+
</defs>
|
|
142
|
+
<g
|
|
143
|
+
inkscape:label="Layer 1"
|
|
144
|
+
inkscape:groupmode="layer"
|
|
145
|
+
id="layer1">
|
|
146
|
+
<path
|
|
147
|
+
id="rect1-0"
|
|
148
|
+
style="display:inline;fill:url(#linearGradient10);fill-opacity:1;stroke:none;stroke-width:7.73234;stroke-dasharray:185.577, 185.577"
|
|
149
|
+
d="M 66.225236,2.6986748 C 101.7823,2.2567532 149.52676,4.2503538 130.21805,68.209597 120.37569,100.81197 101.08916,133.42648 66.225236,133.72052 30.77447,134.01952 -6.0050013,103.55547 2.2324068,68.209597 9.8323743,35.59892 31.053259,3.1358101 66.225236,2.6986748 Z"
|
|
150
|
+
inkscape:label="background"
|
|
151
|
+
sodipodi:nodetypes="sssss" />
|
|
152
|
+
<path
|
|
153
|
+
id="rect1"
|
|
154
|
+
style="fill:url(#linearGradient2);stroke:url(#linearGradient16);stroke-width:3.868;stroke-dasharray:92.8322, 92.8322;stroke-dashoffset:0"
|
|
155
|
+
d="m 66.979985,10.952257 c 32.406976,0 60.215815,-11.12007734 60.215815,22.055685 0,33.175759 -11.78736,80.083628 -59.580381,95.488398 C 23.815672,142.61398 -2.6580111,107.12136 8.4836391,70.836305 18.221809,39.121966 34.57301,10.952257 66.979985,10.952257 Z"
|
|
156
|
+
clip-path="url(#clipPath14)"
|
|
157
|
+
sodipodi:nodetypes="sssss"
|
|
158
|
+
inkscape:path-effect="#path-effect14"
|
|
159
|
+
inkscape:original-d="m 66.979985,10.952257 c 32.406976,0 60.215815,-11.12007734 60.215815,22.055685 0,33.175759 -11.78736,80.083628 -59.580381,95.488398 C 23.815672,142.61398 -2.6580111,107.12136 8.4836391,70.836305 18.221809,39.121966 34.57301,10.952257 66.979985,10.952257 Z"
|
|
160
|
+
transform="matrix(1.0197765,0,0,1.0197765,-0.02319825,-3.6171792)" />
|
|
161
|
+
</g>
|
|
162
|
+
</svg>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*! typeflow-caret | MIT */
|
|
2
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TypeflowCaret={})}(this,function(t){"use strict";const e=/^\s+$/u;function i(t){return"undefined"!=typeof Intl&&Intl.Segmenter?Array.from(new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function n(t){var e,i;"undefined"!=typeof window&&(null==(i=null==(e=window.TypeFlow)?void 0:e.stop)||i.call(e,t))}function s(t,e){const i=null==t?e:Number(t);return Number.isFinite(i)?Math.max(0,Math.min(Math.round(i),e)):e}class r{
|
|
3
|
+
/**
|
|
4
|
+
* @param {Element | string} target - Element or CSS selector.
|
|
5
|
+
* @param {object} [config={}]
|
|
6
|
+
* @param {string} [config.text] - Initial text. Defaults to the target's current text content.
|
|
7
|
+
* @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.
|
|
8
|
+
* @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.
|
|
9
|
+
* @param {boolean} [config.cursorBlink=true]
|
|
10
|
+
* @param {number} [config.speed=40] - Default ms per character for `insert()`.
|
|
11
|
+
* @param {boolean} [config.respectReducedMotion=true]
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m "wip"' });
|
|
15
|
+
* caret.jump(3).deleteWord('forward').insert('push');
|
|
16
|
+
* // "git push -m "wip""
|
|
17
|
+
*/
|
|
18
|
+
constructor(t,e={}){var r,l,h,o,u,d,a;if(this.el="string"==typeof t?document.querySelector(t):t,!this.el)throw new Error("TypeFlowCaret: target element not found.");n(this.el),this.cursor=null!=(r=e.cursor)?r:"|",this.cursorBlink=null==(l=e.cursorBlink)||l,this.speed=null!=(h=e.speed)?h:40,this.respectReducedMotion=null==(o=e.respectReducedMotion)||o,this.units=i(null!=(d=null!=(u=e.text)?u:this.el.textContent)?d:""),this.index=s(null!=(a=e.position)?a:this.units.length,this.units.length),this._blinkInterval=null,this._activeInsert=null,this._render()}
|
|
19
|
+
/** Current plain-text content, cursor excluded. */get text(){return this.units.join("")}
|
|
20
|
+
/** Current caret index, in graphemes, from the start of the text. */get position(){return this.index}
|
|
21
|
+
/** Total number of graphemes in the current text. */get length(){return this.units.length}
|
|
22
|
+
/** Moves the caret to an absolute grapheme index, clamped to bounds. */jump(t){return this._stopInsert(),this.index=s(t,this.units.length),this._render(),this}
|
|
23
|
+
/** Moves the caret by a relative number of graphemes (positive = right, negative = left). */move(t){return this.jump(this.index+(Number(t)||0))}
|
|
24
|
+
/**
|
|
25
|
+
* Deletes one grapheme at the caret.
|
|
26
|
+
* @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).
|
|
27
|
+
*/deleteChar(t="backward"){return this._stopInsert(),"forward"===t?this.index<this.units.length&&this.units.splice(this.index,1):this.index>0&&(this.units.splice(this.index-1,1),this.index-=1),this._render(),this}
|
|
28
|
+
/**
|
|
29
|
+
* Deletes the whitespace-delimited word adjacent to the caret.
|
|
30
|
+
* @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.
|
|
31
|
+
*/deleteWord(t="backward"){if(this._stopInsert(),"forward"===t){let t=this.index,i=!1;for(;t<this.units.length&&e.test(this.units[t]);)t+=1,i=!0;for(;t<this.units.length&&!e.test(this.units[t]);)t+=1;if(!i)for(;t<this.units.length&&e.test(this.units[t]);)t+=1;if(t===this.index)return this;this.units.splice(this.index,t-this.index)}else{let t=this.index;for(;t>0&&e.test(this.units[t-1]);)t-=1;for(;t>0&&!e.test(this.units[t-1]);)t-=1;if(t===this.index)return this;this.units.splice(t,this.index-t),this.index=t}return this._render(),this}
|
|
32
|
+
/**
|
|
33
|
+
* Types `text` at the current caret position, one grapheme at a time,
|
|
34
|
+
* shifting any following content to the right as it goes.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} text
|
|
37
|
+
* @param {object} [config={}]
|
|
38
|
+
* @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.
|
|
39
|
+
* @param {boolean} [config.respectReducedMotion]
|
|
40
|
+
* @param {() => void} [config.onStart]
|
|
41
|
+
* @param {(text: string) => void} [config.onComplete]
|
|
42
|
+
* @param {() => void} [config.onInterrupt]
|
|
43
|
+
* @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}
|
|
44
|
+
*/insert(t,e={}){var n,s,r;this._stopInsert();const l=i(null==t?"":String(t)),h=Math.max(0,Number(null!=(n=e.speed)?n:this.speed)||0),o=this.index;let u=!1,d=null,a=null;const c=t=>{var i,n;u||(u=!0,null!==d&&clearTimeout(d),this._activeInsert=null,t?null==(i=e.onComplete)||i.call(e,this.text):null==(n=e.onInterrupt)||n.call(e),null==a||a({completed:t,text:this.text}))},p={stop:()=>c(!1),promise:new Promise(t=>{a=t})};if(this._activeInsert=p,0===l.length)return c(!0),p;if(!0===(null!=(s=e.respectReducedMotion)?s:this.respectReducedMotion)&&"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches)return this.units.splice(o,0,...l),this.index=o+l.length,this._render(),c(!0),p;null==(r=e.onStart)||r.call(e);let f=0;const m=()=>{this.units.splice(o+f,0,l[f]),this.index=o+f+1,this._render(),f+=1,f>=l.length?c(!0):d=setTimeout(m,h)};return d=setTimeout(m,h),p}
|
|
45
|
+
/** Stops an in-flight `insert()`, if any. */stop(){return this._stopInsert(),this}
|
|
46
|
+
/** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */destroy(){return this._stopInsert(),null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null),this}_stopInsert(){var t;null==(t=this._activeInsert)||t.stop(),n(this.el)}_render(){var t,e;null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null);const i=this.units.slice(0,this.index).join(""),n=this.units.slice(this.index).join("");if(this.el.replaceChildren(document.createTextNode(i)),this.cursor){const i=function(t){const e=document.createElement("span");return e.className="tf-cursor",e.setAttribute("aria-hidden","true"),e.textContent=t,e}(this.cursor);if(this.el.appendChild(i),this.cursorBlink){let n=!0;this._blinkInterval=setInterval(()=>{n=!n,i.style.visibility=n?"visible":"hidden"},530),null==(e=(t=this._blinkInterval).unref)||e.call(t)}}this.el.appendChild(document.createTextNode(n))}}function l(t,e){return new r(t,e)}"undefined"!=typeof window&&(window.TypeFlowCaret=r,window.Caret=l),t.Caret=l,t.TypeFlowCaret=r,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
47
|
+
//# sourceMappingURL=typeflow-caret.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-caret.cjs","sources":["../../../src/typeflow-caret.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.\n * A persistent, editable cursor bound to an element's text: jump or move to any\n * grapheme index, delete a character or whole word at that position, and type\n * new text in with the usual character-by-character animation. Zero\n * dependencies; opportunistically defers to a page-global TypeFlow so starting\n * a `TypeFlow.type()`/`erase()` on the same element interrupts an in-flight\n * caret edit, without requiring a hard import of the core engine.\n *\n * @module @staticcanvas/typeflow/caret\n */\n\nconst CURSOR_BLINK_INTERVAL = 530;\nconst WHITESPACE_UNIT = /^\\s+$/u;\n\nfunction segmentGraphemes(text) {\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n return Array.from(\n new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text),\n (s) => s.segment\n );\n }\n return Array.from(text);\n}\n\nfunction prefersReducedMotion() {\n return (\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/** Stops any animation the page-global TypeFlow engine may have active on `el`, if present. */\nfunction stopPageTypeFlow(el) {\n if (typeof window !== 'undefined') window.TypeFlow?.stop?.(el);\n}\n\nfunction clampCaretIndex(position, max) {\n const value = position === undefined || position === null ? max : Number(position);\n if (!Number.isFinite(value)) return max;\n return Math.max(0, Math.min(Math.round(value), max));\n}\n\nfunction createCursorSpan(cursorChar) {\n const span = document.createElement('span');\n span.className = 'tf-cursor';\n span.setAttribute('aria-hidden', 'true');\n span.textContent = cursorChar;\n return span;\n}\n\n/**\n * Interactive, stateful cursor bound to an element's text content.\n *\n * Unlike `TypeFlow.type()`/`erase()`, which run once from start to finish, a\n * Caret is persistent: `jump()`, `move()`, `deleteChar()`, and `deleteWord()`\n * edit `text`/`position` and re-render immediately; `insert()` is animated\n * and returns a `{ stop, promise }` controller.\n */\nexport class TypeFlowCaret {\n /**\n * @param {Element | string} target - Element or CSS selector.\n * @param {object} [config={}]\n * @param {string} [config.text] - Initial text. Defaults to the target's current text content.\n * @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.\n * @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.\n * @param {boolean} [config.cursorBlink=true]\n * @param {number} [config.speed=40] - Default ms per character for `insert()`.\n * @param {boolean} [config.respectReducedMotion=true]\n *\n * @example\n * const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m \"wip\"' });\n * caret.jump(3).deleteWord('forward').insert('push');\n * // \"git push -m \"wip\"\"\n */\n constructor(target, config = {}) {\n this.el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!this.el) throw new Error('TypeFlowCaret: target element not found.');\n stopPageTypeFlow(this.el);\n\n this.cursor = config.cursor ?? '|';\n this.cursorBlink = config.cursorBlink ?? true;\n this.speed = config.speed ?? 40;\n this.respectReducedMotion = config.respectReducedMotion ?? true;\n this.units = segmentGraphemes(config.text ?? this.el.textContent ?? '');\n this.index = clampCaretIndex(config.position ?? this.units.length, this.units.length);\n this._blinkInterval = null;\n this._activeInsert = null;\n this._render();\n }\n\n /** Current plain-text content, cursor excluded. */\n get text() {\n return this.units.join('');\n }\n\n /** Current caret index, in graphemes, from the start of the text. */\n get position() {\n return this.index;\n }\n\n /** Total number of graphemes in the current text. */\n get length() {\n return this.units.length;\n }\n\n /** Moves the caret to an absolute grapheme index, clamped to bounds. */\n jump(position) {\n this._stopInsert();\n this.index = clampCaretIndex(position, this.units.length);\n this._render();\n return this;\n }\n\n /** Moves the caret by a relative number of graphemes (positive = right, negative = left). */\n move(delta) {\n return this.jump(this.index + (Number(delta) || 0));\n }\n\n /**\n * Deletes one grapheme at the caret.\n * @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).\n */\n deleteChar(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n if (this.index < this.units.length) this.units.splice(this.index, 1);\n } else if (this.index > 0) {\n this.units.splice(this.index - 1, 1);\n this.index -= 1;\n }\n this._render();\n return this;\n }\n\n /**\n * Deletes the whitespace-delimited word adjacent to the caret.\n * @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.\n */\n deleteWord(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n let end = this.index;\n let ateLeadingSpace = false;\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) {\n end += 1;\n ateLeadingSpace = true;\n }\n while (end < this.units.length && !WHITESPACE_UNIT.test(this.units[end])) end += 1;\n if (!ateLeadingSpace) {\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) end += 1;\n }\n if (end === this.index) return this;\n this.units.splice(this.index, end - this.index);\n } else {\n let start = this.index;\n while (start > 0 && WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n while (start > 0 && !WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n if (start === this.index) return this;\n this.units.splice(start, this.index - start);\n this.index = start;\n }\n this._render();\n return this;\n }\n\n /**\n * Types `text` at the current caret position, one grapheme at a time,\n * shifting any following content to the right as it goes.\n *\n * @param {string} text\n * @param {object} [config={}]\n * @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.\n * @param {boolean} [config.respectReducedMotion]\n * @param {() => void} [config.onStart]\n * @param {(text: string) => void} [config.onComplete]\n * @param {() => void} [config.onInterrupt]\n * @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}\n */\n insert(text, config = {}) {\n this._stopInsert();\n const insertUnits = segmentGraphemes(text === null || text === undefined ? '' : String(text));\n const speed = Math.max(0, Number(config.speed ?? this.speed) || 0);\n const startIndex = this.index;\n\n let settled = false;\n let timeoutId = null;\n let resolveFn = null;\n const settle = (completed) => {\n if (settled) return;\n settled = true;\n if (timeoutId !== null) clearTimeout(timeoutId);\n this._activeInsert = null;\n if (completed) config.onComplete?.(this.text);\n else config.onInterrupt?.();\n resolveFn?.({ completed, text: this.text });\n };\n const controller = {\n stop: () => settle(false),\n promise: new Promise((resolve) => {\n resolveFn = resolve;\n }),\n };\n this._activeInsert = controller;\n\n if (insertUnits.length === 0) {\n settle(true);\n return controller;\n }\n\n const respectReducedMotion = config.respectReducedMotion ?? this.respectReducedMotion;\n if (respectReducedMotion === true && prefersReducedMotion()) {\n this.units.splice(startIndex, 0, ...insertUnits);\n this.index = startIndex + insertUnits.length;\n this._render();\n settle(true);\n return controller;\n }\n\n config.onStart?.();\n let step = 0;\n const tick = () => {\n this.units.splice(startIndex + step, 0, insertUnits[step]);\n this.index = startIndex + step + 1;\n this._render();\n step += 1;\n if (step >= insertUnits.length) {\n settle(true);\n return;\n }\n timeoutId = setTimeout(tick, speed);\n };\n timeoutId = setTimeout(tick, speed);\n return controller;\n }\n\n /** Stops an in-flight `insert()`, if any. */\n stop() {\n this._stopInsert();\n return this;\n }\n\n /** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */\n destroy() {\n this._stopInsert();\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n return this;\n }\n\n _stopInsert() {\n this._activeInsert?.stop();\n stopPageTypeFlow(this.el);\n }\n\n _render() {\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n const before = this.units.slice(0, this.index).join('');\n const after = this.units.slice(this.index).join('');\n this.el.replaceChildren(document.createTextNode(before));\n\n if (this.cursor) {\n const cursorSpan = createCursorSpan(this.cursor);\n this.el.appendChild(cursorSpan);\n if (this.cursorBlink) {\n let visible = true;\n this._blinkInterval = setInterval(() => {\n visible = !visible;\n cursorSpan.style.visibility = visible ? 'visible' : 'hidden';\n }, CURSOR_BLINK_INTERVAL);\n this._blinkInterval.unref?.();\n }\n }\n\n this.el.appendChild(document.createTextNode(after));\n }\n}\n\n/**\n * Factory form of `new TypeFlowCaret(target, config)`.\n *\n * @param {Element | string} target\n * @param {object} [config]\n * @returns {TypeFlowCaret}\n *\n * @example\n * const caret = Caret('#terminal', { text: 'git commit' });\n * caret.move(-6).insert('push --force ');\n */\nexport function Caret(target, config) {\n return new TypeFlowCaret(target, config);\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowCaret = TypeFlowCaret;\n window.Caret = Caret;\n}\n"],"names":["WHITESPACE_UNIT","segmentGraphemes","text","Intl","Segmenter","Array","from","granularity","segment","s","stopPageTypeFlow","el","window","TypeFlow","_a","stop","_b","call","clampCaretIndex","position","max","value","Number","isFinite","Math","min","round","TypeFlowCaret","constructor","target","config","this","document","querySelector","Error","cursor","cursorBlink","speed","_c","respectReducedMotion","_d","units","_f","_e","textContent","index","_g","length","_blinkInterval","_activeInsert","_render","join","jump","_stopInsert","move","delta","deleteChar","direction","splice","deleteWord","end","ateLeadingSpace","test","start","insert","insertUnits","String","startIndex","settled","timeoutId","resolveFn","settle","completed","clearTimeout","onComplete","onInterrupt","controller","promise","Promise","resolve","matchMedia","matches","onStart","step","tick","setTimeout","destroy","clearInterval","before","slice","after","replaceChildren","createTextNode","cursorSpan","cursorChar","span","createElement","className","setAttribute","createCursorSpan","appendChild","visible","setInterval","style","visibility","unref","Caret"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*! typeflow-caret | MIT */
|
|
2
|
+
const t=/^\s+$/u;function e(t){return"undefined"!=typeof Intl&&Intl.Segmenter?Array.from(new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function i(t){var e,i;"undefined"!=typeof window&&(null==(i=null==(e=window.TypeFlow)?void 0:e.stop)||i.call(e,t))}function n(t,e){const i=null==t?e:Number(t);return Number.isFinite(i)?Math.max(0,Math.min(Math.round(i),e)):e}class s{
|
|
3
|
+
/**
|
|
4
|
+
* @param {Element | string} target - Element or CSS selector.
|
|
5
|
+
* @param {object} [config={}]
|
|
6
|
+
* @param {string} [config.text] - Initial text. Defaults to the target's current text content.
|
|
7
|
+
* @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.
|
|
8
|
+
* @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.
|
|
9
|
+
* @param {boolean} [config.cursorBlink=true]
|
|
10
|
+
* @param {number} [config.speed=40] - Default ms per character for `insert()`.
|
|
11
|
+
* @param {boolean} [config.respectReducedMotion=true]
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m "wip"' });
|
|
15
|
+
* caret.jump(3).deleteWord('forward').insert('push');
|
|
16
|
+
* // "git push -m "wip""
|
|
17
|
+
*/
|
|
18
|
+
constructor(t,s={}){var r,l,h,u,o,d,c;if(this.el="string"==typeof t?document.querySelector(t):t,!this.el)throw new Error("TypeFlowCaret: target element not found.");i(this.el),this.cursor=null!=(r=s.cursor)?r:"|",this.cursorBlink=null==(l=s.cursorBlink)||l,this.speed=null!=(h=s.speed)?h:40,this.respectReducedMotion=null==(u=s.respectReducedMotion)||u,this.units=e(null!=(d=null!=(o=s.text)?o:this.el.textContent)?d:""),this.index=n(null!=(c=s.position)?c:this.units.length,this.units.length),this._blinkInterval=null,this._activeInsert=null,this._render()}
|
|
19
|
+
/** Current plain-text content, cursor excluded. */get text(){return this.units.join("")}
|
|
20
|
+
/** Current caret index, in graphemes, from the start of the text. */get position(){return this.index}
|
|
21
|
+
/** Total number of graphemes in the current text. */get length(){return this.units.length}
|
|
22
|
+
/** Moves the caret to an absolute grapheme index, clamped to bounds. */jump(t){return this._stopInsert(),this.index=n(t,this.units.length),this._render(),this}
|
|
23
|
+
/** Moves the caret by a relative number of graphemes (positive = right, negative = left). */move(t){return this.jump(this.index+(Number(t)||0))}
|
|
24
|
+
/**
|
|
25
|
+
* Deletes one grapheme at the caret.
|
|
26
|
+
* @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).
|
|
27
|
+
*/deleteChar(t="backward"){return this._stopInsert(),"forward"===t?this.index<this.units.length&&this.units.splice(this.index,1):this.index>0&&(this.units.splice(this.index-1,1),this.index-=1),this._render(),this}
|
|
28
|
+
/**
|
|
29
|
+
* Deletes the whitespace-delimited word adjacent to the caret.
|
|
30
|
+
* @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.
|
|
31
|
+
*/deleteWord(e="backward"){if(this._stopInsert(),"forward"===e){let e=this.index,i=!1;for(;e<this.units.length&&t.test(this.units[e]);)e+=1,i=!0;for(;e<this.units.length&&!t.test(this.units[e]);)e+=1;if(!i)for(;e<this.units.length&&t.test(this.units[e]);)e+=1;if(e===this.index)return this;this.units.splice(this.index,e-this.index)}else{let e=this.index;for(;e>0&&t.test(this.units[e-1]);)e-=1;for(;e>0&&!t.test(this.units[e-1]);)e-=1;if(e===this.index)return this;this.units.splice(e,this.index-e),this.index=e}return this._render(),this}
|
|
32
|
+
/**
|
|
33
|
+
* Types `text` at the current caret position, one grapheme at a time,
|
|
34
|
+
* shifting any following content to the right as it goes.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} text
|
|
37
|
+
* @param {object} [config={}]
|
|
38
|
+
* @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.
|
|
39
|
+
* @param {boolean} [config.respectReducedMotion]
|
|
40
|
+
* @param {() => void} [config.onStart]
|
|
41
|
+
* @param {(text: string) => void} [config.onComplete]
|
|
42
|
+
* @param {() => void} [config.onInterrupt]
|
|
43
|
+
* @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}
|
|
44
|
+
*/insert(t,i={}){var n,s,r;this._stopInsert();const l=e(null==t?"":String(t)),h=Math.max(0,Number(null!=(n=i.speed)?n:this.speed)||0),u=this.index;let o=!1,d=null,c=null;const a=t=>{var e,n;o||(o=!0,null!==d&&clearTimeout(d),this._activeInsert=null,t?null==(e=i.onComplete)||e.call(i,this.text):null==(n=i.onInterrupt)||n.call(i),null==c||c({completed:t,text:this.text}))},p={stop:()=>a(!1),promise:new Promise(t=>{c=t})};if(this._activeInsert=p,0===l.length)return a(!0),p;if(!0===(null!=(s=i.respectReducedMotion)?s:this.respectReducedMotion)&&"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches)return this.units.splice(u,0,...l),this.index=u+l.length,this._render(),a(!0),p;null==(r=i.onStart)||r.call(i);let f=0;const m=()=>{this.units.splice(u+f,0,l[f]),this.index=u+f+1,this._render(),f+=1,f>=l.length?a(!0):d=setTimeout(m,h)};return d=setTimeout(m,h),p}
|
|
45
|
+
/** Stops an in-flight `insert()`, if any. */stop(){return this._stopInsert(),this}
|
|
46
|
+
/** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */destroy(){return this._stopInsert(),null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null),this}_stopInsert(){var t;null==(t=this._activeInsert)||t.stop(),i(this.el)}_render(){var t,e;null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null);const i=this.units.slice(0,this.index).join(""),n=this.units.slice(this.index).join("");if(this.el.replaceChildren(document.createTextNode(i)),this.cursor){const i=function(t){const e=document.createElement("span");return e.className="tf-cursor",e.setAttribute("aria-hidden","true"),e.textContent=t,e}(this.cursor);if(this.el.appendChild(i),this.cursorBlink){let n=!0;this._blinkInterval=setInterval(()=>{n=!n,i.style.visibility=n?"visible":"hidden"},530),null==(e=(t=this._blinkInterval).unref)||e.call(t)}}this.el.appendChild(document.createTextNode(n))}}function r(t,e){return new s(t,e)}"undefined"!=typeof window&&(window.TypeFlowCaret=s,window.Caret=r);export{r as Caret,s as TypeFlowCaret};
|
|
47
|
+
//# sourceMappingURL=typeflow-caret.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-caret.esm.js","sources":["../../../src/typeflow-caret.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.\n * A persistent, editable cursor bound to an element's text: jump or move to any\n * grapheme index, delete a character or whole word at that position, and type\n * new text in with the usual character-by-character animation. Zero\n * dependencies; opportunistically defers to a page-global TypeFlow so starting\n * a `TypeFlow.type()`/`erase()` on the same element interrupts an in-flight\n * caret edit, without requiring a hard import of the core engine.\n *\n * @module @staticcanvas/typeflow/caret\n */\n\nconst CURSOR_BLINK_INTERVAL = 530;\nconst WHITESPACE_UNIT = /^\\s+$/u;\n\nfunction segmentGraphemes(text) {\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n return Array.from(\n new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text),\n (s) => s.segment\n );\n }\n return Array.from(text);\n}\n\nfunction prefersReducedMotion() {\n return (\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/** Stops any animation the page-global TypeFlow engine may have active on `el`, if present. */\nfunction stopPageTypeFlow(el) {\n if (typeof window !== 'undefined') window.TypeFlow?.stop?.(el);\n}\n\nfunction clampCaretIndex(position, max) {\n const value = position === undefined || position === null ? max : Number(position);\n if (!Number.isFinite(value)) return max;\n return Math.max(0, Math.min(Math.round(value), max));\n}\n\nfunction createCursorSpan(cursorChar) {\n const span = document.createElement('span');\n span.className = 'tf-cursor';\n span.setAttribute('aria-hidden', 'true');\n span.textContent = cursorChar;\n return span;\n}\n\n/**\n * Interactive, stateful cursor bound to an element's text content.\n *\n * Unlike `TypeFlow.type()`/`erase()`, which run once from start to finish, a\n * Caret is persistent: `jump()`, `move()`, `deleteChar()`, and `deleteWord()`\n * edit `text`/`position` and re-render immediately; `insert()` is animated\n * and returns a `{ stop, promise }` controller.\n */\nexport class TypeFlowCaret {\n /**\n * @param {Element | string} target - Element or CSS selector.\n * @param {object} [config={}]\n * @param {string} [config.text] - Initial text. Defaults to the target's current text content.\n * @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.\n * @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.\n * @param {boolean} [config.cursorBlink=true]\n * @param {number} [config.speed=40] - Default ms per character for `insert()`.\n * @param {boolean} [config.respectReducedMotion=true]\n *\n * @example\n * const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m \"wip\"' });\n * caret.jump(3).deleteWord('forward').insert('push');\n * // \"git push -m \"wip\"\"\n */\n constructor(target, config = {}) {\n this.el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!this.el) throw new Error('TypeFlowCaret: target element not found.');\n stopPageTypeFlow(this.el);\n\n this.cursor = config.cursor ?? '|';\n this.cursorBlink = config.cursorBlink ?? true;\n this.speed = config.speed ?? 40;\n this.respectReducedMotion = config.respectReducedMotion ?? true;\n this.units = segmentGraphemes(config.text ?? this.el.textContent ?? '');\n this.index = clampCaretIndex(config.position ?? this.units.length, this.units.length);\n this._blinkInterval = null;\n this._activeInsert = null;\n this._render();\n }\n\n /** Current plain-text content, cursor excluded. */\n get text() {\n return this.units.join('');\n }\n\n /** Current caret index, in graphemes, from the start of the text. */\n get position() {\n return this.index;\n }\n\n /** Total number of graphemes in the current text. */\n get length() {\n return this.units.length;\n }\n\n /** Moves the caret to an absolute grapheme index, clamped to bounds. */\n jump(position) {\n this._stopInsert();\n this.index = clampCaretIndex(position, this.units.length);\n this._render();\n return this;\n }\n\n /** Moves the caret by a relative number of graphemes (positive = right, negative = left). */\n move(delta) {\n return this.jump(this.index + (Number(delta) || 0));\n }\n\n /**\n * Deletes one grapheme at the caret.\n * @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).\n */\n deleteChar(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n if (this.index < this.units.length) this.units.splice(this.index, 1);\n } else if (this.index > 0) {\n this.units.splice(this.index - 1, 1);\n this.index -= 1;\n }\n this._render();\n return this;\n }\n\n /**\n * Deletes the whitespace-delimited word adjacent to the caret.\n * @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.\n */\n deleteWord(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n let end = this.index;\n let ateLeadingSpace = false;\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) {\n end += 1;\n ateLeadingSpace = true;\n }\n while (end < this.units.length && !WHITESPACE_UNIT.test(this.units[end])) end += 1;\n if (!ateLeadingSpace) {\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) end += 1;\n }\n if (end === this.index) return this;\n this.units.splice(this.index, end - this.index);\n } else {\n let start = this.index;\n while (start > 0 && WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n while (start > 0 && !WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n if (start === this.index) return this;\n this.units.splice(start, this.index - start);\n this.index = start;\n }\n this._render();\n return this;\n }\n\n /**\n * Types `text` at the current caret position, one grapheme at a time,\n * shifting any following content to the right as it goes.\n *\n * @param {string} text\n * @param {object} [config={}]\n * @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.\n * @param {boolean} [config.respectReducedMotion]\n * @param {() => void} [config.onStart]\n * @param {(text: string) => void} [config.onComplete]\n * @param {() => void} [config.onInterrupt]\n * @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}\n */\n insert(text, config = {}) {\n this._stopInsert();\n const insertUnits = segmentGraphemes(text === null || text === undefined ? '' : String(text));\n const speed = Math.max(0, Number(config.speed ?? this.speed) || 0);\n const startIndex = this.index;\n\n let settled = false;\n let timeoutId = null;\n let resolveFn = null;\n const settle = (completed) => {\n if (settled) return;\n settled = true;\n if (timeoutId !== null) clearTimeout(timeoutId);\n this._activeInsert = null;\n if (completed) config.onComplete?.(this.text);\n else config.onInterrupt?.();\n resolveFn?.({ completed, text: this.text });\n };\n const controller = {\n stop: () => settle(false),\n promise: new Promise((resolve) => {\n resolveFn = resolve;\n }),\n };\n this._activeInsert = controller;\n\n if (insertUnits.length === 0) {\n settle(true);\n return controller;\n }\n\n const respectReducedMotion = config.respectReducedMotion ?? this.respectReducedMotion;\n if (respectReducedMotion === true && prefersReducedMotion()) {\n this.units.splice(startIndex, 0, ...insertUnits);\n this.index = startIndex + insertUnits.length;\n this._render();\n settle(true);\n return controller;\n }\n\n config.onStart?.();\n let step = 0;\n const tick = () => {\n this.units.splice(startIndex + step, 0, insertUnits[step]);\n this.index = startIndex + step + 1;\n this._render();\n step += 1;\n if (step >= insertUnits.length) {\n settle(true);\n return;\n }\n timeoutId = setTimeout(tick, speed);\n };\n timeoutId = setTimeout(tick, speed);\n return controller;\n }\n\n /** Stops an in-flight `insert()`, if any. */\n stop() {\n this._stopInsert();\n return this;\n }\n\n /** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */\n destroy() {\n this._stopInsert();\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n return this;\n }\n\n _stopInsert() {\n this._activeInsert?.stop();\n stopPageTypeFlow(this.el);\n }\n\n _render() {\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n const before = this.units.slice(0, this.index).join('');\n const after = this.units.slice(this.index).join('');\n this.el.replaceChildren(document.createTextNode(before));\n\n if (this.cursor) {\n const cursorSpan = createCursorSpan(this.cursor);\n this.el.appendChild(cursorSpan);\n if (this.cursorBlink) {\n let visible = true;\n this._blinkInterval = setInterval(() => {\n visible = !visible;\n cursorSpan.style.visibility = visible ? 'visible' : 'hidden';\n }, CURSOR_BLINK_INTERVAL);\n this._blinkInterval.unref?.();\n }\n }\n\n this.el.appendChild(document.createTextNode(after));\n }\n}\n\n/**\n * Factory form of `new TypeFlowCaret(target, config)`.\n *\n * @param {Element | string} target\n * @param {object} [config]\n * @returns {TypeFlowCaret}\n *\n * @example\n * const caret = Caret('#terminal', { text: 'git commit' });\n * caret.move(-6).insert('push --force ');\n */\nexport function Caret(target, config) {\n return new TypeFlowCaret(target, config);\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowCaret = TypeFlowCaret;\n window.Caret = Caret;\n}\n"],"names":["WHITESPACE_UNIT","segmentGraphemes","text","Intl","Segmenter","Array","from","granularity","segment","s","stopPageTypeFlow","el","_a","_b","window","TypeFlow","stop","call","clampCaretIndex","position","max","value","Number","isFinite","Math","min","round","TypeFlowCaret","constructor","target","config","_c","_d","_e","_f","_g","this","document","querySelector","Error","cursor","cursorBlink","speed","respectReducedMotion","units","textContent","index","length","_blinkInterval","_activeInsert","_render","join","jump","_stopInsert","move","delta","deleteChar","direction","splice","deleteWord","end","ateLeadingSpace","test","start","insert","insertUnits","String","startIndex","settled","timeoutId","resolveFn","settle","completed","clearTimeout","onComplete","onInterrupt","controller","promise","Promise","resolve","matchMedia","matches","onStart","step","tick","setTimeout","destroy","clearInterval","before","slice","after","replaceChildren","createTextNode","cursorSpan","cursorChar","span","createElement","className","setAttribute","createCursorSpan","appendChild","visible","setInterval","style","visibility","unref","Caret"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const t=/^\s+$/u;function e(t){return"undefined"!=typeof Intl&&Intl.Segmenter?Array.from(new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function i(t){var e,i;"undefined"!=typeof window&&(null==(i=null==(e=window.TypeFlow)?void 0:e.stop)||i.call(e,t))}function n(t,e){const i=null==t?e:Number(t);return Number.isFinite(i)?Math.max(0,Math.min(Math.round(i),e)):e}class s{constructor(t,s={}){var r,l,h,u,o,d,c;if(this.el="string"==typeof t?document.querySelector(t):t,!this.el)throw new Error("TypeFlowCaret: target element not found.");i(this.el),this.cursor=null!=(r=s.cursor)?r:"|",this.cursorBlink=null==(l=s.cursorBlink)||l,this.speed=null!=(h=s.speed)?h:40,this.respectReducedMotion=null==(u=s.respectReducedMotion)||u,this.units=e(null!=(d=null!=(o=s.text)?o:this.el.textContent)?d:""),this.index=n(null!=(c=s.position)?c:this.units.length,this.units.length),this._blinkInterval=null,this._activeInsert=null,this._render()}get text(){return this.units.join("")}get position(){return this.index}get length(){return this.units.length}jump(t){return this._stopInsert(),this.index=n(t,this.units.length),this._render(),this}move(t){return this.jump(this.index+(Number(t)||0))}deleteChar(t="backward"){return this._stopInsert(),"forward"===t?this.index<this.units.length&&this.units.splice(this.index,1):this.index>0&&(this.units.splice(this.index-1,1),this.index-=1),this._render(),this}deleteWord(e="backward"){if(this._stopInsert(),"forward"===e){let e=this.index,i=!1;for(;e<this.units.length&&t.test(this.units[e]);)e+=1,i=!0;for(;e<this.units.length&&!t.test(this.units[e]);)e+=1;if(!i)for(;e<this.units.length&&t.test(this.units[e]);)e+=1;if(e===this.index)return this;this.units.splice(this.index,e-this.index)}else{let e=this.index;for(;e>0&&t.test(this.units[e-1]);)e-=1;for(;e>0&&!t.test(this.units[e-1]);)e-=1;if(e===this.index)return this;this.units.splice(e,this.index-e),this.index=e}return this._render(),this}insert(t,i={}){var n,s,r;this._stopInsert();const l=e(null==t?"":String(t)),h=Math.max(0,Number(null!=(n=i.speed)?n:this.speed)||0),u=this.index;let o=!1,d=null,c=null;const a=t=>{var e,n;o||(o=!0,null!==d&&clearTimeout(d),this._activeInsert=null,t?null==(e=i.onComplete)||e.call(i,this.text):null==(n=i.onInterrupt)||n.call(i),null==c||c({completed:t,text:this.text}))},p={stop:()=>a(!1),promise:new Promise(t=>{c=t})};if(this._activeInsert=p,0===l.length)return a(!0),p;if(!0===(null!=(s=i.respectReducedMotion)?s:this.respectReducedMotion)&&"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches)return this.units.splice(u,0,...l),this.index=u+l.length,this._render(),a(!0),p;null==(r=i.onStart)||r.call(i);let f=0;const m=()=>{this.units.splice(u+f,0,l[f]),this.index=u+f+1,this._render(),f+=1,f>=l.length?a(!0):d=setTimeout(m,h)};return d=setTimeout(m,h),p}stop(){return this._stopInsert(),this}destroy(){return this._stopInsert(),null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null),this}_stopInsert(){var t;null==(t=this._activeInsert)||t.stop(),i(this.el)}_render(){var t,e;null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null);const i=this.units.slice(0,this.index).join(""),n=this.units.slice(this.index).join("");if(this.el.replaceChildren(document.createTextNode(i)),this.cursor){const i=function(t){const e=document.createElement("span");return e.className="tf-cursor",e.setAttribute("aria-hidden","true"),e.textContent=t,e}(this.cursor);if(this.el.appendChild(i),this.cursorBlink){let n=!0;this._blinkInterval=setInterval(()=>{n=!n,i.style.visibility=n?"visible":"hidden"},530),null==(e=(t=this._blinkInterval).unref)||e.call(t)}}this.el.appendChild(document.createTextNode(n))}}function r(t,e){return new s(t,e)}"undefined"!=typeof window&&(window.TypeFlowCaret=s,window.Caret=r);export{r as Caret,s as TypeFlowCaret};
|
|
2
|
+
//# sourceMappingURL=typeflow-caret.esm.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-caret.esm.min.js","sources":["../../../src/typeflow-caret.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.\n * A persistent, editable cursor bound to an element's text: jump or move to any\n * grapheme index, delete a character or whole word at that position, and type\n * new text in with the usual character-by-character animation. Zero\n * dependencies; opportunistically defers to a page-global TypeFlow so starting\n * a `TypeFlow.type()`/`erase()` on the same element interrupts an in-flight\n * caret edit, without requiring a hard import of the core engine.\n *\n * @module @staticcanvas/typeflow/caret\n */\n\nconst CURSOR_BLINK_INTERVAL = 530;\nconst WHITESPACE_UNIT = /^\\s+$/u;\n\nfunction segmentGraphemes(text) {\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n return Array.from(\n new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text),\n (s) => s.segment\n );\n }\n return Array.from(text);\n}\n\nfunction prefersReducedMotion() {\n return (\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/** Stops any animation the page-global TypeFlow engine may have active on `el`, if present. */\nfunction stopPageTypeFlow(el) {\n if (typeof window !== 'undefined') window.TypeFlow?.stop?.(el);\n}\n\nfunction clampCaretIndex(position, max) {\n const value = position === undefined || position === null ? max : Number(position);\n if (!Number.isFinite(value)) return max;\n return Math.max(0, Math.min(Math.round(value), max));\n}\n\nfunction createCursorSpan(cursorChar) {\n const span = document.createElement('span');\n span.className = 'tf-cursor';\n span.setAttribute('aria-hidden', 'true');\n span.textContent = cursorChar;\n return span;\n}\n\n/**\n * Interactive, stateful cursor bound to an element's text content.\n *\n * Unlike `TypeFlow.type()`/`erase()`, which run once from start to finish, a\n * Caret is persistent: `jump()`, `move()`, `deleteChar()`, and `deleteWord()`\n * edit `text`/`position` and re-render immediately; `insert()` is animated\n * and returns a `{ stop, promise }` controller.\n */\nexport class TypeFlowCaret {\n /**\n * @param {Element | string} target - Element or CSS selector.\n * @param {object} [config={}]\n * @param {string} [config.text] - Initial text. Defaults to the target's current text content.\n * @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.\n * @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.\n * @param {boolean} [config.cursorBlink=true]\n * @param {number} [config.speed=40] - Default ms per character for `insert()`.\n * @param {boolean} [config.respectReducedMotion=true]\n *\n * @example\n * const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m \"wip\"' });\n * caret.jump(3).deleteWord('forward').insert('push');\n * // \"git push -m \"wip\"\"\n */\n constructor(target, config = {}) {\n this.el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!this.el) throw new Error('TypeFlowCaret: target element not found.');\n stopPageTypeFlow(this.el);\n\n this.cursor = config.cursor ?? '|';\n this.cursorBlink = config.cursorBlink ?? true;\n this.speed = config.speed ?? 40;\n this.respectReducedMotion = config.respectReducedMotion ?? true;\n this.units = segmentGraphemes(config.text ?? this.el.textContent ?? '');\n this.index = clampCaretIndex(config.position ?? this.units.length, this.units.length);\n this._blinkInterval = null;\n this._activeInsert = null;\n this._render();\n }\n\n /** Current plain-text content, cursor excluded. */\n get text() {\n return this.units.join('');\n }\n\n /** Current caret index, in graphemes, from the start of the text. */\n get position() {\n return this.index;\n }\n\n /** Total number of graphemes in the current text. */\n get length() {\n return this.units.length;\n }\n\n /** Moves the caret to an absolute grapheme index, clamped to bounds. */\n jump(position) {\n this._stopInsert();\n this.index = clampCaretIndex(position, this.units.length);\n this._render();\n return this;\n }\n\n /** Moves the caret by a relative number of graphemes (positive = right, negative = left). */\n move(delta) {\n return this.jump(this.index + (Number(delta) || 0));\n }\n\n /**\n * Deletes one grapheme at the caret.\n * @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).\n */\n deleteChar(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n if (this.index < this.units.length) this.units.splice(this.index, 1);\n } else if (this.index > 0) {\n this.units.splice(this.index - 1, 1);\n this.index -= 1;\n }\n this._render();\n return this;\n }\n\n /**\n * Deletes the whitespace-delimited word adjacent to the caret.\n * @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.\n */\n deleteWord(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n let end = this.index;\n let ateLeadingSpace = false;\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) {\n end += 1;\n ateLeadingSpace = true;\n }\n while (end < this.units.length && !WHITESPACE_UNIT.test(this.units[end])) end += 1;\n if (!ateLeadingSpace) {\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) end += 1;\n }\n if (end === this.index) return this;\n this.units.splice(this.index, end - this.index);\n } else {\n let start = this.index;\n while (start > 0 && WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n while (start > 0 && !WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n if (start === this.index) return this;\n this.units.splice(start, this.index - start);\n this.index = start;\n }\n this._render();\n return this;\n }\n\n /**\n * Types `text` at the current caret position, one grapheme at a time,\n * shifting any following content to the right as it goes.\n *\n * @param {string} text\n * @param {object} [config={}]\n * @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.\n * @param {boolean} [config.respectReducedMotion]\n * @param {() => void} [config.onStart]\n * @param {(text: string) => void} [config.onComplete]\n * @param {() => void} [config.onInterrupt]\n * @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}\n */\n insert(text, config = {}) {\n this._stopInsert();\n const insertUnits = segmentGraphemes(text === null || text === undefined ? '' : String(text));\n const speed = Math.max(0, Number(config.speed ?? this.speed) || 0);\n const startIndex = this.index;\n\n let settled = false;\n let timeoutId = null;\n let resolveFn = null;\n const settle = (completed) => {\n if (settled) return;\n settled = true;\n if (timeoutId !== null) clearTimeout(timeoutId);\n this._activeInsert = null;\n if (completed) config.onComplete?.(this.text);\n else config.onInterrupt?.();\n resolveFn?.({ completed, text: this.text });\n };\n const controller = {\n stop: () => settle(false),\n promise: new Promise((resolve) => {\n resolveFn = resolve;\n }),\n };\n this._activeInsert = controller;\n\n if (insertUnits.length === 0) {\n settle(true);\n return controller;\n }\n\n const respectReducedMotion = config.respectReducedMotion ?? this.respectReducedMotion;\n if (respectReducedMotion === true && prefersReducedMotion()) {\n this.units.splice(startIndex, 0, ...insertUnits);\n this.index = startIndex + insertUnits.length;\n this._render();\n settle(true);\n return controller;\n }\n\n config.onStart?.();\n let step = 0;\n const tick = () => {\n this.units.splice(startIndex + step, 0, insertUnits[step]);\n this.index = startIndex + step + 1;\n this._render();\n step += 1;\n if (step >= insertUnits.length) {\n settle(true);\n return;\n }\n timeoutId = setTimeout(tick, speed);\n };\n timeoutId = setTimeout(tick, speed);\n return controller;\n }\n\n /** Stops an in-flight `insert()`, if any. */\n stop() {\n this._stopInsert();\n return this;\n }\n\n /** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */\n destroy() {\n this._stopInsert();\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n return this;\n }\n\n _stopInsert() {\n this._activeInsert?.stop();\n stopPageTypeFlow(this.el);\n }\n\n _render() {\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n const before = this.units.slice(0, this.index).join('');\n const after = this.units.slice(this.index).join('');\n this.el.replaceChildren(document.createTextNode(before));\n\n if (this.cursor) {\n const cursorSpan = createCursorSpan(this.cursor);\n this.el.appendChild(cursorSpan);\n if (this.cursorBlink) {\n let visible = true;\n this._blinkInterval = setInterval(() => {\n visible = !visible;\n cursorSpan.style.visibility = visible ? 'visible' : 'hidden';\n }, CURSOR_BLINK_INTERVAL);\n this._blinkInterval.unref?.();\n }\n }\n\n this.el.appendChild(document.createTextNode(after));\n }\n}\n\n/**\n * Factory form of `new TypeFlowCaret(target, config)`.\n *\n * @param {Element | string} target\n * @param {object} [config]\n * @returns {TypeFlowCaret}\n *\n * @example\n * const caret = Caret('#terminal', { text: 'git commit' });\n * caret.move(-6).insert('push --force ');\n */\nexport function Caret(target, config) {\n return new TypeFlowCaret(target, config);\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowCaret = TypeFlowCaret;\n window.Caret = Caret;\n}\n"],"names":["WHITESPACE_UNIT","segmentGraphemes","text","Intl","Segmenter","Array","from","granularity","segment","s","stopPageTypeFlow","el","_a","_b","window","TypeFlow","stop","call","clampCaretIndex","position","max","value","Number","isFinite","Math","min","round","TypeFlowCaret","constructor","target","config","_c","_d","_e","_f","_g","this","document","querySelector","Error","cursor","cursorBlink","speed","respectReducedMotion","units","textContent","index","length","_blinkInterval","_activeInsert","_render","join","jump","_stopInsert","move","delta","deleteChar","direction","splice","deleteWord","end","ateLeadingSpace","test","start","insert","insertUnits","String","startIndex","settled","timeoutId","resolveFn","settle","completed","clearTimeout","onComplete","onInterrupt","controller","promise","Promise","resolve","matchMedia","matches","onStart","step","tick","setTimeout","destroy","clearInterval","before","slice","after","replaceChildren","createTextNode","cursorSpan","cursorChar","span","createElement","className","setAttribute","createCursorSpan","appendChild","visible","setInterval","style","visibility","unref","Caret"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*! typeflow-caret | MIT */
|
|
2
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TypeflowCaret={})}(this,function(t){"use strict";const e=/^\s+$/u;function i(t){return"undefined"!=typeof Intl&&Intl.Segmenter?Array.from(new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function n(t){var e,i;"undefined"!=typeof window&&(null==(i=null==(e=window.TypeFlow)?void 0:e.stop)||i.call(e,t))}function s(t,e){const i=null==t?e:Number(t);return Number.isFinite(i)?Math.max(0,Math.min(Math.round(i),e)):e}class r{
|
|
3
|
+
/**
|
|
4
|
+
* @param {Element | string} target - Element or CSS selector.
|
|
5
|
+
* @param {object} [config={}]
|
|
6
|
+
* @param {string} [config.text] - Initial text. Defaults to the target's current text content.
|
|
7
|
+
* @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.
|
|
8
|
+
* @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.
|
|
9
|
+
* @param {boolean} [config.cursorBlink=true]
|
|
10
|
+
* @param {number} [config.speed=40] - Default ms per character for `insert()`.
|
|
11
|
+
* @param {boolean} [config.respectReducedMotion=true]
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m "wip"' });
|
|
15
|
+
* caret.jump(3).deleteWord('forward').insert('push');
|
|
16
|
+
* // "git push -m "wip""
|
|
17
|
+
*/
|
|
18
|
+
constructor(t,e={}){var r,l,h,o,u,d,a;if(this.el="string"==typeof t?document.querySelector(t):t,!this.el)throw new Error("TypeFlowCaret: target element not found.");n(this.el),this.cursor=null!=(r=e.cursor)?r:"|",this.cursorBlink=null==(l=e.cursorBlink)||l,this.speed=null!=(h=e.speed)?h:40,this.respectReducedMotion=null==(o=e.respectReducedMotion)||o,this.units=i(null!=(d=null!=(u=e.text)?u:this.el.textContent)?d:""),this.index=s(null!=(a=e.position)?a:this.units.length,this.units.length),this._blinkInterval=null,this._activeInsert=null,this._render()}
|
|
19
|
+
/** Current plain-text content, cursor excluded. */get text(){return this.units.join("")}
|
|
20
|
+
/** Current caret index, in graphemes, from the start of the text. */get position(){return this.index}
|
|
21
|
+
/** Total number of graphemes in the current text. */get length(){return this.units.length}
|
|
22
|
+
/** Moves the caret to an absolute grapheme index, clamped to bounds. */jump(t){return this._stopInsert(),this.index=s(t,this.units.length),this._render(),this}
|
|
23
|
+
/** Moves the caret by a relative number of graphemes (positive = right, negative = left). */move(t){return this.jump(this.index+(Number(t)||0))}
|
|
24
|
+
/**
|
|
25
|
+
* Deletes one grapheme at the caret.
|
|
26
|
+
* @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).
|
|
27
|
+
*/deleteChar(t="backward"){return this._stopInsert(),"forward"===t?this.index<this.units.length&&this.units.splice(this.index,1):this.index>0&&(this.units.splice(this.index-1,1),this.index-=1),this._render(),this}
|
|
28
|
+
/**
|
|
29
|
+
* Deletes the whitespace-delimited word adjacent to the caret.
|
|
30
|
+
* @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.
|
|
31
|
+
*/deleteWord(t="backward"){if(this._stopInsert(),"forward"===t){let t=this.index,i=!1;for(;t<this.units.length&&e.test(this.units[t]);)t+=1,i=!0;for(;t<this.units.length&&!e.test(this.units[t]);)t+=1;if(!i)for(;t<this.units.length&&e.test(this.units[t]);)t+=1;if(t===this.index)return this;this.units.splice(this.index,t-this.index)}else{let t=this.index;for(;t>0&&e.test(this.units[t-1]);)t-=1;for(;t>0&&!e.test(this.units[t-1]);)t-=1;if(t===this.index)return this;this.units.splice(t,this.index-t),this.index=t}return this._render(),this}
|
|
32
|
+
/**
|
|
33
|
+
* Types `text` at the current caret position, one grapheme at a time,
|
|
34
|
+
* shifting any following content to the right as it goes.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} text
|
|
37
|
+
* @param {object} [config={}]
|
|
38
|
+
* @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.
|
|
39
|
+
* @param {boolean} [config.respectReducedMotion]
|
|
40
|
+
* @param {() => void} [config.onStart]
|
|
41
|
+
* @param {(text: string) => void} [config.onComplete]
|
|
42
|
+
* @param {() => void} [config.onInterrupt]
|
|
43
|
+
* @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}
|
|
44
|
+
*/insert(t,e={}){var n,s,r;this._stopInsert();const l=i(null==t?"":String(t)),h=Math.max(0,Number(null!=(n=e.speed)?n:this.speed)||0),o=this.index;let u=!1,d=null,a=null;const c=t=>{var i,n;u||(u=!0,null!==d&&clearTimeout(d),this._activeInsert=null,t?null==(i=e.onComplete)||i.call(e,this.text):null==(n=e.onInterrupt)||n.call(e),null==a||a({completed:t,text:this.text}))},p={stop:()=>c(!1),promise:new Promise(t=>{a=t})};if(this._activeInsert=p,0===l.length)return c(!0),p;if(!0===(null!=(s=e.respectReducedMotion)?s:this.respectReducedMotion)&&"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches)return this.units.splice(o,0,...l),this.index=o+l.length,this._render(),c(!0),p;null==(r=e.onStart)||r.call(e);let f=0;const m=()=>{this.units.splice(o+f,0,l[f]),this.index=o+f+1,this._render(),f+=1,f>=l.length?c(!0):d=setTimeout(m,h)};return d=setTimeout(m,h),p}
|
|
45
|
+
/** Stops an in-flight `insert()`, if any. */stop(){return this._stopInsert(),this}
|
|
46
|
+
/** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */destroy(){return this._stopInsert(),null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null),this}_stopInsert(){var t;null==(t=this._activeInsert)||t.stop(),n(this.el)}_render(){var t,e;null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null);const i=this.units.slice(0,this.index).join(""),n=this.units.slice(this.index).join("");if(this.el.replaceChildren(document.createTextNode(i)),this.cursor){const i=function(t){const e=document.createElement("span");return e.className="tf-cursor",e.setAttribute("aria-hidden","true"),e.textContent=t,e}(this.cursor);if(this.el.appendChild(i),this.cursorBlink){let n=!0;this._blinkInterval=setInterval(()=>{n=!n,i.style.visibility=n?"visible":"hidden"},530),null==(e=(t=this._blinkInterval).unref)||e.call(t)}}this.el.appendChild(document.createTextNode(n))}}function l(t,e){return new r(t,e)}"undefined"!=typeof window&&(window.TypeFlowCaret=r,window.Caret=l),t.Caret=l,t.TypeFlowCaret=r,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
47
|
+
//# sourceMappingURL=typeflow-caret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-caret.js","sources":["../../../src/typeflow-caret.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.\n * A persistent, editable cursor bound to an element's text: jump or move to any\n * grapheme index, delete a character or whole word at that position, and type\n * new text in with the usual character-by-character animation. Zero\n * dependencies; opportunistically defers to a page-global TypeFlow so starting\n * a `TypeFlow.type()`/`erase()` on the same element interrupts an in-flight\n * caret edit, without requiring a hard import of the core engine.\n *\n * @module @staticcanvas/typeflow/caret\n */\n\nconst CURSOR_BLINK_INTERVAL = 530;\nconst WHITESPACE_UNIT = /^\\s+$/u;\n\nfunction segmentGraphemes(text) {\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n return Array.from(\n new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text),\n (s) => s.segment\n );\n }\n return Array.from(text);\n}\n\nfunction prefersReducedMotion() {\n return (\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/** Stops any animation the page-global TypeFlow engine may have active on `el`, if present. */\nfunction stopPageTypeFlow(el) {\n if (typeof window !== 'undefined') window.TypeFlow?.stop?.(el);\n}\n\nfunction clampCaretIndex(position, max) {\n const value = position === undefined || position === null ? max : Number(position);\n if (!Number.isFinite(value)) return max;\n return Math.max(0, Math.min(Math.round(value), max));\n}\n\nfunction createCursorSpan(cursorChar) {\n const span = document.createElement('span');\n span.className = 'tf-cursor';\n span.setAttribute('aria-hidden', 'true');\n span.textContent = cursorChar;\n return span;\n}\n\n/**\n * Interactive, stateful cursor bound to an element's text content.\n *\n * Unlike `TypeFlow.type()`/`erase()`, which run once from start to finish, a\n * Caret is persistent: `jump()`, `move()`, `deleteChar()`, and `deleteWord()`\n * edit `text`/`position` and re-render immediately; `insert()` is animated\n * and returns a `{ stop, promise }` controller.\n */\nexport class TypeFlowCaret {\n /**\n * @param {Element | string} target - Element or CSS selector.\n * @param {object} [config={}]\n * @param {string} [config.text] - Initial text. Defaults to the target's current text content.\n * @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.\n * @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.\n * @param {boolean} [config.cursorBlink=true]\n * @param {number} [config.speed=40] - Default ms per character for `insert()`.\n * @param {boolean} [config.respectReducedMotion=true]\n *\n * @example\n * const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m \"wip\"' });\n * caret.jump(3).deleteWord('forward').insert('push');\n * // \"git push -m \"wip\"\"\n */\n constructor(target, config = {}) {\n this.el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!this.el) throw new Error('TypeFlowCaret: target element not found.');\n stopPageTypeFlow(this.el);\n\n this.cursor = config.cursor ?? '|';\n this.cursorBlink = config.cursorBlink ?? true;\n this.speed = config.speed ?? 40;\n this.respectReducedMotion = config.respectReducedMotion ?? true;\n this.units = segmentGraphemes(config.text ?? this.el.textContent ?? '');\n this.index = clampCaretIndex(config.position ?? this.units.length, this.units.length);\n this._blinkInterval = null;\n this._activeInsert = null;\n this._render();\n }\n\n /** Current plain-text content, cursor excluded. */\n get text() {\n return this.units.join('');\n }\n\n /** Current caret index, in graphemes, from the start of the text. */\n get position() {\n return this.index;\n }\n\n /** Total number of graphemes in the current text. */\n get length() {\n return this.units.length;\n }\n\n /** Moves the caret to an absolute grapheme index, clamped to bounds. */\n jump(position) {\n this._stopInsert();\n this.index = clampCaretIndex(position, this.units.length);\n this._render();\n return this;\n }\n\n /** Moves the caret by a relative number of graphemes (positive = right, negative = left). */\n move(delta) {\n return this.jump(this.index + (Number(delta) || 0));\n }\n\n /**\n * Deletes one grapheme at the caret.\n * @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).\n */\n deleteChar(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n if (this.index < this.units.length) this.units.splice(this.index, 1);\n } else if (this.index > 0) {\n this.units.splice(this.index - 1, 1);\n this.index -= 1;\n }\n this._render();\n return this;\n }\n\n /**\n * Deletes the whitespace-delimited word adjacent to the caret.\n * @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.\n */\n deleteWord(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n let end = this.index;\n let ateLeadingSpace = false;\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) {\n end += 1;\n ateLeadingSpace = true;\n }\n while (end < this.units.length && !WHITESPACE_UNIT.test(this.units[end])) end += 1;\n if (!ateLeadingSpace) {\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) end += 1;\n }\n if (end === this.index) return this;\n this.units.splice(this.index, end - this.index);\n } else {\n let start = this.index;\n while (start > 0 && WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n while (start > 0 && !WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n if (start === this.index) return this;\n this.units.splice(start, this.index - start);\n this.index = start;\n }\n this._render();\n return this;\n }\n\n /**\n * Types `text` at the current caret position, one grapheme at a time,\n * shifting any following content to the right as it goes.\n *\n * @param {string} text\n * @param {object} [config={}]\n * @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.\n * @param {boolean} [config.respectReducedMotion]\n * @param {() => void} [config.onStart]\n * @param {(text: string) => void} [config.onComplete]\n * @param {() => void} [config.onInterrupt]\n * @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}\n */\n insert(text, config = {}) {\n this._stopInsert();\n const insertUnits = segmentGraphemes(text === null || text === undefined ? '' : String(text));\n const speed = Math.max(0, Number(config.speed ?? this.speed) || 0);\n const startIndex = this.index;\n\n let settled = false;\n let timeoutId = null;\n let resolveFn = null;\n const settle = (completed) => {\n if (settled) return;\n settled = true;\n if (timeoutId !== null) clearTimeout(timeoutId);\n this._activeInsert = null;\n if (completed) config.onComplete?.(this.text);\n else config.onInterrupt?.();\n resolveFn?.({ completed, text: this.text });\n };\n const controller = {\n stop: () => settle(false),\n promise: new Promise((resolve) => {\n resolveFn = resolve;\n }),\n };\n this._activeInsert = controller;\n\n if (insertUnits.length === 0) {\n settle(true);\n return controller;\n }\n\n const respectReducedMotion = config.respectReducedMotion ?? this.respectReducedMotion;\n if (respectReducedMotion === true && prefersReducedMotion()) {\n this.units.splice(startIndex, 0, ...insertUnits);\n this.index = startIndex + insertUnits.length;\n this._render();\n settle(true);\n return controller;\n }\n\n config.onStart?.();\n let step = 0;\n const tick = () => {\n this.units.splice(startIndex + step, 0, insertUnits[step]);\n this.index = startIndex + step + 1;\n this._render();\n step += 1;\n if (step >= insertUnits.length) {\n settle(true);\n return;\n }\n timeoutId = setTimeout(tick, speed);\n };\n timeoutId = setTimeout(tick, speed);\n return controller;\n }\n\n /** Stops an in-flight `insert()`, if any. */\n stop() {\n this._stopInsert();\n return this;\n }\n\n /** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */\n destroy() {\n this._stopInsert();\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n return this;\n }\n\n _stopInsert() {\n this._activeInsert?.stop();\n stopPageTypeFlow(this.el);\n }\n\n _render() {\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n const before = this.units.slice(0, this.index).join('');\n const after = this.units.slice(this.index).join('');\n this.el.replaceChildren(document.createTextNode(before));\n\n if (this.cursor) {\n const cursorSpan = createCursorSpan(this.cursor);\n this.el.appendChild(cursorSpan);\n if (this.cursorBlink) {\n let visible = true;\n this._blinkInterval = setInterval(() => {\n visible = !visible;\n cursorSpan.style.visibility = visible ? 'visible' : 'hidden';\n }, CURSOR_BLINK_INTERVAL);\n this._blinkInterval.unref?.();\n }\n }\n\n this.el.appendChild(document.createTextNode(after));\n }\n}\n\n/**\n * Factory form of `new TypeFlowCaret(target, config)`.\n *\n * @param {Element | string} target\n * @param {object} [config]\n * @returns {TypeFlowCaret}\n *\n * @example\n * const caret = Caret('#terminal', { text: 'git commit' });\n * caret.move(-6).insert('push --force ');\n */\nexport function Caret(target, config) {\n return new TypeFlowCaret(target, config);\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowCaret = TypeFlowCaret;\n window.Caret = Caret;\n}\n"],"names":["WHITESPACE_UNIT","segmentGraphemes","text","Intl","Segmenter","Array","from","granularity","segment","s","stopPageTypeFlow","el","window","TypeFlow","_a","stop","_b","call","clampCaretIndex","position","max","value","Number","isFinite","Math","min","round","TypeFlowCaret","constructor","target","config","this","document","querySelector","Error","cursor","cursorBlink","speed","_c","respectReducedMotion","_d","units","_f","_e","textContent","index","_g","length","_blinkInterval","_activeInsert","_render","join","jump","_stopInsert","move","delta","deleteChar","direction","splice","deleteWord","end","ateLeadingSpace","test","start","insert","insertUnits","String","startIndex","settled","timeoutId","resolveFn","settle","completed","clearTimeout","onComplete","onInterrupt","controller","promise","Promise","resolve","matchMedia","matches","onStart","step","tick","setTimeout","destroy","clearInterval","before","slice","after","replaceChildren","createTextNode","cursorSpan","cursorChar","span","createElement","className","setAttribute","createCursorSpan","appendChild","visible","setInterval","style","visibility","unref","Caret"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TypeflowCaret={})}(this,function(t){"use strict";const e=/^\s+$/u;function i(t){return"undefined"!=typeof Intl&&Intl.Segmenter?Array.from(new Intl.Segmenter(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function n(t){var e,i;"undefined"!=typeof window&&(null==(i=null==(e=window.TypeFlow)?void 0:e.stop)||i.call(e,t))}function s(t,e){const i=null==t?e:Number(t);return Number.isFinite(i)?Math.max(0,Math.min(Math.round(i),e)):e}class r{constructor(t,e={}){var r,l,h,o,u,d,a;if(this.el="string"==typeof t?document.querySelector(t):t,!this.el)throw new Error("TypeFlowCaret: target element not found.");n(this.el),this.cursor=null!=(r=e.cursor)?r:"|",this.cursorBlink=null==(l=e.cursorBlink)||l,this.speed=null!=(h=e.speed)?h:40,this.respectReducedMotion=null==(o=e.respectReducedMotion)||o,this.units=i(null!=(d=null!=(u=e.text)?u:this.el.textContent)?d:""),this.index=s(null!=(a=e.position)?a:this.units.length,this.units.length),this._blinkInterval=null,this._activeInsert=null,this._render()}get text(){return this.units.join("")}get position(){return this.index}get length(){return this.units.length}jump(t){return this._stopInsert(),this.index=s(t,this.units.length),this._render(),this}move(t){return this.jump(this.index+(Number(t)||0))}deleteChar(t="backward"){return this._stopInsert(),"forward"===t?this.index<this.units.length&&this.units.splice(this.index,1):this.index>0&&(this.units.splice(this.index-1,1),this.index-=1),this._render(),this}deleteWord(t="backward"){if(this._stopInsert(),"forward"===t){let t=this.index,i=!1;for(;t<this.units.length&&e.test(this.units[t]);)t+=1,i=!0;for(;t<this.units.length&&!e.test(this.units[t]);)t+=1;if(!i)for(;t<this.units.length&&e.test(this.units[t]);)t+=1;if(t===this.index)return this;this.units.splice(this.index,t-this.index)}else{let t=this.index;for(;t>0&&e.test(this.units[t-1]);)t-=1;for(;t>0&&!e.test(this.units[t-1]);)t-=1;if(t===this.index)return this;this.units.splice(t,this.index-t),this.index=t}return this._render(),this}insert(t,e={}){var n,s,r;this._stopInsert();const l=i(null==t?"":String(t)),h=Math.max(0,Number(null!=(n=e.speed)?n:this.speed)||0),o=this.index;let u=!1,d=null,a=null;const c=t=>{var i,n;u||(u=!0,null!==d&&clearTimeout(d),this._activeInsert=null,t?null==(i=e.onComplete)||i.call(e,this.text):null==(n=e.onInterrupt)||n.call(e),null==a||a({completed:t,text:this.text}))},p={stop:()=>c(!1),promise:new Promise(t=>{a=t})};if(this._activeInsert=p,0===l.length)return c(!0),p;if(!0===(null!=(s=e.respectReducedMotion)?s:this.respectReducedMotion)&&"undefined"!=typeof window&&"function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches)return this.units.splice(o,0,...l),this.index=o+l.length,this._render(),c(!0),p;null==(r=e.onStart)||r.call(e);let f=0;const m=()=>{this.units.splice(o+f,0,l[f]),this.index=o+f+1,this._render(),f+=1,f>=l.length?c(!0):d=setTimeout(m,h)};return d=setTimeout(m,h),p}stop(){return this._stopInsert(),this}destroy(){return this._stopInsert(),null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null),this}_stopInsert(){var t;null==(t=this._activeInsert)||t.stop(),n(this.el)}_render(){var t,e;null!==this._blinkInterval&&(clearInterval(this._blinkInterval),this._blinkInterval=null);const i=this.units.slice(0,this.index).join(""),n=this.units.slice(this.index).join("");if(this.el.replaceChildren(document.createTextNode(i)),this.cursor){const i=function(t){const e=document.createElement("span");return e.className="tf-cursor",e.setAttribute("aria-hidden","true"),e.textContent=t,e}(this.cursor);if(this.el.appendChild(i),this.cursorBlink){let n=!0;this._blinkInterval=setInterval(()=>{n=!n,i.style.visibility=n?"visible":"hidden"},530),null==(e=(t=this._blinkInterval).unref)||e.call(t)}}this.el.appendChild(document.createTextNode(n))}}function l(t,e){return new r(t,e)}"undefined"!=typeof window&&(window.TypeFlowCaret=r,window.Caret=l),t.Caret=l,t.TypeFlowCaret=r,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})});
|
|
2
|
+
//# sourceMappingURL=typeflow-caret.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typeflow-caret.min.js","sources":["../../../src/typeflow-caret.js"],"sourcesContent":["/** * * * * * * * * * * * * * * * * * * * * * * * * * * **\n * @description TypeFlow Caret - Interactive Cursor Positioning & Mid-String Editing.\n * A persistent, editable cursor bound to an element's text: jump or move to any\n * grapheme index, delete a character or whole word at that position, and type\n * new text in with the usual character-by-character animation. Zero\n * dependencies; opportunistically defers to a page-global TypeFlow so starting\n * a `TypeFlow.type()`/`erase()` on the same element interrupts an in-flight\n * caret edit, without requiring a hard import of the core engine.\n *\n * @module @staticcanvas/typeflow/caret\n */\n\nconst CURSOR_BLINK_INTERVAL = 530;\nconst WHITESPACE_UNIT = /^\\s+$/u;\n\nfunction segmentGraphemes(text) {\n if (typeof Intl !== 'undefined' && Intl.Segmenter) {\n return Array.from(\n new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(text),\n (s) => s.segment\n );\n }\n return Array.from(text);\n}\n\nfunction prefersReducedMotion() {\n return (\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n}\n\n/** Stops any animation the page-global TypeFlow engine may have active on `el`, if present. */\nfunction stopPageTypeFlow(el) {\n if (typeof window !== 'undefined') window.TypeFlow?.stop?.(el);\n}\n\nfunction clampCaretIndex(position, max) {\n const value = position === undefined || position === null ? max : Number(position);\n if (!Number.isFinite(value)) return max;\n return Math.max(0, Math.min(Math.round(value), max));\n}\n\nfunction createCursorSpan(cursorChar) {\n const span = document.createElement('span');\n span.className = 'tf-cursor';\n span.setAttribute('aria-hidden', 'true');\n span.textContent = cursorChar;\n return span;\n}\n\n/**\n * Interactive, stateful cursor bound to an element's text content.\n *\n * Unlike `TypeFlow.type()`/`erase()`, which run once from start to finish, a\n * Caret is persistent: `jump()`, `move()`, `deleteChar()`, and `deleteWord()`\n * edit `text`/`position` and re-render immediately; `insert()` is animated\n * and returns a `{ stop, promise }` controller.\n */\nexport class TypeFlowCaret {\n /**\n * @param {Element | string} target - Element or CSS selector.\n * @param {object} [config={}]\n * @param {string} [config.text] - Initial text. Defaults to the target's current text content.\n * @param {number} [config.position] - Initial caret index in graphemes. Defaults to the end of the text.\n * @param {string} [config.cursor='|'] - Cursor glyph. Empty string hides it.\n * @param {boolean} [config.cursorBlink=true]\n * @param {number} [config.speed=40] - Default ms per character for `insert()`.\n * @param {boolean} [config.respectReducedMotion=true]\n *\n * @example\n * const caret = new TypeFlowCaret('#terminal', { text: 'git commit -m \"wip\"' });\n * caret.jump(3).deleteWord('forward').insert('push');\n * // \"git push -m \"wip\"\"\n */\n constructor(target, config = {}) {\n this.el = typeof target === 'string' ? document.querySelector(target) : target;\n if (!this.el) throw new Error('TypeFlowCaret: target element not found.');\n stopPageTypeFlow(this.el);\n\n this.cursor = config.cursor ?? '|';\n this.cursorBlink = config.cursorBlink ?? true;\n this.speed = config.speed ?? 40;\n this.respectReducedMotion = config.respectReducedMotion ?? true;\n this.units = segmentGraphemes(config.text ?? this.el.textContent ?? '');\n this.index = clampCaretIndex(config.position ?? this.units.length, this.units.length);\n this._blinkInterval = null;\n this._activeInsert = null;\n this._render();\n }\n\n /** Current plain-text content, cursor excluded. */\n get text() {\n return this.units.join('');\n }\n\n /** Current caret index, in graphemes, from the start of the text. */\n get position() {\n return this.index;\n }\n\n /** Total number of graphemes in the current text. */\n get length() {\n return this.units.length;\n }\n\n /** Moves the caret to an absolute grapheme index, clamped to bounds. */\n jump(position) {\n this._stopInsert();\n this.index = clampCaretIndex(position, this.units.length);\n this._render();\n return this;\n }\n\n /** Moves the caret by a relative number of graphemes (positive = right, negative = left). */\n move(delta) {\n return this.jump(this.index + (Number(delta) || 0));\n }\n\n /**\n * Deletes one grapheme at the caret.\n * @param {'backward'|'forward'} [direction='backward'] - `'backward'` removes the character before the caret (Backspace); `'forward'` removes the character at/after it (Delete).\n */\n deleteChar(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n if (this.index < this.units.length) this.units.splice(this.index, 1);\n } else if (this.index > 0) {\n this.units.splice(this.index - 1, 1);\n this.index -= 1;\n }\n this._render();\n return this;\n }\n\n /**\n * Deletes the whitespace-delimited word adjacent to the caret.\n * @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.\n */\n deleteWord(direction = 'backward') {\n this._stopInsert();\n if (direction === 'forward') {\n let end = this.index;\n let ateLeadingSpace = false;\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) {\n end += 1;\n ateLeadingSpace = true;\n }\n while (end < this.units.length && !WHITESPACE_UNIT.test(this.units[end])) end += 1;\n if (!ateLeadingSpace) {\n while (end < this.units.length && WHITESPACE_UNIT.test(this.units[end])) end += 1;\n }\n if (end === this.index) return this;\n this.units.splice(this.index, end - this.index);\n } else {\n let start = this.index;\n while (start > 0 && WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n while (start > 0 && !WHITESPACE_UNIT.test(this.units[start - 1])) start -= 1;\n if (start === this.index) return this;\n this.units.splice(start, this.index - start);\n this.index = start;\n }\n this._render();\n return this;\n }\n\n /**\n * Types `text` at the current caret position, one grapheme at a time,\n * shifting any following content to the right as it goes.\n *\n * @param {string} text\n * @param {object} [config={}]\n * @param {number} [config.speed] - Ms per character. Defaults to the speed passed to the constructor.\n * @param {boolean} [config.respectReducedMotion]\n * @param {() => void} [config.onStart]\n * @param {(text: string) => void} [config.onComplete]\n * @param {() => void} [config.onInterrupt]\n * @returns {{ stop: () => void, promise: Promise<{completed: boolean, text: string}> }}\n */\n insert(text, config = {}) {\n this._stopInsert();\n const insertUnits = segmentGraphemes(text === null || text === undefined ? '' : String(text));\n const speed = Math.max(0, Number(config.speed ?? this.speed) || 0);\n const startIndex = this.index;\n\n let settled = false;\n let timeoutId = null;\n let resolveFn = null;\n const settle = (completed) => {\n if (settled) return;\n settled = true;\n if (timeoutId !== null) clearTimeout(timeoutId);\n this._activeInsert = null;\n if (completed) config.onComplete?.(this.text);\n else config.onInterrupt?.();\n resolveFn?.({ completed, text: this.text });\n };\n const controller = {\n stop: () => settle(false),\n promise: new Promise((resolve) => {\n resolveFn = resolve;\n }),\n };\n this._activeInsert = controller;\n\n if (insertUnits.length === 0) {\n settle(true);\n return controller;\n }\n\n const respectReducedMotion = config.respectReducedMotion ?? this.respectReducedMotion;\n if (respectReducedMotion === true && prefersReducedMotion()) {\n this.units.splice(startIndex, 0, ...insertUnits);\n this.index = startIndex + insertUnits.length;\n this._render();\n settle(true);\n return controller;\n }\n\n config.onStart?.();\n let step = 0;\n const tick = () => {\n this.units.splice(startIndex + step, 0, insertUnits[step]);\n this.index = startIndex + step + 1;\n this._render();\n step += 1;\n if (step >= insertUnits.length) {\n settle(true);\n return;\n }\n timeoutId = setTimeout(tick, speed);\n };\n timeoutId = setTimeout(tick, speed);\n return controller;\n }\n\n /** Stops an in-flight `insert()`, if any. */\n stop() {\n this._stopInsert();\n return this;\n }\n\n /** Stops any in-flight `insert()` and clears the blink interval. Call when done with a caret. */\n destroy() {\n this._stopInsert();\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n return this;\n }\n\n _stopInsert() {\n this._activeInsert?.stop();\n stopPageTypeFlow(this.el);\n }\n\n _render() {\n if (this._blinkInterval !== null) {\n clearInterval(this._blinkInterval);\n this._blinkInterval = null;\n }\n const before = this.units.slice(0, this.index).join('');\n const after = this.units.slice(this.index).join('');\n this.el.replaceChildren(document.createTextNode(before));\n\n if (this.cursor) {\n const cursorSpan = createCursorSpan(this.cursor);\n this.el.appendChild(cursorSpan);\n if (this.cursorBlink) {\n let visible = true;\n this._blinkInterval = setInterval(() => {\n visible = !visible;\n cursorSpan.style.visibility = visible ? 'visible' : 'hidden';\n }, CURSOR_BLINK_INTERVAL);\n this._blinkInterval.unref?.();\n }\n }\n\n this.el.appendChild(document.createTextNode(after));\n }\n}\n\n/**\n * Factory form of `new TypeFlowCaret(target, config)`.\n *\n * @param {Element | string} target\n * @param {object} [config]\n * @returns {TypeFlowCaret}\n *\n * @example\n * const caret = Caret('#terminal', { text: 'git commit' });\n * caret.move(-6).insert('push --force ');\n */\nexport function Caret(target, config) {\n return new TypeFlowCaret(target, config);\n}\n\nif (typeof window !== 'undefined') {\n window.TypeFlowCaret = TypeFlowCaret;\n window.Caret = Caret;\n}\n"],"names":["WHITESPACE_UNIT","segmentGraphemes","text","Intl","Segmenter","Array","from","granularity","segment","s","stopPageTypeFlow","el","window","TypeFlow","_a","stop","_b","call","clampCaretIndex","position","max","value","Number","isFinite","Math","min","round","TypeFlowCaret","constructor","target","config","this","document","querySelector","Error","cursor","cursorBlink","speed","_c","respectReducedMotion","_d","units","_f","_e","textContent","index","_g","length","_blinkInterval","_activeInsert","_render","join","jump","_stopInsert","move","delta","deleteChar","direction","splice","deleteWord","end","ateLeadingSpace","test","start","insert","insertUnits","String","startIndex","settled","timeoutId","resolveFn","settle","completed","clearTimeout","onComplete","onInterrupt","controller","promise","Promise","resolve","matchMedia","matches","onStart","step","tick","setTimeout","destroy","clearInterval","before","slice","after","replaceChildren","createTextNode","cursorSpan","cursorChar","span","createElement","className","setAttribute","createCursorSpan","appendChild","visible","setInterval","style","visibility","unref","Caret"],"mappings":"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"}
|