@lowdefy/shell 3.23.3 → 7.2.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/LICENSE +183 -183
- package/README.md +364 -2
- package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
- package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
- package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
- package/app/+native-intent.js +39 -0
- package/app/_layout.js +36 -0
- package/app.config.ts +183 -0
- package/appLinks.js +65 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +172 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native/LowdefyAppShortcuts.swift.template +84 -0
- package/native/withAppShortcuts.js +60 -0
- package/native-fingerprint.json +16 -0
- package/package.json +120 -40
- package/profiles.js +607 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +112 -0
- package/scripts/checkReleaseBundle.mjs +358 -0
- package/scripts/createDayjsLocales.mjs +67 -0
- package/scripts/createShellAssets.mjs +54 -0
- package/scripts/createShellVersion.mjs +83 -0
- package/scripts/createTypeManifest.mjs +205 -0
- package/shell.dev.json +11 -0
- package/shellVariant.js +126 -0
- package/src/ShellContext.js +32 -0
- package/src/actions/CopyToClipboard.js +29 -0
- package/src/actions/CopyToClipboard.test.js +33 -0
- package/src/actions/GeolocationCurrentPosition.js +61 -0
- package/src/actions/GeolocationCurrentPosition.test.js +79 -0
- package/src/actions/GetLocalStorage.js +36 -0
- package/src/actions/RemoveLocalStorage.js +31 -0
- package/src/actions/ScrollTo.js +66 -0
- package/src/actions/ScrollTo.test.js +83 -0
- package/src/actions/SetFocus.js +33 -0
- package/src/actions/SetFocus.test.js +49 -0
- package/src/actions/SetLocalStorage.js +34 -0
- package/src/actions/localStorage.test.js +68 -0
- package/src/actions/measureInScrollView.js +36 -0
- package/src/actions/nativeActions.js +41 -0
- package/src/auth/AuthButton.js +44 -0
- package/src/auth/AuthGate.js +57 -0
- package/src/auth/AuthLink.js +43 -0
- package/src/auth/LockScreen.js +67 -0
- package/src/auth/LockScreen.test.js +37 -0
- package/src/auth/SignInScreen.js +268 -0
- package/src/auth/SignInScreen.test.js +187 -0
- package/src/auth/createAppAuth.js +325 -0
- package/src/auth/createAppAuth.test.js +424 -0
- package/src/auth/createAuthApi.js +115 -0
- package/src/auth/createAuthApi.test.js +138 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createDeviceKey.js +62 -0
- package/src/auth/createDeviceKey.test.js +54 -0
- package/src/auth/createSessionStores.js +45 -0
- package/src/auth/createTokenStore.js +103 -0
- package/src/auth/createTokenStore.test.js +116 -0
- package/src/auth/getAuthSettings.js +24 -0
- package/src/auth/getAuthView.js +39 -0
- package/src/auth/getAuthView.test.js +52 -0
- package/src/auth/getSheetReturnUrl.js +35 -0
- package/src/auth/getTokenStoreOptions.js +46 -0
- package/src/auth/isBackgroundShell.js +26 -0
- package/src/auth/isSheetReturnLink.js +35 -0
- package/src/auth/parseQuery.js +39 -0
- package/src/auth/purgeAppData.js +80 -0
- package/src/auth/purgeAppData.test.js +104 -0
- package/src/auth/shouldLock.js +26 -0
- package/src/auth/signInWithSheet.js +97 -0
- package/src/auth/signInWithSheet.test.js +205 -0
- package/src/auth/styles.js +78 -0
- package/src/auth/unregisterPushToken.js +62 -0
- package/src/auth/unregisterPushToken.test.js +67 -0
- package/src/auth/usesBiometricUnlock.js +23 -0
- package/src/auth/watchSession.js +47 -0
- package/src/auth/withoutCookies.js +26 -0
- package/src/cache/createSqliteCacheStore.js +113 -0
- package/src/cache/createSqliteCacheStore.test.js +93 -0
- package/src/cache/getCacheKey.js +25 -0
- package/src/cache/getLastCacheKeyName.js +22 -0
- package/src/components/AppProvider.js +118 -0
- package/src/components/Frame.js +116 -0
- package/src/components/Frame.test.js +101 -0
- package/src/components/LoadingScreen.js +35 -0
- package/src/components/MenuButton.js +121 -0
- package/src/components/MenuButton.test.js +74 -0
- package/src/components/MessageScreen.js +51 -0
- package/src/components/NavigationTheme.js +33 -0
- package/src/components/NoConnectionBanner.js +51 -0
- package/src/components/NoConnectionBanner.test.js +73 -0
- package/src/components/OtherPageScreen.js +95 -0
- package/src/components/OtherPageScreen.test.js +74 -0
- package/src/components/PageScreen.js +254 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/RouteErrorScreen.js +47 -0
- package/src/components/ShellRoot.js +34 -0
- package/src/components/ShellRoot.test.js +62 -0
- package/src/components/ShellThemeProvider.js +45 -0
- package/src/components/SwitchToTab.js +36 -0
- package/src/components/SwitchToTab.test.js +63 -0
- package/src/components/TabIconImages.js +72 -0
- package/src/components/UpdateScreen.js +40 -0
- package/src/components/getNavigationTheme.js +44 -0
- package/src/components/getNavigationTheme.test.js +41 -0
- package/src/components/styles.js +50 -0
- package/src/components/useSurfaceTokens.js +47 -0
- package/src/config/getAppApiBase.js +34 -0
- package/src/config/getShellConfig.js +37 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +126 -0
- package/src/dev/DevShell.js +239 -0
- package/src/dev/DevShell.test.js +233 -0
- package/src/dev/connect/ConfirmSheet.js +71 -0
- package/src/dev/connect/ConnectScreen.js +117 -0
- package/src/dev/connect/QrScanner.js +89 -0
- package/src/dev/connection/createDevShellConfig.js +47 -0
- package/src/dev/connection/createDevTransport.js +51 -0
- package/src/dev/connection/createDevTransport.test.js +87 -0
- package/src/dev/connection/createServerList.js +48 -0
- package/src/dev/connection/isLoopbackHost.js +25 -0
- package/src/dev/connection/pairedShellApp.test.js +104 -0
- package/src/dev/connection/parseDevLink.js +66 -0
- package/src/dev/connection/parseDevLink.test.js +97 -0
- package/src/dev/connection/parseQuery.js +33 -0
- package/src/dev/connection/parseServerInput.js +40 -0
- package/src/dev/connection/parseServerUrl.js +45 -0
- package/src/dev/devStyles.js +97 -0
- package/src/dev/index.js +21 -0
- package/src/dev/inject/createDeviceInjections.js +188 -0
- package/src/dev/inject/createDeviceInjections.test.js +224 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/inject/injectPush.js +60 -0
- package/src/dev/inject/injectShare.js +55 -0
- package/src/dev/inject/withInjectedDevice.js +98 -0
- package/src/dev/menu/BlockGallery.js +33 -0
- package/src/dev/menu/DevMenu.js +121 -0
- package/src/dev/menu/createLogBuffer.js +83 -0
- package/src/dev/menu/watchShake.js +52 -0
- package/src/dev/menu/watchShake.test.js +61 -0
- package/src/dev/recorder/createNativeRecorder.js +204 -0
- package/src/dev/recorder/createNativeRecorder.test.js +307 -0
- package/src/dev/redirectDevLink.js +30 -0
- package/src/dev/redirectDevLink.test.js +49 -0
- package/src/dev/steps/createStepRunner.js +184 -0
- package/src/dev/steps/createStepRunner.test.js +262 -0
- package/src/dev/steps/findBlock.js +78 -0
- package/src/dev/steps/isContextSettled.js +37 -0
- package/src/dev/steps/waitForSettle.js +39 -0
- package/src/dev/steps/waitUntil.js +40 -0
- package/src/dev/tab/DevTabClient.js +97 -0
- package/src/dev/tab/answerEval.js +39 -0
- package/src/dev/tab/answerInspect.js +45 -0
- package/src/dev/tab/createDevTabClient.js +272 -0
- package/src/dev/tab/createDevTabClient.test.js +317 -0
- package/src/dev/tab/describeTabError.js +35 -0
- package/src/dev/tab/devClientMarker.js +22 -0
- package/src/dev/tab/devClientMarker.test.js +23 -0
- package/src/dev/tab/findPageContext.js +43 -0
- package/src/dev/tab/getShownPage.js +42 -0
- package/src/dev/tab/readEventStream.js +68 -0
- package/src/dev/tab/reloadApp.js +42 -0
- package/src/dev/tab/reloadApp.test.js +115 -0
- package/src/device/DeviceHosts.js +32 -0
- package/src/device/basic/createFilesFeature.js +41 -0
- package/src/device/basic/createHapticsFeature.js +46 -0
- package/src/device/basic/createKeepAwakeFeature.js +37 -0
- package/src/device/basic/createPhotosFeature.js +40 -0
- package/src/device/basic/createRequestQueueFeature.js +37 -0
- package/src/device/basic/createShareFeature.js +63 -0
- package/src/device/basic/createShareFeature.test.js +47 -0
- package/src/device/basic/createSpeechFeature.js +43 -0
- package/src/device/basic/createUploadFeature.js +41 -0
- package/src/device/basic/toImagePickerOptions.js +41 -0
- package/src/device/basic/toPickedFile.js +54 -0
- package/src/device/calendar/index.js +68 -0
- package/src/device/contacts/index.js +67 -0
- package/src/device/createDevice.js +65 -0
- package/src/device/device.js +27 -0
- package/src/device/field/MapView.js +93 -0
- package/src/device/field/ScanCodeSheet.js +86 -0
- package/src/device/field/ScannerView.js +52 -0
- package/src/device/field/createAudioFeature.js +70 -0
- package/src/device/field/createBrightnessFeature.js +40 -0
- package/src/device/field/createCameraFeature.js +56 -0
- package/src/device/field/createDocumentScannerFeature.js +51 -0
- package/src/device/field/createLocationFeature.js +39 -0
- package/src/device/field/createScanRequests.js +55 -0
- package/src/device/field/index.js +39 -0
- package/src/device/field/scanRequests.js +22 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/device/nfc/index.js +89 -0
- package/src/device/nfc/toNfcRecord.js +44 -0
- package/src/device/nfc/toNfcRecord.test.js +48 -0
- package/src/device/tracking/createBacklog.js +63 -0
- package/src/device/tracking/createBacklog.test.js +47 -0
- package/src/device/tracking/index.js +211 -0
- package/src/device/tracking/index.test.js +110 -0
- package/src/host/createDeviceStore.js +32 -0
- package/src/host/createMemoryStore.js +32 -0
- package/src/host/createNetworkStatus.js +94 -0
- package/src/host/createNetworkStatus.test.js +94 -0
- package/src/host/createRouterAdapter.js +33 -0
- package/src/host/createShellFetch.js +62 -0
- package/src/host/createShellFetch.test.js +165 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +83 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +31 -0
- package/src/host/isNetworkError.js +24 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/readServerError.js +45 -0
- package/src/host/readServerError.test.js +85 -0
- package/src/host/reportShellError.js +47 -0
- package/src/host/reportShellError.test.js +79 -0
- package/src/host/withNetworkErrors.js +38 -0
- package/src/host/withNetworkErrors.test.js +61 -0
- package/src/host/withShellHeader.js +25 -0
- package/src/host/withShellHeader.test.js +28 -0
- package/src/js/evaluateJsEntries.js +43 -0
- package/src/js/evaluateJsEntries.test.js +48 -0
- package/src/locale/createShellLocale.js +112 -0
- package/src/locale/createShellLocale.test.js +74 -0
- package/src/locale/dayjsLocales.js +165 -0
- package/src/locale/getDeviceLocales.js +23 -0
- package/src/locale/loadDayjsLocale.js +32 -0
- package/src/locale/resolveActiveLocale.js +46 -0
- package/src/locale/resolveActiveLocale.test.js +40 -0
- package/src/native/backgroundFetch.js +97 -0
- package/src/native/callEndpoint.js +63 -0
- package/src/native/createAppEventTrigger.js +70 -0
- package/src/native/currentApp.js +49 -0
- package/src/native/getPagePath.js +32 -0
- package/src/native/openPath.js +55 -0
- package/src/native/openPath.test.js +39 -0
- package/src/native/parseShellLink.js +38 -0
- package/src/native/parseShellLink.test.js +29 -0
- package/src/native/startAppServices.js +94 -0
- package/src/native/startAppServices.test.js +97 -0
- package/src/native/startShareIn.js +59 -0
- package/src/native/startShortcuts.js +88 -0
- package/src/native/startShortcuts.test.js +59 -0
- package/src/native/toSharePayload.js +47 -0
- package/src/native/toSharePayload.test.js +47 -0
- package/src/native/whenAppReady.js +48 -0
- package/src/navigation/SlotContext.js +24 -0
- package/src/navigation/frameSlots.js +27 -0
- package/src/navigation/getFrame.js +30 -0
- package/src/navigation/getInitialScreenOptions.js +63 -0
- package/src/navigation/getInitialScreenOptions.test.js +66 -0
- package/src/navigation/getPresentation.js +28 -0
- package/src/navigation/getSlotRootPath.js +27 -0
- package/src/navigation/getStackOptions.js +185 -0
- package/src/navigation/getTabHref.js +45 -0
- package/src/navigation/getTabIcons.js +43 -0
- package/src/navigation/isPageUnderHeader.js +46 -0
- package/src/navigation/navigation.test.js +342 -0
- package/src/navigation/toAppLinkPath.js +43 -0
- package/src/navigation/toAppLinkPath.test.js +53 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/notifications/createNotificationsFeature.js +61 -0
- package/src/notifications/getCategoryAction.js +31 -0
- package/src/notifications/handleNotificationResponse.js +82 -0
- package/src/notifications/registerPushToken.js +71 -0
- package/src/notifications/startNotifications.js +114 -0
- package/src/notifications/toCategoryActions.js +42 -0
- package/src/notifications/toNotificationEvent.js +36 -0
- package/src/notifications/toScheduleRequest.js +62 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +15 -0
- package/src/plugins/typeManifest.js +789 -0
- package/src/plugins/types.js +64 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/queue/createRequestQueue.js +198 -0
- package/src/queue/createRequestQueue.test.js +215 -0
- package/src/queue/createSqliteQueueStore.js +121 -0
- package/src/queue/summarizeQueue.js +39 -0
- package/src/session/createAppSession.js +323 -0
- package/src/session/createAppSession.test.js +391 -0
- package/src/session/getPageView.js +59 -0
- package/src/session/getPageView.test.js +137 -0
- package/src/sync/encodePagePath.js +27 -0
- package/src/sync/fetchPage.js +72 -0
- package/src/sync/fetchPage.test.js +84 -0
- package/src/sync/fetchRoot.js +43 -0
- package/src/sync/fetchUser.js +33 -0
- package/src/sync/runLimited.js +36 -0
- package/src/sync/toRootConfig.js +35 -0
- package/src/test/createFakeNetwork.js +40 -0
- package/src/test/createFakeSecureStore.js +40 -0
- package/src/test/createFakeServer.js +98 -0
- package/src/test/createMemoryCacheStore.js +59 -0
- package/src/test/createNetworkError.js +24 -0
- package/src/test/expoImage.js +42 -0
- package/src/test/expoNotifications.js +40 -0
- package/src/test/expoQuickActions.js +22 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoTaskManager.js +25 -0
- package/src/test/expoUi.js +55 -0
- package/src/test/expoUiBottomSheet.js +64 -0
- package/src/test/expoUiDateTimePicker.js +26 -0
- package/src/test/expoUiMenu.js +46 -0
- package/src/test/expoUiSegmentedControl.js +47 -0
- package/src/test/expoUiSwiftUi.js +62 -0
- package/src/test/expoUiSwiftUiModifiers.js +23 -0
- package/src/test/expoWebBrowser.js +23 -0
- package/src/test/fixtures.js +98 -0
- package/src/test/nativeModule.js +33 -0
- package/src/test/renderPageScreen.js +109 -0
- package/src/types/checkTypeSet.js +39 -0
- package/src/types/collectPageTypes.js +49 -0
- package/src/types/collectPageTypes.test.js +90 -0
- package/src/types/findMissingPageTypes.js +40 -0
- package/src/version/compareVersions.js +38 -0
- package/src/version/isShellTooOld.js +30 -0
- package/src/version/shellVersion.js +21 -0
- package/src/version/shellVersion.test.js +32 -0
- package/src/widgets/fetchWidgetData.js +59 -0
- package/src/widgets/index.js +156 -0
- package/src/widgets/refreshWidgets.js +31 -0
- package/withExcludedNativeModules.js +93 -0
- package/dist/location.js +0 -33
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/icon-32.png +0 -0
- package/dist/public/icon-512.png +0 -0
- package/dist/public/icon.svg +0 -17
- package/dist/public/logo-dark-theme.png +0 -0
- package/dist/public/logo-light-theme.png +0 -0
- package/dist/public/logo-square-dark-theme.png +0 -0
- package/dist/public/logo-square-light-theme.png +0 -0
- package/dist/public/manifest.webmanifest +0 -16
- package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
- package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
- package/dist/shell/454.b597ca21366afda000af.js +0 -2
- package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
- package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
- package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
- package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
- package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
- package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
- package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
- package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
- package/dist/shell/index.html +0 -57
- package/dist/shell/main.f01501a75909f38ccc32.js +0 -1
- package/dist/shell/runtime.e50f6c3a5f7c74a0a717.js +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Waits until the open app can act for its caller: the auth has read the token and is open (not
|
|
19
|
+
* starting, not locked) and the session holds a root. Launch work (a notification button, a quick
|
|
20
|
+
* action, share-in, the queue's replay, background fetch) waits on it, so none of it runs with no
|
|
21
|
+
* token loaded or no root to read.
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} params
|
|
24
|
+
* @param {Object} params.auth The app's auth (createAppAuth).
|
|
25
|
+
* @param {Object} params.session The app's session (createAppSession).
|
|
26
|
+
* @returns {{ promise: Promise<void>, stop: () => void }} stop stops waiting; the promise then never
|
|
27
|
+
* settles.
|
|
28
|
+
*/
|
|
29
|
+
function whenAppReady({ auth, session }) {
|
|
30
|
+
const unsubscribes = [];
|
|
31
|
+
const stop = () => unsubscribes.splice(0).forEach((unsubscribe) => unsubscribe());
|
|
32
|
+
const promise = new Promise((resolve) => {
|
|
33
|
+
function check() {
|
|
34
|
+
if (auth.getState().status === 'open' && session.getState().root !== null) {
|
|
35
|
+
stop();
|
|
36
|
+
resolve();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (!check()) {
|
|
42
|
+
unsubscribes.push(auth.subscribe(check), session.subscribe(check));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return { promise, stop };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default whenAppReady;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createContext } from 'react';
|
|
18
|
+
|
|
19
|
+
// The index of the frame group (app/(t0,t1,t2,t3,t4)) a screen lives in, which its group layout
|
|
20
|
+
// provides from the segment Expo Router gives it. A screen cannot read it from useSegments: those
|
|
21
|
+
// are the focused route's, so every tab rendered at once would take the focused tab's index.
|
|
22
|
+
const SlotContext = createContext(0);
|
|
23
|
+
|
|
24
|
+
export default SlotContext;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The route groups the frame's screens live in: app/(t0,t1,t2,t3,t4) is one set of routes shared
|
|
18
|
+
// by five groups. With a tabs frame each tab is a group, with its own stack; mobile.menus.tabs has
|
|
19
|
+
// at most five items. A drawer or stack frame uses the first group only.
|
|
20
|
+
const FRAME_SLOTS = ['t0', 't1', 't2', 't3', 't4'];
|
|
21
|
+
|
|
22
|
+
function getSlotIndex(segment) {
|
|
23
|
+
return FRAME_SLOTS.indexOf(String(segment).replace(/^\(|\)$/g, ''));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { getSlotIndex };
|
|
27
|
+
export default FRAME_SLOTS;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The frame an app's mobile menus declare: { kind: 'tabs', items }, { kind: 'drawer', items } or
|
|
18
|
+
// { kind: 'stack' }. The server drops the items a caller may not open, so a tabs frame left with
|
|
19
|
+
// fewer than two items, or a drawer with none, falls back to a stack.
|
|
20
|
+
function getFrame(menus) {
|
|
21
|
+
if ((menus?.tabs?.length ?? 0) >= 2) {
|
|
22
|
+
return { kind: 'tabs', items: menus.tabs };
|
|
23
|
+
}
|
|
24
|
+
if ((menus?.drawer?.length ?? 0) >= 1) {
|
|
25
|
+
return { kind: 'drawer', items: menus.drawer };
|
|
26
|
+
}
|
|
27
|
+
return { kind: 'stack', items: [] };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default getFrame;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
function noop() {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The header options a Screen page's config gives before the page renders, in the shape a Screen
|
|
23
|
+
* reports (client-native screen/Screen.js): the parts that size the bar (a large title, a search
|
|
24
|
+
* field) and how it is drawn (a backgroundTint's clear bar), from literal properties. The Screen
|
|
25
|
+
* reports its full options once it renders; these set the bar first, so the page's scroll view is
|
|
26
|
+
* first laid out under the bar it will have. iOS keeps a large title collapsed when the page's
|
|
27
|
+
* scroll view was laid out under a standard bar before the large title came.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} params
|
|
30
|
+
* @param {Object} params.pageConfig The page config, as the server sends it.
|
|
31
|
+
* @returns {Object|null} The options, or null for a page that is not a Screen.
|
|
32
|
+
*/
|
|
33
|
+
function getInitialScreenOptions({ pageConfig }) {
|
|
34
|
+
if (pageConfig.type !== 'Screen') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const properties = pageConfig.properties ?? {};
|
|
38
|
+
const headerSearch = properties.headerSearch;
|
|
39
|
+
return {
|
|
40
|
+
backgroundTint: !type.isNone(properties.backgroundTint),
|
|
41
|
+
headerActions: [],
|
|
42
|
+
headerBottom: false,
|
|
43
|
+
headerLeft: undefined,
|
|
44
|
+
headerRight: undefined,
|
|
45
|
+
headerSearch: type.isObject(headerSearch)
|
|
46
|
+
? {
|
|
47
|
+
autoFocus: false,
|
|
48
|
+
hideWhenScrolling: headerSearch.hideWhenScrolling === true,
|
|
49
|
+
onCancel: noop,
|
|
50
|
+
onChangeText: noop,
|
|
51
|
+
onSearch: noop,
|
|
52
|
+
placeholder: type.isString(headerSearch.placeholder)
|
|
53
|
+
? headerSearch.placeholder
|
|
54
|
+
: undefined,
|
|
55
|
+
}
|
|
56
|
+
: undefined,
|
|
57
|
+
largeTitle: properties.largeTitle === true,
|
|
58
|
+
presentation: type.isString(properties.presentation) ? properties.presentation : 'card',
|
|
59
|
+
title: type.isString(properties.title) ? properties.title : undefined,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default getInitialScreenOptions;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import getInitialScreenOptions from './getInitialScreenOptions.js';
|
|
18
|
+
|
|
19
|
+
test('getInitialScreenOptions reads the bar a Screen config gives before it renders', () => {
|
|
20
|
+
const options = getInitialScreenOptions({
|
|
21
|
+
pageConfig: {
|
|
22
|
+
type: 'Screen',
|
|
23
|
+
properties: {
|
|
24
|
+
title: 'My work',
|
|
25
|
+
largeTitle: true,
|
|
26
|
+
headerSearch: { placeholder: 'Search designs', hideWhenScrolling: true },
|
|
27
|
+
backgroundTint: { color: '#ff7a45' },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
expect(options).toMatchObject({
|
|
32
|
+
backgroundTint: true,
|
|
33
|
+
headerActions: [],
|
|
34
|
+
headerBottom: false,
|
|
35
|
+
largeTitle: true,
|
|
36
|
+
presentation: 'card',
|
|
37
|
+
title: 'My work',
|
|
38
|
+
});
|
|
39
|
+
expect(options.headerSearch).toMatchObject({
|
|
40
|
+
autoFocus: false,
|
|
41
|
+
hideWhenScrolling: true,
|
|
42
|
+
placeholder: 'Search designs',
|
|
43
|
+
});
|
|
44
|
+
// The Screen sets the search handlers once it renders.
|
|
45
|
+
expect(() => options.headerSearch.onChangeText('a')).not.toThrow();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('getInitialScreenOptions takes only literal properties, and leaves the rest to the Screen', () => {
|
|
49
|
+
const options = getInitialScreenOptions({
|
|
50
|
+
pageConfig: {
|
|
51
|
+
type: 'Screen',
|
|
52
|
+
properties: { title: { _state: 'task.key' }, largeTitle: { _state: 'large' } },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
expect(options).toMatchObject({
|
|
56
|
+
backgroundTint: false,
|
|
57
|
+
headerSearch: undefined,
|
|
58
|
+
largeTitle: false,
|
|
59
|
+
title: undefined,
|
|
60
|
+
});
|
|
61
|
+
expect(getInitialScreenOptions({ pageConfig: { type: 'Screen' } }).largeTitle).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('getInitialScreenOptions gives nothing for a page that is not a Screen', () => {
|
|
65
|
+
expect(getInitialScreenOptions({ pageConfig: { type: 'Box', properties: {} } })).toBeNull();
|
|
66
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// The native stack presentation for a Screen page's presentation property.
|
|
18
|
+
const PRESENTATIONS = {
|
|
19
|
+
card: 'card',
|
|
20
|
+
modal: 'modal',
|
|
21
|
+
sheet: 'formSheet',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function getPresentation(presentation) {
|
|
25
|
+
return PRESENTATIONS[presentation] ?? 'card';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default getPresentation;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import getFrame from './getFrame.js';
|
|
18
|
+
|
|
19
|
+
// The page path a frame group's first screen shows: its tab's page, or the app's home page.
|
|
20
|
+
function getSlotRootPath({ root, slotIndex }) {
|
|
21
|
+
const frame = getFrame(root.menus);
|
|
22
|
+
const pageId = frame.kind === 'tabs' ? frame.items[slotIndex]?.pageId : root.homePageId;
|
|
23
|
+
const entry = root.pageIndex.find((indexEntry) => indexEntry.pageId === pageId);
|
|
24
|
+
return entry?.path ?? null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default getSlotRootPath;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import { View } from 'react-native';
|
|
19
|
+
|
|
20
|
+
import getPresentation from './getPresentation.js';
|
|
21
|
+
|
|
22
|
+
const SIDES = { left: 'Left', right: 'Right' };
|
|
23
|
+
|
|
24
|
+
function toSfSymbol(name) {
|
|
25
|
+
return name === null ? undefined : { type: 'sfSymbol', name };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A menu item as a native UIMenu action: its title, its SF Symbol, and red when destructive.
|
|
29
|
+
function toNativeMenuAction(item) {
|
|
30
|
+
return {
|
|
31
|
+
type: 'action',
|
|
32
|
+
destructive: item.destructive,
|
|
33
|
+
disabled: item.disabled,
|
|
34
|
+
icon: toSfSymbol(item.symbol),
|
|
35
|
+
label: item.title,
|
|
36
|
+
onPress: item.onPress,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// An action as a native iOS bar button: its SF Symbol, in the bar's shared glass background. An
|
|
41
|
+
// action with a menu is a bar button that opens a native menu (UIMenu) of its items, drawn with
|
|
42
|
+
// its title when its icon has no SF Symbol. Any other icon with no SF Symbol draws as its Lucide
|
|
43
|
+
// icon in a custom item.
|
|
44
|
+
function toNativeItem(action) {
|
|
45
|
+
if (action.menu !== null) {
|
|
46
|
+
return {
|
|
47
|
+
type: 'menu',
|
|
48
|
+
accessibilityLabel: action.title,
|
|
49
|
+
disabled: action.disabled,
|
|
50
|
+
icon: toSfSymbol(action.sfSymbol),
|
|
51
|
+
identifier: action.id,
|
|
52
|
+
label: action.title,
|
|
53
|
+
menu: { items: action.menu.map(toNativeMenuAction) },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (action.sfSymbol === null) {
|
|
57
|
+
return { type: 'custom', element: action.render() };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
type: 'button',
|
|
61
|
+
accessibilityLabel: action.title,
|
|
62
|
+
disabled: action.disabled,
|
|
63
|
+
icon: { type: 'sfSymbol', name: action.sfSymbol },
|
|
64
|
+
identifier: action.id,
|
|
65
|
+
label: action.title,
|
|
66
|
+
onPress: action.onPress,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// One side of the header (left or right): the Screen's slot blocks, then its actions on that
|
|
71
|
+
// side. On iOS, a side with actions is a list of native bar items (the slot as a custom item); a
|
|
72
|
+
// side with only the slot is its custom view. Elsewhere, a side is a row of the slot and the
|
|
73
|
+
// actions as icon buttons. A side with neither is left out, so a drawer's menu button stays.
|
|
74
|
+
function getSideOptions({ actions, platform, side, slot }) {
|
|
75
|
+
const sideActions = actions.filter((action) => action.placement === side);
|
|
76
|
+
if (sideActions.length === 0 && slot === undefined) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
const name = SIDES[side];
|
|
80
|
+
if (platform === 'ios') {
|
|
81
|
+
if (sideActions.length === 0) {
|
|
82
|
+
return { [`header${name}`]: slot, [`unstable_header${name}Items`]: undefined };
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
[`header${name}`]: undefined,
|
|
86
|
+
[`unstable_header${name}Items`]: () => [
|
|
87
|
+
...(slot === undefined ? [] : [{ type: 'custom', element: slot() }]),
|
|
88
|
+
...sideActions.map(toNativeItem),
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
[`header${name}`]: () => (
|
|
94
|
+
<View style={{ alignItems: 'center', columnGap: 4, flexDirection: 'row' }}>
|
|
95
|
+
{slot === undefined ? null : slot()}
|
|
96
|
+
{sideActions.map((action) => action.render())}
|
|
97
|
+
</View>
|
|
98
|
+
),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The native search bar's options from a Screen's headerSearch: its text events carry the text
|
|
103
|
+
// in nativeEvent.text. searchBarRef lets the screen focus it (autoFocus).
|
|
104
|
+
function getSearchOptions({ headerSearch, searchBarRef }) {
|
|
105
|
+
if (headerSearch === undefined) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
autoFocus: headerSearch.autoFocus,
|
|
110
|
+
hideWhenScrolling: headerSearch.hideWhenScrolling,
|
|
111
|
+
onCancelButtonPress: () => headerSearch.onCancel(),
|
|
112
|
+
onChangeText: (event) => headerSearch.onChangeText(event.nativeEvent.text),
|
|
113
|
+
onSearchButtonPress: (event) => headerSearch.onSearch(event.nativeEvent.text),
|
|
114
|
+
placeholder: headerSearch.placeholder,
|
|
115
|
+
ref: searchBarRef,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A Screen with a backgroundTint on iOS: a clear bar the page scrolls under, so the tint shows
|
|
120
|
+
// behind the status bar, the bar, the large title and the search field. iOS 26's glass bar is
|
|
121
|
+
// clear already, with the system's scroll edge effect under it once the page scrolls; before
|
|
122
|
+
// iOS 26 the bar stays clear at the scroll edge (the large title's appearance, which a standard
|
|
123
|
+
// title's scroll edge takes too) and takes the system chrome material once content scrolls
|
|
124
|
+
// under it. Elsewhere the bar is the stack's own: the options are set to undefined, since
|
|
125
|
+
// navigation.setOptions merges, so a tint that goes gives the bar back.
|
|
126
|
+
function getTintOptions({ backgroundTint, platform, platformVersion }) {
|
|
127
|
+
if (backgroundTint !== true || platform !== 'ios') {
|
|
128
|
+
return {
|
|
129
|
+
headerBlurEffect: undefined,
|
|
130
|
+
headerLargeStyle: undefined,
|
|
131
|
+
headerTransparent: undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (Number.parseInt(`${platformVersion}`, 10) >= 26) {
|
|
135
|
+
return { headerBlurEffect: undefined, headerLargeStyle: undefined, headerTransparent: true };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
headerBlurEffect: 'systemChromeMaterial',
|
|
139
|
+
headerLargeStyle: { backgroundColor: 'transparent' },
|
|
140
|
+
headerTransparent: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The native stack options for a Screen page's header options (client-native screen/Screen.js):
|
|
146
|
+
* the title (a large title on iOS that collapses into the bar as the page scrolls), the
|
|
147
|
+
* presentation, a native search bar, and the header's slots and icon buttons at their natural
|
|
148
|
+
* size, as native bar buttons on iOS. A page with a headerBottom hides the bar's own hairline:
|
|
149
|
+
* the pinned strip under the bar draws one under itself. A page with a backgroundTint has a clear
|
|
150
|
+
* bar on iOS (getTintOptions).
|
|
151
|
+
*
|
|
152
|
+
* @param {Object} params
|
|
153
|
+
* @param {Object} params.options What the Screen reported.
|
|
154
|
+
* @param {string} params.platform Platform.OS.
|
|
155
|
+
* @param {string|number} [params.platformVersion] Platform.Version: iOS gives a string, "26.0".
|
|
156
|
+
* @param {Object} params.searchBarRef The ref the search bar's commands (focus) are set on.
|
|
157
|
+
*/
|
|
158
|
+
function getStackOptions({ options, platform, platformVersion, searchBarRef }) {
|
|
159
|
+
const {
|
|
160
|
+
backgroundTint,
|
|
161
|
+
headerActions,
|
|
162
|
+
headerBottom,
|
|
163
|
+
headerLeft,
|
|
164
|
+
headerRight,
|
|
165
|
+
headerSearch,
|
|
166
|
+
largeTitle,
|
|
167
|
+
presentation,
|
|
168
|
+
title,
|
|
169
|
+
} = options;
|
|
170
|
+
return {
|
|
171
|
+
headerBackVisible: true,
|
|
172
|
+
// Undefined keeps the stack's own default.
|
|
173
|
+
headerShadowVisible: headerBottom === true ? false : undefined,
|
|
174
|
+
headerLargeTitleEnabled: largeTitle,
|
|
175
|
+
headerLargeTitleShadowVisible: false,
|
|
176
|
+
headerSearchBarOptions: getSearchOptions({ headerSearch, searchBarRef }),
|
|
177
|
+
presentation: getPresentation(presentation),
|
|
178
|
+
title: title ?? '',
|
|
179
|
+
...getSideOptions({ actions: headerActions, platform, side: 'left', slot: headerLeft }),
|
|
180
|
+
...getSideOptions({ actions: headerActions, platform, side: 'right', slot: headerRight }),
|
|
181
|
+
...getTintOptions({ backgroundTint, platform, platformVersion }),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default getStackOptions;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import getFrame from './getFrame.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The router href of the tab whose page is at `path`, with the query as the tab's params, or null
|
|
21
|
+
* when no tab shows that page (a stack or drawer frame, or a page no tab has). A tab's own page is
|
|
22
|
+
* shown on its tab, never stacked on another tab's screens, so the tab bar shows the tab that
|
|
23
|
+
* holds the page on screen.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} params
|
|
26
|
+
* @param {string} params.path The page path, without the leading "/".
|
|
27
|
+
* @param {string} [params.query] The query string, without "?".
|
|
28
|
+
* @param {Object} params.root The app's mobile root.
|
|
29
|
+
* @returns {string|null}
|
|
30
|
+
*/
|
|
31
|
+
function getTabHref({ path, query, root }) {
|
|
32
|
+
const frame = getFrame(root?.menus);
|
|
33
|
+
if (frame.kind !== 'tabs') {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const slotIndex = frame.items.findIndex(
|
|
37
|
+
(item) => root.pageIndex.find((entry) => entry.pageId === item.pageId)?.path === path
|
|
38
|
+
);
|
|
39
|
+
if (slotIndex === -1) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return `/(t${slotIndex})${query ? `?${query}` : ''}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default getTabHref;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getSfSymbol } from '@lowdefy/client-native';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How each tab of a tabs frame draws its icon: on iOS, an SF Symbol (the item's `sfSymbol`, or
|
|
21
|
+
* the symbol mapped from its Lucide or semantic icon name); otherwise, or for an icon with no
|
|
22
|
+
* symbol, its Lucide icon as an image (TabIconImages), from the icon data the build adds to the
|
|
23
|
+
* item. A tab with no icon has none.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} params
|
|
26
|
+
* @param {Object[]} params.items The tabs frame's items.
|
|
27
|
+
* @param {string} params.platform Platform.OS.
|
|
28
|
+
* @returns {Object[]} Per item, `{ pageId, sf, iconData }`: `sf` an SF Symbol name or null, and
|
|
29
|
+
* `iconData` the data to draw as an image, or null.
|
|
30
|
+
*/
|
|
31
|
+
function getTabIcons({ items, platform }) {
|
|
32
|
+
return items.map((item) => {
|
|
33
|
+
const sf =
|
|
34
|
+
platform === 'ios' ? getSfSymbol({ icon: item.icon, sfSymbol: item.sfSymbol }) : null;
|
|
35
|
+
return {
|
|
36
|
+
iconData: sf === null ? item.iconData ?? null : null,
|
|
37
|
+
pageId: item.pageId,
|
|
38
|
+
sf,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default getTabIcons;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether a Screen page scrolls under its navigation bar, as the native stack lays it out: on iOS
|
|
19
|
+
* 26 and later the bar is clear Liquid Glass over the page; before that, iOS makes the bar
|
|
20
|
+
* translucent over the page for a large title or a search bar, and a Screen with a backgroundTint
|
|
21
|
+
* has a clear bar over the page (getStackOptions). Otherwise (and on Android) the page
|
|
22
|
+
* sits below the bar. A page under the bar starts its content at the bar's bottom edge with
|
|
23
|
+
* automatic content insets, so a headerBottom pins under the bar at the header height; a page
|
|
24
|
+
* below the bar pins it at the top of its scroll view.
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} params
|
|
27
|
+
* @param {Object} params.options The Screen's header options (client-native screen/Screen.js).
|
|
28
|
+
* @param {string} params.platform Platform.OS.
|
|
29
|
+
* @param {string|number} params.platformVersion Platform.Version: iOS gives a string, "26.0".
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
function isPageUnderHeader({ options, platform, platformVersion }) {
|
|
33
|
+
if (platform !== 'ios') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (Number.parseInt(`${platformVersion}`, 10) >= 26) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return (
|
|
40
|
+
options.backgroundTint === true ||
|
|
41
|
+
options.largeTitle === true ||
|
|
42
|
+
options.headerSearch !== undefined
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default isPageUnderHeader;
|