lecodes-cli 0.7.0 → 0.7.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.
@@ -1,68 +1,112 @@
1
- import { UIScreen } from "./UIScreen";
1
+ import { _bumpNavEpoch, _descOf, _navSupported, _setCurrent, Presentable, type Transition } from "./presentable"
2
2
 
3
3
  type RouterOptions = {
4
4
  showDefaultBackButton?: boolean
5
5
  }
6
6
 
7
+ type NavigateOptions = {
8
+ /** Transition to play. Defaults: `"push"` for push, `"fade"` for replace, `"pop"` for pop. */
9
+ transition?: Transition
10
+ }
11
+
7
12
  type Router = {
8
- get current(): UIScreen<boolean>
9
- init<T extends boolean>(homePage: UIScreen<T>, opts?: RouterOptions): void
10
- push<T extends boolean>(screen: UIScreen<T>): void
11
- replace<T extends boolean>(screen: UIScreen<T>, options?: ReplaceOptions): void
12
- pop(to?: number): void
13
+ get current(): Presentable
14
+ init(homePage: Presentable, opts?: RouterOptions): void
15
+ /** Push a destination onto the stack — a `UIScreen`, a `Scene`, a `Scene2D`, a `NativeView`,
16
+ * or a `UIVideo`. The back gesture/button pops it. Destinations with async preparation
17
+ * (`ARScene`: camera permission + warm render) return a Promise — the current page stays
18
+ * visible until they're ready, and a rejection (permission denied / superseded) leaves the
19
+ * stack untouched. */
20
+ push(page: Presentable, opts?: NavigateOptions): void | Promise<void>
21
+ replace(page: Presentable, opts?: NavigateOptions): void | Promise<void>
22
+ pop(to?: number, opts?: NavigateOptions): void
13
23
  hide(): void
14
24
  restore(): void
15
25
 
16
- addEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void
17
- removeEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void
26
+ addEventListener(channel: "change", callback: (page: Presentable) => void): void
27
+ removeEventListener(channel: "change", callback: (page: Presentable) => void): void
18
28
  }
19
29
 
20
- let currentScreen: UIScreen<any> | null = null
21
- let onChange = <T extends boolean>(screen: UIScreen<T>) => {
22
- currentScreen = screen;
30
+ let currentPage: Presentable | null = null
31
+ let onChange = (page: any) => {
32
+ // Hosts hand back the wire object: the screen itself, or a descriptor carrying `_p`.
33
+ currentPage = page && page._p ? page._p : page
34
+ // The router's page is the visible destination (hosts fire this on every stack change,
35
+ // including back gestures) — keep Presentable.current in sync.
36
+ _setCurrent(currentPage)
23
37
  for (let callback of (Router as any)._changeListeners) {
24
- callback(screen)
38
+ callback(currentPage)
25
39
  }
26
40
  }
27
41
 
28
- type ReplaceOptions = {
29
- transition: "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom" |
30
- "zoom" | "zoom-out" | "zoom-in" | "fade" | "none"
42
+ // Non-screen destinations have no legacy bridge to fall back to — surface that clearly instead
43
+ // of letting an old host try to build a screen out of a scene descriptor.
44
+ const wireDesc = (page: Presentable): object => {
45
+ const desc = _descOf(page)
46
+ if (desc !== page && !_navSupported()) {
47
+ throw new Error("This host does not support scene/native-view navigation (requires _creatorUI.openView)")
48
+ }
49
+ return desc
31
50
  }
32
51
 
33
52
  export const Router: Router = /*#__PURE__*/ Object.freeze({
34
53
 
35
- init(homePage: UIScreen<any>, opts: RouterOptions = {}): void {
36
- _creatorUI.routerOpen(homePage, onChange, opts.showDefaultBackButton ?? false)
37
- currentScreen = homePage
54
+ init(homePage: Presentable, opts: RouterOptions = {}): void {
55
+ _bumpNavEpoch()
56
+ _creatorUI.routerOpen(wireDesc(homePage), onChange, opts.showDefaultBackButton ?? false)
57
+ currentPage = homePage
58
+ _setCurrent(homePage)
38
59
  },
39
- push(screen: UIScreen<any>): void {
40
- _creatorUI.routerPush(screen)
60
+ push(page: Presentable, opts?: NavigateOptions): void | Promise<void> {
61
+ const desc = wireDesc(page) // throws early on unsupported hosts, before any async work
62
+ const go = () => {
63
+ _bumpNavEpoch()
64
+ // Legacy hosts ignore the extra transition argument.
65
+ _creatorUI.routerPush(desc, opts?.transition ?? "push")
66
+ _setCurrent(page) // optimistic — host onChange confirms (async on native hosts)
67
+ }
68
+ // A preparing destination (ARScene) keeps the current page visible until it's ready; a
69
+ // rejection (camera denied / superseded) propagates to the caller and nothing is pushed.
70
+ const prep = page._prepare?.()
71
+ return prep ? prep.then(go) : go()
41
72
  },
42
- replace(screen: UIScreen<any>, opts?: ReplaceOptions): void {
43
- _creatorUI.routerReplace(screen, opts?.transition ?? "fade")
73
+ replace(page: Presentable, opts?: NavigateOptions): void | Promise<void> {
74
+ const desc = wireDesc(page)
75
+ const go = () => {
76
+ _bumpNavEpoch()
77
+ _creatorUI.routerReplace(desc, opts?.transition ?? "fade")
78
+ _setCurrent(page) // optimistic — host onChange confirms (async on native hosts)
79
+ }
80
+ const prep = page._prepare?.()
81
+ return prep ? prep.then(go) : go()
44
82
  },
45
- pop(to?: number): void {
46
- _creatorUI.routerPop(to ?? -1)
83
+ pop(to?: number, opts?: NavigateOptions): void {
84
+ _bumpNavEpoch()
85
+ _creatorUI.routerPop(to ?? -1, opts?.transition ?? "pop")
47
86
  },
48
87
  hide() {
88
+ _bumpNavEpoch()
49
89
  _creatorUI.routerHide()
90
+ // hide() only blanks the router's own page — a directly-opened destination stays visible.
91
+ if (Presentable.current === currentPage) _setCurrent(null)
50
92
  },
51
93
  restore() {
94
+ _bumpNavEpoch()
52
95
  _creatorUI.routerRestore()
96
+ _setCurrent(currentPage) // the stack top is visible again
53
97
  },
54
98
 
55
- get current(): UIScreen<any> {
56
- return currentScreen!
99
+ get current(): Presentable {
100
+ return currentPage!
57
101
  },
58
-
102
+
59
103
  _changeListeners: [] as any[],
60
- addEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void {
104
+ addEventListener(channel: "change", callback: (page: Presentable) => void): void {
61
105
  if (channel === "change") {
62
106
  this._changeListeners.push(callback)
63
107
  }
64
108
  },
65
- removeEventListener(channel: "change", callback: (screen: UIScreen<boolean>) => void): void {
109
+ removeEventListener(channel: "change", callback: (page: Presentable) => void): void {
66
110
  if (channel === "change") {
67
111
  let index = this._changeListeners.indexOf(callback)
68
112
  if (index >= 0) {