@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 +35 -40
- package/api/index.d.ts +4 -0
- package/app/index.d.ts +4 -0
- package/app/index.js +288 -19
- package/app/utils/Route.d.ts +15 -8
- package/app/utils/UniversalRouteAdapter.d.ts +73 -0
- package/app/utils/index.d.ts +1 -0
- package/native/index.js +63 -250
- package/native/utils/index.d.ts +0 -2
- package/package.json +1 -1
- package/web/index.js +2 -278
- package/web/utils/Route.d.ts +6 -12
- package/web/utils/index.d.ts +0 -1
- package/native/utils/NativeRoute.d.ts +0 -44
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/
|
|
131
|
-
- `examples/
|
|
132
|
-
- `examples/
|
|
133
|
-
- `examples/
|
|
134
|
-
- `examples/
|
|
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
|
|
194
|
+
Voltra routing is unified under `@resistdesign/voltra/app`.
|
|
193
195
|
|
|
194
|
-
|
|
196
|
+
Reference example: `examples/routing/app-routing.ts`
|
|
195
197
|
|
|
196
198
|
```tsx
|
|
197
|
-
import { Route } from "@resistdesign/voltra/
|
|
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
|
-
|
|
214
|
+
How it works:
|
|
201
215
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
222
|
+
Escape hatches (root-only):
|
|
208
223
|
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
+
- `examples/api/backend-routing.ts`
|
|
236
231
|
|
|
237
232
|
## Form Suites (Web + Native + BYOCS)
|
|
238
233
|
|
package/api/index.d.ts
CHANGED
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
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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 };
|
package/app/utils/Route.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/app/utils/index.d.ts
CHANGED
|
@@ -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,
|
|
2
|
-
import
|
|
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
|
-
|
|
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/
|
|
1224
|
-
var
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
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
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
|
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,
|
|
1124
|
+
export { ArrayContainer, ArrayItemWrapper, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, buildHistoryPath, buildPathFromRouteChain, createMemoryHistory, createNativeBackHandler, createNativeFormRenderer, createNativeHistory, createNavigationStateRouteAdapter, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, parseHistoryPath, useNativeEasyLayout };
|
package/native/utils/index.d.ts
CHANGED
package/package.json
CHANGED
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 {
|
|
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
|
-
|
|
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 };
|
package/web/utils/Route.d.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
*
|
|
4
|
-
* Web
|
|
4
|
+
* Web routing exports unified app Route implementation.
|
|
5
5
|
*/
|
|
6
6
|
import { PropsWithChildren } from "react";
|
|
7
|
-
import {
|
|
7
|
+
import { Route, useRouteContext } from "../../app/utils/Route";
|
|
8
|
+
import { createBrowserRouteAdapter } from "../../app/utils/UniversalRouteAdapter";
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
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
|
-
|
|
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";
|
package/web/utils/index.d.ts
CHANGED
|
@@ -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;
|