@solidrt/core 0.0.66 → 0.0.67
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/agents/performance.md +24 -22
- package/docs/index.md +7 -3
- package/package.json +3 -3
- package/src/core.ts +4 -2
- package/src/double-tap.ts +9 -2
- package/src/input-pointer.ts +12 -0
- package/src/long-press.ts +27 -5
- package/src/pan.ts +21 -3
- package/src/runtime-modules.d.ts +6 -1
- package/src/swipe.ts +16 -0
- package/src/transform.ts +22 -2
- package/src/types.d.ts +11 -1
- package/src/window.ts +6 -0
package/agents/performance.md
CHANGED
|
@@ -151,27 +151,29 @@ Rules, in order of leverage:
|
|
|
151
151
|
snapshot instead of re-rasterizing. A window shader's output is invisible
|
|
152
152
|
to get_snapshot and every other MCP tool; `bun run sol render` is the only
|
|
153
153
|
way to see it (see @solidrt/cli AGENTS.md).
|
|
154
|
-
8. `flux:wasm`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
154
|
+
8. `flux:wasm` compiles a module to machine code on devices that allow it
|
|
155
|
+
(`Flux.capabilities` lists `wasm-native`: desktop, 64-bit Android) and
|
|
156
|
+
interprets it elsewhere (iOS, 32-bit Android). Both lanes are the fast
|
|
157
|
+
lane for compute: measured on a desktop, an arm64 tablet and a 32-bit
|
|
158
|
+
TV, a numeric kernel (typed-array math, tight inner loops, no host calls
|
|
159
|
+
inside the loop) runs 18x to 960x faster than the same loop in
|
|
160
|
+
JavaScript when compiled and 4x to 88x when interpreted, float at the low
|
|
161
|
+
end, integer and SIMD-shaped loops at the high end. So when profiling
|
|
162
|
+
shows such a kernel is where the app spends its time, port it: measure
|
|
163
|
+
the JS loop, port the kernel, measure again, keep whichever wins. The
|
|
164
|
+
boundary is the cost, not the code: a host call out of the module costs
|
|
165
|
+
3x to 7x a plain JavaScript call, two to three hundred iterations of a
|
|
166
|
+
tight loop, so batch at the boundary - one call per frame over a byte
|
|
167
|
+
buffer, never a call per element. What wasm does not do is speed up
|
|
168
|
+
render-path work (rules 1-3 are that leverage). It is also the way to
|
|
169
|
+
ship one compiled module across every target with no native toolchain,
|
|
170
|
+
and it pairs with an isolate when a call runs long enough to block.
|
|
171
|
+
9. There is no native-library route from app code. A shared library is
|
|
172
|
+
compiled per platform, so it cannot travel in a portable app payload,
|
|
173
|
+
and Android loads only what arrived inside the APK. Binding one is a
|
|
174
|
+
custom runtime build: the runtime crates with the library registered
|
|
175
|
+
beside the built-in modules. For speed, everything above comes first;
|
|
176
|
+
for a proven library that has a wasm build, rule 8.
|
|
175
177
|
10. Text costs shaping, once per word per style, through a shared word cache;
|
|
176
178
|
line breaking is arithmetic over the shaped words. So re-breaking text
|
|
177
179
|
every frame with prepareText + layoutNextLine (a shape that breathes, an
|
|
@@ -188,7 +190,7 @@ Rules, in order of leverage:
|
|
|
188
190
|
## Isolates: heavy work off the JS thread
|
|
189
191
|
|
|
190
192
|
A long synchronous computation (a big parse, a simulation step, a blocking
|
|
191
|
-
`flux:
|
|
193
|
+
`flux:wasm` call) freezes rendering and input for its duration.
|
|
192
194
|
Move it into an isolate module: a file whose first statement is the
|
|
193
195
|
`"use isolate"` directive runs in a second runtime on its own thread, and
|
|
194
196
|
main calls its exports as async functions.
|
package/docs/index.md
CHANGED
|
@@ -91,9 +91,13 @@ ancestor with `position="relative"`.
|
|
|
91
91
|
## Input
|
|
92
92
|
|
|
93
93
|
Pointer, wheel, and key events are props on any element:
|
|
94
|
-
`onPointerDown`, `onPointerMove`, `onPointerUp`, `
|
|
95
|
-
`onPointerLeave`, `onWheel`, `onKeyDown`, `onKeyUp`.
|
|
96
|
-
hit leaf up to the root, and `stopPropagation()`
|
|
94
|
+
`onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerCancel`,
|
|
95
|
+
`onPointerEnter`, `onPointerLeave`, `onWheel`, `onKeyDown`, `onKeyUp`.
|
|
96
|
+
Events travel from the hit leaf up to the root, and `stopPropagation()`
|
|
97
|
+
ends the walk. A pointer ends with an up or a cancel, never both: the
|
|
98
|
+
cancel is the system taking the touch away (a back swipe, palm rejection),
|
|
99
|
+
delivered along the pressed path with the pointer's last position, so a
|
|
100
|
+
press ends without firing and a drag ends without a fling. Key events
|
|
97
101
|
bubble the same way, starting at the focused node - or at the window root
|
|
98
102
|
when nothing is focused, so `onKeyDown` on the window is where app-global
|
|
99
103
|
shortcuts live.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.67",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"funding": "https://github.com/sponsors/wellawaretech",
|
|
6
6
|
"author": "Antoine van Wel",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"AGENTS.md"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@solidrt/flux-types": "0.0.
|
|
37
|
-
"@solidrt/test": "0.0.
|
|
36
|
+
"@solidrt/flux-types": "0.0.67",
|
|
37
|
+
"@solidrt/test": "0.0.67"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@solidjs/signals": "2.0.0-rc.13",
|
package/src/core.ts
CHANGED
|
@@ -8,8 +8,9 @@ let handlers = new Map<number, Map<string, Function>>()
|
|
|
8
8
|
// Pointer-handler presence mirrored into the render tree, so the runtime can
|
|
9
9
|
// skip building deliveries that would reach nobody (moves over static content
|
|
10
10
|
// are the flood case). Bits match alloy's EventInterest (rendertree/hit.rs);
|
|
11
|
-
// keep the two in sync. Down
|
|
12
|
-
// them: focus and gesture side effects hang off them regardless
|
|
11
|
+
// keep the two in sync. Down, up and cancel are recorded but the runtime
|
|
12
|
+
// never gates them: focus and gesture side effects hang off them regardless
|
|
13
|
+
// of handlers.
|
|
13
14
|
const MOVE_BIT = 1
|
|
14
15
|
const POINTER_INTEREST: Record<string, number> = {
|
|
15
16
|
onPointerMove: MOVE_BIT,
|
|
@@ -18,6 +19,7 @@ const POINTER_INTEREST: Record<string, number> = {
|
|
|
18
19
|
onPointerEnter: 8,
|
|
19
20
|
onPointerLeave: 16,
|
|
20
21
|
onWheel: 32,
|
|
22
|
+
onPointerCancel: 64,
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
let interests = new Map<number, number>()
|
package/src/double-tap.ts
CHANGED
|
@@ -48,8 +48,8 @@ export interface TapSequenceOptions {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** The tap bookkeeping under a double-tap: one first tap at a time,
|
|
51
|
-
* decided by the second down, by travel, by a long hold, by
|
|
52
|
-
* timer or by cancel(). */
|
|
51
|
+
* decided by the second down, by travel, by a long hold, by a cancelled
|
|
52
|
+
* pointer, by the window timer or by cancel(). */
|
|
53
53
|
export function createTapSequence(options: TapSequenceOptions) {
|
|
54
54
|
let first: { id: number; at: PointerPoint; downAt: number; upAt: number | null } | null = null
|
|
55
55
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
@@ -99,6 +99,9 @@ export function createTapSequence(options: TapSequenceOptions) {
|
|
|
99
99
|
settle(false)
|
|
100
100
|
}, DOUBLE_TAP_MS)
|
|
101
101
|
},
|
|
102
|
+
cancelled(e: PointerEvent) {
|
|
103
|
+
if (first && first.id === e.pointerId) settle(false)
|
|
104
|
+
},
|
|
102
105
|
cancel: () => settle(false),
|
|
103
106
|
}
|
|
104
107
|
}
|
|
@@ -145,6 +148,10 @@ export function createDoubleTap(options: DoubleTapOptions) {
|
|
|
145
148
|
if (second === e.pointerId) release()
|
|
146
149
|
taps.up(e)
|
|
147
150
|
},
|
|
151
|
+
onPointerCancel: (e: PointerEvent) => {
|
|
152
|
+
if (second === e.pointerId) release()
|
|
153
|
+
taps.cancelled(e)
|
|
154
|
+
},
|
|
148
155
|
}
|
|
149
156
|
return { handlers, cancel }
|
|
150
157
|
}
|
package/src/input-pointer.ts
CHANGED
|
@@ -133,6 +133,7 @@ export interface PointerFeed extends InputDevice {
|
|
|
133
133
|
onPointerDown(event: PointerEvent): void
|
|
134
134
|
onPointerMove(event: PointerEvent): void
|
|
135
135
|
onPointerUp(event: PointerEvent): void
|
|
136
|
+
onPointerCancel(event: PointerEvent): void
|
|
136
137
|
onPointerLeave(event: PointerEvent): void
|
|
137
138
|
onWheel(event: WheelEvent): void
|
|
138
139
|
}
|
|
@@ -481,6 +482,17 @@ export function createPointerFeed(options: PointerFeedOptions = {}): PointerFeed
|
|
|
481
482
|
opening = null
|
|
482
483
|
}
|
|
483
484
|
},
|
|
485
|
+
onPointerCancel(e) {
|
|
486
|
+
if (!downs.has(e.pointerId)) return
|
|
487
|
+
// The brackets close with no velocity and no swipe: the pointer
|
|
488
|
+
// ended without its lift.
|
|
489
|
+
transform.handlers.onPointerCancel(e)
|
|
490
|
+
released = null
|
|
491
|
+
lifted(e)
|
|
492
|
+
hold.cancelled(e)
|
|
493
|
+
taps.cancelled(e)
|
|
494
|
+
if (opening && opening.id === e.pointerId) opening = null
|
|
495
|
+
},
|
|
484
496
|
onPointerLeave() {},
|
|
485
497
|
onWheel(e) {
|
|
486
498
|
if (e.deltaY === 0) return
|
package/src/long-press.ts
CHANGED
|
@@ -36,7 +36,8 @@ export interface HoldTimerOptions {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** The arm / slop / timer machine under a long-press: one pointer at a
|
|
39
|
-
* time, disarmed by travel, by a lift, by a
|
|
39
|
+
* time, disarmed by travel, by a lift, by a cancelled pointer, by a second
|
|
40
|
+
* pointer or by cancel(). */
|
|
40
41
|
export function createHoldTimer(options: HoldTimerOptions) {
|
|
41
42
|
let armed: { id: number; at: PointerPoint; mods: { shiftKey: boolean; ctrlKey: boolean; altKey: boolean; metaKey: boolean; button?: number } } | null = null
|
|
42
43
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
@@ -69,6 +70,9 @@ export function createHoldTimer(options: HoldTimerOptions) {
|
|
|
69
70
|
up(e: PointerEvent) {
|
|
70
71
|
if (armed && armed.id === e.pointerId) disarm()
|
|
71
72
|
},
|
|
73
|
+
cancelled(e: PointerEvent) {
|
|
74
|
+
if (armed && armed.id === e.pointerId) disarm()
|
|
75
|
+
},
|
|
72
76
|
cancel: disarm,
|
|
73
77
|
}
|
|
74
78
|
}
|
|
@@ -84,6 +88,10 @@ export interface LongPressOptions {
|
|
|
84
88
|
onLongPressMove?: (dx: number, dy: number) => void
|
|
85
89
|
/** The lift after a fire. */
|
|
86
90
|
onLongPressEnd?: () => void
|
|
91
|
+
/** A fired long press ended without its lift: the system cancelled the
|
|
92
|
+
* pointer (onPointerCancel), or another recognizer took it in the
|
|
93
|
+
* arena. Fired instead of onLongPressEnd. */
|
|
94
|
+
onLongPressCancel?: () => void
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
export function createLongPress(options: LongPressOptions) {
|
|
@@ -99,16 +107,26 @@ export function createLongPress(options: LongPressOptions) {
|
|
|
99
107
|
options.onLongPress?.(at)
|
|
100
108
|
},
|
|
101
109
|
})
|
|
102
|
-
let
|
|
103
|
-
hold.cancel()
|
|
110
|
+
let release = () => {
|
|
104
111
|
if (fired !== null) {
|
|
105
112
|
arena.release(fired, owner)
|
|
106
113
|
fired = null
|
|
107
114
|
last = null
|
|
108
115
|
}
|
|
109
116
|
}
|
|
117
|
+
// The press ends without its lift, whatever took the pointer: the arena
|
|
118
|
+
// (cancel) or the system (onPointerCancel). An unmount releases alone.
|
|
119
|
+
let cancel = () => {
|
|
120
|
+
hold.cancel()
|
|
121
|
+
let won = fired !== null
|
|
122
|
+
release()
|
|
123
|
+
if (won) options.onLongPressCancel?.()
|
|
124
|
+
}
|
|
110
125
|
let owner: ArenaOwner = { cancel }
|
|
111
|
-
onSettled(() =>
|
|
126
|
+
onSettled(() => () => {
|
|
127
|
+
hold.cancel()
|
|
128
|
+
release()
|
|
129
|
+
})
|
|
112
130
|
|
|
113
131
|
let handlers = {
|
|
114
132
|
onPointerDown: (e: PointerEvent) => {
|
|
@@ -126,12 +144,16 @@ export function createLongPress(options: LongPressOptions) {
|
|
|
126
144
|
},
|
|
127
145
|
onPointerUp: (e: PointerEvent) => {
|
|
128
146
|
if (fired === e.pointerId) {
|
|
129
|
-
|
|
147
|
+
release()
|
|
130
148
|
options.onLongPressEnd?.()
|
|
131
149
|
return
|
|
132
150
|
}
|
|
133
151
|
hold.up(e)
|
|
134
152
|
},
|
|
153
|
+
onPointerCancel: (e: PointerEvent) => {
|
|
154
|
+
if (fired === e.pointerId) cancel()
|
|
155
|
+
else hold.cancelled(e)
|
|
156
|
+
},
|
|
135
157
|
}
|
|
136
158
|
return { handlers, cancel }
|
|
137
159
|
}
|
package/src/pan.ts
CHANGED
|
@@ -35,6 +35,13 @@ export interface PanOptions {
|
|
|
35
35
|
* finger rested before lifting or moved under FLING_MIN_VELOCITY.
|
|
36
36
|
*/
|
|
37
37
|
onPanEnd?: (velocity: Velocity) => void
|
|
38
|
+
/**
|
|
39
|
+
* The pan ended without its result: the system cancelled the pointer
|
|
40
|
+
* (onPointerCancel), or another recognizer took it in the arena. Fired
|
|
41
|
+
* instead of onPanEnd, only after onPanStart; whoever animates on
|
|
42
|
+
* settles here, nothing flings.
|
|
43
|
+
*/
|
|
44
|
+
onPanCancel?: () => void
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
// The pan recognizer: turns a drag into a movement-delta stream. On a down it
|
|
@@ -49,8 +56,9 @@ export interface PanOptions {
|
|
|
49
56
|
// fling minimum) for momentum. Moves and the up arrive on the frozen down
|
|
50
57
|
// path, so an active pan keeps streaming when the pointer leaves the node
|
|
51
58
|
// or the window.
|
|
52
|
-
// cancel() is the external-cancel hook; it
|
|
53
|
-
//
|
|
59
|
+
// cancel() is the external-cancel hook, the arena's; it and a pointer cancel
|
|
60
|
+
// end an active pan with onPanCancel instead of onPanEnd. Options are read
|
|
61
|
+
// at event time. Single-pointer by design; for
|
|
54
62
|
// multi-pointer pinch/rotate compose createTransform instead.
|
|
55
63
|
//
|
|
56
64
|
// Frames: the slop is finger travel, so it is measured in window pixels
|
|
@@ -87,7 +95,13 @@ export function createPan(options: PanOptions) {
|
|
|
87
95
|
armed = null
|
|
88
96
|
origin = null
|
|
89
97
|
}
|
|
90
|
-
|
|
98
|
+
// The pan ends without its result, whatever took the pointer: the arena
|
|
99
|
+
// (cancel) or the system (onPointerCancel). An unmount resets alone.
|
|
100
|
+
let cancel = () => {
|
|
101
|
+
let started = active != null
|
|
102
|
+
reset()
|
|
103
|
+
if (started) options.onPanCancel?.()
|
|
104
|
+
}
|
|
91
105
|
let owner = { cancel }
|
|
92
106
|
|
|
93
107
|
// An unmount mid-drag must not leave a resolved claim behind.
|
|
@@ -134,6 +148,10 @@ export function createPan(options: PanOptions) {
|
|
|
134
148
|
reset()
|
|
135
149
|
}
|
|
136
150
|
},
|
|
151
|
+
onPointerCancel: (e: PointerEvent) => {
|
|
152
|
+
if (active === e.pointerId) cancel()
|
|
153
|
+
else if (armed === e.pointerId) reset()
|
|
154
|
+
},
|
|
137
155
|
}
|
|
138
156
|
|
|
139
157
|
return { handlers, cancel }
|
package/src/runtime-modules.d.ts
CHANGED
|
@@ -26,7 +26,8 @@ declare module "*.frag" {
|
|
|
26
26
|
|
|
27
27
|
// Binary asset imports: `import data from "./pic.png" with { type: "binary" }`.
|
|
28
28
|
// The bundler inlines the file's bytes as a Uint8Array (see packages/cli
|
|
29
|
-
// bundler `inlineImport`); feed it straight into createImage/decodeImage
|
|
29
|
+
// bundler `inlineImport`); feed it straight into createImage/decodeImage, or a
|
|
30
|
+
// `.wasm` into `new Module` from flux:wasm.
|
|
30
31
|
declare module "*.png" {
|
|
31
32
|
const bytes: Uint8Array
|
|
32
33
|
export default bytes
|
|
@@ -55,6 +56,10 @@ declare module "*.sol3m" {
|
|
|
55
56
|
const bytes: Uint8Array
|
|
56
57
|
export default bytes
|
|
57
58
|
}
|
|
59
|
+
declare module "*.wasm" {
|
|
60
|
+
const bytes: Uint8Array
|
|
61
|
+
export default bytes
|
|
62
|
+
}
|
|
58
63
|
|
|
59
64
|
// UI event bus (lattice), provided by the runtime as a builtin module.
|
|
60
65
|
// on/once return an unsubscribe function. Notable events: the routed pointer
|
package/src/swipe.ts
CHANGED
|
@@ -67,6 +67,10 @@ export interface SwipeOptions {
|
|
|
67
67
|
onSwipe?: (direction: SwipeDirection, velocity: Velocity) => void
|
|
68
68
|
/** Every lift of an active swipe, qualified or not. */
|
|
69
69
|
onSwipeEnd?: (velocity: Velocity) => void
|
|
70
|
+
/** An active swipe ended without its lift: the system cancelled the
|
|
71
|
+
* pointer (onPointerCancel), or another recognizer took it in the
|
|
72
|
+
* arena. Fired instead of onSwipe and onSwipeEnd. */
|
|
73
|
+
onSwipeCancel?: () => void
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
export function createSwipe(options: SwipeOptions) {
|
|
@@ -97,6 +101,11 @@ export function createSwipe(options: SwipeOptions) {
|
|
|
97
101
|
if (direction) options.onSwipe?.(direction, velocity)
|
|
98
102
|
options.onSwipeEnd?.(velocity)
|
|
99
103
|
},
|
|
104
|
+
onPanCancel: () => {
|
|
105
|
+
down = null
|
|
106
|
+
lift = null
|
|
107
|
+
options.onSwipeCancel?.()
|
|
108
|
+
},
|
|
100
109
|
})
|
|
101
110
|
|
|
102
111
|
let handlers = {
|
|
@@ -120,6 +129,13 @@ export function createSwipe(options: SwipeOptions) {
|
|
|
120
129
|
lift = null
|
|
121
130
|
}
|
|
122
131
|
},
|
|
132
|
+
onPointerCancel: (e: PointerEvent) => {
|
|
133
|
+
pan.handlers.onPointerCancel(e)
|
|
134
|
+
if (down && down.id === e.pointerId) {
|
|
135
|
+
down = null
|
|
136
|
+
lift = null
|
|
137
|
+
}
|
|
138
|
+
},
|
|
123
139
|
}
|
|
124
140
|
let cancel = () => {
|
|
125
141
|
pan.cancel()
|
package/src/transform.ts
CHANGED
|
@@ -59,6 +59,10 @@ export interface TransformOptions {
|
|
|
59
59
|
* parent-frame pixels per second (velocity.ts; zero after a rest or
|
|
60
60
|
* under the fling minimum). */
|
|
61
61
|
onTransformEnd?: (velocity: Velocity) => void
|
|
62
|
+
/** The gesture ended without its result: the system cancelled a finger
|
|
63
|
+
* (onPointerCancel), or another recognizer took the pointers in the
|
|
64
|
+
* arena. Fired instead of onTransformEnd, only after onTransformStart. */
|
|
65
|
+
onTransformCancel?: () => void
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
// The merged transform recognizer: pan + pinch + rotate as ONE gesture over
|
|
@@ -116,7 +120,8 @@ export interface TransformOptions {
|
|
|
116
120
|
// configuration, so the change itself never emits a jump delta. The gesture
|
|
117
121
|
// ends when the last finger lifts. Moves and ups arrive on the frozen down
|
|
118
122
|
// path, so an active transform survives leaving the node or the window.
|
|
119
|
-
// cancel() is the external-cancel hook; it
|
|
123
|
+
// cancel() is the external-cancel hook, the arena's; it and a cancelled
|
|
124
|
+
// finger end an active gesture with onTransformCancel instead of
|
|
120
125
|
// onTransformEnd. Options are read at event time.
|
|
121
126
|
//
|
|
122
127
|
// Frames: the slop, the span filter and its rate gates are finger-travel
|
|
@@ -206,7 +211,11 @@ export function createTransform(options: TransformOptions) {
|
|
|
206
211
|
rebase = false
|
|
207
212
|
tracker.reset()
|
|
208
213
|
}
|
|
209
|
-
let cancel =
|
|
214
|
+
let cancel = () => {
|
|
215
|
+
let started = active
|
|
216
|
+
reset()
|
|
217
|
+
if (started) options.onTransformCancel?.()
|
|
218
|
+
}
|
|
210
219
|
let owner: ArenaOwner = { cancel }
|
|
211
220
|
|
|
212
221
|
// The per-frame measure point: runs at the pointerFrame terminator, when
|
|
@@ -355,6 +364,17 @@ export function createTransform(options: TransformOptions) {
|
|
|
355
364
|
pointers.delete(e.pointerId)
|
|
356
365
|
ref = pointers.size > 0 ? measure() : null
|
|
357
366
|
},
|
|
367
|
+
onPointerCancel: (e: PointerEvent) => {
|
|
368
|
+
if (!pointers.has(e.pointerId)) return
|
|
369
|
+
// The system cancels the gesture, not a finger: every platform
|
|
370
|
+
// cancels all of them together, so the whole gesture ends.
|
|
371
|
+
if (active) {
|
|
372
|
+
cancel()
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
pointers.delete(e.pointerId)
|
|
376
|
+
ref = pointers.size > 0 ? measure() : null
|
|
377
|
+
},
|
|
358
378
|
}
|
|
359
379
|
|
|
360
380
|
return { handlers, cancel }
|
package/src/types.d.ts
CHANGED
|
@@ -278,7 +278,8 @@ export interface TransformProps {
|
|
|
278
278
|
|
|
279
279
|
// Window-relative pointer coordinates are reported as clientX/clientY (matching
|
|
280
280
|
// the DOM MouseEvent). pointerType distinguishes mouse from touch; button is the
|
|
281
|
-
// pressed button on down/up (0 = primary)
|
|
281
|
+
// pressed button on down/up (0 = primary) and absent on a cancel; the modifier
|
|
282
|
+
// flags mirror the DOM.
|
|
282
283
|
export interface PointerEvent {
|
|
283
284
|
/**
|
|
284
285
|
* When the event happened, in milliseconds. Every input event carries one,
|
|
@@ -408,6 +409,15 @@ export interface NodeProps {
|
|
|
408
409
|
export interface PointerProps extends NodeProps {
|
|
409
410
|
onPointerDown?: (event: PointerEvent) => void
|
|
410
411
|
onPointerUp?: (event: PointerEvent) => void
|
|
412
|
+
/**
|
|
413
|
+
* The system took the pointer away: a system gesture (the back swipe,
|
|
414
|
+
* the notification shade), palm rejection, the window losing the touch.
|
|
415
|
+
* The last event for that pointer, and no up follows it, so a press
|
|
416
|
+
* ends without firing and a drag ends without a fling. Routed along the
|
|
417
|
+
* pressed path like an up, with the pointer's last known position and
|
|
418
|
+
* no `button`.
|
|
419
|
+
*/
|
|
420
|
+
onPointerCancel?: (event: PointerEvent) => void
|
|
411
421
|
onPointerMove?: (event: PointerEvent) => void
|
|
412
422
|
onPointerEnter?: (event: PointerEvent) => void
|
|
413
423
|
onPointerLeave?: (event: PointerEvent) => void
|
package/src/window.ts
CHANGED
|
@@ -553,6 +553,7 @@ export function attachWindow(nodeId: number) {
|
|
|
553
553
|
let unsubscribe: () => void = null!
|
|
554
554
|
let unsubDown: () => void = null!
|
|
555
555
|
let unsubUp: () => void = null!
|
|
556
|
+
let unsubCancel: () => void = null!
|
|
556
557
|
let unsubMove: () => void = null!
|
|
557
558
|
let unsubEnter: () => void = null!
|
|
558
559
|
let unsubLeave: () => void = null!
|
|
@@ -664,6 +665,10 @@ export function attachWindow(nodeId: number) {
|
|
|
664
665
|
bubble(raw, "onPointerUp")
|
|
665
666
|
})
|
|
666
667
|
|
|
668
|
+
unsubCancel = on("pointerCancel", (raw: RawPointer) => {
|
|
669
|
+
bubble(raw, "onPointerCancel")
|
|
670
|
+
})
|
|
671
|
+
|
|
667
672
|
unsubMove = on("pointerMove", (raw: RawPointer) => {
|
|
668
673
|
bubble(raw, "onPointerMove")
|
|
669
674
|
})
|
|
@@ -761,6 +766,7 @@ export function attachWindow(nodeId: number) {
|
|
|
761
766
|
if (unsubscribe) unsubscribe()
|
|
762
767
|
if (unsubDown) unsubDown()
|
|
763
768
|
if (unsubUp) unsubUp()
|
|
769
|
+
if (unsubCancel) unsubCancel()
|
|
764
770
|
if (unsubMove) unsubMove()
|
|
765
771
|
if (unsubEnter) unsubEnter()
|
|
766
772
|
if (unsubLeave) unsubLeave()
|