@resistdesign/voltra 3.0.0-alpha.26 → 3.0.0-alpha.27

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 CHANGED
@@ -218,6 +218,14 @@ How it works:
218
218
  - Strategy is auto-selected:
219
219
  - DOM + History API => browser history strategy.
220
220
  - Otherwise => in-memory native strategy.
221
+ - Native strategy automatically wires Android hardware back to route history.
222
+ - If Voltra can go back (`history.index > 0`), it consumes the event and navigates back.
223
+ - If Voltra cannot go back, the event is not consumed so OS/native container behavior continues.
224
+
225
+ Optional back affordances:
226
+
227
+ - `adapter.back?.()` navigates backward when supported.
228
+ - `adapter.canGoBack?.()` reports whether back navigation is currently possible.
221
229
 
222
230
  Escape hatches (root-only):
223
231
 
package/app/index.js CHANGED
@@ -603,6 +603,20 @@ var computeTrackPixels = ({
603
603
  };
604
604
 
605
605
  // src/app/utils/History.ts
606
+ var createHistoryBackHandler = (history) => {
607
+ return {
608
+ /**
609
+ * @returns True when back navigation was handled by history.
610
+ */
611
+ handle: () => {
612
+ if (history.index > 0) {
613
+ history.back();
614
+ return true;
615
+ }
616
+ return false;
617
+ }
618
+ };
619
+ };
606
620
  var ensurePrefix = (value, prefix) => value ? value.startsWith(prefix) ? value : `${prefix}${value}` : "";
607
621
  var parseHistoryPath = (inputPath) => {
608
622
  const raw = String(inputPath ?? "").trim();
@@ -756,7 +770,9 @@ var createRouteAdapterFromHistory = (history) => {
756
770
  },
757
771
  replace: (path) => {
758
772
  history.replace(path, { replaceSearch: true });
759
- }
773
+ },
774
+ back: history.back,
775
+ canGoBack: () => history.index > 0
760
776
  };
761
777
  };
762
778
 
@@ -770,6 +786,37 @@ var getWindow = () => {
770
786
  }
771
787
  return void 0;
772
788
  };
789
+ var getRuntimeRequire = () => {
790
+ const runtimeRequire = globalThis.__voltra_require__;
791
+ if (typeof runtimeRequire === "function") {
792
+ return runtimeRequire;
793
+ }
794
+ try {
795
+ return (0, eval)("require");
796
+ } catch (error) {
797
+ return void 0;
798
+ }
799
+ };
800
+ var tryGetReactNativeBackHandler = () => {
801
+ const runtimeRequire = getRuntimeRequire();
802
+ if (!runtimeRequire) {
803
+ return void 0;
804
+ }
805
+ try {
806
+ const reactNativeModule = runtimeRequire("react-native");
807
+ const platform = reactNativeModule?.Platform;
808
+ const backHandler = reactNativeModule?.BackHandler;
809
+ if (platform?.OS !== "android") {
810
+ return void 0;
811
+ }
812
+ if (typeof backHandler?.addEventListener !== "function") {
813
+ return void 0;
814
+ }
815
+ return backHandler;
816
+ } catch (error) {
817
+ return void 0;
818
+ }
819
+ };
773
820
  var canUseBrowserHistory = () => {
774
821
  const WINDOW = getWindow();
775
822
  return Boolean(
@@ -823,7 +870,9 @@ var createBrowserRouteAdapter = () => {
823
870
  }
824
871
  WINDOW.history.replaceState({}, title, path);
825
872
  notify();
826
- }
873
+ },
874
+ back: () => WINDOW?.history?.back(),
875
+ canGoBack: () => (WINDOW?.history?.length ?? 0) > 1
827
876
  };
828
877
  };
829
878
  var createNativeRouteAdapter = (initialPath = "/", ingress) => {
@@ -832,7 +881,8 @@ var createNativeRouteAdapter = (initialPath = "/", ingress) => {
832
881
  const history = createMemoryHistory(initialPath);
833
882
  const adapter = createRouteAdapterFromHistory(history);
834
883
  let stopIngress;
835
- let started = false;
884
+ let stopBackHandler;
885
+ let ingressStarted = false;
836
886
  let subscribers = 0;
837
887
  const applyPath = (path, mode) => {
838
888
  const normalizedPath = parseHistoryPath(path).path;
@@ -846,10 +896,10 @@ var createNativeRouteAdapter = (initialPath = "/", ingress) => {
846
896
  history.replace(normalizedPath, { replaceSearch: true });
847
897
  };
848
898
  const startIngress = async () => {
849
- if (started || !ingress) {
899
+ if (ingressStarted || !ingress) {
850
900
  return;
851
901
  }
852
- started = true;
902
+ ingressStarted = true;
853
903
  const startKey = history.location.key;
854
904
  const startIndex = history.index;
855
905
  if (ingress.subscribe) {
@@ -870,6 +920,31 @@ var createNativeRouteAdapter = (initialPath = "/", ingress) => {
870
920
  }
871
921
  }
872
922
  };
923
+ const startBackHandler = () => {
924
+ if (stopBackHandler) {
925
+ return;
926
+ }
927
+ const reactNativeBackHandler = tryGetReactNativeBackHandler();
928
+ if (!reactNativeBackHandler) {
929
+ return;
930
+ }
931
+ const historyBackHandler = createHistoryBackHandler(history);
932
+ const handleHardwareBackPress = () => historyBackHandler.handle();
933
+ const subscription = reactNativeBackHandler.addEventListener(
934
+ "hardwareBackPress",
935
+ handleHardwareBackPress
936
+ );
937
+ stopBackHandler = () => {
938
+ if (typeof subscription?.remove === "function") {
939
+ subscription.remove();
940
+ return;
941
+ }
942
+ reactNativeBackHandler.removeEventListener?.(
943
+ "hardwareBackPress",
944
+ handleHardwareBackPress
945
+ );
946
+ };
947
+ };
873
948
  return {
874
949
  ...adapter,
875
950
  push: (path, title) => {
@@ -888,18 +963,23 @@ var createNativeRouteAdapter = (initialPath = "/", ingress) => {
888
963
  subscribers += 1;
889
964
  if (subscribers === 1) {
890
965
  void startIngress();
966
+ startBackHandler();
891
967
  }
892
968
  const unlisten = adapter.subscribe(listener);
893
969
  return () => {
894
970
  unlisten();
895
971
  subscribers = Math.max(0, subscribers - 1);
896
- if (subscribers === 0 && stopIngress) {
897
- stopIngress();
972
+ if (subscribers === 0) {
973
+ stopIngress?.();
898
974
  stopIngress = void 0;
899
- started = false;
975
+ ingressStarted = false;
976
+ stopBackHandler?.();
977
+ stopBackHandler = void 0;
900
978
  }
901
979
  };
902
- }
980
+ },
981
+ back: adapter.back,
982
+ canGoBack: adapter.canGoBack
903
983
  };
904
984
  };
905
985
  var createUniversalAdapter = (options = {}) => {
@@ -1735,4 +1815,4 @@ var AutoForm = ({
1735
1815
  );
1736
1816
  };
1737
1817
 
1738
- export { ApplicationStateContext, ApplicationStateProvider, AutoForm, AutoFormView, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, TypeInfoORMClient, buildHistoryPath, buildQueryString, buildRoutePath, canUseBrowserHistory, computeAreaBounds, computeTrackPixels, createAutoField, createBrowserRouteAdapter, createEasyLayout, createFormRenderer, createManualRouteAdapter, createMemoryHistory, createNativeRouteAdapter, createRouteAdapterFromHistory, createUniversalAdapter, getApplicationStateIdentifier, getApplicationStateModified, getApplicationStateValue, getApplicationStateValueStructure, getChangedDependencyIndexes, getEasyLayoutTemplateDetails, getFieldKind, getFullUrl, getPascalCaseAreaName, handleRequest, mergeSuites, parseHistoryPath, parseTemplate, requestHandlerFactory, resolveSuite, sendServiceRequest, setApplicationStateModified, setApplicationStateValue, useApplicationStateLoader, useApplicationStateValue, useApplicationStateValueStructure, useController, useDebugDependencies, useFormEngine, useRouteContext, useTypeInfoORMAPI, validateAreas, withRendererOverride };
1818
+ export { ApplicationStateContext, ApplicationStateProvider, AutoForm, AutoFormView, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, TypeInfoORMClient, buildHistoryPath, buildQueryString, buildRoutePath, canUseBrowserHistory, computeAreaBounds, computeTrackPixels, createAutoField, createBrowserRouteAdapter, createEasyLayout, createFormRenderer, createHistoryBackHandler, createManualRouteAdapter, createMemoryHistory, createNativeRouteAdapter, createRouteAdapterFromHistory, createUniversalAdapter, getApplicationStateIdentifier, getApplicationStateModified, getApplicationStateValue, getApplicationStateValueStructure, getChangedDependencyIndexes, getEasyLayoutTemplateDetails, getFieldKind, getFullUrl, getPascalCaseAreaName, handleRequest, mergeSuites, parseHistoryPath, parseTemplate, requestHandlerFactory, resolveSuite, sendServiceRequest, setApplicationStateModified, setApplicationStateValue, tryGetReactNativeBackHandler, useApplicationStateLoader, useApplicationStateValue, useApplicationStateValueStructure, useController, useDebugDependencies, useFormEngine, useRouteContext, useTypeInfoORMAPI, validateAreas, withRendererOverride };
@@ -98,6 +98,23 @@ export type HistoryController = {
98
98
  */
99
99
  listen: (listener: HistoryListener) => () => void;
100
100
  };
101
+ /**
102
+ * Back-navigation consumption helper for shared history controllers.
103
+ *
104
+ * Returns `true` only when history consumed the back action.
105
+ *
106
+ * Example:
107
+ * ```ts
108
+ * const handler = createHistoryBackHandler(history);
109
+ * const consumed = handler.handle();
110
+ * ```
111
+ */
112
+ export declare const createHistoryBackHandler: (history: HistoryController) => {
113
+ /**
114
+ * @returns True when back navigation was handled by history.
115
+ */
116
+ handle: () => boolean;
117
+ };
101
118
  /**
102
119
  * Parse a path-like value into normalized path/search/hash parts.
103
120
  *
@@ -18,6 +18,10 @@ export type RouteAdapter = {
18
18
  push?: (path: string, title?: string) => void;
19
19
  /** Optional navigation helper for adapters that can replace state. */
20
20
  replace?: (path: string, title?: string) => void;
21
+ /** Optional navigation helper for adapters that can go backward. */
22
+ back?: () => void;
23
+ /** Optional capability check for backward navigation. */
24
+ canGoBack?: () => boolean;
21
25
  };
22
26
  /**
23
27
  * Supported query value types for route serialization.
@@ -55,6 +55,16 @@ export type UniversalRouteIngress = {
55
55
  */
56
56
  mapURLToPath?: (url: string) => string;
57
57
  };
58
+ type ReactNativeBackHandler = {
59
+ addEventListener: (eventName: "hardwareBackPress", listener: () => boolean) => {
60
+ remove?: () => void;
61
+ } | void;
62
+ removeEventListener?: (eventName: "hardwareBackPress", listener: () => boolean) => void;
63
+ };
64
+ /**
65
+ * Safely resolve React Native BackHandler for Android runtimes only.
66
+ */
67
+ export declare const tryGetReactNativeBackHandler: () => ReactNativeBackHandler | undefined;
58
68
  /**
59
69
  * Detect whether browser history is available at runtime.
60
70
  */
@@ -71,3 +81,4 @@ export declare const createNativeRouteAdapter: (initialPath?: string, ingress?:
71
81
  * Create a runtime-selected RouteAdapter for web/native environments.
72
82
  */
73
83
  export declare const createUniversalAdapter: (options?: CreateUniversalAdapterOptions) => RouteAdapter;
84
+ export {};
package/native/index.js CHANGED
@@ -721,6 +721,20 @@ var AutoForm2 = (props) => {
721
721
  };
722
722
 
723
723
  // src/app/utils/History.ts
724
+ var createHistoryBackHandler = (history) => {
725
+ return {
726
+ /**
727
+ * @returns True when back navigation was handled by history.
728
+ */
729
+ handle: () => {
730
+ if (history.index > 0) {
731
+ history.back();
732
+ return true;
733
+ }
734
+ return false;
735
+ }
736
+ };
737
+ };
724
738
  var ensurePrefix = (value, prefix) => value ? value.startsWith(prefix) ? value : `${prefix}${value}` : "";
725
739
  var parseHistoryPath = (inputPath) => {
726
740
  const raw = String(inputPath ?? "").trim();
@@ -1252,20 +1266,7 @@ var createNativeHistory = (options = {}) => {
1252
1266
  }
1253
1267
  };
1254
1268
  };
1255
- var createNativeBackHandler = (history) => {
1256
- return {
1257
- /**
1258
- * @returns True when back navigation was handled by history.
1259
- */
1260
- handle: () => {
1261
- if (history.index > 0) {
1262
- history.back();
1263
- return true;
1264
- }
1265
- return false;
1266
- }
1267
- };
1268
- };
1269
+ var createNativeBackHandler = (history) => createHistoryBackHandler(history);
1269
1270
 
1270
1271
  // src/common/Routing.ts
1271
1272
  var PATH_DELIMITER = "/";
@@ -1356,4 +1357,4 @@ var buildPathFromRouteChain = (routeChain, config, query) => {
1356
1357
  return buildRoutePath(segments, query);
1357
1358
  };
1358
1359
 
1359
- export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, buildHistoryPath, buildPathFromRouteChain, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout };
1360
+ export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, buildHistoryPath, buildPathFromRouteChain, createHistoryBackHandler, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout };
@@ -95,19 +95,8 @@ export declare const mapNativeURLToPath: (url: string) => string;
95
95
  */
96
96
  export declare const createNativeHistory: (options?: CreateNativeHistoryOptions) => NativeHistoryController;
97
97
  /**
98
- * Create an Android back-handler helper for native history.
99
- *
100
- * Returns `true` only when history consumed the back action.
101
- *
102
- * Example:
103
- * ```ts
104
- * const handler = createNativeBackHandler(history);
105
- * const consumed = handler.handle();
106
- * ```
98
+ * @deprecated Use {@link createHistoryBackHandler} from `app/utils/History`.
107
99
  */
108
100
  export declare const createNativeBackHandler: (history: HistoryController) => {
109
- /**
110
- * @returns True when back navigation was handled by history.
111
- */
112
101
  handle: () => boolean;
113
102
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resistdesign/voltra",
3
- "version": "3.0.0-alpha.26",
3
+ "version": "3.0.0-alpha.27",
4
4
  "description": "With our powers combined!",
5
5
  "homepage": "https://voltra.app",
6
6
  "repository": "git@github.com:resistdesign/voltra.git",