@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.
- package/README.md +4 -3
- package/dist/native/Button.tsrx.js +1 -1
- package/dist/native/CommandPalette.tsrx.js +7 -1
- package/dist/native/Grid.tsrx.js +31 -2
- package/dist/native/KeyboardAvoiding.mobile.tsrx.js +2 -2
- package/dist/native/Markdown.tsrx.js +233 -0
- package/dist/native/Overlay.tsrx.js +15 -26
- package/dist/native/Pagination.tsrx.js +15 -12
- package/dist/native/PinInput.tsrx.js +25 -7
- package/dist/native/Popover.tsrx.js +33 -23
- package/dist/native/Pressable.tsrx.js +4 -1
- package/dist/native/Screen.tsrx.js +1 -1
- package/dist/native/SearchInput.tsrx.js +6 -3
- package/dist/native/Sheet.tsrx.js +23 -38
- package/dist/native/Slider.tsrx.js +23 -14
- package/dist/native/Text.tsrx.js +16 -0
- package/dist/native/TextArea.tsrx.js +6 -3
- package/dist/native/TextInput.tsrx.js +6 -3
- package/dist/native/VirtualList.tsrx.js +305 -121
- package/dist/native/WebView.tsrx.js +17 -2
- package/dist/native/_virtual/_@oxc-project_runtime@0.150.0/helpers/esm/decorate.js +9 -0
- package/dist/native/escape-props.js +2 -0
- package/dist/native/grid-auto-placement.js +83 -0
- package/dist/native/grid-placement.js +20 -0
- package/dist/native/index.shared.js +7 -5
- package/dist/native/keyboard-inset.mobile.js +5 -5
- package/dist/native/navigation-request.js +26 -0
- package/dist/native/octane-client-build.json +1 -1
- package/dist/native/overlay-lifetime.mobile.js +65 -0
- package/dist/native/pan.tsrx.js +16 -2
- package/dist/native/route-table.js +90 -10
- package/dist/native/route.js +26 -16
- package/dist/native/shared.js +7 -5
- package/dist/native/sheet-service.js +2 -2
- package/dist/native/svg.mobile.js +1 -1
- package/dist/native/{tap-to-blur.mobile.js → tap-to-blur.ios.js} +20 -32
- package/dist/native/text-focus.js +10 -0
- package/dist/native/text-write.js +18 -11
- package/dist/native/toast-anchor.tsrx.js +1 -1
- package/dist/native/ui.js +7 -6
- package/dist/native/useMeasure.tsrx.js +17 -18
- package/dist/native/vendor/ui-svg/index.android.js +2 -2
- package/dist/native/vendor/ui-svg/index.ios.js +2 -2
- package/dist/native/vendor/ui-svg/{index.common.js → src/index.common.js} +1 -1
- package/dist/native/virtual-list-cell-pool.js +63 -0
- package/dist/web/Button.web.tsrx.js +1 -1
- package/dist/web/KeyboardAvoiding.web.tsrx.js +78 -0
- package/dist/web/Markdown.tsrx.js +98 -0
- package/dist/web/Overlay.web.tsrx.js +81 -32
- package/dist/web/PinInput.web.tsrx.js +24 -10
- package/dist/web/Pressable.web.tsrx.js +169 -53
- package/dist/web/SearchInput.web.tsrx.js +2 -2
- package/dist/web/Sheet.web.tsrx.js +20 -7
- package/dist/web/TextArea.web.tsrx.js +22 -17
- package/dist/web/TextInput.web.tsrx.js +2 -2
- package/dist/web/VirtualList.web.tsrx.js +220 -109
- package/dist/web/WebView.web.tsrx.js +3 -3
- package/dist/web/{index.shared.js → index.shared.web.js} +7 -5
- package/dist/web/modal-focus.web.js +95 -0
- package/dist/web/navigation-request.js +26 -0
- package/dist/web/octane-client-build.json +1 -1
- package/dist/web/pan.web.tsrx.js +27 -10
- package/dist/web/route-table.js +90 -10
- package/dist/web/route.web.js +88 -22
- package/dist/web/sheet-service.web.js +8 -0
- package/dist/web/theme/colorScheme.web.js +31 -4
- package/dist/web/ui.js +8 -6
- package/dist/web/virtual-list-cell-pool.js +63 -0
- package/package.json +21 -19
- package/platforms/android/native-api-usage.json +7 -7
- package/src/CommandPalette.tsrx +8 -1
- package/src/Grid.tsrx +32 -1
- package/src/Icon.macos.tsrx +1 -0
- package/src/KeyboardAvoiding.macos.tsrx +7 -1
- package/src/KeyboardAvoiding.mobile.tsrx +2 -2
- package/src/KeyboardAvoiding.tsrx +15 -0
- package/src/KeyboardAvoiding.web.tsrx +15 -0
- package/src/Markdown.tsrx +121 -0
- package/src/Meter.tsrx +1 -0
- package/src/Overlay.tsrx +15 -30
- package/src/Overlay.web.tsrx +12 -3
- package/src/Pagination.tsrx +6 -4
- package/src/PinInput.macos.tsrx +20 -6
- package/src/PinInput.mobile.test.ts +107 -0
- package/src/PinInput.tsrx +20 -6
- package/src/PinInput.web.test.tsrx +42 -0
- package/src/PinInput.web.tsrx +20 -6
- package/src/Popover.tsrx +36 -22
- package/src/Pressable.tsrx +1 -1
- package/src/Pressable.web.test.tsrx +36 -0
- package/src/Pressable.web.tsrx +37 -6
- package/src/RouteHost.macos.tsrx +1 -0
- package/src/Screen.tsrx +1 -1
- package/src/SearchInput.tsrx +4 -3
- package/src/SearchInput.web.tsrx +1 -1
- package/src/Sheet.tsrx +21 -42
- package/src/Sheet.web.tsrx +9 -1
- package/src/Slider.tsrx +13 -11
- package/src/Text.tsrx +36 -0
- package/src/TextArea.tsrx +6 -5
- package/src/TextArea.web.test.tsrx +39 -0
- package/src/TextArea.web.tsrx +7 -5
- package/src/TextInput.tsrx +6 -6
- package/src/TextInput.web.tsrx +1 -1
- package/src/VirtualList.macos.tsrx +93 -6
- package/src/VirtualList.mobile.test.tsrx +232 -0
- package/src/VirtualList.tsrx +285 -81
- package/src/VirtualList.web.tsrx +131 -25
- package/src/WebView.tsrx +20 -10
- package/src/WebView.web.tsrx +3 -0
- package/src/android/BottomNavigationView.android.tsrx +1 -0
- package/src/android/MaterialDialog.android.tsrx +1 -0
- package/src/anim.macos.tsrx +1 -0
- package/src/apply-native-modifiers.native.ts +4 -1
- package/src/child-array.ts +12 -7
- package/src/colorScheme.macos.ts +1 -0
- package/src/controls.macos.tsrx +4 -1
- package/src/data-driver.mobile.test.ts +120 -0
- package/src/data-lifecycle.mobile.test.ts +251 -0
- package/src/escape-props.ts +13 -1
- package/src/grid-auto-placement.ts +163 -0
- package/src/grid-placement.ts +29 -0
- package/src/hosted-unsupported.macos.tsrx +1 -0
- package/src/index.macos.ts +23 -3
- package/src/index.mobile.ts +0 -1
- package/src/index.shared.ts +22 -1
- package/src/index.shared.web.ts +291 -0
- package/src/index.web.ts +1 -1
- package/src/input-readiness.web.test.tsrx +82 -0
- package/src/ios/UITabBar.ios.tsrx +1 -0
- package/src/keyboard-inset.mobile.test.ts +62 -0
- package/src/keyboard-inset.mobile.ts +63 -43
- package/src/layout-shells.macos.tsrx +1 -5
- package/src/macos-extras.macos.tsrx +2 -0
- package/src/modal-focus.web.test.tsrx +63 -0
- package/src/modal-focus.web.ts +162 -0
- package/src/navigation-request.ts +37 -0
- package/src/overlay-lifecycle.mobile.test.ts +316 -0
- package/src/overlay-lifetime.mobile.ts +107 -0
- package/src/pan.mobile.test.tsrx +116 -0
- package/src/pan.tsrx +20 -2
- package/src/pan.web.tsrx +35 -7
- package/src/platformProps.ts +1 -0
- package/src/popover.mobile.test.ts +9 -1
- package/src/pressable-accessibility.mobile.test.ts +30 -0
- package/src/props.ts +163 -3
- package/src/refresh.ts +6 -1
- package/src/refresh.web.test.ts +77 -77
- package/src/route-android.test.ts +162 -0
- package/src/route-history.web.test.ts +80 -0
- package/src/route-navigation.web.test.ts +173 -0
- package/src/route-table.test.ts +188 -9
- package/src/route-table.ts +138 -10
- package/src/route.macos.ts +56 -13
- package/src/route.ts +54 -24
- package/src/route.web.ts +141 -24
- package/src/sheet-detents.ts +5 -3
- package/src/sheet-service.macos.ts +5 -1
- package/src/sheet-service.ts +1 -1
- package/src/sheet-service.web.ts +8 -0
- package/src/sheet-snap.test.ts +23 -23
- package/src/sheet-snap.ts +6 -1
- package/src/svg.mobile.ts +9 -3
- package/src/tap-to-blur.ios.ts +106 -0
- package/src/tap-to-blur.mobile.ts +21 -95
- package/src/text-focus.ts +12 -0
- package/src/text-input.mobile.test.ts +89 -0
- package/src/text-write.ts +21 -11
- package/src/theme/colorScheme.linux.ts +2 -4
- package/src/theme/colorScheme.web.ts +51 -4
- package/src/theme/theme-scheme.macos.ts +8 -2
- package/src/theme/tokens.css +1740 -1718
- package/src/translate.macos.ts +4 -1
- package/src/useMeasure.tsrx +20 -21
- package/src/virtual-list-cell-pool.test.ts +53 -0
- package/src/virtual-list-cell-pool.ts +81 -0
- package/src/virtual-list-layout.test.ts +11 -8
- package/src/virtual-list-layout.ts +4 -8
- package/src/windows.linux.ts +7 -2
- package/src/windows.macos.ts +1 -0
- package/types/generated/.tsrx-typegen-manifest.json +16 -3
- package/types/generated/Grid.d.ts +0 -1
- package/types/generated/KeyboardAvoiding.d.ts +3 -0
- package/types/generated/KeyboardAvoiding.web.d.ts +2 -0
- package/types/generated/Markdown.d.ts +59 -0
- package/types/generated/TextArea.d.ts +2 -2
- package/types/generated/TextArea.web.d.ts +1 -1
- package/types/generated/TextInput.d.ts +2 -3
- package/types/generated/VirtualList.d.ts +3 -3
- package/types/generated/escape-props.d.ts +4 -0
- package/types/generated/grid-auto-placement.d.ts +13 -0
- package/types/generated/grid-placement.d.ts +5 -0
- package/types/generated/index.mobile.d.ts +0 -1
- package/types/generated/index.shared.d.ts +6 -3
- package/types/generated/index.shared.web.d.ts +114 -0
- package/types/generated/index.web.d.ts +1 -1
- package/types/generated/modal-focus.web.d.ts +3 -0
- package/types/generated/navigation-request.d.ts +10 -0
- package/types/generated/overlay-lifetime.mobile.d.ts +10 -0
- package/types/generated/props.d.ts +111 -3
- package/types/generated/route-table.d.ts +14 -3
- package/types/generated/svg.mobile.d.ts +2 -2
- package/types/generated/tap-to-blur.ios.d.ts +13 -0
- package/types/generated/tap-to-blur.mobile.d.ts +1 -15
- package/types/generated/text-focus.d.ts +3 -0
- package/types/generated/text-write.d.ts +3 -1
- package/types/generated/theme/colorScheme.web.d.ts +5 -0
- package/types/generated/virtual-list-cell-pool.d.ts +18 -0
- package/types/index.macos.d.ts +15 -3
- /package/src/vendor/ui-svg/{index.android.ts → src/index.android.ts} +0 -0
- /package/src/vendor/ui-svg/{index.common.ts → src/index.common.ts} +0 -0
- /package/src/vendor/ui-svg/{index.ios.ts → src/index.ios.ts} +0 -0
- /package/src/vendor/ui-svg/{index.ts → src/index.ts} +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/SVGKit.d.ts +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/android.d.ts +0 -0
- /package/src/vendor/ui-svg/{typings → src/typings}/ios.d.ts +0 -0
- /package/types/generated/vendor/ui-svg/{index.common.d.ts → src/index.common.d.ts} +0 -0
- /package/types/generated/vendor/ui-svg/{index.d.ts → src/index.d.ts} +0 -0
- /package/types/generated/vendor/ui-svg/{index.ios.d.ts → src/index.ios.d.ts} +0 -0
package/src/route.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* Every drop path warns loudly — silent no-ops are how apps ship a
|
|
16
16
|
* default route on native while working fine on web. */
|
|
17
17
|
|
|
18
|
+
import { createNavigationRequests, type NavigationRequest } from './navigation-request'
|
|
18
19
|
import { Application, Frame, GridLayout, Page } from '@nativescript/core'
|
|
19
20
|
import { createNativeScriptRoot } from '@nativescript-community/octane'
|
|
20
21
|
import type { UniversalComponent } from 'octane/universal/native'
|
|
@@ -38,6 +39,8 @@ export type { Route } from './props'
|
|
|
38
39
|
|
|
39
40
|
// ---------- screen registry ----------
|
|
40
41
|
|
|
42
|
+
const navigationRequests = createNavigationRequests()
|
|
43
|
+
|
|
41
44
|
// Effective tables = the file-derived base manifest merged under every
|
|
42
45
|
// addRoutes layer. Keeping the split means an HMR re-run of routes.gen.*
|
|
43
46
|
// (registerRoutes again) can't wipe routes an app added at runtime.
|
|
@@ -120,13 +123,17 @@ function warnOnce(key: string, msg: string): void {
|
|
|
120
123
|
/** Attach the route-store emission hook once per frame. `navigatedTo`
|
|
121
124
|
* fires on push AND on pop (the revealed page re-fires it), so one
|
|
122
125
|
* listener keeps every useRoute snapshot honest. */
|
|
123
|
-
function trackFrame(frame: Frame): void {
|
|
126
|
+
function trackFrame(frame: Frame, stack: string): void {
|
|
124
127
|
if (tracked.has(frame)) {
|
|
125
128
|
return
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
tracked.add(frame)
|
|
129
|
-
frame.on('navigatedTo', () => {
|
|
132
|
+
frame.on('navigatedTo', (event) => {
|
|
133
|
+
if ((event as typeof event & { isBackNavigation?: boolean })?.isBackNavigation) {
|
|
134
|
+
navigationRequests.invalidate(stack)
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
// A page re-shown by pop can stay unloaded when the frame's nav
|
|
131
138
|
// bookkeeping stalls mid-transition (the iOS strand of #11444 — the
|
|
132
139
|
// same hole the isLoaded/callLoaded workaround in commitRoute covers
|
|
@@ -146,7 +153,7 @@ function trackFrame(frame: Frame): void {
|
|
|
146
153
|
function resolveStack(stack: string): Frame | undefined {
|
|
147
154
|
const frame = getStack(stack)
|
|
148
155
|
if (frame) {
|
|
149
|
-
trackFrame(frame)
|
|
156
|
+
trackFrame(frame, stack)
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
return frame
|
|
@@ -163,8 +170,8 @@ function rearmFrameLoaded(frame: Frame): void {
|
|
|
163
170
|
}
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
onStackRegistered((
|
|
167
|
-
trackFrame(frame)
|
|
173
|
+
onStackRegistered((name, frame) => {
|
|
174
|
+
trackFrame(frame, name)
|
|
168
175
|
ensureBackWired()
|
|
169
176
|
emit()
|
|
170
177
|
})
|
|
@@ -273,11 +280,12 @@ function ensureBackWired(): void {
|
|
|
273
280
|
}
|
|
274
281
|
|
|
275
282
|
app.__octaneXplatBackWired = true
|
|
276
|
-
Application.android.on(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
283
|
+
Application.android.on(
|
|
284
|
+
Application.AndroidApplication.activityBackPressedEvent,
|
|
285
|
+
(e: { cancel: boolean }) => {
|
|
286
|
+
app.__octaneXplatBackPress?.(e)
|
|
287
|
+
},
|
|
288
|
+
)
|
|
281
289
|
}
|
|
282
290
|
|
|
283
291
|
function presentationFor(name: string): Route['presentation'] {
|
|
@@ -299,10 +307,18 @@ function contextFor(r: Route): Record<string, unknown> {
|
|
|
299
307
|
return r.context ?? {}
|
|
300
308
|
}
|
|
301
309
|
|
|
302
|
-
async function prepareRoute(r: Route, redirects = 0): Promise<void> {
|
|
310
|
+
async function prepareRoute(r: Route, request: NavigationRequest, redirects = 0): Promise<void> {
|
|
311
|
+
if (!request.isCurrent()) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!request.claim(r.stack)) {
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
|
|
303
319
|
const beforeLoad = metaFor(r.name)?.beforeLoad
|
|
304
320
|
if (!beforeLoad) {
|
|
305
|
-
commitRoute(r)
|
|
321
|
+
commitRoute(r, request)
|
|
306
322
|
return
|
|
307
323
|
}
|
|
308
324
|
|
|
@@ -314,13 +330,20 @@ async function prepareRoute(r: Route, redirects = 0): Promise<void> {
|
|
|
314
330
|
try {
|
|
315
331
|
const returned = await beforeLoad({ params: r.params, context: contextFor(r) })
|
|
316
332
|
const context = returned ? { ...contextFor(r), ...returned } : contextFor(r)
|
|
317
|
-
commitRoute(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
333
|
+
commitRoute(
|
|
334
|
+
{
|
|
335
|
+
...r,
|
|
336
|
+
context,
|
|
337
|
+
},
|
|
338
|
+
request,
|
|
339
|
+
)
|
|
321
340
|
} catch (e) {
|
|
341
|
+
if (!request.isCurrent()) {
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
322
345
|
if (e instanceof RouteRedirect) {
|
|
323
|
-
await prepareRoute(e.route, redirects + 1)
|
|
346
|
+
await prepareRoute(e.route, request, redirects + 1)
|
|
324
347
|
return
|
|
325
348
|
}
|
|
326
349
|
|
|
@@ -333,15 +356,20 @@ export function redirect(r: Route): never {
|
|
|
333
356
|
}
|
|
334
357
|
|
|
335
358
|
export function pushRoute(r: Route): void {
|
|
359
|
+
const request = navigationRequests.begin(r.stack)
|
|
336
360
|
if (metaFor(r.name)?.beforeLoad) {
|
|
337
|
-
void prepareRoute(r)
|
|
361
|
+
void prepareRoute(r, request)
|
|
338
362
|
return
|
|
339
363
|
}
|
|
340
364
|
|
|
341
|
-
commitRoute(r)
|
|
365
|
+
commitRoute(r, request)
|
|
342
366
|
}
|
|
343
367
|
|
|
344
|
-
function commitRoute(r: Route): void {
|
|
368
|
+
function commitRoute(r: Route, request: NavigationRequest): void {
|
|
369
|
+
if (!request.isCurrent()) {
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
345
373
|
const C = screenFor(r.name)
|
|
346
374
|
if (!C) {
|
|
347
375
|
warnOnce(
|
|
@@ -362,8 +390,8 @@ function commitRoute(r: Route): void {
|
|
|
362
390
|
void Promise.resolve()
|
|
363
391
|
.then(() => loader(r.params))
|
|
364
392
|
.then(
|
|
365
|
-
(loaderData) => commitRoute({ ...r, loaderData }),
|
|
366
|
-
(loaderError) => commitRoute({ ...r, loaderError }),
|
|
393
|
+
(loaderData) => commitRoute({ ...r, loaderData }, request),
|
|
394
|
+
(loaderError) => commitRoute({ ...r, loaderError }, request),
|
|
367
395
|
)
|
|
368
396
|
|
|
369
397
|
return
|
|
@@ -484,6 +512,7 @@ function pushModal(frame: Frame, r: Route, C: any): void {
|
|
|
484
512
|
}
|
|
485
513
|
|
|
486
514
|
modalHosts.splice(i, 1)
|
|
515
|
+
root.unmount?.()
|
|
487
516
|
emit()
|
|
488
517
|
}
|
|
489
518
|
|
|
@@ -532,7 +561,6 @@ function pushModal(frame: Frame, r: Route, C: any): void {
|
|
|
532
561
|
emit()
|
|
533
562
|
} catch (e) {
|
|
534
563
|
entry.dismiss()
|
|
535
|
-
root.unmount?.()
|
|
536
564
|
console.warn('[octane-xplat] modal pushRoute(' + r.name + ') threw: ' + (e as Error)?.message)
|
|
537
565
|
}
|
|
538
566
|
}
|
|
@@ -542,8 +570,10 @@ function pushModal(frame: Frame, r: Route, C: any): void {
|
|
|
542
570
|
* where back at the app root is a no-op; loud only when the stack
|
|
543
571
|
* itself doesn't exist. */
|
|
544
572
|
export function popRoute(stack = 'root'): void {
|
|
573
|
+
navigationRequests.invalidate(stack)
|
|
545
574
|
const modal = modalHosts[modalHosts.length - 1]
|
|
546
575
|
if (modal) {
|
|
576
|
+
navigationRequests.invalidate(modal.route.stack)
|
|
547
577
|
// Bookkeeping first: iOS drops dismissViewController completions
|
|
548
578
|
// that race a still-in-flight presentation, so the NS closeCallback
|
|
549
579
|
// isn't guaranteed to run — the route store can't gate on it.
|
|
@@ -639,7 +669,7 @@ export function currentModalRoute(): Route | null {
|
|
|
639
669
|
* `onDeepLink(pushDeepLink)` plus one `consumeInitialUrl()` call. */
|
|
640
670
|
export function pushDeepLink(url: string): boolean {
|
|
641
671
|
const r = matchUrl(routes, linkPath(url))
|
|
642
|
-
if (!r) {
|
|
672
|
+
if (!r || !screenFor(r.name)) {
|
|
643
673
|
warnOnce('link:' + url, `pushDeepLink('${url}') dropped — no route matches.`)
|
|
644
674
|
return false
|
|
645
675
|
}
|
package/src/route.web.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createNavigationRequests, type NavigationRequest } from './navigation-request'
|
|
1
2
|
import { useSyncExternalStore } from 'octane'
|
|
2
3
|
|
|
3
4
|
/** Web route store — the browser half of the nav contract. One linear URL
|
|
@@ -27,6 +28,8 @@ import {
|
|
|
27
28
|
|
|
28
29
|
// ---------- screen registry ----------
|
|
29
30
|
|
|
31
|
+
const navigationRequests = createNavigationRequests()
|
|
32
|
+
|
|
30
33
|
// Effective tables = the file-derived base manifest merged under every
|
|
31
34
|
// addRoutes layer. Keeping the split means an HMR re-run of routes.gen.*
|
|
32
35
|
// (registerRoutes again) can't wipe routes an app added at runtime.
|
|
@@ -46,7 +49,7 @@ function rebuildRegistry(): void {
|
|
|
46
49
|
// Registration can land after the first lazy parse (deep link read
|
|
47
50
|
// before the routes module ran) — re-parse with patterns present.
|
|
48
51
|
if (current !== undefined) {
|
|
49
|
-
|
|
52
|
+
restoreRouteState()
|
|
50
53
|
emit()
|
|
51
54
|
}
|
|
52
55
|
}
|
|
@@ -66,7 +69,8 @@ export function registerScreens(table: ScreenTable, manifest?: RouteMeta[]): voi
|
|
|
66
69
|
export function registerRoutes(manifest: RouteManifest): void {
|
|
67
70
|
baseManifest = manifest
|
|
68
71
|
rebuildRegistry()
|
|
69
|
-
|
|
72
|
+
read()
|
|
73
|
+
applyHead(modalRoute ?? current ?? null)
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/** Layer a programmatic manifest (from `defineRoutes`) over the registered
|
|
@@ -76,7 +80,8 @@ export function registerRoutes(manifest: RouteManifest): void {
|
|
|
76
80
|
export function addRoutes(manifest: RouteManifest): void {
|
|
77
81
|
dynamicManifests.push(manifest)
|
|
78
82
|
rebuildRegistry()
|
|
79
|
-
|
|
83
|
+
read()
|
|
84
|
+
applyHead(modalRoute ?? current ?? null)
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
export function screenFor(name: string): ScreenTable[string] | undefined {
|
|
@@ -100,6 +105,82 @@ let current: Route | null | undefined
|
|
|
100
105
|
let modalRoute: Route | null = null
|
|
101
106
|
let historyDepth = history.state?.__octaneXplatDepth ?? 0
|
|
102
107
|
|
|
108
|
+
// History stores an identity rather than loader results: loaders may return
|
|
109
|
+
// values that structured clone cannot serialize. Retain the prepared route
|
|
110
|
+
// for this document's back/forward traversal; fresh boots still parse the URL.
|
|
111
|
+
const historyRoutes = new Map<string, { current: Route | null; modal: Route | null }>()
|
|
112
|
+
const historyEntryPrefix = Math.random().toString(36).slice(2)
|
|
113
|
+
let nextHistoryEntry = 0
|
|
114
|
+
|
|
115
|
+
function rememberRouteState(entry = historyEntryPrefix + ':' + ++nextHistoryEntry): string {
|
|
116
|
+
historyRoutes.set(entry, { current: current ?? null, modal: modalRoute })
|
|
117
|
+
return entry
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function restoreRouteState(): void {
|
|
121
|
+
const saved = historyRoutes.get(history.state?.__octaneXplatEntry)
|
|
122
|
+
if (saved) {
|
|
123
|
+
current = saved.current
|
|
124
|
+
modalRoute = saved.modal
|
|
125
|
+
} else {
|
|
126
|
+
const route = parse()
|
|
127
|
+
modalRoute = route?.presentation === 'modal' ? route : null
|
|
128
|
+
current = modalRoute ? null : route
|
|
129
|
+
if (route) {
|
|
130
|
+
hydrateRoute(route)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Direct URLs and history entries from a previous document have no prepared
|
|
136
|
+
// loader result. Run their loader without pushing another history entry or
|
|
137
|
+
// changing the documented bootstrap guard boundary.
|
|
138
|
+
function hydrateRoute(route: Route): void {
|
|
139
|
+
const loader = routeLoaders[route.name] ?? metaFor(route.name)?.loader
|
|
140
|
+
if (
|
|
141
|
+
!loader ||
|
|
142
|
+
Object.prototype.hasOwnProperty.call(route, 'loaderData') ||
|
|
143
|
+
Object.prototype.hasOwnProperty.call(route, 'loaderError')
|
|
144
|
+
) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const finish = (result: Pick<Route, 'loaderData' | 'loaderError'>) => {
|
|
149
|
+
const prepared = { ...route, ...result }
|
|
150
|
+
let changed = false
|
|
151
|
+
if (current === route) {
|
|
152
|
+
current = prepared
|
|
153
|
+
changed = true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (modalRoute === route) {
|
|
157
|
+
modalRoute = prepared
|
|
158
|
+
changed = true
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const saved of historyRoutes.values()) {
|
|
162
|
+
if (saved.current === route) {
|
|
163
|
+
saved.current = prepared
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (saved.modal === route) {
|
|
167
|
+
saved.modal = prepared
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (changed) {
|
|
172
|
+
emit()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
void Promise.resolve()
|
|
177
|
+
.then(() => loader(route.params))
|
|
178
|
+
.then(
|
|
179
|
+
(loaderData) => finish({ loaderData }),
|
|
180
|
+
(loaderError) => finish({ loaderError }),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
103
184
|
const ROUTE_HEAD_ATTR = 'data-octane-xplat-route-head'
|
|
104
185
|
|
|
105
186
|
// Scroll positions keyed by URL — restored on popstate (native keeps
|
|
@@ -123,10 +204,10 @@ function parse(): Route | null {
|
|
|
123
204
|
|
|
124
205
|
function read(): Route | null {
|
|
125
206
|
if (current === undefined) {
|
|
126
|
-
|
|
207
|
+
restoreRouteState()
|
|
127
208
|
}
|
|
128
209
|
|
|
129
|
-
return current
|
|
210
|
+
return current ?? null
|
|
130
211
|
}
|
|
131
212
|
|
|
132
213
|
function emit() {
|
|
@@ -171,10 +252,14 @@ function contextFor(r: Route): Record<string, unknown> {
|
|
|
171
252
|
return r.context ?? {}
|
|
172
253
|
}
|
|
173
254
|
|
|
174
|
-
async function prepareRoute(r: Route, redirects = 0): Promise<void> {
|
|
255
|
+
async function prepareRoute(r: Route, request: NavigationRequest, redirects = 0): Promise<void> {
|
|
256
|
+
if (!request.isCurrent()) {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
175
260
|
const beforeLoad = metaFor(r.name)?.beforeLoad
|
|
176
261
|
if (!beforeLoad) {
|
|
177
|
-
commitRoute(r)
|
|
262
|
+
commitRoute(r, request)
|
|
178
263
|
return
|
|
179
264
|
}
|
|
180
265
|
|
|
@@ -186,13 +271,20 @@ async function prepareRoute(r: Route, redirects = 0): Promise<void> {
|
|
|
186
271
|
try {
|
|
187
272
|
const returned = await beforeLoad({ params: r.params, context: contextFor(r) })
|
|
188
273
|
const context = returned ? { ...contextFor(r), ...returned } : contextFor(r)
|
|
189
|
-
commitRoute(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
274
|
+
commitRoute(
|
|
275
|
+
{
|
|
276
|
+
...r,
|
|
277
|
+
context,
|
|
278
|
+
},
|
|
279
|
+
request,
|
|
280
|
+
)
|
|
193
281
|
} catch (e) {
|
|
282
|
+
if (!request.isCurrent()) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
194
286
|
if (e instanceof RouteRedirect) {
|
|
195
|
-
await prepareRoute(e.route, redirects + 1)
|
|
287
|
+
await prepareRoute(e.route, request, redirects + 1)
|
|
196
288
|
return
|
|
197
289
|
}
|
|
198
290
|
|
|
@@ -205,15 +297,20 @@ export function redirect(r: Route): never {
|
|
|
205
297
|
}
|
|
206
298
|
|
|
207
299
|
export function pushRoute(r: Route): void {
|
|
300
|
+
const request = navigationRequests.begin('history')
|
|
208
301
|
if (metaFor(r.name)?.beforeLoad) {
|
|
209
|
-
void prepareRoute(r)
|
|
302
|
+
void prepareRoute(r, request)
|
|
210
303
|
return
|
|
211
304
|
}
|
|
212
305
|
|
|
213
|
-
commitRoute(r)
|
|
306
|
+
commitRoute(r, request)
|
|
214
307
|
}
|
|
215
308
|
|
|
216
|
-
function commitRoute(r: Route): void {
|
|
309
|
+
function commitRoute(r: Route, request: NavigationRequest): void {
|
|
310
|
+
if (!request.isCurrent()) {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
217
314
|
const route: Route = { ...r, presentation: r.presentation ?? presentationFor(r.name) }
|
|
218
315
|
const loader = routeLoaders[route.name] ?? routes.find((meta) => meta.name === route.name)?.loader
|
|
219
316
|
if (
|
|
@@ -224,17 +321,26 @@ function commitRoute(r: Route): void {
|
|
|
224
321
|
void Promise.resolve()
|
|
225
322
|
.then(() => loader(route.params))
|
|
226
323
|
.then(
|
|
227
|
-
(loaderData) => commitRoute({ ...route, loaderData }),
|
|
228
|
-
(loaderError) => commitRoute({ ...route, loaderError }),
|
|
324
|
+
(loaderData) => commitRoute({ ...route, loaderData }, request),
|
|
325
|
+
(loaderError) => commitRoute({ ...route, loaderError }, request),
|
|
229
326
|
)
|
|
230
327
|
|
|
231
328
|
return
|
|
232
329
|
}
|
|
233
330
|
|
|
234
331
|
saveScroll()
|
|
332
|
+
read()
|
|
333
|
+
// Include the entry we are leaving, even if it was the initial URL.
|
|
334
|
+
history.replaceState(
|
|
335
|
+
{
|
|
336
|
+
...history.state,
|
|
337
|
+
__octaneXplatDepth: historyDepth,
|
|
338
|
+
__octaneXplatEntry: rememberRouteState(history.state?.__octaneXplatEntry),
|
|
339
|
+
},
|
|
340
|
+
'',
|
|
341
|
+
)
|
|
342
|
+
|
|
235
343
|
historyDepth += 1
|
|
236
|
-
history.pushState({ __octaneXplatDepth: historyDepth }, '', buildRoutePath(routes, route))
|
|
237
|
-
lastKey = scrollKey()
|
|
238
344
|
if (route.presentation === 'modal') {
|
|
239
345
|
modalRoute = route
|
|
240
346
|
} else {
|
|
@@ -242,6 +348,14 @@ function commitRoute(r: Route): void {
|
|
|
242
348
|
modalRoute = null
|
|
243
349
|
}
|
|
244
350
|
|
|
351
|
+
history.pushState(
|
|
352
|
+
{ __octaneXplatDepth: historyDepth, __octaneXplatEntry: rememberRouteState() },
|
|
353
|
+
'',
|
|
354
|
+
buildRoutePath(routes, route),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
lastKey = scrollKey()
|
|
358
|
+
|
|
245
359
|
applyHead(modalRoute ?? current ?? null)
|
|
246
360
|
emit()
|
|
247
361
|
}
|
|
@@ -254,6 +368,7 @@ function presentationFor(name: string): Route['presentation'] {
|
|
|
254
368
|
* per-stack pops aren't expressible, so `stack` is accepted for parity
|
|
255
369
|
* and ignored. A pushed modal is the top entry, so back dismisses it. */
|
|
256
370
|
export function popRoute(_stack = 'root'): void {
|
|
371
|
+
navigationRequests.invalidate('history')
|
|
257
372
|
history.back()
|
|
258
373
|
}
|
|
259
374
|
|
|
@@ -275,12 +390,12 @@ export function addBackInterceptor(_fn: () => boolean): () => void {
|
|
|
275
390
|
}
|
|
276
391
|
|
|
277
392
|
window.addEventListener('popstate', () => {
|
|
393
|
+
navigationRequests.invalidate('history')
|
|
278
394
|
saveScroll()
|
|
279
395
|
historyDepth = history.state?.__octaneXplatDepth ?? 0
|
|
280
396
|
lastKey = scrollKey()
|
|
281
|
-
|
|
282
|
-
current
|
|
283
|
-
applyHead(current)
|
|
397
|
+
restoreRouteState()
|
|
398
|
+
applyHead(modalRoute ?? current ?? null)
|
|
284
399
|
restoreScroll()
|
|
285
400
|
emit()
|
|
286
401
|
})
|
|
@@ -304,6 +419,7 @@ export function currentRoute(): Route | null {
|
|
|
304
419
|
|
|
305
420
|
/** The modal route overlaying the current one, if any. */
|
|
306
421
|
export function currentModalRoute(): Route | null {
|
|
422
|
+
read()
|
|
307
423
|
return modalRoute
|
|
308
424
|
}
|
|
309
425
|
|
|
@@ -312,7 +428,7 @@ export function currentModalRoute(): Route | null {
|
|
|
312
428
|
* apps wire `onDeepLink(pushDeepLink)`). */
|
|
313
429
|
export function pushDeepLink(url: string): boolean {
|
|
314
430
|
const r = matchUrl(routes, linkPath(url))
|
|
315
|
-
if (!r) {
|
|
431
|
+
if (!r || !screenFor(r.name)) {
|
|
316
432
|
console.warn(`[octane-xplat] pushDeepLink('${url}') dropped — no route matches.`)
|
|
317
433
|
return false
|
|
318
434
|
}
|
|
@@ -339,7 +455,8 @@ export function useRoute(stack: string): Route | null {
|
|
|
339
455
|
/** Whether the selected conceptual stack has an in-app route to pop. A
|
|
340
456
|
* browser's pre-app history is intentionally not counted. */
|
|
341
457
|
export function canGoBack(stack = 'root'): boolean {
|
|
342
|
-
|
|
458
|
+
read()
|
|
459
|
+
const route = modalRoute ?? current
|
|
343
460
|
return historyDepth > 0 && route?.stack === stack
|
|
344
461
|
}
|
|
345
462
|
|
package/src/sheet-detents.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FlexboxLayout, Screen } from '@nativescript/core'
|
|
1
|
+
import { FlexboxLayout, Screen, type View } from '@nativescript/core'
|
|
2
2
|
import { detentOffset, normalizeDetents, snapDetentIndex } from './sheet-snap'
|
|
3
3
|
|
|
4
4
|
const SNAP_MS = 200
|
|
@@ -38,7 +38,7 @@ export function attachSheetDetents(
|
|
|
38
38
|
|
|
39
39
|
// Parentless at attach (before RootLayout.open) — fall back to screen
|
|
40
40
|
// dips; every gesture-time read uses the real parent height.
|
|
41
|
-
const vh = () =>
|
|
41
|
+
const vh = () => host.parent?.getActualSize?.().height || Screen.mainScreen.heightDIPs
|
|
42
42
|
const max = sorted[sorted.length - 1]
|
|
43
43
|
let cur = 0
|
|
44
44
|
|
|
@@ -50,7 +50,9 @@ export function attachSheetDetents(
|
|
|
50
50
|
grabber.verticalAlignment = 'top'
|
|
51
51
|
const grip = new FlexboxLayout()
|
|
52
52
|
grip.className = 'vx-sheet-grip'
|
|
53
|
-
|
|
53
|
+
// FlexboxLayout is a View at runtime; NativeScript's generated symbol
|
|
54
|
+
// setter index signatures make its Android declaration incompatible.
|
|
55
|
+
grabber.addChild(grip as unknown as View)
|
|
54
56
|
host.addChild(grabber)
|
|
55
57
|
|
|
56
58
|
let dragging = false
|
|
@@ -33,7 +33,10 @@ export const openSheet: OpenSheet = (Component, params, options: SheetOpenOption
|
|
|
33
33
|
return new Promise<ModalOpenResult>((resolve) => {
|
|
34
34
|
let finished = false
|
|
35
35
|
const finish = (result?: ModalOpenResult) => {
|
|
36
|
-
if (finished)
|
|
36
|
+
if (finished) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
37
40
|
finished = true
|
|
38
41
|
active.delete(entry)
|
|
39
42
|
try {
|
|
@@ -41,6 +44,7 @@ export const openSheet: OpenSheet = (Component, params, options: SheetOpenOption
|
|
|
41
44
|
} catch (error) {
|
|
42
45
|
console.error('[octane-xplat] sheet close failed', error)
|
|
43
46
|
}
|
|
47
|
+
|
|
44
48
|
resolve(result)
|
|
45
49
|
}
|
|
46
50
|
|
package/src/sheet-service.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { topRootLayout } from './root-layout.mobile'
|
|
|
6
6
|
import { applyThemeClasses } from './theme/theme-scheme'
|
|
7
7
|
import { attachSheetDetents } from './sheet-detents'
|
|
8
8
|
import { bindBottomInsetToKeyboard } from './keyboard-inset.mobile'
|
|
9
|
-
import { attachTapToBlur } from './tap-to-blur
|
|
9
|
+
import { attachTapToBlur } from './tap-to-blur'
|
|
10
10
|
|
|
11
11
|
interface ActiveSheet {
|
|
12
12
|
host: GridLayout
|
package/src/sheet-service.web.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createRoot } from 'octane'
|
|
|
2
2
|
import type { ModalOpenResult, OpenSheet } from './props'
|
|
3
3
|
import { applyThemeClasses } from './theme/theme-scheme'
|
|
4
4
|
import { attachSheetDetents } from './sheet-detents.web'
|
|
5
|
+
import { isolateModalFocus } from './modal-focus.web'
|
|
5
6
|
|
|
6
7
|
interface ActiveSheet {
|
|
7
8
|
layer: HTMLElement
|
|
@@ -28,6 +29,7 @@ export const openSheet: OpenSheet = (Component, params, options = {}) =>
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
let finished = false
|
|
32
|
+
let releaseFocus: (() => void) | undefined
|
|
31
33
|
const finish = (result?: ModalOpenResult) => {
|
|
32
34
|
if (finished) {
|
|
33
35
|
return
|
|
@@ -35,6 +37,7 @@ export const openSheet: OpenSheet = (Component, params, options = {}) =>
|
|
|
35
37
|
|
|
36
38
|
finished = true
|
|
37
39
|
active.delete(entry)
|
|
40
|
+
releaseFocus?.()
|
|
38
41
|
unbindTheme()
|
|
39
42
|
detents?.detach()
|
|
40
43
|
root.unmount()
|
|
@@ -61,4 +64,9 @@ export const openSheet: OpenSheet = (Component, params, options = {}) =>
|
|
|
61
64
|
const root = createRoot(panel)
|
|
62
65
|
root.render(Component, { params, close: finish })
|
|
63
66
|
active.add(entry)
|
|
67
|
+
if (options.shadeCover ?? true) {
|
|
68
|
+
panel.setAttribute('role', 'dialog')
|
|
69
|
+
panel.setAttribute('aria-modal', 'true')
|
|
70
|
+
releaseFocus = isolateModalFocus(layer, panel, () => finish())
|
|
71
|
+
}
|
|
64
72
|
})
|
package/src/sheet-snap.test.ts
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { detentOffset, normalizeDetents, snapDetentIndex } from './sheet-snap'
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { detentOffset, normalizeDetents, snapDetentIndex } from './sheet-snap'
|
|
3
3
|
|
|
4
4
|
// Pure math behind the sheet detents: fractions → sorted snap points →
|
|
5
5
|
// resting translateY offsets → release-time snap targets. The DOM/NS
|
|
6
6
|
// attachments are thin adapters over this.
|
|
7
7
|
|
|
8
|
-
const D = [0.25, 0.5, 0.9]
|
|
9
|
-
const VH = 800
|
|
8
|
+
const D = [0.25, 0.5, 0.9]
|
|
9
|
+
const VH = 800
|
|
10
10
|
|
|
11
11
|
describe('normalizeDetents', () => {
|
|
12
12
|
it('sorts, filters, and dedupes', () => {
|
|
13
|
-
expect(normalizeDetents([0.9, 0.25, 0, 1.5, 0.25])).toEqual([0.25, 0.9])
|
|
14
|
-
expect(normalizeDetents()).toEqual([])
|
|
15
|
-
expect(normalizeDetents([0])).toEqual([])
|
|
16
|
-
})
|
|
17
|
-
})
|
|
13
|
+
expect(normalizeDetents([0.9, 0.25, 0, 1.5, 0.25])).toEqual([0.25, 0.9])
|
|
14
|
+
expect(normalizeDetents()).toEqual([])
|
|
15
|
+
expect(normalizeDetents([0])).toEqual([])
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
18
|
|
|
19
19
|
describe('detentOffset', () => {
|
|
20
20
|
it('parks the largest-detent-sized panel per detent', () => {
|
|
21
|
-
expect(detentOffset(D, 0, VH)).toBe((0.9 - 0.25) * VH)
|
|
22
|
-
expect(detentOffset(D, 2, VH)).toBe(0)
|
|
23
|
-
})
|
|
24
|
-
})
|
|
21
|
+
expect(detentOffset(D, 0, VH)).toBe((0.9 - 0.25) * VH)
|
|
22
|
+
expect(detentOffset(D, 2, VH)).toBe(0)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
25
|
|
|
26
26
|
describe('snapDetentIndex', () => {
|
|
27
27
|
it('snaps to the nearest detent on a slow release', () => {
|
|
28
28
|
// frac 0.55 → detent 0.5 wins
|
|
29
|
-
expect(snapDetentIndex((0.9 - 0.55) * VH, 0, D, VH)).toBe(1)
|
|
30
|
-
expect(snapDetentIndex(0, 0, D, VH)).toBe(2)
|
|
31
|
-
})
|
|
29
|
+
expect(snapDetentIndex((0.9 - 0.55) * VH, 0, D, VH)).toBe(1)
|
|
30
|
+
expect(snapDetentIndex(0, 0, D, VH)).toBe(2) // fully open
|
|
31
|
+
})
|
|
32
32
|
|
|
33
33
|
it('dismisses below half of the smallest detent', () => {
|
|
34
34
|
// frac 0.1 < 0.25/2
|
|
35
|
-
expect(snapDetentIndex((0.9 - 0.1) * VH, 0, D, VH)).toBe(-1)
|
|
36
|
-
})
|
|
35
|
+
expect(snapDetentIndex((0.9 - 0.1) * VH, 0, D, VH)).toBe(-1)
|
|
36
|
+
})
|
|
37
37
|
|
|
38
38
|
it('fling direction overrides proximity', () => {
|
|
39
39
|
// frac 0.6 (nearer 0.5) flung up → 0.9
|
|
40
|
-
expect(snapDetentIndex((0.9 - 0.6) * VH, -600, D, VH)).toBe(2)
|
|
40
|
+
expect(snapDetentIndex((0.9 - 0.6) * VH, -600, D, VH)).toBe(2)
|
|
41
41
|
// frac 0.4 (nearer 0.5) flung down → 0.25
|
|
42
|
-
expect(snapDetentIndex((0.9 - 0.4) * VH, 600, D, VH)).toBe(0)
|
|
42
|
+
expect(snapDetentIndex((0.9 - 0.4) * VH, 600, D, VH)).toBe(0)
|
|
43
43
|
// flung down at/below the smallest detent → dismiss
|
|
44
|
-
expect(snapDetentIndex((0.9 - 0.25) * VH, 600, D, VH)).toBe(-1)
|
|
45
|
-
})
|
|
46
|
-
})
|
|
44
|
+
expect(snapDetentIndex((0.9 - 0.25) * VH, 600, D, VH)).toBe(-1)
|
|
45
|
+
})
|
|
46
|
+
})
|
package/src/sheet-snap.ts
CHANGED
|
@@ -27,7 +27,12 @@ export function detentOffset(detents: readonly number[], index: number, vh: numb
|
|
|
27
27
|
* `vy` (positive = dragging down). Returns the detent index, or -1 to
|
|
28
28
|
* dismiss: released below half of the smallest detent, or a downward
|
|
29
29
|
* fling while at/below it. */
|
|
30
|
-
export function snapDetentIndex(
|
|
30
|
+
export function snapDetentIndex(
|
|
31
|
+
ty: number,
|
|
32
|
+
vy: number,
|
|
33
|
+
detents: readonly number[],
|
|
34
|
+
vh: number,
|
|
35
|
+
): number {
|
|
31
36
|
const max = detents[detents.length - 1]
|
|
32
37
|
const frac = Math.min(Math.max(max - ty / vh, 0), 1)
|
|
33
38
|
|
package/src/svg.mobile.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { registerElement } from '@nativescript-community/octane'
|
|
2
|
-
import
|
|
3
|
-
import { SVGView } from './vendor/ui-svg'
|
|
2
|
+
import { SVGView } from './vendor/ui-svg/src'
|
|
4
3
|
import type { IconGlyph } from './props'
|
|
5
4
|
|
|
5
|
+
// The emitted declaration keeps the import() form: declaration emit elides
|
|
6
|
+
// type-only imports referenced solely inside `declare module`, and the
|
|
7
|
+
// augmentation must still resolve Attributes when the module isn't otherwise
|
|
8
|
+
// loaded in the consumer's program.
|
|
6
9
|
declare module '@nativescript-community/octane/intrinsics' {
|
|
7
10
|
interface NativeScriptElements {
|
|
8
|
-
svgview: Omit<
|
|
11
|
+
svgview: Omit<
|
|
12
|
+
import('@nativescript-community/octane/intrinsics').Attributes<typeof SVGView>,
|
|
13
|
+
'src'
|
|
14
|
+
> & {
|
|
9
15
|
/** SVGView awaits promise srcs — remote .svg URLs arrive as fetched markup. */
|
|
10
16
|
src?: string | Promise<string>
|
|
11
17
|
stretch?: 'none' | 'fill' | 'aspectFit' | 'aspectFill'
|