@resistdesign/voltra 3.0.0-alpha.20 → 3.0.0-alpha.22

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
@@ -125,13 +125,15 @@ EasyLayout now has:
125
125
  - Web rendering via CSS Grid in `@resistdesign/voltra/web`.
126
126
  - Native coordinate computation in `@resistdesign/voltra/native`.
127
127
 
128
- ### Examples
128
+ ### Reference Examples
129
129
 
130
- - `examples/api.ts`
131
- - `examples/common.ts`
132
- - `examples/web.ts`
133
- - `examples/native.ts`
134
- - `examples/build.ts`
130
+ - Index: `examples/README.md`
131
+ - Client routing: `examples/routing/app-routing.ts`
132
+ - Backend API routing: `examples/api/backend-routing.ts`
133
+ - Forms: `examples/forms/`
134
+ - Layout: `examples/layout/`
135
+ - Common types: `examples/common/types.ts`
136
+ - Build-time parsing: `examples/build/type-parsing.ts`
135
137
 
136
138
  ### Template syntax
137
139
 
@@ -189,50 +191,43 @@ const coords = layout.computeNativeCoords({
189
191
 
190
192
  ## Routing (Web + Native)
191
193
 
192
- Voltra ships a render-agnostic Route core in `@resistdesign/voltra/app` plus platform adapters.
194
+ Voltra routing is unified under `@resistdesign/voltra/app`.
193
195
 
194
- Web usage (auto-wires `window.history`):
196
+ Reference example: `examples/routing/app-routing.ts`
195
197
 
196
198
  ```tsx
197
- import { Route } from "@resistdesign/voltra/web";
199
+ import { Route } from "@resistdesign/voltra/app";
200
+
201
+ <Route>
202
+ <Route path="/" exact>
203
+ <HomeScreen />
204
+ </Route>
205
+ <Route path="/login" exact>
206
+ <LoginScreen />
207
+ </Route>
208
+ <Route path="/signup" exact>
209
+ <SignUpScreen />
210
+ </Route>
211
+ </Route>;
198
212
  ```
199
213
 
200
- Native usage (adapter-driven):
214
+ How it works:
201
215
 
202
- ```tsx
203
- import { Route, RouteProvider, createManualRouteAdapter } from "@resistdesign/voltra/native";
204
- const { adapter, updatePath } = createManualRouteAdapter("/home");
205
- ```
216
+ - Root `<Route>` (no `path`) is provider mode.
217
+ - Nested `<Route path="...">` entries are matcher mode.
218
+ - Strategy is auto-selected:
219
+ - DOM + History API => browser history strategy.
220
+ - Otherwise => in-memory native strategy.
206
221
 
207
- For React Native navigation libraries, Voltra is optimized for react-navigation as the primary native default. Provide a RouteAdapter that maps navigation state to a path and call `RouteProvider`.
222
+ Escape hatches (root-only):
208
223
 
209
- Native navigation mapping example:
224
+ - `initialPath` sets fallback startup path when no ingress URL exists.
225
+ - `adapter` allows full custom adapter control.
226
+ - `ingress` supports deep-link ingress wiring (`getInitialURL`, `subscribe`, URL mapping, push/replace mode).
210
227
 
211
- ```tsx
212
- import { buildPathFromRouteChain, createNavigationStateRouteAdapter } from "@resistdesign/voltra/native";
213
-
214
- const adapter = createNavigationStateRouteAdapter({
215
- getState: () => navigationRef.getRootState(),
216
- subscribe: (listener) => navigationRef.addListener("state", listener),
217
- toPath: (state) =>
218
- buildPathFromRouteChain(
219
- state.routes.map((route) => ({
220
- name: route.name,
221
- params: route.params as Record<string, any>,
222
- })),
223
- {
224
- Home: "home",
225
- Book: "books/:id",
226
- },
227
- ),
228
- navigate: (path) => {
229
- const routeName = path === "/home" ? "Home" : "Book";
230
- navigationRef.navigate(routeName);
231
- },
232
- });
233
- ```
228
+ If you are looking for backend request routing (Cloud Function/API event routing), see:
234
229
 
235
- For RN web builds, keep your navigation library linking config in sync with the same route patterns used in `buildPathFromRouteChain`.
230
+ - `examples/api/backend-routing.ts`
236
231
 
237
232
  ## Form Suites (Web + Native + BYOCS)
238
233
 
package/api/index.d.ts CHANGED
@@ -45,6 +45,10 @@
45
45
  * ```
46
46
  *
47
47
  * See also: `@resistdesign/voltra/app` for client-side app helpers.
48
+ *
49
+ * Reference examples:
50
+ * - `examples/README.md`
51
+ * - `examples/api/backend-routing.ts`
48
52
  */
49
53
  /**
50
54
  * @category api
package/app/index.d.ts CHANGED
@@ -17,6 +17,10 @@
17
17
  * @see {@link useApplicationStateValueStructure} and
18
18
  * {@link useApplicationStateLoader} for good starting points.
19
19
  *
20
+ * Reference examples:
21
+ * - `examples/README.md`
22
+ * - `examples/routing/app-routing.ts`
23
+ *
20
24
  * @example
21
25
  * ```tsx
22
26
  * import {
package/app/index.js CHANGED
@@ -157,6 +157,23 @@ var mergeStringPaths = (path1, path2, delimiter = PATH_DELIMITER, filterEmptyOut
157
157
  useJson,
158
158
  uriEncodeParts
159
159
  );
160
+ var resolvePath = (currentPath, newPath) => {
161
+ const newSegments = getPathArray(newPath, PATH_DELIMITER, true);
162
+ let currentSegments = getPathArray(currentPath, PATH_DELIMITER, true);
163
+ if (newPath.startsWith("/")) {
164
+ currentSegments = [];
165
+ }
166
+ newSegments.forEach((segment) => {
167
+ if (segment === "..") {
168
+ if (currentSegments.length > 0) {
169
+ currentSegments.pop();
170
+ }
171
+ } else if (segment !== ".") {
172
+ currentSegments.push(segment);
173
+ }
174
+ });
175
+ return "/" + currentSegments.join("/");
176
+ };
160
177
  var getParamsAndTestPath = (path, testPath, exact = false) => {
161
178
  const pathList = getPathArray(path);
162
179
  const testPathList = getPathArray(testPath);
@@ -724,6 +741,177 @@ var createMemoryHistory = (initialPath = "/") => {
724
741
  }
725
742
  };
726
743
  };
744
+
745
+ // src/app/utils/RouteHistory.ts
746
+ var createRouteAdapterFromHistory = (history) => {
747
+ return {
748
+ getPath: () => history.location.path,
749
+ subscribe: (listener) => {
750
+ return history.listen((location) => {
751
+ listener(location.path);
752
+ });
753
+ },
754
+ push: (path) => {
755
+ history.push(path, { replaceSearch: true });
756
+ },
757
+ replace: (path) => {
758
+ history.replace(path, { replaceSearch: true });
759
+ }
760
+ };
761
+ };
762
+
763
+ // src/app/utils/UniversalRouteAdapter.ts
764
+ var getWindow = () => {
765
+ if (typeof globalThis === "undefined") {
766
+ return void 0;
767
+ }
768
+ if ("window" in globalThis) {
769
+ return globalThis.window;
770
+ }
771
+ return void 0;
772
+ };
773
+ var canUseBrowserHistory = () => {
774
+ const WINDOW = getWindow();
775
+ return Boolean(
776
+ WINDOW && WINDOW.location && WINDOW.history && typeof WINDOW.history.pushState === "function"
777
+ );
778
+ };
779
+ var createBrowserRouteAdapter = () => {
780
+ const WINDOW = getWindow();
781
+ const listeners = /* @__PURE__ */ new Set();
782
+ const notify = () => {
783
+ const path = WINDOW?.location?.pathname ?? "";
784
+ listeners.forEach((listener) => listener(path));
785
+ };
786
+ const handleHistoryEvent = () => {
787
+ notify();
788
+ };
789
+ return {
790
+ getPath: () => WINDOW?.location?.pathname ?? "",
791
+ subscribe: (listener) => {
792
+ listeners.add(listener);
793
+ if (WINDOW) {
794
+ WINDOW.addEventListener("popstate", handleHistoryEvent);
795
+ WINDOW.addEventListener("statechanged", handleHistoryEvent);
796
+ }
797
+ return () => {
798
+ listeners.delete(listener);
799
+ if (WINDOW) {
800
+ WINDOW.removeEventListener("popstate", handleHistoryEvent);
801
+ WINDOW.removeEventListener("statechanged", handleHistoryEvent);
802
+ }
803
+ };
804
+ },
805
+ push: (path, title = "") => {
806
+ if (!WINDOW?.history) {
807
+ return;
808
+ }
809
+ const targetPath = parseHistoryPath(path).path;
810
+ if (targetPath === (WINDOW.location?.pathname ?? "")) {
811
+ return;
812
+ }
813
+ WINDOW.history.pushState({}, title, path);
814
+ notify();
815
+ },
816
+ replace: (path, title = "") => {
817
+ if (!WINDOW?.history?.replaceState) {
818
+ return;
819
+ }
820
+ const targetPath = parseHistoryPath(path).path;
821
+ if (targetPath === (WINDOW.location?.pathname ?? "")) {
822
+ return;
823
+ }
824
+ WINDOW.history.replaceState({}, title, path);
825
+ notify();
826
+ }
827
+ };
828
+ };
829
+ var createNativeRouteAdapter = (initialPath = "/", ingress) => {
830
+ const mapURLToPath = ingress?.mapURLToPath ?? ((url) => buildHistoryPath(parseHistoryPath(url)));
831
+ const onIncomingURL = ingress?.onIncomingURL ?? "replace";
832
+ const history = createMemoryHistory(initialPath);
833
+ const adapter = createRouteAdapterFromHistory(history);
834
+ let stopIngress;
835
+ let started = false;
836
+ let subscribers = 0;
837
+ const applyPath = (path, mode) => {
838
+ const normalizedPath = parseHistoryPath(path).path;
839
+ if (!normalizedPath || normalizedPath === history.location.path) {
840
+ return;
841
+ }
842
+ if (mode === "push") {
843
+ history.push(normalizedPath, { replaceSearch: true });
844
+ return;
845
+ }
846
+ history.replace(normalizedPath, { replaceSearch: true });
847
+ };
848
+ const startIngress = async () => {
849
+ if (started || !ingress) {
850
+ return;
851
+ }
852
+ started = true;
853
+ const startKey = history.location.key;
854
+ const startIndex = history.index;
855
+ if (ingress.subscribe) {
856
+ stopIngress = ingress.subscribe((url) => {
857
+ const nextPath = mapURLToPath(url);
858
+ if (!nextPath) {
859
+ return;
860
+ }
861
+ applyPath(nextPath, onIncomingURL);
862
+ });
863
+ }
864
+ const initialURL = await ingress.getInitialURL?.();
865
+ const userNavigated = history.location.key !== startKey || history.index !== startIndex;
866
+ if (!userNavigated && initialURL) {
867
+ const nextPath = mapURLToPath(initialURL);
868
+ if (nextPath) {
869
+ applyPath(nextPath, "replace");
870
+ }
871
+ }
872
+ };
873
+ return {
874
+ ...adapter,
875
+ push: (path, title) => {
876
+ if (parseHistoryPath(path).path === history.location.path) {
877
+ return;
878
+ }
879
+ adapter.push?.(path, title);
880
+ },
881
+ replace: (path, title) => {
882
+ if (parseHistoryPath(path).path === history.location.path) {
883
+ return;
884
+ }
885
+ adapter.replace?.(path, title);
886
+ },
887
+ subscribe: (listener) => {
888
+ subscribers += 1;
889
+ if (subscribers === 1) {
890
+ void startIngress();
891
+ }
892
+ const unlisten = adapter.subscribe(listener);
893
+ return () => {
894
+ unlisten();
895
+ subscribers = Math.max(0, subscribers - 1);
896
+ if (subscribers === 0 && stopIngress) {
897
+ stopIngress();
898
+ stopIngress = void 0;
899
+ started = false;
900
+ }
901
+ };
902
+ }
903
+ };
904
+ };
905
+ var createUniversalAdapter = (options = {}) => {
906
+ const { strategy = "auto", initialPath = "/", ingress } = options;
907
+ if (strategy === "web") {
908
+ return createBrowserRouteAdapter();
909
+ }
910
+ if (strategy === "native") {
911
+ return createNativeRouteAdapter(initialPath, ingress);
912
+ }
913
+ return canUseBrowserHistory() ? createBrowserRouteAdapter() : createNativeRouteAdapter(initialPath, ingress);
914
+ };
727
915
  var createManualRouteAdapter = (initialPath = "/") => {
728
916
  let currentPath = initialPath;
729
917
  const listeners = /* @__PURE__ */ new Set();
@@ -747,6 +935,10 @@ var createManualRouteAdapter = (initialPath = "/") => {
747
935
  updatePath
748
936
  };
749
937
  };
938
+ var isDevelopmentMode = () => {
939
+ const env = globalThis?.process?.env?.NODE_ENV;
940
+ return env !== "production";
941
+ };
750
942
  var buildQueryString = (query = {}) => {
751
943
  const parts = [];
752
944
  for (const [key, rawValue] of Object.entries(query)) {
@@ -788,6 +980,50 @@ var {
788
980
  Consumer: RouteContextConsumer
789
981
  } = RouteContext;
790
982
  var useRouteContext = () => useContext(RouteContext);
983
+ var getWindow2 = () => {
984
+ if (typeof globalThis === "undefined") {
985
+ return void 0;
986
+ }
987
+ if ("window" in globalThis) {
988
+ return globalThis.window;
989
+ }
990
+ return void 0;
991
+ };
992
+ var useBrowserLinkInterceptor = (adapter) => {
993
+ useEffect(() => {
994
+ const WINDOW = getWindow2();
995
+ if (!WINDOW || !adapter?.push) {
996
+ return void 0;
997
+ }
998
+ const handleAnchorClick = (event) => {
999
+ let target = event.target;
1000
+ while (target && target.nodeName !== "A") {
1001
+ target = target.parentNode;
1002
+ }
1003
+ if (!target || target.nodeName !== "A") {
1004
+ return;
1005
+ }
1006
+ const anchor = target;
1007
+ const href = anchor.getAttribute("href");
1008
+ const title = anchor.getAttribute("title") ?? "";
1009
+ if (!href) {
1010
+ return;
1011
+ }
1012
+ try {
1013
+ new URL(href);
1014
+ return;
1015
+ } catch (error) {
1016
+ const nextPath = resolvePath(WINDOW.location?.pathname ?? "", href);
1017
+ event.preventDefault();
1018
+ adapter.push?.(nextPath, title);
1019
+ }
1020
+ };
1021
+ WINDOW.document?.addEventListener("click", handleAnchorClick);
1022
+ return () => {
1023
+ WINDOW.document?.removeEventListener("click", handleAnchorClick);
1024
+ };
1025
+ }, [adapter]);
1026
+ };
791
1027
  var RouteProvider = ({
792
1028
  adapter,
793
1029
  initialPath,
@@ -813,11 +1049,11 @@ var RouteProvider = ({
813
1049
  );
814
1050
  return /* @__PURE__ */ jsx(RouteContextProvider, { value: contextValue, children });
815
1051
  };
816
- var Route = ({
1052
+ var RouteMatcher = ({
817
1053
  /**
818
1054
  * Use `:` as the first character to denote a parameter in the path.
819
1055
  */
820
- path = "",
1056
+ path,
821
1057
  onParamsChange,
822
1058
  exact = false,
823
1059
  children
@@ -864,23 +1100,56 @@ var Route = ({
864
1100
  }, [params, onParamsChange]);
865
1101
  return newParams ? /* @__PURE__ */ jsx(RouteContextProvider, { value: newRouteContext, children }) : null;
866
1102
  };
867
-
868
- // src/app/utils/RouteHistory.ts
869
- var createRouteAdapterFromHistory = (history) => {
870
- return {
871
- getPath: () => history.location.path,
872
- subscribe: (listener) => {
873
- return history.listen((location) => {
874
- listener(location.path);
875
- });
876
- },
877
- push: (path) => {
878
- history.push(path, { replaceSearch: true });
879
- },
880
- replace: (path) => {
881
- history.replace(path, { replaceSearch: true });
1103
+ var RouteRootProvider = ({
1104
+ children,
1105
+ adapter,
1106
+ initialPath,
1107
+ ingress
1108
+ }) => {
1109
+ const routeContext = useRouteContext();
1110
+ const autoAdapterRef = useRef(null);
1111
+ if (typeof routeContext.adapter !== "undefined" && isDevelopmentMode()) {
1112
+ throw new Error(
1113
+ "Route provider mode is root-only. Nested Route requires a path."
1114
+ );
1115
+ }
1116
+ if (!autoAdapterRef.current) {
1117
+ autoAdapterRef.current = adapter ?? createUniversalAdapter({ initialPath, ingress });
1118
+ }
1119
+ useBrowserLinkInterceptor(autoAdapterRef.current);
1120
+ return /* @__PURE__ */ jsx(RouteProvider, { adapter: autoAdapterRef.current, initialPath, children });
1121
+ };
1122
+ var Route = (props) => {
1123
+ if (typeof props.path === "undefined") {
1124
+ if ((typeof props.exact !== "undefined" || typeof props.onParamsChange !== "undefined") && isDevelopmentMode()) {
1125
+ throw new Error(
1126
+ "Route provider mode does not support matcher props. Set a path to use matcher behavior."
1127
+ );
882
1128
  }
883
- };
1129
+ return /* @__PURE__ */ jsx(
1130
+ RouteRootProvider,
1131
+ {
1132
+ adapter: props.adapter,
1133
+ initialPath: props.initialPath,
1134
+ ingress: props.ingress,
1135
+ children: props.children
1136
+ }
1137
+ );
1138
+ }
1139
+ if ((typeof props.initialPath !== "undefined" || typeof props.adapter !== "undefined" || typeof props.ingress !== "undefined") && isDevelopmentMode()) {
1140
+ throw new Error(
1141
+ "Route matcher mode does not support provider props. Remove initialPath/adapter/ingress or use a root Route without path."
1142
+ );
1143
+ }
1144
+ return /* @__PURE__ */ jsx(
1145
+ RouteMatcher,
1146
+ {
1147
+ path: props.path,
1148
+ onParamsChange: props.onParamsChange,
1149
+ exact: props.exact,
1150
+ children: props.children
1151
+ }
1152
+ );
884
1153
  };
885
1154
 
886
1155
  // src/common/IdGeneration/getSimpleId.ts
@@ -1383,4 +1652,4 @@ var useFormEngine = (initialValues = {}, typeInfo, options) => {
1383
1652
  };
1384
1653
  };
1385
1654
 
1386
- export { ApplicationStateContext, ApplicationStateProvider, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, TypeInfoORMClient, buildHistoryPath, buildQueryString, buildRoutePath, computeAreaBounds, computeTrackPixels, createAutoField, createEasyLayout, createFormRenderer, createManualRouteAdapter, createMemoryHistory, createRouteAdapterFromHistory, 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 };
1655
+ export { ApplicationStateContext, ApplicationStateProvider, 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 };
@@ -2,9 +2,10 @@
2
2
  * @packageDocumentation
3
3
  *
4
4
  * Render-agnostic routing helpers with nested Route contexts.
5
- * Supply a RouteAdapter via RouteProvider or a platform-specific wrapper.
5
+ * Supply a RouteAdapter via RouteProvider or use root Route provider mode.
6
6
  */
7
7
  import React, { PropsWithChildren } from "react";
8
+ import { type UniversalRouteIngress } from "./UniversalRouteAdapter";
8
9
  /**
9
10
  * Platform adapter that supplies the current path and change notifications.
10
11
  */
@@ -119,11 +120,17 @@ export type RouteProps<ParamsType extends Record<string, any>> = {
119
120
  * Require an exact match for the route path.
120
121
  */
121
122
  exact?: boolean;
123
+ /**
124
+ * Optional initial path override for root provider mode only.
125
+ */
126
+ initialPath?: string;
127
+ /**
128
+ * Optional adapter override for root provider mode only.
129
+ */
130
+ adapter?: RouteAdapter;
131
+ /**
132
+ * Optional external URL ingress for root provider mode only.
133
+ */
134
+ ingress?: UniversalRouteIngress;
122
135
  };
123
- /**
124
- * Organize nested routes with parameters.
125
- *
126
- * @typeParam ParamsType - Param shape for this route.
127
- * @param props - Route props including path, params handler, and children.
128
- */
129
- export declare const Route: <ParamsType extends Record<string, any>>({ path, onParamsChange, exact, children, }: PropsWithChildren<RouteProps<ParamsType>>) => import("react/jsx-runtime").JSX.Element | null;
136
+ export declare const Route: <ParamsType extends Record<string, any>>(props: PropsWithChildren<RouteProps<ParamsType>>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,73 @@
1
+ import type { RouteAdapter } from "./Route";
2
+ /**
3
+ * Runtime strategy for universal route adapters.
4
+ */
5
+ export type UniversalRouteStrategy = "auto" | "web" | "native";
6
+ /**
7
+ * Options for {@link createUniversalAdapter}.
8
+ */
9
+ export type CreateUniversalAdapterOptions = {
10
+ /**
11
+ * Runtime strategy selection.
12
+ *
13
+ * - `"auto"`: use web when DOM + History API exist, else native.
14
+ * - `"web"`: force browser history.
15
+ * - `"native"`: force in-memory history.
16
+ *
17
+ * Default: `"auto"`.
18
+ */
19
+ strategy?: UniversalRouteStrategy;
20
+ /**
21
+ * Initial path used by native strategy memory history.
22
+ *
23
+ * Default: `"/"`.
24
+ */
25
+ initialPath?: string;
26
+ /**
27
+ * Optional ingress hook for native strategy.
28
+ *
29
+ * Use this to bridge deep-link systems into the adapter.
30
+ */
31
+ ingress?: UniversalRouteIngress;
32
+ };
33
+ /**
34
+ * Ingress hook for applying external URLs/paths to native strategy.
35
+ */
36
+ export type UniversalRouteIngress = {
37
+ /**
38
+ * Optional initial URL/path provider.
39
+ */
40
+ getInitialURL?: () => Promise<string | null | undefined> | string | null | undefined;
41
+ /**
42
+ * Optional URL/path event subscription.
43
+ */
44
+ subscribe?: (listener: (url: string) => void) => () => void;
45
+ /**
46
+ * Incoming event application mode.
47
+ *
48
+ * Default: `"replace"`.
49
+ */
50
+ onIncomingURL?: "push" | "replace";
51
+ /**
52
+ * Optional mapper converting URL -> route path.
53
+ *
54
+ * Default maps using shared history path parsing.
55
+ */
56
+ mapURLToPath?: (url: string) => string;
57
+ };
58
+ /**
59
+ * Detect whether browser history is available at runtime.
60
+ */
61
+ export declare const canUseBrowserHistory: () => boolean;
62
+ /**
63
+ * Create a browser RouteAdapter backed by the History API.
64
+ */
65
+ export declare const createBrowserRouteAdapter: () => RouteAdapter;
66
+ /**
67
+ * Create an in-memory RouteAdapter for non-DOM runtimes.
68
+ */
69
+ export declare const createNativeRouteAdapter: (initialPath?: string, ingress?: UniversalRouteIngress) => RouteAdapter;
70
+ /**
71
+ * Create a runtime-selected RouteAdapter for web/native environments.
72
+ */
73
+ export declare const createUniversalAdapter: (options?: CreateUniversalAdapterOptions) => RouteAdapter;
@@ -18,6 +18,7 @@ export * from "./easy-layout";
18
18
  export * from "./History";
19
19
  export * from "./Route";
20
20
  export * from "./RouteHistory";
21
+ export * from "./UniversalRouteAdapter";
21
22
  export * from "./Service";
22
23
  export * from "./TypeInfoORMAPIUtils";
23
24
  export * from "./TypeInfoORMClient";
package/native/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createContext, useContext, createElement, useState, useEffect, useMemo, useRef } from 'react';
2
- import { jsx } from 'react/jsx-runtime';
1
+ import { createContext, createElement, useMemo } from 'react';
2
+ import 'react/jsx-runtime';
3
3
 
4
4
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
5
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -485,206 +485,6 @@ var createNativeFormRenderer = (options) => {
485
485
  });
486
486
  };
487
487
 
488
- // src/common/Routing.ts
489
- var PATH_DELIMITER = "/";
490
- var getPotentialJSONValue = (value) => {
491
- try {
492
- return JSON.parse(value);
493
- } catch (error) {
494
- return value;
495
- }
496
- };
497
- var getPathArray = (path, delimiter = PATH_DELIMITER, filterEmptyOutput = false, filterEmptyInput = true, useJson = true, uriDecodeParts = true) => path.split(delimiter).filter(filterEmptyInput ? (p) => p !== "" : () => true).map(uriDecodeParts ? decodeURIComponent : (x) => x).map(useJson ? getPotentialJSONValue : (p) => p).filter(filterEmptyOutput ? (p) => p ?? false : () => true);
498
- var getPathString = (parts = [], delimiter = PATH_DELIMITER, filterEmptyInput = false, useJson = true, uriEncodeParts = false) => parts.filter(filterEmptyInput ? (p) => p ?? false : () => true).map(useJson ? (p) => JSON.stringify(p) : (x) => x).map(uriEncodeParts ? encodeURIComponent : (x) => x).join(delimiter);
499
- var mergeStringPaths = (path1, path2, delimiter = PATH_DELIMITER, filterEmptyOutput = false, filterEmptyInput = true, useJson = true, uriEncodeParts = false) => getPathString(
500
- [
501
- ...getPathArray(
502
- path1,
503
- delimiter,
504
- filterEmptyOutput,
505
- filterEmptyInput,
506
- useJson,
507
- uriEncodeParts
508
- ),
509
- ...getPathArray(
510
- path2,
511
- delimiter,
512
- filterEmptyOutput,
513
- filterEmptyInput,
514
- useJson,
515
- uriEncodeParts
516
- )
517
- ],
518
- delimiter,
519
- filterEmptyInput,
520
- useJson,
521
- uriEncodeParts
522
- );
523
- var getParamsAndTestPath = (path, testPath, exact = false) => {
524
- const pathList = getPathArray(path);
525
- const testPathList = getPathArray(testPath);
526
- if (exact && pathList.length !== testPathList.length) {
527
- return false;
528
- } else {
529
- let params = {};
530
- if (pathList.length >= testPathList.length) {
531
- for (let i = 0; i < testPathList.length; i++) {
532
- const testPathPart = testPathList[i];
533
- const pathPart = pathList[i];
534
- if (testPathPart.startsWith(":")) {
535
- const paramName = testPathPart.slice(1);
536
- params[paramName] = pathPart;
537
- } else if (pathPart !== testPathPart) {
538
- return false;
539
- }
540
- }
541
- } else {
542
- return false;
543
- }
544
- return params;
545
- }
546
- };
547
- var createManualRouteAdapter = (initialPath = "/") => {
548
- let currentPath = initialPath;
549
- const listeners = /* @__PURE__ */ new Set();
550
- const updatePath = (nextPath) => {
551
- currentPath = nextPath;
552
- listeners.forEach((listener) => listener(nextPath));
553
- };
554
- const adapter = {
555
- getPath: () => currentPath,
556
- subscribe: (listener) => {
557
- listeners.add(listener);
558
- return () => {
559
- listeners.delete(listener);
560
- };
561
- },
562
- push: (path) => updatePath(path),
563
- replace: (path) => updatePath(path)
564
- };
565
- return {
566
- adapter,
567
- updatePath
568
- };
569
- };
570
- var buildQueryString = (query = {}) => {
571
- const parts = [];
572
- for (const [key, rawValue] of Object.entries(query)) {
573
- if (rawValue === void 0) {
574
- continue;
575
- }
576
- const values = Array.isArray(rawValue) ? rawValue : [rawValue];
577
- for (const value of values) {
578
- if (value === void 0) {
579
- continue;
580
- }
581
- const encodedKey = encodeURIComponent(key);
582
- const encodedValue = value === null ? "" : encodeURIComponent(String(value));
583
- parts.push(`${encodedKey}=${encodedValue}`);
584
- }
585
- }
586
- return parts.join("&");
587
- };
588
- var buildRoutePath = (segments, query) => {
589
- const normalizedSegments = segments.map((segment) => String(segment));
590
- const basePath = "/" + getPathString(normalizedSegments, "/", true, false, true);
591
- const queryString = query ? buildQueryString(query) : "";
592
- return queryString ? `${basePath}?${queryString}` : basePath;
593
- };
594
- var RouteContext = createContext({
595
- currentWindowPath: "",
596
- parentPath: "",
597
- params: {},
598
- isTopLevel: true
599
- });
600
- var {
601
- /**
602
- * @ignore
603
- */
604
- Provider: RouteContextProvider,
605
- /**
606
- * @ignore
607
- */
608
- Consumer: RouteContextConsumer
609
- } = RouteContext;
610
- var useRouteContext = () => useContext(RouteContext);
611
- var RouteProvider = ({
612
- adapter,
613
- initialPath,
614
- children
615
- }) => {
616
- const [currentPath, setCurrentPath] = useState(
617
- initialPath ?? adapter.getPath()
618
- );
619
- useEffect(() => {
620
- return adapter.subscribe((nextPath) => {
621
- setCurrentPath(nextPath);
622
- });
623
- }, [adapter]);
624
- const contextValue = useMemo(
625
- () => ({
626
- currentWindowPath: currentPath,
627
- parentPath: "",
628
- params: {},
629
- isTopLevel: true,
630
- adapter
631
- }),
632
- [currentPath, adapter]
633
- );
634
- return /* @__PURE__ */ jsx(RouteContextProvider, { value: contextValue, children });
635
- };
636
- var Route = ({
637
- /**
638
- * Use `:` as the first character to denote a parameter in the path.
639
- */
640
- path = "",
641
- onParamsChange,
642
- exact = false,
643
- children
644
- }) => {
645
- const {
646
- currentWindowPath = "",
647
- parentPath = "",
648
- params: parentParams = {},
649
- adapter
650
- } = useRouteContext();
651
- const targetCurrentPath = useMemo(
652
- () => currentWindowPath,
653
- [currentWindowPath]
654
- );
655
- const fullPath = useMemo(
656
- () => mergeStringPaths(parentPath, path),
657
- [parentPath, path]
658
- );
659
- const newParams = useMemo(
660
- () => getParamsAndTestPath(targetCurrentPath, fullPath, exact),
661
- [targetCurrentPath, fullPath, exact]
662
- );
663
- const params = useMemo(
664
- () => ({
665
- ...parentParams,
666
- ...newParams ? newParams : {}
667
- }),
668
- [parentParams, newParams]
669
- );
670
- const newRouteContext = useMemo(
671
- () => ({
672
- currentWindowPath: targetCurrentPath,
673
- parentPath: fullPath,
674
- params,
675
- isTopLevel: false,
676
- adapter
677
- }),
678
- [targetCurrentPath, fullPath, params, adapter]
679
- );
680
- useEffect(() => {
681
- if (onParamsChange) {
682
- onParamsChange(params);
683
- }
684
- }, [params, onParamsChange]);
685
- return newParams ? /* @__PURE__ */ jsx(RouteContextProvider, { value: newRouteContext, children }) : null;
686
- };
687
-
688
488
  // src/app/utils/History.ts
689
489
  var ensurePrefix = (value, prefix) => value ? value.startsWith(prefix) ? value : `${prefix}${value}` : "";
690
490
  var parseHistoryPath = (inputPath) => {
@@ -1188,7 +988,19 @@ var createNativeHistory = (options = {}) => {
1188
988
  const startKey = history.location.key;
1189
989
  const startIndex = history.index;
1190
990
  unsubscribe = adapter.subscribe((url) => {
1191
- applyIncomingURL(url);
991
+ const targetPath = mapURLToPath(url);
992
+ if (!targetPath) {
993
+ return;
994
+ }
995
+ const userNavigated = history.location.key !== startKey || history.index !== startIndex;
996
+ if (userNavigated && targetPath === initialPath) {
997
+ return;
998
+ }
999
+ if (onIncomingURL === "push") {
1000
+ history.push(targetPath, { replaceSearch: true });
1001
+ } else {
1002
+ history.replace(targetPath, { replaceSearch: true });
1003
+ }
1192
1004
  });
1193
1005
  const initialURL = await adapter.getInitialURL();
1194
1006
  if (history.location.key === startKey && history.index === startIndex) {
@@ -1220,56 +1032,57 @@ var createNativeBackHandler = (history) => {
1220
1032
  };
1221
1033
  };
1222
1034
 
1223
- // src/app/utils/RouteHistory.ts
1224
- var createRouteAdapterFromHistory = (history) => {
1225
- return {
1226
- getPath: () => history.location.path,
1227
- subscribe: (listener) => {
1228
- return history.listen((location) => {
1229
- listener(location.path);
1230
- });
1231
- },
1232
- push: (path) => {
1233
- history.push(path, { replaceSearch: true });
1234
- },
1235
- replace: (path) => {
1236
- history.replace(path, { replaceSearch: true });
1237
- }
1238
- };
1239
- };
1240
- var NativeRouteProvider = ({
1241
- children,
1242
- history,
1243
- ...historyOptions
1244
- }) => {
1245
- const historyRef = useRef(history ?? null);
1246
- if (!historyRef.current) {
1247
- historyRef.current = createNativeHistory(historyOptions);
1035
+ // src/common/Routing.ts
1036
+ var PATH_DELIMITER = "/";
1037
+ var getPotentialJSONValue = (value) => {
1038
+ try {
1039
+ return JSON.parse(value);
1040
+ } catch (error) {
1041
+ return value;
1248
1042
  }
1249
- const adapterRef = useRef(createRouteAdapterFromHistory(historyRef.current));
1250
- useEffect(() => {
1251
- const targetHistory = historyRef.current;
1252
- if (!targetHistory) {
1253
- return;
1254
- }
1255
- void targetHistory.start();
1256
- return () => {
1257
- targetHistory.stop();
1258
- };
1259
- }, []);
1260
- return /* @__PURE__ */ jsx(RouteProvider, { adapter: adapterRef.current, children });
1261
1043
  };
1262
- var NativeRoute = (props) => {
1263
- const routeContext = useRouteContext();
1264
- const hasAdapter = useMemo(
1265
- () => typeof routeContext.adapter !== "undefined",
1266
- [routeContext.adapter]
1267
- );
1268
- if (hasAdapter) {
1269
- return /* @__PURE__ */ jsx(Route, { ...props });
1044
+ var getPathArray = (path, delimiter = PATH_DELIMITER, filterEmptyOutput = false, filterEmptyInput = true, useJson = true, uriDecodeParts = true) => path.split(delimiter).filter(filterEmptyInput ? (p) => p !== "" : () => true).map(uriDecodeParts ? decodeURIComponent : (x) => x).map(useJson ? getPotentialJSONValue : (p) => p).filter(filterEmptyOutput ? (p) => p ?? false : () => true);
1045
+ var getPathString = (parts = [], delimiter = PATH_DELIMITER, filterEmptyInput = false, useJson = true, uriEncodeParts = false) => parts.filter(filterEmptyInput ? (p) => p ?? false : () => true).map(useJson ? (p) => JSON.stringify(p) : (x) => x).map(uriEncodeParts ? encodeURIComponent : (x) => x).join(delimiter);
1046
+ var buildQueryString = (query = {}) => {
1047
+ const parts = [];
1048
+ for (const [key, rawValue] of Object.entries(query)) {
1049
+ if (rawValue === void 0) {
1050
+ continue;
1051
+ }
1052
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
1053
+ for (const value of values) {
1054
+ if (value === void 0) {
1055
+ continue;
1056
+ }
1057
+ const encodedKey = encodeURIComponent(key);
1058
+ const encodedValue = value === null ? "" : encodeURIComponent(String(value));
1059
+ parts.push(`${encodedKey}=${encodedValue}`);
1060
+ }
1270
1061
  }
1271
- return /* @__PURE__ */ jsx(NativeRouteProvider, { children: /* @__PURE__ */ jsx(Route, { ...props }) });
1062
+ return parts.join("&");
1272
1063
  };
1064
+ var buildRoutePath = (segments, query) => {
1065
+ const normalizedSegments = segments.map((segment) => String(segment));
1066
+ const basePath = "/" + getPathString(normalizedSegments, "/", true, false, true);
1067
+ const queryString = query ? buildQueryString(query) : "";
1068
+ return queryString ? `${basePath}?${queryString}` : basePath;
1069
+ };
1070
+ var RouteContext = createContext({
1071
+ currentWindowPath: "",
1072
+ parentPath: "",
1073
+ params: {},
1074
+ isTopLevel: true
1075
+ });
1076
+ var {
1077
+ /**
1078
+ * @ignore
1079
+ */
1080
+ Provider: RouteContextProvider,
1081
+ /**
1082
+ * @ignore
1083
+ */
1084
+ Consumer: RouteContextConsumer
1085
+ } = RouteContext;
1273
1086
 
1274
1087
  // src/native/utils/Route.ts
1275
1088
  var createNavigationStateRouteAdapter = (options) => {
@@ -1308,4 +1121,4 @@ var buildPathFromRouteChain = (routeChain, config, query) => {
1308
1121
  return buildRoutePath(segments, query);
1309
1122
  };
1310
1123
 
1311
- export { ArrayContainer, ArrayItemWrapper, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, NativeRoute, NativeRouteProvider, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, buildHistoryPath, buildPathFromRouteChain, buildQueryString, buildRoutePath, createManualRouteAdapter, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout, useRouteContext };
1124
+ export { ArrayContainer, ArrayItemWrapper, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, buildHistoryPath, buildPathFromRouteChain, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout };
@@ -3,9 +3,7 @@
3
3
  *
4
4
  * Native (render-agnostic) utilities.
5
5
  */
6
- export * from "../../app/utils/Route";
7
6
  export * from "../../app/utils/History";
8
7
  export * from "./EasyLayout";
9
8
  export * from "./History";
10
- export * from "./NativeRoute";
11
9
  export * from "./Route";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resistdesign/voltra",
3
- "version": "3.0.0-alpha.20",
3
+ "version": "3.0.0-alpha.22",
4
4
  "description": "With our powers combined!",
5
5
  "homepage": "https://voltra.app",
6
6
  "repository": "git@github.com:resistdesign/voltra.git",
package/web/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as styledBase from 'styled-components';
2
2
  import styledBase__default from 'styled-components';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
- import { createContext, useContext, useEffect, useRef, useMemo, useState, useCallback } from 'react';
4
+ import { useEffect, useState, useCallback, useMemo } from 'react';
5
5
 
6
6
  var __defProp = Object.defineProperty;
7
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1018,280 +1018,4 @@ var getEasyLayout = (extendFrom, areasExtendFrom, options = {}) => {
1018
1018
  return createEasyLayout(styledFactory, extendFrom, areasExtendFrom, options);
1019
1019
  };
1020
1020
 
1021
- // src/common/Routing.ts
1022
- var PATH_DELIMITER = "/";
1023
- var getPotentialJSONValue = (value) => {
1024
- try {
1025
- return JSON.parse(value);
1026
- } catch (error) {
1027
- return value;
1028
- }
1029
- };
1030
- var getPathArray = (path, delimiter = PATH_DELIMITER, filterEmptyOutput = false, filterEmptyInput = true, useJson = true, uriDecodeParts = true) => path.split(delimiter).filter(filterEmptyInput ? (p) => p !== "" : () => true).map(uriDecodeParts ? decodeURIComponent : (x) => x).map(useJson ? getPotentialJSONValue : (p) => p).filter(filterEmptyOutput ? (p) => p ?? false : () => true);
1031
- var getPathString = (parts = [], delimiter = PATH_DELIMITER, filterEmptyInput = false, useJson = true, uriEncodeParts = false) => parts.filter(filterEmptyInput ? (p) => p ?? false : () => true).map(useJson ? (p) => JSON.stringify(p) : (x) => x).map(uriEncodeParts ? encodeURIComponent : (x) => x).join(delimiter);
1032
- var mergeStringPaths = (path1, path2, delimiter = PATH_DELIMITER, filterEmptyOutput = false, filterEmptyInput = true, useJson = true, uriEncodeParts = false) => getPathString(
1033
- [
1034
- ...getPathArray(
1035
- path1,
1036
- delimiter,
1037
- filterEmptyOutput,
1038
- filterEmptyInput,
1039
- useJson,
1040
- uriEncodeParts
1041
- ),
1042
- ...getPathArray(
1043
- path2,
1044
- delimiter,
1045
- filterEmptyOutput,
1046
- filterEmptyInput,
1047
- useJson,
1048
- uriEncodeParts
1049
- )
1050
- ],
1051
- delimiter,
1052
- filterEmptyInput,
1053
- useJson,
1054
- uriEncodeParts
1055
- );
1056
- var resolvePath = (currentPath, newPath) => {
1057
- const newSegments = getPathArray(newPath, PATH_DELIMITER, true);
1058
- let currentSegments = getPathArray(currentPath, PATH_DELIMITER, true);
1059
- if (newPath.startsWith("/")) {
1060
- currentSegments = [];
1061
- }
1062
- newSegments.forEach((segment) => {
1063
- if (segment === "..") {
1064
- if (currentSegments.length > 0) {
1065
- currentSegments.pop();
1066
- }
1067
- } else if (segment !== ".") {
1068
- currentSegments.push(segment);
1069
- }
1070
- });
1071
- return "/" + currentSegments.join("/");
1072
- };
1073
- var getParamsAndTestPath = (path, testPath, exact = false) => {
1074
- const pathList = getPathArray(path);
1075
- const testPathList = getPathArray(testPath);
1076
- if (exact && pathList.length !== testPathList.length) {
1077
- return false;
1078
- } else {
1079
- let params = {};
1080
- if (pathList.length >= testPathList.length) {
1081
- for (let i = 0; i < testPathList.length; i++) {
1082
- const testPathPart = testPathList[i];
1083
- const pathPart = pathList[i];
1084
- if (testPathPart.startsWith(":")) {
1085
- const paramName = testPathPart.slice(1);
1086
- params[paramName] = pathPart;
1087
- } else if (pathPart !== testPathPart) {
1088
- return false;
1089
- }
1090
- }
1091
- } else {
1092
- return false;
1093
- }
1094
- return params;
1095
- }
1096
- };
1097
- var RouteContext = createContext({
1098
- currentWindowPath: "",
1099
- parentPath: "",
1100
- params: {},
1101
- isTopLevel: true
1102
- });
1103
- var {
1104
- /**
1105
- * @ignore
1106
- */
1107
- Provider: RouteContextProvider,
1108
- /**
1109
- * @ignore
1110
- */
1111
- Consumer: RouteContextConsumer
1112
- } = RouteContext;
1113
- var useRouteContext = () => useContext(RouteContext);
1114
- var RouteProvider = ({
1115
- adapter,
1116
- initialPath,
1117
- children
1118
- }) => {
1119
- const [currentPath, setCurrentPath] = useState(
1120
- initialPath ?? adapter.getPath()
1121
- );
1122
- useEffect(() => {
1123
- return adapter.subscribe((nextPath) => {
1124
- setCurrentPath(nextPath);
1125
- });
1126
- }, [adapter]);
1127
- const contextValue = useMemo(
1128
- () => ({
1129
- currentWindowPath: currentPath,
1130
- parentPath: "",
1131
- params: {},
1132
- isTopLevel: true,
1133
- adapter
1134
- }),
1135
- [currentPath, adapter]
1136
- );
1137
- return /* @__PURE__ */ jsx(RouteContextProvider, { value: contextValue, children });
1138
- };
1139
- var Route = ({
1140
- /**
1141
- * Use `:` as the first character to denote a parameter in the path.
1142
- */
1143
- path = "",
1144
- onParamsChange,
1145
- exact = false,
1146
- children
1147
- }) => {
1148
- const {
1149
- currentWindowPath = "",
1150
- parentPath = "",
1151
- params: parentParams = {},
1152
- adapter
1153
- } = useRouteContext();
1154
- const targetCurrentPath = useMemo(
1155
- () => currentWindowPath,
1156
- [currentWindowPath]
1157
- );
1158
- const fullPath = useMemo(
1159
- () => mergeStringPaths(parentPath, path),
1160
- [parentPath, path]
1161
- );
1162
- const newParams = useMemo(
1163
- () => getParamsAndTestPath(targetCurrentPath, fullPath, exact),
1164
- [targetCurrentPath, fullPath, exact]
1165
- );
1166
- const params = useMemo(
1167
- () => ({
1168
- ...parentParams,
1169
- ...newParams ? newParams : {}
1170
- }),
1171
- [parentParams, newParams]
1172
- );
1173
- const newRouteContext = useMemo(
1174
- () => ({
1175
- currentWindowPath: targetCurrentPath,
1176
- parentPath: fullPath,
1177
- params,
1178
- isTopLevel: false,
1179
- adapter
1180
- }),
1181
- [targetCurrentPath, fullPath, params, adapter]
1182
- );
1183
- useEffect(() => {
1184
- if (onParamsChange) {
1185
- onParamsChange(params);
1186
- }
1187
- }, [params, onParamsChange]);
1188
- return newParams ? /* @__PURE__ */ jsx(RouteContextProvider, { value: newRouteContext, children }) : null;
1189
- };
1190
- var getWindow = () => {
1191
- if (typeof globalThis === "undefined") {
1192
- return void 0;
1193
- }
1194
- if ("window" in globalThis) {
1195
- return globalThis.window;
1196
- }
1197
- return void 0;
1198
- };
1199
- var createBrowserRouteAdapter = () => {
1200
- const WINDOW = getWindow();
1201
- const listeners = /* @__PURE__ */ new Set();
1202
- const notify = () => {
1203
- const path = WINDOW?.location?.pathname ?? "";
1204
- listeners.forEach((listener) => listener(path));
1205
- };
1206
- const handlePopState = () => {
1207
- notify();
1208
- };
1209
- const subscribe = (listener) => {
1210
- listeners.add(listener);
1211
- if (WINDOW) {
1212
- WINDOW.addEventListener("popstate", handlePopState);
1213
- WINDOW.addEventListener("statechanged", handlePopState);
1214
- }
1215
- return () => {
1216
- listeners.delete(listener);
1217
- if (WINDOW) {
1218
- WINDOW.removeEventListener("popstate", handlePopState);
1219
- WINDOW.removeEventListener("statechanged", handlePopState);
1220
- }
1221
- };
1222
- };
1223
- return {
1224
- getPath: () => WINDOW?.location?.pathname ?? "",
1225
- subscribe,
1226
- push: (path, title = "") => {
1227
- if (!WINDOW?.history) {
1228
- return;
1229
- }
1230
- WINDOW.history.pushState({}, title, path);
1231
- notify();
1232
- },
1233
- replace: (path, title = "") => {
1234
- if (!WINDOW?.history?.replaceState) {
1235
- return;
1236
- }
1237
- WINDOW.history.replaceState({}, title, path);
1238
- notify();
1239
- }
1240
- };
1241
- };
1242
- var useBrowserLinkInterceptor = (adapter) => {
1243
- useEffect(() => {
1244
- const WINDOW = getWindow();
1245
- if (!WINDOW || !adapter?.push) {
1246
- return void 0;
1247
- }
1248
- const handleAnchorClick = (event) => {
1249
- let target = event.target;
1250
- while (target && target.nodeName !== "A") {
1251
- target = target.parentNode;
1252
- }
1253
- if (!target || target.nodeName !== "A") {
1254
- return;
1255
- }
1256
- const anchor = target;
1257
- const href = anchor.getAttribute("href");
1258
- const title = anchor.getAttribute("title") ?? "";
1259
- if (!href) {
1260
- return;
1261
- }
1262
- try {
1263
- new URL(href);
1264
- return;
1265
- } catch (error) {
1266
- const nextPath = resolvePath(WINDOW.location?.pathname ?? "", href);
1267
- event.preventDefault();
1268
- adapter.push?.(nextPath, title);
1269
- }
1270
- };
1271
- WINDOW.document.addEventListener("click", handleAnchorClick);
1272
- return () => {
1273
- WINDOW.document.removeEventListener("click", handleAnchorClick);
1274
- };
1275
- }, [adapter]);
1276
- };
1277
- var RouteProvider2 = ({ children }) => {
1278
- const adapterRef = useRef(null);
1279
- if (!adapterRef.current) {
1280
- adapterRef.current = createBrowserRouteAdapter();
1281
- }
1282
- useBrowserLinkInterceptor(adapterRef.current);
1283
- return /* @__PURE__ */ jsx(RouteProvider, { adapter: adapterRef.current, children });
1284
- };
1285
- var Route2 = (props) => {
1286
- const routeContext = useRouteContext();
1287
- const hasAdapter = useMemo(
1288
- () => typeof routeContext.adapter !== "undefined",
1289
- [routeContext.adapter]
1290
- );
1291
- if (hasAdapter) {
1292
- return /* @__PURE__ */ jsx(Route, { ...props });
1293
- }
1294
- return /* @__PURE__ */ jsx(RouteProvider2, { children: /* @__PURE__ */ jsx(Route, { ...props }) });
1295
- };
1296
-
1297
- export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm, AutoFormView, ErrorMessage, FieldWrapper, Route2 as Route, RouteProvider2 as RouteProvider, createBrowserRouteAdapter, createWebFormRenderer, getEasyLayout, useRouteContext, webAutoField, webSuite };
1021
+ export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm, AutoFormView, ErrorMessage, FieldWrapper, createWebFormRenderer, getEasyLayout, webAutoField, webSuite };
@@ -1,20 +1,14 @@
1
1
  /**
2
2
  * @packageDocumentation
3
3
  *
4
- * Web (DOM) routing helpers that wire the app-level Route to browser history.
4
+ * Web routing exports unified app Route implementation.
5
5
  */
6
6
  import { PropsWithChildren } from "react";
7
- import { type RouteAdapter, type RouteProps, useRouteContext } from "../../app/utils/Route";
7
+ import { Route, useRouteContext } from "../../app/utils/Route";
8
+ import { createBrowserRouteAdapter } from "../../app/utils/UniversalRouteAdapter";
8
9
  /**
9
- * Create a browser RouteAdapter backed by the History API.
10
- */
11
- export declare const createBrowserRouteAdapter: () => RouteAdapter;
12
- /**
13
- * Web RouteProvider using the browser adapter.
10
+ * Backward-compatible web RouteProvider that auto-creates a browser adapter.
14
11
  */
15
12
  export declare const RouteProvider: ({ children }: PropsWithChildren) => import("react/jsx-runtime").JSX.Element;
16
- /**
17
- * Web Route component that auto-provides the browser adapter at the top level.
18
- */
19
- export declare const Route: <ParamsType extends Record<string, any>>(props: PropsWithChildren<RouteProps<ParamsType>>) => import("react/jsx-runtime").JSX.Element;
20
- export { useRouteContext };
13
+ export { Route, useRouteContext, createBrowserRouteAdapter };
14
+ export type { RouteAdapter, RouteContextType, RouteProps, RouteProviderProps, RouteQuery, RouteQueryValue, } from "../../app/utils/Route";
@@ -4,4 +4,3 @@
4
4
  * Web-only utilities.
5
5
  */
6
6
  export * from "./EasyLayout";
7
- export * from "./Route";
@@ -1,44 +0,0 @@
1
- /**
2
- * @packageDocumentation
3
- *
4
- * Native Route wrappers that bridge native history into app Route context.
5
- */
6
- import { PropsWithChildren } from "react";
7
- import { type RouteProps } from "../../app/utils/Route";
8
- import { type CreateNativeHistoryOptions, type NativeHistoryController } from "./History";
9
- /**
10
- * Native route-provider props.
11
- */
12
- export type NativeRouteProviderProps = PropsWithChildren<CreateNativeHistoryOptions & {
13
- /**
14
- * Optional externally managed history instance.
15
- *
16
- * When provided, `CreateNativeHistoryOptions` are ignored for construction
17
- * and the provider will use this instance directly.
18
- */
19
- history?: NativeHistoryController;
20
- }>;
21
- /**
22
- * Native RouteProvider that starts/stops native history lifecycle automatically.
23
- *
24
- * Behavior:
25
- * - If `history` is provided, it is used as-is.
26
- * - Otherwise, a history instance is created once from options.
27
- * - On mount: calls `history.start()`.
28
- * - On unmount: calls `history.stop()`.
29
- *
30
- * Example:
31
- * ```tsx
32
- * <NativeRouteProvider adapter={linkingAdapter}>
33
- * <NativeRoute path="/app" />
34
- * </NativeRouteProvider>
35
- * ```
36
- */
37
- export declare const NativeRouteProvider: ({ children, history, ...historyOptions }: NativeRouteProviderProps) => import("react/jsx-runtime").JSX.Element;
38
- /**
39
- * Native Route component that auto-provides native history at the top level.
40
- *
41
- * If a route adapter already exists in context, this renders the core route
42
- * directly. Otherwise it creates a top-level {@link NativeRouteProvider}.
43
- */
44
- export declare const NativeRoute: <ParamsType extends Record<string, any>>(props: PropsWithChildren<RouteProps<ParamsType>>) => import("react/jsx-runtime").JSX.Element;