@solidrt/core 0.0.34 → 0.0.36
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/package.json +2 -2
- package/src/core.ts +16 -5
- package/src/gpu.ts +102 -20
- package/src/index.ts +1 -2
- package/src/runtime-modules.d.ts +29 -7
- package/src/window.ts +16 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidrt/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Antoine van Wel",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"colord": "^2.9.3"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@solidrt/flux-types": "0.0.
|
|
30
|
+
"@solidrt/flux-types": "0.0.36"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@solidjs/signals": "2.0.0-beta.20",
|
package/src/core.ts
CHANGED
|
@@ -64,16 +64,27 @@ export interface BoundingBox {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Returns the node's
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
67
|
+
* Returns the node's bounding box from the most recently computed layout,
|
|
68
|
+
* relative to its nearest positioning context (an ancestor with an explicit
|
|
69
|
+
* `position="relative"`, falling back to the window), or `null` if the node
|
|
70
|
+
* has no layout or has not been laid out yet. This is a snapshot read, not
|
|
71
|
+
* reactive: call it inside `onLayout` (or an event handler) to get values for
|
|
72
|
+
* the current frame. Transforms anywhere in the chain (including the node's
|
|
73
|
+
* own) compose fully; the box is the axis-aligned bounds of the transformed
|
|
74
|
+
* quad.
|
|
72
75
|
*/
|
|
73
76
|
export function getBoundingBox(node: { id: number }): BoundingBox | null {
|
|
74
77
|
return tree.getBoundingBox(node.id)
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Like getBoundingBox, but always window-relative (getBoundingClientRect
|
|
82
|
+
* semantics), the frame pointer event clientX/clientY are reported in.
|
|
83
|
+
*/
|
|
84
|
+
export function getBoundingBoxViewport(node: { id: number }): BoundingBox | null {
|
|
85
|
+
return tree.getBoundingBoxViewport(node.id)
|
|
86
|
+
}
|
|
87
|
+
|
|
77
88
|
/**
|
|
78
89
|
* Measures the rendered size of `text` in layout pixels under the given font
|
|
79
90
|
* options (family, size, weight, style, maxLines), without adding it to the
|
package/src/gpu.ts
CHANGED
|
@@ -8,16 +8,35 @@
|
|
|
8
8
|
// sampler2D input. The imperative primitives (uploadTexture, setShaderParams,
|
|
9
9
|
// destroyTexture, ...) live in the `flux:gpu` module.
|
|
10
10
|
|
|
11
|
-
import { getOwner, onCleanup } from "@solidjs/signals"
|
|
11
|
+
import { createEffect, createSignal, getOwner, onCleanup, untrack } from "@solidjs/signals"
|
|
12
12
|
import * as gpu from "flux:gpu"
|
|
13
13
|
|
|
14
|
+
// The create* helpers accept { manual: true } to opt out of the owner-scoped
|
|
15
|
+
// auto-free, for resources whose lifetime is managed by hand (rebuilt on
|
|
16
|
+
// signal changes inside a long-lived component, handed across owners, ...).
|
|
17
|
+
// Without it, each rebuild would stack another onCleanup on the component
|
|
18
|
+
// owner: a leak until unmount, then a double-free against manual destroys.
|
|
19
|
+
export type CreateOptions = { manual?: boolean }
|
|
20
|
+
|
|
14
21
|
// Re-exported so callers that depend on @solidrt/core -- like @solidrt/components
|
|
15
22
|
// -- need not import flux directly: destroyTexture for the manual-cleanup path
|
|
16
23
|
// (textures made outside a reactive scope, e.g. after an await, are not
|
|
17
24
|
// auto-freed), uploadTexture to push new pixels into a mutable texture, and
|
|
18
25
|
// setShaderParams as the non-reactive exception described above - prefer
|
|
19
26
|
// `<texture params={...}>` when a `<texture>` element is already in the tree.
|
|
20
|
-
|
|
27
|
+
// resizeTexture and setShaderSize resize in place at a stable id (so
|
|
28
|
+
// `<texture src>` and sampler bindings stay valid); because the id survives,
|
|
29
|
+
// the owner-scoped auto-free registered at creation keeps working and no
|
|
30
|
+
// re-registration is needed. setShaderTextures is the sampler analog of
|
|
31
|
+
// setShaderParams: retarget a shader's sampler2D inputs without recompiling.
|
|
32
|
+
export {
|
|
33
|
+
destroyTexture,
|
|
34
|
+
resizeTexture,
|
|
35
|
+
setShaderParams,
|
|
36
|
+
setShaderSize,
|
|
37
|
+
setShaderTextures,
|
|
38
|
+
uploadTexture,
|
|
39
|
+
} from "flux:gpu"
|
|
21
40
|
|
|
22
41
|
// Pipeline plumbing re-exported raw: setDrawCount re-renders a pipeline after
|
|
23
42
|
// its buffer gained or lost dynamic geometry; destroyBuffer is the manual
|
|
@@ -40,11 +59,12 @@ export { captureSnapshot, readTexture } from "flux:gpu"
|
|
|
40
59
|
* texture is freed automatically once that owner is disposed; when called
|
|
41
60
|
* outside one (e.g. after an `await`, where the owner is no longer current)
|
|
42
61
|
* nothing is registered and you must call `destroyTexture` (from flux:gpu)
|
|
43
|
-
* yourself.
|
|
62
|
+
* yourself. Pass `{ manual: true }` to skip the auto-free and own the
|
|
63
|
+
* disposal yourself even inside a reactive scope.
|
|
44
64
|
*/
|
|
45
|
-
export function createTexture(data: Uint8Array, width: number, height: number): number {
|
|
65
|
+
export function createTexture(data: Uint8Array, width: number, height: number, opts?: CreateOptions): number {
|
|
46
66
|
let id = gpu.createTexture(data, width, height)
|
|
47
|
-
if (getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
67
|
+
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
48
68
|
return id
|
|
49
69
|
}
|
|
50
70
|
|
|
@@ -53,12 +73,13 @@ export function createTexture(data: Uint8Array, width: number, height: number):
|
|
|
53
73
|
* then call `uploadTexture(id, data)` (from flux:gpu) to push new pixels. `data`
|
|
54
74
|
* is RGBA8 and must hold at least `width * height * 4` bytes (it may hold several
|
|
55
75
|
* frames). Like `createTexture`, the texture is freed automatically when the
|
|
56
|
-
* reactive owner is disposed
|
|
57
|
-
* `destroyTexture` (from flux:gpu)
|
|
76
|
+
* reactive owner is disposed (opt out with `{ manual: true }`); created
|
|
77
|
+
* outside a reactive scope you must call `destroyTexture` (from flux:gpu)
|
|
78
|
+
* yourself.
|
|
58
79
|
*/
|
|
59
|
-
export function createMutableTexture(data: Uint8Array, width: number, height: number): number {
|
|
80
|
+
export function createMutableTexture(data: Uint8Array, width: number, height: number, opts?: CreateOptions): number {
|
|
60
81
|
let id = gpu.createMutableTexture(data, width, height)
|
|
61
|
-
if (getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
82
|
+
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
62
83
|
return id
|
|
63
84
|
}
|
|
64
85
|
|
|
@@ -72,8 +93,10 @@ export function createMutableTexture(data: Uint8Array, width: number, height: nu
|
|
|
72
93
|
* `textures` binds each declared `uniform sampler2D` to an existing texture id
|
|
73
94
|
* (e.g. a camera or decoded image) so the shader can read it; those inputs are
|
|
74
95
|
* re-sampled on every params update, so live sources stay current. Frees the
|
|
75
|
-
* texture and shader program when the reactive owner is disposed
|
|
76
|
-
* outside any reactive scope for
|
|
96
|
+
* texture and shader program when the reactive owner is disposed (opt out
|
|
97
|
+
* with `{ manual: true }`); create outside any reactive scope for
|
|
98
|
+
* app-lifetime shaders. For a shader whose source or inputs change
|
|
99
|
+
* reactively, use {@link createShaderMemo} instead.
|
|
77
100
|
*/
|
|
78
101
|
export function createShader(
|
|
79
102
|
fragmentSrc: string,
|
|
@@ -81,9 +104,67 @@ export function createShader(
|
|
|
81
104
|
height: number,
|
|
82
105
|
params?: Record<string, number>,
|
|
83
106
|
textures?: Record<string, number>,
|
|
107
|
+
opts?: CreateOptions,
|
|
84
108
|
): number {
|
|
85
109
|
let id = gpu.createShader(fragmentSrc, width, height, params, textures)
|
|
86
|
-
if (getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
110
|
+
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
111
|
+
return id
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The reactive shader description `createShaderMemo` builds from. */
|
|
115
|
+
export type ShaderSpec = {
|
|
116
|
+
fragmentSrc: string
|
|
117
|
+
width: number
|
|
118
|
+
height: number
|
|
119
|
+
params?: Record<string, number>
|
|
120
|
+
textures?: Record<string, number>
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Shallow name->number equality for params/textures records; treats undefined
|
|
124
|
+
// as the empty record.
|
|
125
|
+
function sameRecord(a: Record<string, number> | undefined, b: Record<string, number> | undefined): boolean {
|
|
126
|
+
if (a === b) return true
|
|
127
|
+
let ka = a ? Object.keys(a) : []
|
|
128
|
+
let kb = b ? Object.keys(b) : []
|
|
129
|
+
return ka.length === kb.length && ka.every(k => a![k] === b![k])
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A fragment shader whose spec is reactive: returns an accessor for the
|
|
134
|
+
* current texture id (use it as `<texture src={id()} />`) and keeps the GPU
|
|
135
|
+
* resource in step with `spec` from then on. Changes that keep the compiled
|
|
136
|
+
* program valid mutate in place at a stable id - a size change routes to
|
|
137
|
+
* `setShaderSize`, a params change to `setShaderParams` - while a change to
|
|
138
|
+
* the fragment source or the sampler bindings rebuilds at a fresh id, updates
|
|
139
|
+
* the accessor, and destroys the old id. That destroy is frame-safe (the
|
|
140
|
+
* runtime reclaims an id only once the render tree no longer references it),
|
|
141
|
+
* so the swap never paints a blank frame. The current id is freed when the
|
|
142
|
+
* owning scope is disposed. Data textures need no analog: `uploadTexture` and
|
|
143
|
+
* `resizeTexture` already cover their reactive changes id-stably.
|
|
144
|
+
*/
|
|
145
|
+
export function createShaderMemo(spec: () => ShaderSpec): () => number {
|
|
146
|
+
let current = untrack(spec)
|
|
147
|
+
let currentId = gpu.createShader(current.fragmentSrc, current.width, current.height, current.params, current.textures)
|
|
148
|
+
let [id, setId] = createSignal(currentId)
|
|
149
|
+
createEffect(spec, next => {
|
|
150
|
+
if (next.fragmentSrc === current.fragmentSrc && sameRecord(next.textures, current.textures)) {
|
|
151
|
+
// Program and inputs unchanged: mutate in place, the id stays stable.
|
|
152
|
+
if (next.width !== current.width || next.height !== current.height) {
|
|
153
|
+
gpu.setShaderSize(currentId, next.width, next.height)
|
|
154
|
+
}
|
|
155
|
+
if (!sameRecord(next.params, current.params) && next.params) {
|
|
156
|
+
gpu.setShaderParams(currentId, next.params)
|
|
157
|
+
}
|
|
158
|
+
current = next
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
let old = currentId
|
|
162
|
+
current = next
|
|
163
|
+
currentId = gpu.createShader(next.fragmentSrc, next.width, next.height, next.params, next.textures)
|
|
164
|
+
setId(currentId)
|
|
165
|
+
gpu.destroyTexture(old)
|
|
166
|
+
})
|
|
167
|
+
if (getOwner()) onCleanup(() => gpu.destroyTexture(currentId))
|
|
87
168
|
return id
|
|
88
169
|
}
|
|
89
170
|
|
|
@@ -107,8 +188,8 @@ function toUint8(data: ArrayBuffer | ArrayBufferView): Uint8Array {
|
|
|
107
188
|
* `opts.depth` attaches a private depth buffer (cleared + tested per render);
|
|
108
189
|
* `opts.vertexCount` defaults to the whole buffer and can be changed later
|
|
109
190
|
* with `setDrawCount`. Frees the texture and GL program when the reactive
|
|
110
|
-
* owner is disposed; create outside any reactive
|
|
111
|
-
* pipelines.
|
|
191
|
+
* owner is disposed (opt out with `opts.manual`); create outside any reactive
|
|
192
|
+
* scope for app-lifetime pipelines.
|
|
112
193
|
*/
|
|
113
194
|
export function createPipeline(
|
|
114
195
|
vertexSrc: string,
|
|
@@ -124,10 +205,10 @@ export function createPipeline(
|
|
|
124
205
|
vertexCount?: number
|
|
125
206
|
depth?: boolean
|
|
126
207
|
clearColor?: [number, number, number, number]
|
|
127
|
-
},
|
|
208
|
+
} & CreateOptions,
|
|
128
209
|
): number {
|
|
129
210
|
let id = gpu.createPipeline(vertexSrc, fragmentSrc, width, height, opts)
|
|
130
|
-
if (getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
211
|
+
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyTexture(id))
|
|
131
212
|
return id
|
|
132
213
|
}
|
|
133
214
|
|
|
@@ -136,12 +217,13 @@ export function createPipeline(
|
|
|
136
217
|
* Float32Array laid out to match the pipeline's interleaved attribute list).
|
|
137
218
|
* Update it later with {@link writeBuffer}; the buffer's byte size is fixed at
|
|
138
219
|
* creation, so reserve room up front for dynamic geometry. Freed automatically
|
|
139
|
-
* when the reactive owner is disposed
|
|
140
|
-
* must call `destroyBuffer` yourself.
|
|
220
|
+
* when the reactive owner is disposed (opt out with `{ manual: true }`);
|
|
221
|
+
* created outside a reactive scope you must call `destroyBuffer` yourself.
|
|
222
|
+
* Destroy pipelines before their buffer.
|
|
141
223
|
*/
|
|
142
|
-
export function createBuffer(data: ArrayBuffer | ArrayBufferView): number {
|
|
224
|
+
export function createBuffer(data: ArrayBuffer | ArrayBufferView, opts?: CreateOptions): number {
|
|
143
225
|
let id = gpu.createBuffer(toUint8(data))
|
|
144
|
-
if (getOwner()) onCleanup(() => gpu.destroyBuffer(id))
|
|
226
|
+
if (!opts?.manual && getOwner()) onCleanup(() => gpu.destroyBuffer(id))
|
|
145
227
|
return id
|
|
146
228
|
}
|
|
147
229
|
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
export * from "./renderer"
|
|
2
|
-
export { setFocus, getFocusedNodeId, measureText, getBoundingBox } from "./core"
|
|
2
|
+
export { setFocus, getFocusedNodeId, measureText, getBoundingBox, getBoundingBoxViewport } from "./core"
|
|
3
3
|
export type { BoundingBox } from "./core"
|
|
4
4
|
export { parseColor, mixColors, brightness, createLinearGradient, createRadialGradient } from "./color"
|
|
5
5
|
export type { Gradient, GradientStop } from "./color"
|
|
6
6
|
export { onFrame, onLayout, onResize, onWindowFocus, onWindowBlur } from "./window"
|
|
7
|
-
export { setPointerCapture, releasePointerCapture } from "./window"
|
|
8
7
|
export { windowSize, safeArea, displayScale, windowFocused, keyboardHeight } from "./window"
|
|
9
8
|
export { env } from "./environment"
|
|
10
9
|
export type { InputDevices, SystemTheme, Orientation } from "./environment"
|
package/src/runtime-modules.d.ts
CHANGED
|
@@ -67,14 +67,19 @@ declare module "srt:apps" {
|
|
|
67
67
|
export type AppVersion = { id: string; size: number; current: boolean }
|
|
68
68
|
/** One file in a listing: a relative path and its size in bytes. */
|
|
69
69
|
export type AppFile = { path: string; size: number }
|
|
70
|
+
/**
|
|
71
|
+
* One fetch-cache entry: the cached (resolved) url, the response content
|
|
72
|
+
* type (lowercased, parameters stripped; absent when the response had
|
|
73
|
+
* none) and the entry's size on disk.
|
|
74
|
+
*/
|
|
75
|
+
export type AppCacheEntry = { url: string; type?: string; size: number }
|
|
70
76
|
/**
|
|
71
77
|
* Usage details for one installed app: total bytes of its stored versions
|
|
72
|
-
* (assets shared between versions via hardlinks count in each)
|
|
73
|
-
* data sandbox, plus the stored versions (current
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* divergence from the manifest is visible.
|
|
78
|
+
* (assets shared between versions via hardlinks count in each), of its
|
|
79
|
+
* data sandbox and of its fetch cache, plus the stored versions (current
|
|
80
|
+
* first, then newest first) and three listings: `files` and `data` are
|
|
81
|
+
* disk walks of the current version dir and the data sandbox (sorted by
|
|
82
|
+
* path), `cache` is the fetch cache's entries (sorted by url).
|
|
78
83
|
*/
|
|
79
84
|
export type AppInfo = {
|
|
80
85
|
id: string
|
|
@@ -82,10 +87,11 @@ declare module "srt:apps" {
|
|
|
82
87
|
version: string
|
|
83
88
|
installSize: number
|
|
84
89
|
dataSize: number
|
|
90
|
+
cacheSize: number
|
|
85
91
|
versions: AppVersion[]
|
|
86
|
-
assets: AppFile[]
|
|
87
92
|
files: AppFile[]
|
|
88
93
|
data: AppFile[]
|
|
94
|
+
cache: AppCacheEntry[]
|
|
89
95
|
}
|
|
90
96
|
/** Usage details for an installed app. Throws when the app is not installed. */
|
|
91
97
|
export function info(id: string): AppInfo
|
|
@@ -100,6 +106,22 @@ declare module "srt:apps" {
|
|
|
100
106
|
* the app is not installed.
|
|
101
107
|
*/
|
|
102
108
|
export function remove(id: string): void
|
|
109
|
+
/**
|
|
110
|
+
* Delete the app's fetch cache. Clearing a missing or empty cache is a
|
|
111
|
+
* no-op; the id does not need to be installed, so a removed app's
|
|
112
|
+
* leftover cache is still clearable.
|
|
113
|
+
*/
|
|
114
|
+
export function clearCache(id: string): void
|
|
115
|
+
/**
|
|
116
|
+
* Build identity of this runtime, for the launcher's settings screen. Not
|
|
117
|
+
* app-specific, but surfaced here since the launcher already imports this
|
|
118
|
+
* module. `version` is the release version (git describe; "0.0.0-dev" in a
|
|
119
|
+
* plain build), `profile` is "debug" or "release", `platform` is the OS
|
|
120
|
+
* (std::env::consts::OS, e.g. "linux", "android", "windows", "macos").
|
|
121
|
+
*/
|
|
122
|
+
export const version: string
|
|
123
|
+
export const profile: string
|
|
124
|
+
export const platform: string
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
// Frame draw (lattice runner). renderFrame() synchronously renders the current
|
package/src/window.ts
CHANGED
|
@@ -5,27 +5,17 @@ import { on, once } from "srt:events"
|
|
|
5
5
|
import { getEventHandler, getFocusedNodeId, setFocus } from "./core"
|
|
6
6
|
import { scanForOrphans } from "./renderer"
|
|
7
7
|
|
|
8
|
-
// ------ Pointer
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* a drag (e.g. a slider). Auto-releases on the matching pointerup.
|
|
20
|
-
*/
|
|
21
|
-
export function setPointerCapture(nodeId: number, pointerId: number) {
|
|
22
|
-
pointerCaptures.set(pointerId, nodeId)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Ends a capture early. Not needed for the common case (pointerup releases it). */
|
|
26
|
-
export function releasePointerCapture(pointerId: number) {
|
|
27
|
-
pointerCaptures.delete(pointerId)
|
|
28
|
-
}
|
|
8
|
+
// ------ Pointer routing -----------------
|
|
9
|
+
|
|
10
|
+
// Hit path frozen at pointerDown, pointerId -> targets. While a pointer has an
|
|
11
|
+
// active down, its moves and up dispatch along this path (same leaf-to-root
|
|
12
|
+
// bubble) no matter where the pointer currently is, so every node under the
|
|
13
|
+
// original down observes the whole gesture: a drag keeps working off-element,
|
|
14
|
+
// and an ancestor recognizer (a scroller's pan) sees the moves it needs to
|
|
15
|
+
// take over mid-gesture via the arena. There is no exclusive pointer capture;
|
|
16
|
+
// gesture ownership is claim-based, above this layer. Enter/leave stay
|
|
17
|
+
// hover-driven, and moves with no active down follow the live hit path.
|
|
18
|
+
let downPaths = new Map<number, number[]>()
|
|
29
19
|
|
|
30
20
|
// ------ Animation frames ----------------
|
|
31
21
|
|
|
@@ -244,6 +234,7 @@ export function attachWindow(_nodeId: number) {
|
|
|
244
234
|
unsubDown = on(
|
|
245
235
|
"pointerDown",
|
|
246
236
|
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
237
|
+
downPaths.set(e.pointerId, targets)
|
|
247
238
|
bubble(targets, "onPointerDown", e)
|
|
248
239
|
// Outside-tap blur. Read focus AFTER per-node handlers so a tap that
|
|
249
240
|
// moves focus to a new node is not immediately blurred again.
|
|
@@ -255,28 +246,15 @@ export function attachWindow(_nodeId: number) {
|
|
|
255
246
|
)
|
|
256
247
|
|
|
257
248
|
unsubUp = on("pointerUp", ({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
258
|
-
let
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
e.stopPropagation = () => {}
|
|
262
|
-
getEventHandler(captured, "onPointerUp")?.(e)
|
|
263
|
-
pointerCaptures.delete(e.pointerId)
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
bubble(targets, "onPointerUp", e)
|
|
249
|
+
let frozen = downPaths.get(e.pointerId)
|
|
250
|
+
downPaths.delete(e.pointerId)
|
|
251
|
+
bubble(frozen ?? targets, "onPointerUp", e)
|
|
267
252
|
})
|
|
268
253
|
|
|
269
254
|
unsubMove = on(
|
|
270
255
|
"pointerMove",
|
|
271
256
|
({ targets, ...e }: { targets: number[]; [k: string]: any }) => {
|
|
272
|
-
|
|
273
|
-
if (captured != null) {
|
|
274
|
-
// Captured drags get moves anywhere over the window, bypassing hit test.
|
|
275
|
-
e.stopPropagation = () => {}
|
|
276
|
-
getEventHandler(captured, "onPointerMove")?.(e)
|
|
277
|
-
return
|
|
278
|
-
}
|
|
279
|
-
bubble(targets, "onPointerMove", e)
|
|
257
|
+
bubble(downPaths.get(e.pointerId) ?? targets, "onPointerMove", e)
|
|
280
258
|
},
|
|
281
259
|
)
|
|
282
260
|
|