lecodes-sdk 2.4.2 → 2.4.4

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.
@@ -1,22 +1,23 @@
1
1
  // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/ar/sdk/controls.ts — do not edit here.
2
2
  // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
3
  // AR object-placement controls — the classic "drop a model in the room and manipulate it" gesture
4
- // recipe: ONE finger drags the target over what the session finds under the finger (`hitTest`: a
5
- // plane), and where it finds nothing — the web, which finds no surfaces, or before one is known —
6
- // across the ground plane, relative to where the camera looks; TWO fingers pinch to scale and twist
7
- // to rotate (yaw). Lives on ARScene (`scene.addControls`) because it orchestrates the scene's camera,
8
- // its hit test and touch input against a target node; it's built entirely on the public API, so
9
- // it's a convenience, not a primitive.
4
+ // recipe: ONE finger drags the target across the ground plane at its own height, by the finger's
5
+ // move and relative to where the camera looks — the model never jumps to the finger, and no session
6
+ // call is in the loop, so it behaves the same on every host (the web too). Not under the finger on
7
+ // purpose: the finger's ray onto the floor (a hit test, or the plane at the target's height) is
8
+ // metres per point near the horizon, and there the target flies off at the slightest move. TWO
9
+ // fingers pinch to scale and twist to rotate (yaw). Lives on ARScene (`scene.addControls`) because it
10
+ // orchestrates the scene's camera and touch input against a target node; it's built entirely on the
11
+ // public API, so it's a convenience, not a primitive.
10
12
 
11
13
  import { Quat, Vec2, Vec3, type Node, type Scene, type TouchStartEvent } from "../../../../plugin"
12
- import type { HitTarget } from "../contract"
13
14
 
14
15
  export type ControlsOptions = {
15
16
  /** Smallest uniform scale the pinch gesture allows (default 0.08). */
16
17
  minScale?: number
17
18
  /** Largest uniform scale the pinch gesture allows (default 2). */
18
19
  maxScale?: number
19
- /** One-finger drag moves the target over the surfaces under the finger (default true). */
20
+ /** One-finger drag moves the target across the ground plane, at its height (default true). */
20
21
  pan?: boolean
21
22
  /** Two-finger pinch scales the target (default true). */
22
23
  pinch?: boolean
@@ -27,16 +28,11 @@ export type ControlsOptions = {
27
28
  /** Returned by `addControls` — call `remove()` to detach the gesture listeners. */
28
29
  export type ControlsHandle = { remove(): void }
29
30
 
30
- /** The scene the controls work on: ARScene's hit test, a Scene's camera and touches. */
31
- type PlacingScene = Scene & {
32
- hitTest(x: number, y: number, target?: HitTarget): Promise<{ pose: { position: Vec3 } }[]>
33
- }
34
-
35
31
  type ActiveTouch = { pointerId: number, x: number, y: number }
36
32
 
37
33
  const clamp = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
38
34
 
39
- export const attachControls = (scene: PlacingScene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
35
+ export const attachControls = (scene: Scene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
40
36
  const minScale = options.minScale ?? 0.08
41
37
  const maxScale = options.maxScale ?? 2
42
38
  const pan = options.pan ?? true
@@ -44,60 +40,6 @@ export const attachControls = (scene: PlacingScene, target: Node, options: Contr
44
40
  const twist = options.twist ?? true
45
41
 
46
42
  const active: ActiveTouch[] = []
47
- let removed = false
48
-
49
- // --- one finger: the hit test is a call to the session, so ONE is in flight at a time; the moves
50
- // that come meanwhile add up and go with the next ---
51
- let pending: { x: number, y: number, dx: number, dy: number } | null = null
52
- let asking = false
53
-
54
- /** The target onto a point of the world, its height and turn kept by the surface's. */
55
- const placeAt = (world: Vec3): void => {
56
- const w = target.worldMatrix.toArray()
57
- w[12] = world.x; w[13] = world.y; w[14] = world.z
58
- target.worldMatrix = w
59
- }
60
-
61
- /** Nothing under the finger: across the ground plane, relative to where the camera looks. */
62
- const slide = (dx: number, dy: number): void => {
63
- let dir = scene.camera.forward
64
- const parent = target.parent
65
- if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
66
- dir = dir.withY(0)
67
- const k = Math.hypot(dir.x, dir.z)
68
- // the move's delta over the view's size: both logical units (a scene touch's, displaySize's), so
69
- // a drag across the view slides the target by its distance from the camera on every host
70
- const screen = Math.max(...scene.camera.displaySize)
71
- if (k > 1e-4 && screen > 0) {
72
- const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
73
- const right = new Vec3(-dir.z, 0, dir.x).scale(dx / k / screen * dist)
74
- const fwd = dir.scale(-dy / k / screen * dist)
75
- const move = right.add(fwd)
76
- const p = target.position
77
- target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
78
- }
79
- }
80
-
81
- const ask = (): void => {
82
- const drag = pending
83
- if (!drag || asking) return
84
- pending = null
85
- asking = true
86
- scene.hitTest(drag.x, drag.y, "planes").catch(() => []).then((hits) => {
87
- asking = false
88
- if (removed) return
89
- const hit = hits[0]
90
- if (hit) placeAt(hit.pose.position)
91
- else slide(drag.dx, drag.dy)
92
- ask()
93
- })
94
- }
95
-
96
- const drag = (x: number, y: number, dx: number, dy: number): void => {
97
- if (pending) { pending.x = x; pending.y = y; pending.dx += dx; pending.dy += dy }
98
- else pending = { x, y, dx, dy }
99
- ask()
100
- }
101
43
 
102
44
  const onTouchStart = (e: TouchStartEvent<Node | null>): void => {
103
45
  const touch: ActiveTouch = { pointerId: e.pointerId, x: e.clientX, y: e.clientY }
@@ -124,7 +66,23 @@ export const attachControls = (scene: PlacingScene, target: Node, options: Contr
124
66
  }
125
67
  }
126
68
  } else if (active.length === 1 && pan) {
127
- drag(m.clientX, m.clientY, m.deltaX, m.deltaY)
69
+ // --- one-finger: drag across the ground plane, relative to where the camera looks ---
70
+ let dir = scene.camera.forward
71
+ const parent = target.parent
72
+ if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
73
+ dir = dir.withY(0)
74
+ const k = Math.hypot(dir.x, dir.z)
75
+ // the move's delta over the view's size: both logical units (a scene touch's, displaySize's), so
76
+ // a drag across the view slides the target by its distance from the camera on every host
77
+ const screen = Math.max(...scene.camera.displaySize)
78
+ if (k > 1e-4 && screen > 0) {
79
+ const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
80
+ const right = new Vec3(-dir.z, 0, dir.x).scale(m.deltaX / k / screen * dist)
81
+ const fwd = dir.scale(-m.deltaY / k / screen * dist)
82
+ const move = right.add(fwd)
83
+ const p = target.position
84
+ target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
85
+ }
128
86
  }
129
87
  touch.x = m.clientX
130
88
  touch.y = m.clientY
@@ -137,12 +95,5 @@ export const attachControls = (scene: PlacingScene, target: Node, options: Contr
137
95
  }
138
96
 
139
97
  scene.addEventListener("touchstart", onTouchStart)
140
- return {
141
- remove() {
142
- removed = true
143
- pending = null
144
- scene.removeEventListener("touchstart", onTouchStart)
145
- active.length = 0
146
- },
147
- }
98
+ return { remove() { scene.removeEventListener("touchstart", onTouchStart); active.length = 0 } }
148
99
  }
@@ -8,6 +8,9 @@
8
8
  // const back = await Browser.open(checkoutUrl, { returnUrl: [paid, failed], embedded: true })
9
9
  // a checkout in an embedded page,
10
10
  // resolved with the URL it reached
11
+ // (navigated to in the page, or
12
+ // opened by a bank's app through
13
+ // the app's verified link)
11
14
  //
12
15
  // It resolves with the return URL reached, or null when the user left. `returnUrl` says what ENDS
13
16
  // the page; `embedded` says WHERE it runs — the system sheet (shared logins, passkeys, Apple Pay;
@@ -38,7 +41,8 @@ export interface BrowserOpenOptions {
38
41
  /** What ends the page: a URL prefix, or several (a checkout's success and failure URLs). The
39
42
  * promise resolves with the URL reached. In the sheet every prefix must share one scheme — the
40
43
  * platform intercepts one; a custom scheme needs app.json `urlScheme`, an https one the app's
41
- * verified links. */
44
+ * verified links (app.json `appLinks`). Embedded, the view intercepts the navigation itself,
45
+ * and a verified https return from another app (a bank's) ends the page too. */
42
46
  returnUrl?: string | string[]
43
47
  /** Run the page in an embedded WebView under a close bar instead of the system sheet. The
44
48
  * WebView intercepts any return URL itself, https included. */
@@ -157,10 +161,16 @@ const openEmbedded = (url: string, options: BrowserOpenOptions): Promise<string
157
161
  if (routed) Router.pop()
158
162
  else { screen.close(); previous?.open() }
159
163
  }
164
+ // Two ways the return URL arrives: the page navigates to it inside the view (`stop`), or
165
+ // another app does — a bank's app the checkout handed off to sends the person back through the
166
+ // app's verified link (app.json "appLinks"), which reaches the app as its "url" event while the
167
+ // page still shows the bank's form. Both end the flow; `done` makes the second a no-op.
160
168
  let done = false
169
+ const off = stops.length ? _onAppUrl(back => { if (matching(back, stops)) finish(back) }) : () => {}
161
170
  const finish = (back: string | null): void => {
162
171
  if (done) return
163
172
  done = true
173
+ off()
164
174
  leave()
165
175
  resolve(back)
166
176
  }
package/src/version.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // same manifest). Its MAJOR is the bundle ↔ runtime contract: a bundle's `// sdk:` header line
5
5
  // and a host's embedded version must agree on the major for the host to run the bundle; minor
6
6
  // and patch never gate anything (features are detected, never versioned).
7
- export const SDK_VERSION = "2.4.2"
7
+ export const SDK_VERSION = "2.4.4"
8
8
 
9
9
  /** The major of a semver string, or null when it is not one. */
10
10
  export const sdkMajor = (version: string | null | undefined): number | null => {