@octane-xplat/ui 0.7.2 → 0.9.0

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.
Files changed (219) hide show
  1. package/README.md +4 -3
  2. package/dist/native/Button.tsrx.js +1 -1
  3. package/dist/native/CommandPalette.tsrx.js +7 -1
  4. package/dist/native/Grid.tsrx.js +31 -2
  5. package/dist/native/KeyboardAvoiding.mobile.tsrx.js +2 -2
  6. package/dist/native/Markdown.tsrx.js +233 -0
  7. package/dist/native/Overlay.tsrx.js +15 -26
  8. package/dist/native/Pagination.tsrx.js +15 -12
  9. package/dist/native/PinInput.tsrx.js +25 -7
  10. package/dist/native/Popover.tsrx.js +33 -23
  11. package/dist/native/Pressable.tsrx.js +4 -1
  12. package/dist/native/Screen.tsrx.js +1 -1
  13. package/dist/native/SearchInput.tsrx.js +6 -3
  14. package/dist/native/Sheet.tsrx.js +23 -38
  15. package/dist/native/Slider.tsrx.js +23 -14
  16. package/dist/native/Text.tsrx.js +16 -0
  17. package/dist/native/TextArea.tsrx.js +6 -3
  18. package/dist/native/TextInput.tsrx.js +6 -3
  19. package/dist/native/VirtualList.tsrx.js +305 -121
  20. package/dist/native/WebView.tsrx.js +17 -2
  21. package/dist/native/_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js +9 -0
  22. package/dist/native/escape-props.js +2 -0
  23. package/dist/native/grid-auto-placement.js +83 -0
  24. package/dist/native/grid-placement.js +20 -0
  25. package/dist/native/index.shared.js +7 -5
  26. package/dist/native/keyboard-inset.mobile.js +5 -5
  27. package/dist/native/navigation-request.js +26 -0
  28. package/dist/native/octane-client-build.json +1 -1
  29. package/dist/native/overlay-lifetime.mobile.js +65 -0
  30. package/dist/native/pan.tsrx.js +16 -2
  31. package/dist/native/route-table.js +90 -10
  32. package/dist/native/route.js +26 -16
  33. package/dist/native/shared.js +7 -5
  34. package/dist/native/sheet-service.js +2 -2
  35. package/dist/native/svg.mobile.js +1 -1
  36. package/dist/native/{tap-to-blur.mobile.js → tap-to-blur.ios.js} +20 -32
  37. package/dist/native/text-focus.js +10 -0
  38. package/dist/native/text-write.js +18 -11
  39. package/dist/native/toast-anchor.tsrx.js +1 -1
  40. package/dist/native/ui.js +7 -6
  41. package/dist/native/useMeasure.tsrx.js +17 -18
  42. package/dist/native/vendor/ui-svg/index.android.js +2 -2
  43. package/dist/native/vendor/ui-svg/index.ios.js +2 -2
  44. package/dist/native/vendor/ui-svg/{index.common.js → src/index.common.js} +1 -1
  45. package/dist/native/virtual-list-cell-pool.js +63 -0
  46. package/dist/web/Button.web.tsrx.js +1 -1
  47. package/dist/web/KeyboardAvoiding.web.tsrx.js +78 -0
  48. package/dist/web/Markdown.tsrx.js +98 -0
  49. package/dist/web/Overlay.web.tsrx.js +81 -32
  50. package/dist/web/PinInput.web.tsrx.js +24 -10
  51. package/dist/web/Pressable.web.tsrx.js +169 -53
  52. package/dist/web/SearchInput.web.tsrx.js +2 -2
  53. package/dist/web/Sheet.web.tsrx.js +20 -7
  54. package/dist/web/TextArea.web.tsrx.js +22 -17
  55. package/dist/web/TextInput.web.tsrx.js +2 -2
  56. package/dist/web/VirtualList.web.tsrx.js +220 -109
  57. package/dist/web/WebView.web.tsrx.js +3 -3
  58. package/dist/web/{index.shared.js → index.shared.web.js} +7 -5
  59. package/dist/web/modal-focus.web.js +95 -0
  60. package/dist/web/navigation-request.js +26 -0
  61. package/dist/web/octane-client-build.json +1 -1
  62. package/dist/web/pan.web.tsrx.js +27 -10
  63. package/dist/web/route-table.js +90 -10
  64. package/dist/web/route.web.js +88 -22
  65. package/dist/web/sheet-service.web.js +8 -0
  66. package/dist/web/theme/colorScheme.web.js +31 -4
  67. package/dist/web/ui.js +8 -6
  68. package/dist/web/virtual-list-cell-pool.js +63 -0
  69. package/package.json +21 -19
  70. package/platforms/android/native-api-usage.json +7 -7
  71. package/src/CommandPalette.tsrx +8 -1
  72. package/src/Grid.tsrx +32 -1
  73. package/src/Icon.macos.tsrx +1 -0
  74. package/src/KeyboardAvoiding.macos.tsrx +7 -1
  75. package/src/KeyboardAvoiding.mobile.tsrx +2 -2
  76. package/src/KeyboardAvoiding.tsrx +15 -0
  77. package/src/KeyboardAvoiding.web.tsrx +15 -0
  78. package/src/Markdown.tsrx +121 -0
  79. package/src/Meter.tsrx +1 -0
  80. package/src/Overlay.tsrx +15 -30
  81. package/src/Overlay.web.tsrx +12 -3
  82. package/src/Pagination.tsrx +6 -4
  83. package/src/PinInput.macos.tsrx +20 -6
  84. package/src/PinInput.mobile.test.ts +107 -0
  85. package/src/PinInput.tsrx +20 -6
  86. package/src/PinInput.web.test.tsrx +42 -0
  87. package/src/PinInput.web.tsrx +20 -6
  88. package/src/Popover.tsrx +36 -22
  89. package/src/Pressable.tsrx +1 -1
  90. package/src/Pressable.web.test.tsrx +36 -0
  91. package/src/Pressable.web.tsrx +37 -6
  92. package/src/RouteHost.macos.tsrx +1 -0
  93. package/src/Screen.tsrx +1 -1
  94. package/src/SearchInput.tsrx +4 -3
  95. package/src/SearchInput.web.tsrx +1 -1
  96. package/src/Sheet.tsrx +21 -42
  97. package/src/Sheet.web.tsrx +9 -1
  98. package/src/Slider.tsrx +13 -11
  99. package/src/Text.tsrx +36 -0
  100. package/src/TextArea.tsrx +6 -5
  101. package/src/TextArea.web.test.tsrx +39 -0
  102. package/src/TextArea.web.tsrx +7 -5
  103. package/src/TextInput.tsrx +6 -6
  104. package/src/TextInput.web.tsrx +1 -1
  105. package/src/VirtualList.macos.tsrx +93 -6
  106. package/src/VirtualList.mobile.test.tsrx +232 -0
  107. package/src/VirtualList.tsrx +285 -81
  108. package/src/VirtualList.web.tsrx +131 -25
  109. package/src/WebView.tsrx +20 -10
  110. package/src/WebView.web.tsrx +3 -0
  111. package/src/android/BottomNavigationView.android.tsrx +1 -0
  112. package/src/android/MaterialDialog.android.tsrx +1 -0
  113. package/src/anim.macos.tsrx +1 -0
  114. package/src/apply-native-modifiers.native.ts +4 -1
  115. package/src/child-array.ts +12 -7
  116. package/src/colorScheme.macos.ts +1 -0
  117. package/src/controls.macos.tsrx +4 -1
  118. package/src/data-driver.mobile.test.ts +120 -0
  119. package/src/data-lifecycle.mobile.test.ts +251 -0
  120. package/src/escape-props.ts +13 -1
  121. package/src/grid-auto-placement.ts +163 -0
  122. package/src/grid-placement.ts +29 -0
  123. package/src/hosted-unsupported.macos.tsrx +1 -0
  124. package/src/index.macos.ts +23 -3
  125. package/src/index.mobile.ts +0 -1
  126. package/src/index.shared.ts +22 -1
  127. package/src/index.shared.web.ts +291 -0
  128. package/src/index.web.ts +1 -1
  129. package/src/input-readiness.web.test.tsrx +82 -0
  130. package/src/ios/UITabBar.ios.tsrx +1 -0
  131. package/src/keyboard-inset.mobile.test.ts +62 -0
  132. package/src/keyboard-inset.mobile.ts +63 -43
  133. package/src/layout-shells.macos.tsrx +1 -5
  134. package/src/macos-extras.macos.tsrx +2 -0
  135. package/src/modal-focus.web.test.tsrx +63 -0
  136. package/src/modal-focus.web.ts +162 -0
  137. package/src/navigation-request.ts +37 -0
  138. package/src/overlay-lifecycle.mobile.test.ts +316 -0
  139. package/src/overlay-lifetime.mobile.ts +107 -0
  140. package/src/pan.mobile.test.tsrx +116 -0
  141. package/src/pan.tsrx +20 -2
  142. package/src/pan.web.tsrx +35 -7
  143. package/src/platformProps.ts +1 -0
  144. package/src/popover.mobile.test.ts +9 -1
  145. package/src/pressable-accessibility.mobile.test.ts +30 -0
  146. package/src/props.ts +163 -3
  147. package/src/refresh.ts +6 -1
  148. package/src/refresh.web.test.ts +77 -77
  149. package/src/route-android.test.ts +162 -0
  150. package/src/route-history.web.test.ts +80 -0
  151. package/src/route-navigation.web.test.ts +173 -0
  152. package/src/route-table.test.ts +188 -9
  153. package/src/route-table.ts +138 -10
  154. package/src/route.macos.ts +56 -13
  155. package/src/route.ts +54 -24
  156. package/src/route.web.ts +141 -24
  157. package/src/sheet-detents.ts +5 -3
  158. package/src/sheet-service.macos.ts +5 -1
  159. package/src/sheet-service.ts +1 -1
  160. package/src/sheet-service.web.ts +8 -0
  161. package/src/sheet-snap.test.ts +23 -23
  162. package/src/sheet-snap.ts +6 -1
  163. package/src/svg.mobile.ts +9 -3
  164. package/src/tap-to-blur.ios.ts +106 -0
  165. package/src/tap-to-blur.mobile.ts +21 -95
  166. package/src/text-focus.ts +12 -0
  167. package/src/text-input.mobile.test.ts +89 -0
  168. package/src/text-write.ts +21 -11
  169. package/src/theme/colorScheme.linux.ts +2 -4
  170. package/src/theme/colorScheme.web.ts +51 -4
  171. package/src/theme/theme-scheme.macos.ts +8 -2
  172. package/src/theme/tokens.css +1740 -1718
  173. package/src/translate.macos.ts +4 -1
  174. package/src/useMeasure.tsrx +20 -21
  175. package/src/virtual-list-cell-pool.test.ts +53 -0
  176. package/src/virtual-list-cell-pool.ts +81 -0
  177. package/src/virtual-list-layout.test.ts +11 -8
  178. package/src/virtual-list-layout.ts +4 -8
  179. package/src/windows.linux.ts +7 -2
  180. package/src/windows.macos.ts +1 -0
  181. package/types/generated/.tsrx-typegen-manifest.json +16 -3
  182. package/types/generated/Grid.d.ts +0 -1
  183. package/types/generated/KeyboardAvoiding.d.ts +3 -0
  184. package/types/generated/KeyboardAvoiding.web.d.ts +2 -0
  185. package/types/generated/Markdown.d.ts +59 -0
  186. package/types/generated/TextArea.d.ts +2 -2
  187. package/types/generated/TextArea.web.d.ts +1 -1
  188. package/types/generated/TextInput.d.ts +2 -3
  189. package/types/generated/VirtualList.d.ts +3 -3
  190. package/types/generated/escape-props.d.ts +4 -0
  191. package/types/generated/grid-auto-placement.d.ts +13 -0
  192. package/types/generated/grid-placement.d.ts +5 -0
  193. package/types/generated/index.mobile.d.ts +0 -1
  194. package/types/generated/index.shared.d.ts +6 -3
  195. package/types/generated/index.shared.web.d.ts +114 -0
  196. package/types/generated/index.web.d.ts +1 -1
  197. package/types/generated/modal-focus.web.d.ts +3 -0
  198. package/types/generated/navigation-request.d.ts +10 -0
  199. package/types/generated/overlay-lifetime.mobile.d.ts +10 -0
  200. package/types/generated/props.d.ts +111 -3
  201. package/types/generated/route-table.d.ts +14 -3
  202. package/types/generated/svg.mobile.d.ts +2 -2
  203. package/types/generated/tap-to-blur.ios.d.ts +13 -0
  204. package/types/generated/tap-to-blur.mobile.d.ts +1 -15
  205. package/types/generated/text-focus.d.ts +3 -0
  206. package/types/generated/text-write.d.ts +3 -1
  207. package/types/generated/theme/colorScheme.web.d.ts +5 -0
  208. package/types/generated/virtual-list-cell-pool.d.ts +18 -0
  209. package/types/index.macos.d.ts +15 -3
  210. /package/src/vendor/ui-svg/{index.android.ts → src/index.android.ts} +0 -0
  211. /package/src/vendor/ui-svg/{index.common.ts → src/index.common.ts} +0 -0
  212. /package/src/vendor/ui-svg/{index.ios.ts → src/index.ios.ts} +0 -0
  213. /package/src/vendor/ui-svg/{index.ts → src/index.ts} +0 -0
  214. /package/src/vendor/ui-svg/{typings → src/typings}/SVGKit.d.ts +0 -0
  215. /package/src/vendor/ui-svg/{typings → src/typings}/android.d.ts +0 -0
  216. /package/src/vendor/ui-svg/{typings → src/typings}/ios.d.ts +0 -0
  217. /package/types/generated/vendor/ui-svg/{index.common.d.ts → src/index.common.d.ts} +0 -0
  218. /package/types/generated/vendor/ui-svg/{index.d.ts → src/index.d.ts} +0 -0
  219. /package/types/generated/vendor/ui-svg/{index.ios.d.ts → src/index.ios.d.ts} +0 -0
@@ -1,9 +1,11 @@
1
1
  import { applyNativeModifiers } from "./apply-native-modifiers.native.js";
2
+ import { rememberGridChildPlacement } from "./grid-placement.js";
2
3
  import { isAndroid, isIOS } from "@nativescript/core";
3
4
  //#region src/escape-props.ts
4
5
  /** Apply only the active device's escape bag after the primitive's own props. */
5
6
  function applyEscapeProps(view, props) {
6
7
  if (!view) return;
8
+ rememberGridChildPlacement(view, props);
7
9
  const bag = isIOS ? props.ios : isAndroid ? props.android : void 0;
8
10
  if (bag) Object.assign(view, bag);
9
11
  applyNativeModifiers(view, props.modifiers);
@@ -0,0 +1,83 @@
1
+ //#region src/grid-auto-placement.ts
2
+ function gridIndex(value) {
3
+ if (value == null || value === "") return null;
4
+ const index = Number(value);
5
+ return Number.isInteger(index) && index >= 0 ? index : null;
6
+ }
7
+ function gridSpan(value) {
8
+ const span = Number(value);
9
+ return Number.isInteger(span) && span > 0 ? span : 1;
10
+ }
11
+ function trackCount(spec) {
12
+ return spec?.split(",").filter((track) => track.trim().length > 0).length ?? 0;
13
+ }
14
+ /** Match CSS Grid's row-flow placement for NativeScript GridLayout children. */
15
+ function autoPlaceGridChildren(items, rowTracks, columnTracks) {
16
+ const placements = items.map((item) => ({
17
+ row: gridIndex(item.row),
18
+ col: gridIndex(item.col),
19
+ rowSpan: gridSpan(item.rowSpan),
20
+ colSpan: gridSpan(item.colSpan)
21
+ }));
22
+ const columnCount = Math.max(1, trackCount(columnTracks), ...placements.filter((placement) => placement.col != null).map((placement) => placement.col + placement.colSpan), ...placements.map((placement) => placement.colSpan));
23
+ const occupied = /* @__PURE__ */ new Set();
24
+ const isFree = (row, col, rowSpan, colSpan) => {
25
+ for (let y = row; y < row + rowSpan; y++) for (let x = col; x < col + colSpan; x++) if (occupied.has(`${y},${x}`)) return false;
26
+ return true;
27
+ };
28
+ const occupy = (row, col, rowSpan, colSpan) => {
29
+ for (let y = row; y < row + rowSpan; y++) for (let x = col; x < col + colSpan; x++) occupied.add(`${y},${x}`);
30
+ };
31
+ for (const placement of placements) if (placement.row != null && placement.col != null) occupy(placement.row, placement.col, placement.rowSpan, placement.colSpan);
32
+ for (const placement of placements) {
33
+ if (placement.row == null || placement.col != null) continue;
34
+ let col = 0;
35
+ while (!isFree(placement.row, col, placement.rowSpan, placement.colSpan)) col++;
36
+ placement.col = col;
37
+ occupy(placement.row, col, placement.rowSpan, placement.colSpan);
38
+ }
39
+ for (const placement of placements) {
40
+ if (placement.col == null || placement.row != null) continue;
41
+ let row = 0;
42
+ while (!isFree(row, placement.col, placement.rowSpan, placement.colSpan)) row++;
43
+ placement.row = row;
44
+ occupy(row, placement.col, placement.rowSpan, placement.colSpan);
45
+ }
46
+ let cursorRow = 0;
47
+ let cursorCol = 0;
48
+ for (const placement of placements) {
49
+ if (placement.row != null && placement.col != null) continue;
50
+ while (true) {
51
+ if (cursorCol + placement.colSpan > columnCount) {
52
+ cursorRow++;
53
+ cursorCol = 0;
54
+ continue;
55
+ }
56
+ if (isFree(cursorRow, cursorCol, placement.rowSpan, placement.colSpan)) break;
57
+ cursorCol++;
58
+ }
59
+ placement.row = cursorRow;
60
+ placement.col = cursorCol;
61
+ occupy(cursorRow, cursorCol, placement.rowSpan, placement.colSpan);
62
+ cursorCol += placement.colSpan;
63
+ if (cursorCol >= columnCount) {
64
+ cursorRow++;
65
+ cursorCol = 0;
66
+ }
67
+ }
68
+ return {
69
+ cells: placements.map(({ row, col }) => ({
70
+ row,
71
+ col
72
+ })),
73
+ rowCount: Math.max(trackCount(rowTracks), ...placements.map((item) => item.row + item.rowSpan)),
74
+ columnCount: Math.max(columnCount, ...placements.map((item) => item.col + item.colSpan))
75
+ };
76
+ }
77
+ function ensureGridTracks(spec, requiredCount) {
78
+ const tracks = spec?.split(",").map((track) => track.trim()).filter(Boolean) ?? [];
79
+ while (tracks.length < requiredCount) tracks.push("auto");
80
+ return tracks.join(",");
81
+ }
82
+ //#endregion
83
+ export { autoPlaceGridChildren, ensureGridTracks };
@@ -0,0 +1,20 @@
1
+ //#region src/grid-placement.ts
2
+ var placements = /* @__PURE__ */ new WeakMap();
3
+ /** Keep the authored Grid placement next to its mounted NativeScript view. */
4
+ function rememberGridChildPlacement(view, props) {
5
+ if ((typeof view !== "object" || view === null) && typeof view !== "function") return;
6
+ const placement = {};
7
+ for (const key of [
8
+ "row",
9
+ "col",
10
+ "rowSpan",
11
+ "colSpan"
12
+ ]) if (props[key] !== void 0) placement[key] = props[key];
13
+ if (Object.keys(placement).length) placements.set(view, placement);
14
+ else placements.delete(view);
15
+ }
16
+ function gridChildPlacement(view) {
17
+ return typeof view === "object" && view !== null ? placements.get(view) ?? {} : {};
18
+ }
19
+ //#endregion
20
+ export { gridChildPlacement, rememberGridChildPlacement };
@@ -6,9 +6,12 @@ import { Absolute } from "./Absolute.tsrx.js";
6
6
  import { setTranslate } from "./translate.js";
7
7
  import { Spacer } from "./Spacer.tsrx.js";
8
8
  import { Text } from "./Text.tsrx.js";
9
+ import { Screen } from "./Screen.tsrx.js";
10
+ import { ActivityIndicator } from "./ActivityIndicator.tsrx.js";
11
+ import { ScrollView } from "./ScrollView.tsrx.js";
12
+ import { Markdown, MarkdownScreen } from "./Markdown.tsrx.js";
9
13
  import { RichText, RichTextSpan } from "./RichText.tsrx.js";
10
14
  import { Pressable } from "./Pressable.tsrx.js";
11
- import { ActivityIndicator } from "./ActivityIndicator.tsrx.js";
12
15
  import { Button } from "./Button.tsrx.js";
13
16
  import { Collapsible } from "./Collapsible.tsrx.js";
14
17
  import { Accordion } from "./Accordion.tsrx.js";
@@ -58,14 +61,13 @@ import { Meter } from "./Meter.tsrx.js";
58
61
  import { ProgressGroup } from "./ProgressGroup.tsrx.js";
59
62
  import { Link } from "./Link.tsrx.js";
60
63
  import { getStack, registerStack, stackEntries } from "./stacks.js";
61
- import { defineRoutes, deriveRouteManifest, mergeRouteManifests } from "./route-table.js";
64
+ import { defineRoutes, deriveRouteManifest, manifestToJson, mergeRouteManifests } from "./route-table.js";
62
65
  import { addBackInterceptor, addRoutes, canGoBack, currentModalRoute, currentRoute, hrefFor, layoutsForRoute, popRoute, pushDeepLink, pushRoute, redirect, registerRoutes, registerScreens, routeFor, routeStacks, screenFor, useCanGoBack, useModalRoute, useRoute } from "./route.js";
63
66
  import { NavLink } from "./NavLink.tsrx.js";
64
67
  import { TextArea } from "./TextArea.tsrx.js";
65
68
  import { SearchInput } from "./SearchInput.tsrx.js";
66
69
  import { SegmentedControl } from "./SegmentedControl.tsrx.js";
67
70
  import { ScrollBox } from "./ScrollBox.tsrx.js";
68
- import { ScrollView } from "./ScrollView.tsrx.js";
69
71
  import { VirtualList } from "./VirtualList.tsrx.js";
70
72
  import { WebView } from "./WebView.tsrx.js";
71
73
  import { Hoverable } from "./Hoverable.mobile.tsrx.js";
@@ -74,11 +76,11 @@ import { showToast } from "./toast-anchor.tsrx.js";
74
76
  import { useAnimation } from "./anim.tsrx.js";
75
77
  import { styled } from "./styled.tsrx.js";
76
78
  import { openWindow } from "./windows.js";
77
- import { Screen } from "./Screen.tsrx.js";
78
79
  import { Tabs } from "./Tabs.tsrx.js";
79
80
  import { Switch } from "./Switch.tsrx.js";
80
81
  import { useSafeAreaInsets } from "./safeAreaInsets.tsrx.js";
81
82
  import { SafeArea } from "./SafeArea.tsrx.js";
83
+ import { KeyboardAvoiding } from "./KeyboardAvoiding.mobile.tsrx.js";
82
84
  import { Drawer } from "./Drawer.tsrx.js";
83
85
  import { useMeasure } from "./useMeasure.tsrx.js";
84
86
  import { Slider } from "./Slider.tsrx.js";
@@ -89,4 +91,4 @@ import { createStore } from "./store.js";
89
91
  import { useStore } from "./use-store.tsrx.js";
90
92
  import { Sheet } from "./Sheet.tsrx.js";
91
93
  import { closeSheet, openSheet } from "./sheet-service.js";
92
- export { Absolute, Accordion, ActivityIndicator, Alert, Avatar, AvatarGroup, Badge, Banner, Breadcrumb, Button, Card, Checkbox, CheckboxGroup, Chip, Collapsible, View as Column, Combobox, CommandPalette, ContextMenu, Drawer, DropdownMenu, Empty, FieldGroup, FormField, Grid, Heading, Hoverable, Icon, Image, InputMenu, InputNumber, InputRating, InputTags, Kbd, Link, ListItem, Meter, NavLink, NavigationMenu, Overlay, Pagination, PinInput, Popover, Pressable, ProgressGroup, RadioGroup, RichText, RichTextSpan, Row, SafeArea, Screen, ScrollBox, ScrollView, SearchInput, SegmentedControl, Select, SelectMenu, Separator, Sheet, Skeleton, Slider, Spacer, Stack, Stepper, Switch, Table, Tabs, Text, TextArea, TextInput, Timeline, Tooltip, Tree, User, View, VirtualList, WebView, addBackInterceptor, addRoutes, applyThemeClasses, canGoBack, closeSheet, createStore, currentModalRoute, currentRoute, defineRoutes, deriveRouteManifest, getColorScheme, getStack, getThemePreference, getThemeScheme, hrefFor, isNative, layoutsForRoute, mergeRouteManifests, onThemeSchemeChange, openSheet, openWindow, popRoute, pushDeepLink, pushRoute, redirect, registerIcon, registerIcons, registerRoutes, registerScreens, registerStack, routeFor, routeStacks, screenFor, setThemePreference, setTranslate, showToast, stackEntries, styled, themeSchemeClasses, useAnimation, useBackInterceptor, useCanGoBack, useColorScheme, useMeasure, useModalRoute, useRoute, useSafeAreaInsets, useStore, useThemeScheme };
94
+ export { Absolute, Accordion, ActivityIndicator, Alert, Avatar, AvatarGroup, Badge, Banner, Breadcrumb, Button, Card, Checkbox, CheckboxGroup, Chip, Collapsible, View as Column, Combobox, CommandPalette, ContextMenu, Drawer, DropdownMenu, Empty, FieldGroup, FormField, Grid, Heading, Hoverable, Icon, Image, InputMenu, InputNumber, InputRating, InputTags, Kbd, KeyboardAvoiding, Link, ListItem, Markdown, MarkdownScreen, Meter, NavLink, NavigationMenu, Overlay, Pagination, PinInput, Popover, Pressable, ProgressGroup, RadioGroup, RichText, RichTextSpan, Row, SafeArea, Screen, ScrollBox, ScrollView, SearchInput, SegmentedControl, Select, SelectMenu, Separator, Sheet, Skeleton, Slider, Spacer, Stack, Stepper, Switch, Table, Tabs, Text, TextArea, TextInput, Timeline, Tooltip, Tree, User, View, VirtualList, WebView, addBackInterceptor, addRoutes, applyThemeClasses, canGoBack, closeSheet, createStore, currentModalRoute, currentRoute, defineRoutes, deriveRouteManifest, getColorScheme, getStack, getThemePreference, getThemeScheme, hrefFor, isNative, layoutsForRoute, manifestToJson, mergeRouteManifests, onThemeSchemeChange, openSheet, openWindow, popRoute, pushDeepLink, pushRoute, redirect, registerIcon, registerIcons, registerRoutes, registerScreens, registerStack, routeFor, routeStacks, screenFor, setThemePreference, setTranslate, showToast, stackEntries, styled, themeSchemeClasses, useAnimation, useBackInterceptor, useCanGoBack, useColorScheme, useMeasure, useModalRoute, useRoute, useSafeAreaInsets, useStore, useThemeScheme };
@@ -1,4 +1,4 @@
1
- import { Application } from "@nativescript/core";
1
+ import { Application, Utils } from "@nativescript/core";
2
2
  //#region src/keyboard-inset.mobile.ts
3
3
  var bound = /* @__PURE__ */ new Set();
4
4
  var inset = 0;
@@ -39,19 +39,19 @@ function bindBottomInsetToKeyboard(host) {
39
39
  };
40
40
  }
41
41
  if (Application.android) {
42
- const nativeWindow = Application.android.foregroundActivity?.window;
42
+ const nativeWindow = Application.android.foregroundActivity?.getWindow();
43
43
  if (!nativeWindow) return () => {};
44
44
  const params = globalThis.android?.view?.WindowManager?.LayoutParams;
45
45
  if (!params) return () => {};
46
- if ((nativeWindow.attributes.softInputMode & params.SOFT_INPUT_MASK_ADJUST) === params.SOFT_INPUT_ADJUST_RESIZE) return () => {};
46
+ if ((nativeWindow.getAttributes().softInputMode & params.SOFT_INPUT_MASK_ADJUST) === params.SOFT_INPUT_ADJUST_RESIZE) return () => {};
47
47
  const ViewCompat = globalThis.androidx?.core?.view?.ViewCompat;
48
48
  const WindowInsetsCompat = globalThis.androidx?.core?.view?.WindowInsetsCompat;
49
49
  if (!ViewCompat || !WindowInsetsCompat) return () => {};
50
- const decorView = nativeWindow.decorView;
50
+ const decorView = nativeWindow.getDecorView();
51
51
  const listener = new ViewCompat.OnApplyWindowInsetsListener({ onApplyWindowInsets: (v, insets) => {
52
52
  const ime = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
53
53
  const nav = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
54
- host.translateY = -Math.max(0, ime - nav);
54
+ host.translateY = -Utils.layout.toDeviceIndependentPixels(Math.max(0, ime - nav));
55
55
  return insets;
56
56
  } });
57
57
  ViewCompat.setOnApplyWindowInsetsListener(decorView, listener);
@@ -0,0 +1,26 @@
1
+ //#region src/navigation-request.ts
2
+ function createNavigationRequests() {
3
+ const versions = /* @__PURE__ */ new Map();
4
+ const invalidate = (stack) => {
5
+ const version = (versions.get(stack) ?? 0) + 1;
6
+ versions.set(stack, version);
7
+ return version;
8
+ };
9
+ return {
10
+ invalidate,
11
+ begin(stack) {
12
+ const owned = /* @__PURE__ */ new Map([[stack, invalidate(stack)]]);
13
+ const isCurrent = () => [...owned].every(([name, version]) => versions.get(name) === version);
14
+ return {
15
+ isCurrent,
16
+ claim(name) {
17
+ if (!isCurrent()) return false;
18
+ if (!owned.has(name)) owned.set(name, invalidate(name));
19
+ return true;
20
+ }
21
+ };
22
+ }
23
+ };
24
+ }
25
+ //#endregion
26
+ export { createNavigationRequests };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "buildId": "397eabd3-87de-49d4-adf0-133ab9697510",
3
+ "buildId": "346919ac-5da4-4078-bfa3-89cdb83c992f",
4
4
  "mode": "production",
5
5
  "capabilities": {
6
6
  "independentHydration": false
@@ -0,0 +1,65 @@
1
+ //#region src/overlay-lifetime.mobile.ts
2
+ /** Own the resources of one independently mounted overlay. RootLayout emits
3
+ * closed before removing the host, so that notification must never reenter close. */
4
+ function createOverlayLifetime(owner, host, root, label, onFinish) {
5
+ const registry = owner;
6
+ let released = false;
7
+ let closing = false;
8
+ const cleanups = /* @__PURE__ */ new Set();
9
+ const report = (action, error) => console.error(`[${label}] ${action} failed`, error);
10
+ const release = (dismissed) => {
11
+ if (released) return;
12
+ released = true;
13
+ host.off("closed", onClosed);
14
+ for (const cleanup of cleanups) cleanup();
15
+ cleanups.clear();
16
+ root.unmount();
17
+ onFinish(dismissed);
18
+ };
19
+ const onClosed = () => {
20
+ closing = true;
21
+ release(true);
22
+ };
23
+ const detach = () => {
24
+ if (closing || !registry.hasChild(host)) return;
25
+ if (registry.getPopupIndex(host) === -1) return;
26
+ closing = true;
27
+ const failed = (error) => {
28
+ report("close", error);
29
+ if (registry.hasChild(host)) owner.removeChild(host);
30
+ };
31
+ try {
32
+ owner.close(host).catch(failed);
33
+ } catch (error) {
34
+ failed(error);
35
+ }
36
+ };
37
+ host.on("closed", onClosed);
38
+ return {
39
+ own(cleanup) {
40
+ if (released) cleanup();
41
+ else cleanups.add(cleanup);
42
+ },
43
+ close() {
44
+ release(false);
45
+ detach();
46
+ },
47
+ open(options, afterOpen) {
48
+ const failed = (error) => {
49
+ report("open", error);
50
+ release(false);
51
+ detach();
52
+ };
53
+ try {
54
+ owner.open(host, options).then(() => {
55
+ if (released) detach();
56
+ else return afterOpen?.();
57
+ }).catch(failed);
58
+ } catch (error) {
59
+ failed(error);
60
+ }
61
+ }
62
+ };
63
+ }
64
+ //#endregion
65
+ export { createOverlayLifetime };
@@ -11,6 +11,14 @@ var PAN_STATE = [
11
11
  "ended"
12
12
  ];
13
13
  function panVelocity(e, state, trackerRef) {
14
+ if (state === 0) {
15
+ trackerRef.current?.recycle?.();
16
+ trackerRef.current = null;
17
+ return {
18
+ vx: 0,
19
+ vy: 0
20
+ };
21
+ }
14
22
  const recognizer = e.ios;
15
23
  const nativeView = e.view?.nativeViewProtected;
16
24
  if (recognizer?.velocityInView && nativeView) {
@@ -44,11 +52,17 @@ function panVelocity(e, state, trackerRef) {
44
52
  }
45
53
  return velocity;
46
54
  }
55
+ function nativePanState(e) {
56
+ const motionEvent = e.android?.current;
57
+ if (!motionEvent) return e.state;
58
+ return motionEvent.getActionMasked?.() === (globalThis.android?.view?.MotionEvent?.ACTION_CANCEL ?? 3) ? 0 : e.state;
59
+ }
47
60
  function usePan(cb) {
48
61
  const velocityTracker = useRef(null, _h$0);
49
62
  const handler = useCallback((e) => {
50
- const state = PAN_STATE[e.state] ?? "moved";
51
- const velocity = panVelocity(e, e.state, velocityTracker);
63
+ const nativeState = nativePanState(e);
64
+ const state = PAN_STATE[nativeState] ?? "moved";
65
+ const velocity = panVelocity(e, nativeState, velocityTracker);
52
66
  cb?.({
53
67
  x: e.deltaX,
54
68
  y: e.deltaY,
@@ -2,7 +2,22 @@
2
2
  var EXT = /\.(tsrx|tsx|ts|mts|cts|js|mjs|cjs|jsx)$/;
3
3
  var SUFFIX = /\.(web|mobile|ios|android|macos|windows|linux)$/;
4
4
  var PARAM = /^\[(.+)\]$/;
5
+ /** Build-time loader modules — `docs.loader.ts` pairs with route `docs`
6
+ * for `dataMode: 'baked'`. Never a route itself, never in a runtime glob. */
7
+ var LOADER_FILE = /\.loader\.(ts|mts|cts|js|mjs|cjs|jsx)$/;
5
8
  var PRESENT = /\+(modal|fade|push)$/;
9
+ /** Loader for a `dataMode: 'baked'` route — resolves the name out of its
10
+ * owning manifest's `baked` map at push time. The map is assigned after
11
+ * the manifest is built (generated glue) or passed in (RouteSpecSet), so
12
+ * the lookup reads it lazily. A missing entry throws, landing as the
13
+ * screen's `error` prop like a live loader rejection. */
14
+ function bakedLookup(manifest, name) {
15
+ return () => {
16
+ const baked = manifest()?.baked;
17
+ if (baked && Object.prototype.hasOwnProperty.call(baked, name)) return baked[name];
18
+ throw new Error(`[octane-xplat] baked route '${name}' has no baked data — run \`xplat routes\` to regenerate`);
19
+ };
20
+ }
6
21
  /** Internal error carrier used by the public `redirect()` helper. Keeping
7
22
  * this in the shared route table gives both platform leaves the same guard
8
23
  * behavior without importing a platform runtime. */
@@ -35,7 +50,9 @@ function deriveRouteManifest(files, prefer, dir = "app") {
35
50
  const layouts = {};
36
51
  const loaders = {};
37
52
  const warned = /* @__PURE__ */ new Set();
53
+ const self = {};
38
54
  for (const key of Object.keys(files).sort()) {
55
+ if (LOADER_FILE.test(key)) continue;
39
56
  let rel = key.replace(/^\.?\//, "");
40
57
  if (rel.startsWith(prefix)) rel = rel.slice(prefix.length);
41
58
  rel = rel.replace(EXT, "");
@@ -74,8 +91,13 @@ function deriveRouteManifest(files, prefer, dir = "app") {
74
91
  file: key,
75
92
  presentation
76
93
  };
94
+ const dataMode = files[key]?.dataMode;
95
+ if (dataMode === "baked" || dataMode === "live") meta.dataMode = dataMode;
77
96
  const loader = files[key]?.loader;
78
- if (typeof loader === "function") meta.loader = loader;
97
+ if (meta.dataMode === "baked") {
98
+ meta.loader = bakedLookup(() => self.m, name);
99
+ if (typeof loader === "function") console.warn(`[octane-xplat] baked route '${name}' also exports a loader — move it to a .loader.ts sibling so its imports stay out of the bundle`);
100
+ } else if (typeof loader === "function") meta.loader = loader;
79
101
  const beforeLoad = files[key]?.beforeLoad;
80
102
  if (typeof beforeLoad === "function") meta.beforeLoad = beforeLoad;
81
103
  if (files[key]?.head && (typeof files[key].head === "object" || typeof files[key].head === "function")) meta.head = files[key].head;
@@ -102,12 +124,14 @@ function deriveRouteManifest(files, prefer, dir = "app") {
102
124
  if (meta.loader) loaders[meta.name] = meta.loader;
103
125
  }
104
126
  routes.sort(bySpecificity);
105
- return {
127
+ const manifest = {
106
128
  screens,
107
129
  routes,
108
130
  layouts,
109
131
  loaders
110
132
  };
133
+ self.m = manifest;
134
+ return manifest;
111
135
  }
112
136
  /** Match-order comparator: static segments (2) outrank params (1), ties
113
137
  * break alphabetically so ordering is total and stable across merges. */
@@ -131,7 +155,12 @@ var mergeWarned = /* @__PURE__ */ new Set();
131
155
  * from their own sources, e.g. `readdirSync` over a content dir). Each
132
156
  * spec's `path` uses the route-dir vocabulary; `layouts` keys are path
133
157
  * prefixes playing the `_layout` role. Same-name duplicates inside the
134
- * set keep the later entry with a warn, matching merge precedence. */
158
+ * set keep the later entry with a warn, matching merge precedence.
159
+ *
160
+ * The return is branded with the spec-derived route types — read them
161
+ * with `ManifestRouteParams`/`ManifestRouteNames`/
162
+ * `ManifestRoutePresentations` to merge programmatic names into the
163
+ * app's typed navigation surface (`RouteName | …`, `RouteParams & …`). */
135
164
  function defineRoutes(input) {
136
165
  const set = Array.isArray(input) ? { routes: input } : input;
137
166
  const specs = set.routes;
@@ -140,6 +169,7 @@ function defineRoutes(input) {
140
169
  const layouts = {};
141
170
  const metas = /* @__PURE__ */ new Map();
142
171
  const warned = /* @__PURE__ */ new Set();
172
+ const self = {};
143
173
  for (const [key, layout] of Object.entries(extra ?? {})) {
144
174
  const dir = key.replace(/^\/+|\/+$/g, "");
145
175
  if (dir !== key) console.warn(`[octane-xplat] layout key '${key}' normalized to '${dir}'`);
@@ -155,7 +185,11 @@ function defineRoutes(input) {
155
185
  file: spec.source ?? `programmatic:${spec.path}`,
156
186
  presentation: spec.presentation
157
187
  };
158
- if (spec.loader) meta.loader = spec.loader;
188
+ if (spec.dataMode) meta.dataMode = spec.dataMode;
189
+ if (spec.dataMode === "baked") {
190
+ meta.loader = bakedLookup(() => self.m, name);
191
+ if (spec.loader) console.warn(`[octane-xplat] baked route '${name}' also carries a loader — bake it in the host and pass the result via 'baked' so its code stays out of the bundle`);
192
+ } else if (spec.loader) meta.loader = spec.loader;
159
193
  if (spec.beforeLoad) meta.beforeLoad = spec.beforeLoad;
160
194
  if (spec.head) meta.head = spec.head;
161
195
  if (metas.has(name) && !warned.has(name)) {
@@ -172,12 +206,15 @@ function defineRoutes(input) {
172
206
  const routes = [...metas.values()].sort(bySpecificity);
173
207
  const loaders = {};
174
208
  for (const meta of routes) if (meta.loader) loaders[meta.name] = meta.loader;
175
- return {
209
+ const manifest = {
176
210
  screens,
177
211
  routes,
178
212
  layouts,
179
- loaders
213
+ loaders,
214
+ baked: set.baked
180
215
  };
216
+ self.m = manifest;
217
+ return manifest;
181
218
  }
182
219
  /** Compose manifests into one — file-derived base plus any number of
183
220
  * programmatic sets (`registerRoutes(mergeRouteManifests(fromDir, dynamic))`).
@@ -188,9 +225,11 @@ function defineRoutes(input) {
188
225
  function mergeRouteManifests(...manifests) {
189
226
  const screens = {};
190
227
  const layouts = {};
228
+ const baked = {};
191
229
  const winners = /* @__PURE__ */ new Map();
192
230
  for (const m of manifests) {
193
231
  Object.assign(layouts, m.layouts);
232
+ Object.assign(baked, m.baked);
194
233
  Object.assign(screens, m.screens);
195
234
  for (const meta of m.routes) {
196
235
  const prev = winners.get(meta.name);
@@ -208,7 +247,38 @@ function mergeRouteManifests(...manifests) {
208
247
  screens,
209
248
  routes,
210
249
  layouts,
211
- loaders
250
+ loaders,
251
+ baked
252
+ };
253
+ }
254
+ /** Serialize a manifest to the normalized `RouteManifestJson` shape — the
255
+ * same list `xplat routes` emits for file routes, produced here for
256
+ * programmatic/merged manifests so hosts consume one schema. */
257
+ function manifestToJson(manifest) {
258
+ return {
259
+ version: 1,
260
+ layouts: Object.keys(manifest.layouts).sort(),
261
+ screens: Object.keys(manifest.screens).sort(),
262
+ routes: manifest.routes.map((meta) => {
263
+ const segs = meta.name.split("/");
264
+ const layoutDirs = manifest.layouts[""] ? [""] : [];
265
+ for (let i = 1; i < segs.length; i++) {
266
+ const d = segs.slice(0, i).join("/");
267
+ if (manifest.layouts[d]) layoutDirs.push(d);
268
+ }
269
+ return {
270
+ name: meta.name,
271
+ path: meta.name === "index" ? "" : meta.segments.join("/"),
272
+ params: [...meta.params],
273
+ ...meta.presentation ? { presentation: meta.presentation } : {},
274
+ ...meta.dataMode ? { dataMode: meta.dataMode } : {},
275
+ layouts: layoutDirs,
276
+ source: meta.file,
277
+ loader: !!meta.loader,
278
+ guard: !!meta.beforeLoad,
279
+ head: !!meta.head
280
+ };
281
+ })
212
282
  };
213
283
  }
214
284
  /** Match URL path segments against a manifest — returns the winning meta
@@ -309,9 +379,19 @@ function linkPath(url) {
309
379
  * is the stack prefix ('/demos/demo/x' → stack 'demos'). Unmatched names
310
380
  * fall through as literal routes for pre-manifest callers. */
311
381
  function matchUrl(routes, url) {
312
- const [p, qs] = url.split("?");
382
+ url = url.split("#", 1)[0];
383
+ const at = url.indexOf("?");
384
+ const p = at === -1 ? url : url.slice(0, at);
385
+ const qs = at === -1 ? void 0 : url.slice(at + 1);
313
386
  const segs = p.split("/").filter(Boolean);
314
- const query = parseQueryString(qs);
387
+ let query;
388
+ try {
389
+ if (!decodeURIComponent(p)) return null;
390
+ query = parseQueryString(qs);
391
+ } catch (error) {
392
+ if (error instanceof URIError) return null;
393
+ throw error;
394
+ }
315
395
  if (!segs.length) return null;
316
396
  const finish = (stack, m, fallbackName) => ({
317
397
  stack,
@@ -347,4 +427,4 @@ function layoutChain(layouts, name) {
347
427
  return chain;
348
428
  }
349
429
  //#endregion
350
- export { RouteRedirect, buildRoutePath, defineRoutes, deriveRouteManifest, layoutChain, linkPath, matchRoute, matchUrl, mergeRouteManifests, parseQueryString };
430
+ export { RouteRedirect, buildRoutePath, defineRoutes, deriveRouteManifest, layoutChain, linkPath, manifestToJson, matchRoute, matchUrl, mergeRouteManifests, parseQueryString };
@@ -1,3 +1,4 @@
1
+ import { createNavigationRequests } from "./navigation-request.js";
1
2
  import { getStack, onStackRegistered, stackEntries } from "./stacks.js";
2
3
  import { RouteRedirect, buildRoutePath, layoutChain, linkPath, matchUrl, mergeRouteManifests } from "./route-table.js";
3
4
  import { RouteHost } from "./RouteHost.mobile.tsrx.js";
@@ -23,6 +24,7 @@ import { hookSlots as hookSlots$1, useSyncExternalStore as useSyncExternalStore$
23
24
  * - named stacks register via `TabSpec.stack` or `registerStack`.
24
25
  * Every drop path warns loudly — silent no-ops are how apps ship a
25
26
  * default route on native while working fine on web. */
27
+ var navigationRequests = createNavigationRequests();
26
28
  var baseManifest = {
27
29
  screens: {},
28
30
  routes: [],
@@ -95,10 +97,11 @@ function warnOnce(key, msg) {
95
97
  /** Attach the route-store emission hook once per frame. `navigatedTo`
96
98
  * fires on push AND on pop (the revealed page re-fires it), so one
97
99
  * listener keeps every useRoute snapshot honest. */
98
- function trackFrame(frame) {
100
+ function trackFrame(frame, stack) {
99
101
  if (tracked.has(frame)) return;
100
102
  tracked.add(frame);
101
- frame.on("navigatedTo", () => {
103
+ frame.on("navigatedTo", (event) => {
104
+ if (event?.isBackNavigation) navigationRequests.invalidate(stack);
102
105
  const page = frame.currentPage;
103
106
  if (page && page.isLoaded === false) page.callLoaded?.();
104
107
  emit();
@@ -106,7 +109,7 @@ function trackFrame(frame) {
106
109
  }
107
110
  function resolveStack(stack) {
108
111
  const frame = getStack(stack);
109
- if (frame) trackFrame(frame);
112
+ if (frame) trackFrame(frame, stack);
110
113
  return frame;
111
114
  }
112
115
  /** TabViewItem-hosted frames report isLoaded=false after tab-selection
@@ -117,8 +120,8 @@ function resolveStack(stack) {
117
120
  function rearmFrameLoaded(frame) {
118
121
  if (!frame.isLoaded) frame.callLoaded?.();
119
122
  }
120
- onStackRegistered((_name, frame) => {
121
- trackFrame(frame);
123
+ onStackRegistered((name, frame) => {
124
+ trackFrame(frame, name);
122
125
  ensureBackWired();
123
126
  emit();
124
127
  });
@@ -209,10 +212,12 @@ function headFor(r) {
209
212
  function contextFor(r) {
210
213
  return r.context ?? {};
211
214
  }
212
- async function prepareRoute(r, redirects = 0) {
215
+ async function prepareRoute(r, request, redirects = 0) {
216
+ if (!request.isCurrent()) return;
217
+ if (!request.claim(r.stack)) return;
213
218
  const beforeLoad = metaFor(r.name)?.beforeLoad;
214
219
  if (!beforeLoad) {
215
- commitRoute(r);
220
+ commitRoute(r, request);
216
221
  return;
217
222
  }
218
223
  if (redirects > 16) {
@@ -231,10 +236,11 @@ async function prepareRoute(r, redirects = 0) {
231
236
  commitRoute({
232
237
  ...r,
233
238
  context
234
- });
239
+ }, request);
235
240
  } catch (e) {
241
+ if (!request.isCurrent()) return;
236
242
  if (e instanceof RouteRedirect) {
237
- await prepareRoute(e.route, redirects + 1);
243
+ await prepareRoute(e.route, request, redirects + 1);
238
244
  return;
239
245
  }
240
246
  console.warn(`[octane-xplat] beforeLoad('${r.name}') rejected: ${e?.message ?? e}`);
@@ -244,13 +250,15 @@ function redirect(r) {
244
250
  throw new RouteRedirect(r);
245
251
  }
246
252
  function pushRoute(r) {
253
+ const request = navigationRequests.begin(r.stack);
247
254
  if (metaFor(r.name)?.beforeLoad) {
248
- prepareRoute(r);
255
+ prepareRoute(r, request);
249
256
  return;
250
257
  }
251
- commitRoute(r);
258
+ commitRoute(r, request);
252
259
  }
253
- function commitRoute(r) {
260
+ function commitRoute(r, request) {
261
+ if (!request.isCurrent()) return;
254
262
  const C = screenFor(r.name);
255
263
  if (!C) {
256
264
  warnOnce("screen:" + r.name, `pushRoute('${r.name}') dropped — '${r.name}' isn't in the screen table. Call registerScreens({ ${r.name}: ... }) at boot.`);
@@ -262,10 +270,10 @@ function commitRoute(r) {
262
270
  Promise.resolve().then(() => loader(r.params)).then((loaderData) => commitRoute({
263
271
  ...r,
264
272
  loaderData
265
- }), (loaderError) => commitRoute({
273
+ }, request), (loaderError) => commitRoute({
266
274
  ...r,
267
275
  loaderError
268
- }));
276
+ }, request));
269
277
  return;
270
278
  }
271
279
  if (presentation === "modal") {
@@ -347,6 +355,7 @@ function pushModal(frame, r, C) {
347
355
  const i = modalHosts.indexOf(entry);
348
356
  if (i === -1) return;
349
357
  modalHosts.splice(i, 1);
358
+ root.unmount?.();
350
359
  emit();
351
360
  };
352
361
  const presenter = modalPresenter(frame);
@@ -380,7 +389,6 @@ function pushModal(frame, r, C) {
380
389
  emit();
381
390
  } catch (e) {
382
391
  entry.dismiss();
383
- root.unmount?.();
384
392
  console.warn("[octane-xplat] modal pushRoute(" + r.name + ") threw: " + e?.message);
385
393
  }
386
394
  }
@@ -389,8 +397,10 @@ function pushModal(frame, r, C) {
389
397
  * where back at the app root is a no-op; loud only when the stack
390
398
  * itself doesn't exist. */
391
399
  function popRoute(stack = "root") {
400
+ navigationRequests.invalidate(stack);
392
401
  const modal = modalHosts[modalHosts.length - 1];
393
402
  if (modal) {
403
+ navigationRequests.invalidate(modal.route.stack);
394
404
  modal.dismiss();
395
405
  modal.host.closeModal?.();
396
406
  return;
@@ -456,7 +466,7 @@ function currentModalRoute() {
456
466
  * `onDeepLink(pushDeepLink)` plus one `consumeInitialUrl()` call. */
457
467
  function pushDeepLink(url) {
458
468
  const r = matchUrl(routes, linkPath(url));
459
- if (!r) {
469
+ if (!r || !screenFor(r.name)) {
460
470
  warnOnce("link:" + url, `pushDeepLink('${url}') dropped — no route matches.`);
461
471
  return false;
462
472
  }