@cplieger/web-terminal-ui 8.3.3 → 8.3.5-dev.1
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/css/23-toolbar.css +175 -162
- package/css/31-switcher.css +8 -26
- package/package.json +2 -2
- package/src/features/mobile-toolbar.ts +9 -8
- package/src/features/tabs/index.ts +12 -0
package/css/23-toolbar.css
CHANGED
|
@@ -1,182 +1,195 @@
|
|
|
1
|
-
/* ===== Key toolbar (mobileToolbar feature
|
|
1
|
+
/* ===== Key toolbar (mobileToolbar feature) =====
|
|
2
2
|
Uses interpolate-size: allow-keywords (on .wt-root) to animate
|
|
3
3
|
width/height between a fixed collapsed size and auto (intrinsic).
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
The toggle sits top-right (col 5, row 1), so it stays visible when the
|
|
5
|
+
container clips to 1-cell size. Container is right-anchored
|
|
6
6
|
so it grows leftward on open. */
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
/* A self-toggled toolbar shows only under a coarse primary pointer. An
|
|
9
|
+
externally toggled one (`externalToggle`) exists whenever its host opens it,
|
|
10
|
+
because the host owns the trigger and may offer it on a touch-capable device
|
|
11
|
+
whose primary pointer reports fine. */
|
|
12
|
+
@media not (pointer: coarse) {
|
|
13
|
+
:where(.wt-root) .key-toolbar:not(.wt-toolbar-external) {
|
|
14
|
+
display: none;
|
|
15
|
+
}
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
/*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
:where(.wt-root) .kb-toggle {
|
|
18
|
+
/* Externally toggled: the host's trigger replaces the in-grid toggle, so its
|
|
19
|
+
column goes, and collapsed means gone — with no toggle left to keep in view,
|
|
20
|
+
a collapsed pill is just a box parked over the terminal. (0,2,0) beats the
|
|
21
|
+
shared button rule's (0,1,1). */
|
|
22
|
+
:where(.wt-root) .wt-toolbar-external .kb-toggle {
|
|
16
23
|
display: none;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
|
|
22
|
-
01-scope.css). --vv-top keeps it following iOS's visual-viewport shift. */
|
|
23
|
-
position: absolute;
|
|
24
|
-
top: calc(var(--safe-top) + var(--sp-2) + var(--vv-top, 0px));
|
|
25
|
-
right: var(--sp-2);
|
|
26
|
-
z-index: 10;
|
|
27
|
-
display: grid;
|
|
28
|
-
grid-template-columns: repeat(4, var(--touch-target)) var(--touch-target);
|
|
29
|
-
grid-template-rows: var(--touch-target) var(--touch-target);
|
|
30
|
-
place-content: start end;
|
|
31
|
-
gap: var(--sp-1);
|
|
32
|
-
padding: var(--sp-1);
|
|
33
|
-
background: var(--surface);
|
|
34
|
-
backdrop-filter: var(--blur);
|
|
35
|
-
border: 1px solid var(--border);
|
|
36
|
-
border-radius: var(--radius);
|
|
37
|
-
overflow: hidden;
|
|
38
|
-
width: auto;
|
|
39
|
-
height: auto;
|
|
40
|
-
transition:
|
|
41
|
-
width var(--dur-enter) var(--ease-standard),
|
|
42
|
-
height var(--dur-enter) var(--ease-standard);
|
|
43
|
-
}
|
|
26
|
+
:where(.wt-root) .key-toolbar.wt-toolbar-external {
|
|
27
|
+
grid-template-columns: repeat(4, var(--touch-target));
|
|
28
|
+
}
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
includes the 2px border (box-sizing:border-box); omitting it left
|
|
49
|
-
the content box 2px short, pinning the button top-right with
|
|
50
|
-
unequal bottom/left gaps. */
|
|
51
|
-
:where(.wt-root) .key-toolbar.collapsed {
|
|
52
|
-
width: var(--pill-outer);
|
|
53
|
-
height: var(--pill-outer);
|
|
54
|
-
}
|
|
30
|
+
:where(.wt-root) .key-toolbar.wt-toolbar-external.collapsed {
|
|
31
|
+
display: none;
|
|
32
|
+
}
|
|
55
33
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
grid-area: 2 / 3;
|
|
81
|
-
}
|
|
82
|
-
:where(.wt-root) .kb-r2c4 {
|
|
83
|
-
grid-area: 2 / 4;
|
|
84
|
-
}
|
|
34
|
+
:where(.wt-root) .key-toolbar {
|
|
35
|
+
/* Absolute against .wt-root (identical geometry in wt-viewport mode; see
|
|
36
|
+
01-scope.css). --vv-top keeps it following iOS's visual-viewport shift. */
|
|
37
|
+
position: absolute;
|
|
38
|
+
top: calc(var(--safe-top) + var(--sp-2) + var(--vv-top, 0px));
|
|
39
|
+
right: var(--sp-2);
|
|
40
|
+
z-index: 10;
|
|
41
|
+
display: grid;
|
|
42
|
+
grid-template-columns: repeat(4, var(--touch-target)) var(--touch-target);
|
|
43
|
+
grid-template-rows: var(--touch-target) var(--touch-target);
|
|
44
|
+
place-content: start end;
|
|
45
|
+
gap: var(--sp-1);
|
|
46
|
+
padding: var(--sp-1);
|
|
47
|
+
background: var(--surface);
|
|
48
|
+
backdrop-filter: var(--blur);
|
|
49
|
+
border: 1px solid var(--border);
|
|
50
|
+
border-radius: var(--radius);
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
width: auto;
|
|
53
|
+
height: auto;
|
|
54
|
+
transition:
|
|
55
|
+
width var(--dur-enter) var(--ease-standard),
|
|
56
|
+
height var(--dur-enter) var(--ease-standard);
|
|
57
|
+
}
|
|
85
58
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
59
|
+
/* Collapsed: fixed size = 1 button + padding + border. Content is
|
|
60
|
+
right-aligned (justify-content:end) so the toggle (col 5) stays at
|
|
61
|
+
the right edge, visible within the clipped area. --pill-outer
|
|
62
|
+
includes the 2px border (box-sizing:border-box); omitting it left
|
|
63
|
+
the content box 2px short, pinning the button top-right with
|
|
64
|
+
unequal bottom/left gaps. */
|
|
65
|
+
:where(.wt-root) .key-toolbar.collapsed {
|
|
66
|
+
width: var(--pill-outer);
|
|
67
|
+
height: var(--pill-outer);
|
|
68
|
+
}
|
|
95
69
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
70
|
+
/* Grid placement: toggle at col 5 row 1 (top-right). */
|
|
71
|
+
:where(.wt-root) .kb-toggle {
|
|
72
|
+
grid-area: 1 / 5;
|
|
73
|
+
position: relative;
|
|
74
|
+
}
|
|
75
|
+
:where(.wt-root) .kb-r1c1 {
|
|
76
|
+
grid-area: 1 / 1;
|
|
77
|
+
}
|
|
78
|
+
:where(.wt-root) .kb-r1c2 {
|
|
79
|
+
grid-area: 1 / 2;
|
|
80
|
+
}
|
|
81
|
+
:where(.wt-root) .kb-r1c3 {
|
|
82
|
+
grid-area: 1 / 3;
|
|
83
|
+
}
|
|
84
|
+
:where(.wt-root) .kb-r1c4 {
|
|
85
|
+
grid-area: 1 / 4;
|
|
86
|
+
}
|
|
87
|
+
:where(.wt-root) .kb-r2c1 {
|
|
88
|
+
grid-area: 2 / 1;
|
|
89
|
+
}
|
|
90
|
+
:where(.wt-root) .kb-r2c2 {
|
|
91
|
+
grid-area: 2 / 2;
|
|
92
|
+
}
|
|
93
|
+
:where(.wt-root) .kb-r2c3 {
|
|
94
|
+
grid-area: 2 / 3;
|
|
95
|
+
}
|
|
96
|
+
:where(.wt-root) .kb-r2c4 {
|
|
97
|
+
grid-area: 2 / 4;
|
|
98
|
+
}
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
font-family: var(--font-ui);
|
|
111
|
-
font-weight: 500;
|
|
112
|
-
cursor: pointer;
|
|
113
|
-
touch-action: manipulation;
|
|
114
|
-
-webkit-user-select: none;
|
|
115
|
-
user-select: none;
|
|
116
|
-
display: flex;
|
|
117
|
-
align-items: center;
|
|
118
|
-
justify-content: center;
|
|
119
|
-
}
|
|
100
|
+
/* Keys fade in/out during open/close. The container's width/height
|
|
101
|
+
transition handles the background slide; opacity handles the
|
|
102
|
+
individual button appearance. Stagger the fade slightly after
|
|
103
|
+
the container starts growing (delay on open) and before it
|
|
104
|
+
finishes shrinking (no delay on close). */
|
|
105
|
+
:where(.wt-root) .kb-key {
|
|
106
|
+
opacity: 1;
|
|
107
|
+
transition: opacity var(--dur-standard) var(--ease-enter) 0.05s;
|
|
108
|
+
}
|
|
120
109
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
:where(.wt-root) .key-toolbar.collapsed .kb-key {
|
|
111
|
+
opacity: 0;
|
|
112
|
+
transition: opacity var(--dur-exit) var(--ease-exit);
|
|
113
|
+
}
|
|
124
114
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
:
|
|
128
|
-
:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
115
|
+
/* Shared button */
|
|
116
|
+
:where(.wt-root) .key-toolbar button {
|
|
117
|
+
width: var(--touch-target);
|
|
118
|
+
height: var(--touch-target);
|
|
119
|
+
background: transparent;
|
|
120
|
+
color: var(--text);
|
|
121
|
+
border: none;
|
|
122
|
+
border-radius: var(--radius-inner);
|
|
123
|
+
font-size: 0.9375rem;
|
|
124
|
+
font-family: var(--font-ui);
|
|
125
|
+
font-weight: 500;
|
|
126
|
+
cursor: pointer;
|
|
127
|
+
touch-action: manipulation;
|
|
128
|
+
-webkit-user-select: none;
|
|
129
|
+
user-select: none;
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
}
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
stroke: currentcolor;
|
|
137
|
-
fill: none;
|
|
138
|
-
stroke-width: 2;
|
|
139
|
-
stroke-linecap: round;
|
|
140
|
-
stroke-linejoin: round;
|
|
141
|
-
}
|
|
135
|
+
:where(.wt-root) .key-toolbar button:active {
|
|
136
|
+
background: var(--text-active);
|
|
137
|
+
}
|
|
142
138
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
:where(.wt-root) .kb-toggle .icon-close {
|
|
151
|
-
position: absolute;
|
|
152
|
-
inset: 0;
|
|
153
|
-
margin: auto;
|
|
154
|
-
transition:
|
|
155
|
-
opacity var(--dur-standard) linear,
|
|
156
|
-
transform var(--dur-standard) linear;
|
|
157
|
-
}
|
|
139
|
+
/* Sticky Ctrl, armed: inverted fill so the toggle state reads clearly
|
|
140
|
+
in the monochrome palette (stays filled while pressed). */
|
|
141
|
+
:where(.wt-root) .kb-key.armed,
|
|
142
|
+
:where(.wt-root) .kb-key.armed:active {
|
|
143
|
+
background: var(--text);
|
|
144
|
+
color: var(--bg);
|
|
145
|
+
}
|
|
158
146
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
opacity: 0;
|
|
169
|
-
transform: rotate(90deg);
|
|
170
|
-
}
|
|
171
|
-
:where(.wt-root) .key-toolbar.collapsed .kb-toggle .icon-hamburger {
|
|
172
|
-
opacity: 1;
|
|
173
|
-
transform: rotate(0deg);
|
|
174
|
-
}
|
|
147
|
+
:where(.wt-root) .key-toolbar button svg {
|
|
148
|
+
width: var(--icon);
|
|
149
|
+
height: var(--icon);
|
|
150
|
+
stroke: currentcolor;
|
|
151
|
+
fill: none;
|
|
152
|
+
stroke-width: 2;
|
|
153
|
+
stroke-linecap: round;
|
|
154
|
+
stroke-linejoin: round;
|
|
155
|
+
}
|
|
175
156
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
157
|
+
/* Toggle icon crossfade. Both icons are stacked absolute so they
|
|
158
|
+
can independently fade + rotate during the open<->close
|
|
159
|
+
transition. inset:0 + margin:auto centers them within the
|
|
160
|
+
button (works because the SVGs carry explicit width/height); a
|
|
161
|
+
bare `position: absolute` would pin them at the top-left, giving
|
|
162
|
+
the toggle visibly unequal left/right padding when collapsed. */
|
|
163
|
+
:where(.wt-root) .kb-toggle .icon-hamburger,
|
|
164
|
+
:where(.wt-root) .kb-toggle .icon-close {
|
|
165
|
+
position: absolute;
|
|
166
|
+
inset: 0;
|
|
167
|
+
margin: auto;
|
|
168
|
+
transition:
|
|
169
|
+
opacity var(--dur-standard) linear,
|
|
170
|
+
transform var(--dur-standard) linear;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
:where(.wt-root) .kb-toggle .icon-close {
|
|
174
|
+
opacity: 1;
|
|
175
|
+
transform: rotate(0deg);
|
|
176
|
+
}
|
|
177
|
+
:where(.wt-root) .kb-toggle .icon-hamburger {
|
|
178
|
+
opacity: 0;
|
|
179
|
+
transform: rotate(-90deg);
|
|
180
|
+
}
|
|
181
|
+
:where(.wt-root) .key-toolbar.collapsed .kb-toggle .icon-close {
|
|
182
|
+
opacity: 0;
|
|
183
|
+
transform: rotate(90deg);
|
|
184
|
+
}
|
|
185
|
+
:where(.wt-root) .key-toolbar.collapsed .kb-toggle .icon-hamburger {
|
|
186
|
+
opacity: 1;
|
|
187
|
+
transform: rotate(0deg);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* No-transition on first render */
|
|
191
|
+
:where(.wt-root) .key-toolbar.no-transition,
|
|
192
|
+
:where(.wt-root) .key-toolbar.no-transition .kb-toggle .icon-hamburger,
|
|
193
|
+
:where(.wt-root) .key-toolbar.no-transition .kb-toggle .icon-close {
|
|
194
|
+
transition: none;
|
|
182
195
|
}
|
package/css/31-switcher.css
CHANGED
|
@@ -432,39 +432,21 @@
|
|
|
432
432
|
background: var(--tab-hover-bg);
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
-
/* ===== Externally-driven key grid (tabbed presets
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
The BASE rules here are the wide-touchscreen (strip) placement — above
|
|
444
|
-
--wt-tabbar-h, right-aligned to the strip's .wt-tab-kb (the bar's last flex
|
|
445
|
-
item, inset --sp-2 by the bar padding); the .wt-narrow overrides below
|
|
446
|
-
re-anchor above the switcher bar (--wt-switcher-h) and shift left past the
|
|
447
|
-
bar controls sitting right of the switcher's kb button: the "+" (always)
|
|
448
|
-
and, once a second tab reveals it, the switch button (.wt-switcher-multi
|
|
449
|
-
from the tabs feature, read here via :has() since the grid is a sibling
|
|
450
|
-
region of the switcher). Everything else (toggle hidden, grid columns,
|
|
451
|
-
collapsed fully hidden) is identical in all modes. */
|
|
435
|
+
/* ===== Externally-driven key grid: tab-chrome docking (tabbed presets) =====
|
|
436
|
+
A tab-chrome keyboard button drives the grid (the switcher's on a narrow
|
|
437
|
+
phone, the strip's .wt-tab-kb on a wide touchscreen; both exist only under
|
|
438
|
+
a coarse pointer), so the grid docks bottom-right above the tab chrome with
|
|
439
|
+
its RIGHT edge on that button. The mode's own rules (no in-grid toggle, four
|
|
440
|
+
columns, collapsed means gone) are 23-toolbar.css's. The .wt-narrow
|
|
441
|
+
overrides re-anchor above the switcher bar and shift left past the "+" and,
|
|
442
|
+
once a second tab reveals it, the switch button. */
|
|
452
443
|
@media (pointer: coarse) {
|
|
453
|
-
:where(.wt-root) .wt-toolbar-external .kb-toggle {
|
|
454
|
-
display: none;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
444
|
:where(.wt-root) .key-toolbar.wt-toolbar-external {
|
|
458
445
|
top: auto;
|
|
459
446
|
right: var(--sp-2);
|
|
460
447
|
bottom: calc(
|
|
461
448
|
var(--wt-tabbar-h, 44px) + var(--safe-bottom, 0px) + var(--kb-inset, 0px) + var(--sp-2)
|
|
462
449
|
);
|
|
463
|
-
grid-template-columns: repeat(4, var(--touch-target));
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
:where(.wt-root) .key-toolbar.wt-toolbar-external.collapsed {
|
|
467
|
-
display: none;
|
|
468
450
|
}
|
|
469
451
|
|
|
470
452
|
/* Narrow phone (mobile switcher mode): dock above the switcher bar,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cplieger/web-terminal-ui",
|
|
3
|
-
"version": "8.3.
|
|
3
|
+
"version": "8.3.5-dev.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Reference touch-first browser UI for @cplieger/web-terminal-engine: a composable kernel + feature modules (tabs, activity monitor, mobile toolbar, context menu, IME, predictive echo, viewport handling) and CSS. One createTerminal() call.",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@vitest/coverage-v8": "5.0.3",
|
|
58
58
|
"eslint": "10.12.0",
|
|
59
59
|
"fast-check": "4.10.2",
|
|
60
|
-
"html-validate": "11.16.
|
|
60
|
+
"html-validate": "11.16.2",
|
|
61
61
|
"knip": "6.39.0",
|
|
62
62
|
"playwright": "1.63.0",
|
|
63
63
|
"prettier": "3.9.9",
|
|
@@ -20,8 +20,9 @@ export interface MobileToolbarApi {
|
|
|
20
20
|
|
|
21
21
|
/** Options for the mobileToolbar feature. */
|
|
22
22
|
export interface MobileToolbarOptions {
|
|
23
|
-
/** Hide the toolbar's own toggle
|
|
24
|
-
*
|
|
23
|
+
/** Hide the toolbar's own toggle; the grid is then driven through the returned
|
|
24
|
+
* API and renders whenever it is open, whatever the primary pointer, so the
|
|
25
|
+
* host's trigger decides where a grid exists. The tabbed presets set it. */
|
|
25
26
|
externalToggle?: boolean;
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -38,12 +39,12 @@ const TOOLBAR_HTML = `
|
|
|
38
39
|
<button type="button" id="kb-right" class="kb-key kb-r2c4" aria-label="Right"><svg viewBox="0 0 24 24"><polyline points="9 6 15 12 9 18"/></svg></button>
|
|
39
40
|
</div>`;
|
|
40
41
|
|
|
41
|
-
/** Build the mobileToolbar feature. The chrome is always built;
|
|
42
|
-
*
|
|
43
|
-
* `externalToggle` is set, since tabs reads the API
|
|
44
|
-
* Sticky-Ctrl is a kernel INPUT TRANSFORM rather than a
|
|
45
|
-
* it rewrites a character typed on the soft keyboard as
|
|
46
|
-
* buttons. */
|
|
42
|
+
/** Build the mobileToolbar feature. The chrome is always built; a self-toggled
|
|
43
|
+
* toolbar is visible only under a coarse pointer (`css/23-toolbar.css`). Order
|
|
44
|
+
* it before `tabs` when `externalToggle` is set, since tabs reads the API
|
|
45
|
+
* through `ctx.use`. Sticky-Ctrl is a kernel INPUT TRANSFORM rather than a
|
|
46
|
+
* toolbar click path, so it rewrites a character typed on the soft keyboard as
|
|
47
|
+
* well as one from these buttons. */
|
|
47
48
|
export function mobileToolbar(opts: MobileToolbarOptions = {}): TerminalFeature<MobileToolbarApi> {
|
|
48
49
|
return {
|
|
49
50
|
name: "mobileToolbar",
|
|
@@ -639,6 +639,18 @@ export function tabs(opts: TabsOptions = {}): TerminalFeature<TabsApi> {
|
|
|
639
639
|
};
|
|
640
640
|
reflectArmed(kbApi.isCtrlArmed());
|
|
641
641
|
ctx.defer(kbApi.onCtrlArmedChange(reflectArmed));
|
|
642
|
+
// The keyboard buttons render only under a coarse pointer, so a grid
|
|
643
|
+
// still open when the primary pointer turns fine would have no closer.
|
|
644
|
+
const coarse = win.matchMedia("(pointer: coarse)");
|
|
645
|
+
const onPointerChange = (): void => {
|
|
646
|
+
if (!coarse.matches) {
|
|
647
|
+
closeKeyGrid();
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
coarse.addEventListener("change", onPointerChange);
|
|
651
|
+
ctx.defer(() => {
|
|
652
|
+
coarse.removeEventListener("change", onPointerChange);
|
|
653
|
+
});
|
|
642
654
|
}
|
|
643
655
|
}
|
|
644
656
|
// Measured optical centring for every chip label in both layouts: writes
|