@resistdesign/voltra 3.0.0-alpha.47 → 3.0.0-alpha.49

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/app/index.d.ts CHANGED
@@ -8,14 +8,15 @@
8
8
  * ```ts
9
9
  * import {
10
10
  * createFormRenderer,
11
+ * getApplicationStateIdentifier,
11
12
  * useApplicationStateLoader,
12
- * useApplicationStateValueStructure,
13
+ * useApplicationStateValue,
13
14
  * type RemoteProcedureCall,
14
15
  * } from "@resistdesign/voltra/app";
15
16
  * ```
16
17
  *
17
- * @see {@link useApplicationStateValueStructure} and
18
- * {@link useApplicationStateLoader} for good starting points.
18
+ * @see {@link useApplicationStateValue} and {@link useApplicationStateLoader}
19
+ * for good starting points.
19
20
  *
20
21
  * Reference examples:
21
22
  * - `examples/README.md`
@@ -24,17 +25,22 @@
24
25
  * @example
25
26
  * ```tsx
26
27
  * import {
28
+ * getApplicationStateIdentifier,
27
29
  * useApplicationStateLoader,
28
- * useApplicationStateValueStructure,
30
+ * useApplicationStateValue,
29
31
  * type RemoteProcedureCall,
30
32
  * } from "@resistdesign/voltra/app";
31
33
  * import { useCallback } from "react";
32
34
  *
35
+ * const loginUsernameId = getApplicationStateIdentifier<string>();
36
+ * const loginPasswordId = getApplicationStateIdentifier<string>();
37
+ * const loginLoggedInId = getApplicationStateIdentifier<boolean>();
38
+ *
33
39
  * const APP_STATE_IDENTIFIERS = {
34
40
  * LOGIN: {
35
- * USERNAME: {},
36
- * PASSWORD: {},
37
- * LOGGED_IN: {},
41
+ * USERNAME: loginUsernameId,
42
+ * PASSWORD: loginPasswordId,
43
+ * LOGGED_IN: loginLoggedInId,
38
44
  * },
39
45
  * };
40
46
  *
@@ -52,27 +58,24 @@
52
58
  *
53
59
  * export const LoginController = () => {
54
60
  * const {
55
- * valueStructure: {
56
- * username: username = "",
57
- * password: password = "",
58
- * loggedIn: loggedIn = false,
59
- * },
60
- * onChangeStructure: {
61
- * username: setUsername,
62
- * password: setPassword,
63
- * loggedIn: setLoggedIn,
64
- * },
65
- * } = useApplicationStateValueStructure<{
66
- * username: string;
67
- * password: string;
68
- * loggedIn: boolean;
69
- * }>({
70
- * username: APP_STATE_IDENTIFIERS.LOGIN.USERNAME,
71
- * password: APP_STATE_IDENTIFIERS.LOGIN.PASSWORD,
72
- * loggedIn: APP_STATE_IDENTIFIERS.LOGIN.LOGGED_IN,
73
- * });
61
+ * value: username = "",
62
+ * onChange: setUsername,
63
+ * } = useApplicationStateValue(APP_STATE_IDENTIFIERS.LOGIN.USERNAME);
64
+ * const {
65
+ * value: password = "",
66
+ * onChange: setPassword,
67
+ * } = useApplicationStateValue(APP_STATE_IDENTIFIERS.LOGIN.PASSWORD);
68
+ * const {
69
+ * value: loggedIn = false,
70
+ * onChange: setLoggedIn,
71
+ * } = useApplicationStateValue(APP_STATE_IDENTIFIERS.LOGIN.LOGGED_IN);
74
72
  *
75
- * const { loading: loadingLogin, makeRemoteProcedureCall } =
73
+ * const {
74
+ * value: latestLoginState,
75
+ * modified: loginStateModified,
76
+ * loading: loadingLogin,
77
+ * makeRemoteProcedureCall,
78
+ * } =
76
79
  * useApplicationStateLoader({
77
80
  * identifier: APP_STATE_IDENTIFIERS.LOGIN.LOGGED_IN,
78
81
  * remoteProcedureCall: LOGIN_RPC,
@@ -83,7 +86,18 @@
83
86
  * makeRemoteProcedureCall(username, password);
84
87
  * }, [username, password, makeRemoteProcedureCall]);
85
88
  *
86
- * return { username, password, loggedIn, setUsername, setPassword, onSubmit, loadingLogin };
89
+ * return {
90
+ * username,
91
+ * password,
92
+ * loggedIn,
93
+ * latestLoginState,
94
+ * loginStateModified,
95
+ * setUsername,
96
+ * setPassword,
97
+ * setLoggedIn,
98
+ * onSubmit,
99
+ * loadingLogin,
100
+ * };
87
101
  * };
88
102
  * ```
89
103
  *
package/app/index.js CHANGED
@@ -1,25 +1,20 @@
1
- export { createEasyLayout, getEasyLayoutTemplateDetails, getPascalCaseAreaName } from '../chunk-QXYUXJKM.js';
1
+ export { createEasyLayout, getEasyLayoutTemplateDetails, getPascalCaseAreaName } from '../chunk-BSHQIRBV.js';
2
2
  export { computeTrackPixels } from '../chunk-TJFTWPXQ.js';
3
- export { AutoForm, AutoFormView, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, buildHistoryPath, buildQueryString, buildRoutePath, canUseBrowserHistory, computeAreaBounds, createAutoField, createBrowserRouteAdapter, createFormRenderer, createHistoryBackHandler, createManualRouteAdapter, createMemoryHistory, createNativeRouteAdapter, createRouteAdapterFromHistory, createUniversalAdapter, defaultTranslateValidationErrorCode, getFieldKind, parseHistoryPath, parseTemplate, resolveSuite, useFormEngine, useRouteContext, validateAreas, wrapRouteAdapterWithPathResolver } from '../chunk-UD2ALOCE.js';
3
+ export { AutoForm, AutoFormView, Route, RouteContext, RouteContextConsumer, RouteContextProvider, RouteProvider, buildHistoryPath, buildQueryString, buildRoutePath, canUseBrowserHistory, computeAreaBounds, createAutoField, createBrowserRouteAdapter, createFormRenderer, createHistoryBackHandler, createManualRouteAdapter, createMemoryHistory, createNativeRouteAdapter, createRouteAdapterFromHistory, createUniversalAdapter, defaultTranslateValidationErrorCode, getFieldKind, parseHistoryPath, parseTemplate, resolveSuite, useFormEngine, useRouteContext, validateAreas, wrapRouteAdapterWithPathResolver } from '../chunk-K4R2PFNG.js';
4
4
  import '../chunk-RUNXRISF.js';
5
5
  import '../chunk-3HVYVX3S.js';
6
6
  import { mergeStringPaths, PATH_DELIMITER } from '../chunk-2JDOM6PB.js';
7
7
  import '../chunk-I2KLQ2HA.js';
8
- import { createContext, useContext, useRef, useMemo, useCallback, useState, useEffect } from 'react';
8
+ import { createContext, useContext, useMemo, useCallback, useState, useRef, useEffect } from 'react';
9
9
  import { jsx } from 'react/jsx-runtime';
10
10
 
11
- var getApplicationStateIdentifier = (subStateIdMap) => subStateIdMap ? subStateIdMap : {};
11
+ function getApplicationStateIdentifier(subStateIdMap) {
12
+ return subStateIdMap ? subStateIdMap : {};
13
+ }
12
14
  var getApplicationStateModified = (identifier, modificationState) => !!modificationState.get(identifier);
13
15
  var getApplicationStateValue = (identifier, applicationState) => applicationState.get(identifier);
14
16
  var setApplicationStateModified = (identifier, value, modificationState) => new Map(modificationState).set(identifier, value);
15
17
  var setApplicationStateValue = (identifier, value, applicationState) => new Map(applicationState).set(identifier, value);
16
- var getApplicationStateValueStructure = (idStructure, applicationState) => Object.keys(idStructure).reduce(
17
- (acc, k) => ({
18
- ...acc,
19
- [k]: getApplicationStateValue(idStructure[k], applicationState)
20
- }),
21
- {}
22
- );
23
18
  var ApplicationStateContext = createContext({
24
19
  modified: /* @__PURE__ */ new Map(),
25
20
  value: /* @__PURE__ */ new Map(),
@@ -36,10 +31,6 @@ var useApplicationStateValue = (identifier) => {
36
31
  onChange: setApplicationState,
37
32
  setModified: setModificationState
38
33
  } = useContext(ApplicationStateContext);
39
- const appStateRef = useRef(applicationState);
40
- appStateRef.current = applicationState;
41
- const modificationStateRef = useRef(modificationState);
42
- modificationStateRef.current = modificationState;
43
34
  const modified = useMemo(
44
35
  () => getApplicationStateModified(identifier, modificationState),
45
36
  [identifier, modificationState]
@@ -51,23 +42,30 @@ var useApplicationStateValue = (identifier) => {
51
42
  const setModified = useCallback(
52
43
  (isModified) => {
53
44
  setModificationState(
54
- setApplicationStateModified(
55
- identifier,
56
- isModified,
57
- modificationStateRef.current
58
- )
45
+ (previousModified) => setApplicationStateModified(identifier, isModified, previousModified)
59
46
  );
60
47
  },
61
48
  [identifier, setModificationState]
62
49
  );
63
50
  const onChange = useCallback(
64
51
  (newValue) => {
65
- setApplicationState(
66
- setApplicationStateValue(identifier, newValue, appStateRef.current)
52
+ setApplicationState((previousState) => {
53
+ const previousValue = getApplicationStateValue(
54
+ identifier,
55
+ previousState
56
+ );
57
+ const resolvedValue = typeof newValue === "function" ? newValue(previousValue) : newValue;
58
+ return setApplicationStateValue(
59
+ identifier,
60
+ resolvedValue,
61
+ previousState
62
+ );
63
+ });
64
+ setModificationState(
65
+ (previousModified) => setApplicationStateModified(identifier, true, previousModified)
67
66
  );
68
- setModified(true);
69
67
  },
70
- [identifier, setApplicationState]
68
+ [identifier, setApplicationState, setModificationState]
71
69
  );
72
70
  const controller = useMemo(
73
71
  () => ({
@@ -80,41 +78,6 @@ var useApplicationStateValue = (identifier) => {
80
78
  );
81
79
  return controller;
82
80
  };
83
- var useApplicationStateValueStructure = (idStructure) => {
84
- const { value: applicationState, onChange: setApplicationState } = useContext(
85
- ApplicationStateContext
86
- );
87
- const valueStructure = useMemo(
88
- () => getApplicationStateValueStructure(idStructure, applicationState),
89
- [applicationState, idStructure]
90
- );
91
- const onChangeStructure = useMemo(
92
- () => Object.keys(idStructure).reduce(
93
- (acc, k) => ({
94
- ...acc,
95
- [k]: (newValue) => {
96
- setApplicationState(
97
- setApplicationStateValue(
98
- idStructure[k],
99
- newValue,
100
- applicationState
101
- )
102
- );
103
- }
104
- }),
105
- {}
106
- ),
107
- [applicationState, idStructure, setApplicationState]
108
- );
109
- const controller = useMemo(
110
- () => ({
111
- valueStructure,
112
- onChangeStructure
113
- }),
114
- [onChangeStructure, valueStructure]
115
- );
116
- return controller;
117
- };
118
81
  var ApplicationStateProvider = ({
119
82
  children
120
83
  }) => {
@@ -200,13 +163,14 @@ var useApplicationStateLoader = (config) => {
200
163
  const [cacheValidity, setCacheValidity] = useState({});
201
164
  const [loading, setLoading] = useState(false);
202
165
  const [latestError, setLatestError] = useState();
203
- const { onChange, setModified } = useApplicationStateValue(identifier);
166
+ const valueController = useApplicationStateValue(identifier);
167
+ const { onChange, setModified } = valueController;
204
168
  const invalidate = useCallback(() => {
205
169
  setCacheValidity({});
206
170
  }, []);
207
171
  const makeRemoteProcedureCall = useCallback(
208
172
  async (...directArgs) => {
209
- let success;
173
+ let success = false;
210
174
  setLoading(true);
211
175
  setLatestError(void 0);
212
176
  try {
@@ -234,12 +198,13 @@ var useApplicationStateLoader = (config) => {
234
198
  );
235
199
  const appStateLoader = useMemo(
236
200
  () => ({
201
+ ...valueController,
237
202
  loading,
238
203
  latestError,
239
204
  invalidate,
240
205
  makeRemoteProcedureCall
241
206
  }),
242
- [loading, latestError, invalidate, makeRemoteProcedureCall]
207
+ [valueController, loading, latestError, invalidate, makeRemoteProcedureCall]
243
208
  );
244
209
  useEffect(() => {
245
210
  if (!manual && argsRef.current) {
@@ -580,4 +545,4 @@ var withRendererOverride = (kind, renderer) => ({
580
545
  }
581
546
  });
582
547
 
583
- export { ApplicationStateContext, ApplicationStateProvider, TypeInfoORMClient, getApplicationStateIdentifier, getApplicationStateModified, getApplicationStateValue, getApplicationStateValueStructure, getChangedDependencyIndexes, getFullUrl, handleRequest, mergeSuites, requestHandlerFactory, sendServiceRequest, setApplicationStateModified, setApplicationStateValue, useApplicationStateLoader, useApplicationStateValue, useApplicationStateValueStructure, useController, useDebugDependencies, useTypeInfoORMAPI, withRendererOverride };
548
+ export { ApplicationStateContext, ApplicationStateProvider, TypeInfoORMClient, getApplicationStateIdentifier, getApplicationStateModified, getApplicationStateValue, getChangedDependencyIndexes, getFullUrl, handleRequest, mergeSuites, requestHandlerFactory, sendServiceRequest, setApplicationStateModified, setApplicationStateValue, useApplicationStateLoader, useApplicationStateValue, useController, useDebugDependencies, useTypeInfoORMAPI, withRendererOverride };
@@ -3,39 +3,56 @@
3
3
  *
4
4
  * Application-level state container built on React context and maps. Use
5
5
  * {@link ApplicationStateProvider} to host state, then access values with
6
- * {@link useApplicationStateValue} or {@link useApplicationStateValueStructure}.
6
+ * {@link useApplicationStateValue}.
7
7
  */
8
- import { FC, PropsWithChildren } from "react";
8
+ import { FC, PropsWithChildren, type Dispatch, type SetStateAction } from "react";
9
9
  /**
10
- * An object, nested or not, used as the identifier or identifier path for a state value.
10
+ * An object, nested or not, used as the identifier or identifier path for a
11
+ * state value.
12
+ *
13
+ * The generic parameter exists purely for TypeScript. It lets a consumer make
14
+ * the identifier itself the source of truth for what value type lives at that
15
+ * location in application state, without changing the runtime shape.
11
16
  * */
12
- export interface ApplicationStateIdentifier extends Record<string, ApplicationStateIdentifier | {}> {
17
+ declare const applicationStateIdentifierValueTypeSymbol: unique symbol;
18
+ export interface ApplicationStateIdentifier<ValueType = unknown> extends Record<string, ApplicationStateIdentifier<any> | {}> {
19
+ readonly [applicationStateIdentifierValueTypeSymbol]?: ValueType;
13
20
  }
14
21
  /**
15
22
  * The stored value type for application state entries.
16
23
  * */
17
- export type ApplicationStateValue = any;
24
+ export type ApplicationStateValue = unknown;
25
+ /**
26
+ * React-style state action for a specific application-state value.
27
+ *
28
+ * Passing a function follows the same contract as React `useState`: the
29
+ * function receives the previous value and returns the next value.
30
+ * */
31
+ export type ApplicationStateSetAction<ValueType> = ValueType | ((previousValue: ValueType) => ValueType);
32
+ /**
33
+ * React-style stable setter for a specific application-state value.
34
+ * */
35
+ export type ApplicationStateSetter<ValueType> = (value: ApplicationStateSetAction<ValueType>) => void;
18
36
  /**
19
37
  * Map of state identifiers to a "modified" boolean.
20
38
  * */
21
- export type ApplicationStateModificationState = Map<ApplicationStateIdentifier, boolean>;
39
+ export type ApplicationStateModificationState = Map<ApplicationStateIdentifier<any>, boolean>;
22
40
  /**
23
41
  * Map of state identifiers to stored values.
24
42
  * */
25
- export type ApplicationState = Map<ApplicationStateIdentifier, ApplicationStateValue>;
43
+ export type ApplicationState = Map<ApplicationStateIdentifier<any>, ApplicationStateValue>;
26
44
  /**
27
- * Determines the identifier shape for a specific sub-state.
45
+ * Create or forward an application-state identifier.
28
46
  *
29
- * @typeParam SpecificType - The identifier shape, if provided.
30
- * */
31
- export type ApplicationStateIdentifierSubStateType<SpecificType> = SpecificType extends undefined ? ApplicationStateIdentifier : SpecificType extends ApplicationStateIdentifier ? SpecificType : never;
32
- /**
33
- * Normalize a sub-state identifier map to an ApplicationStateIdentifier.
47
+ * Call with no argument to create a new identifier object and attach a value
48
+ * type at the type level:
49
+ * `const profileId = getApplicationStateIdentifier<UserProfile>()`
34
50
  *
35
- * @param subStateIdMap - Optional sub-state identifier map.
36
- * @returns The identifier map or an empty identifier object.
51
+ * Call with an existing identifier object to preserve that exact object
52
+ * reference while keeping its type information intact.
37
53
  * */
38
- export declare const getApplicationStateIdentifier: <SubStateIdStructure extends ApplicationStateIdentifier>(subStateIdMap?: SubStateIdStructure) => ApplicationStateIdentifierSubStateType<SubStateIdStructure>;
54
+ export declare function getApplicationStateIdentifier<ValueType = ApplicationStateValue>(): ApplicationStateIdentifier<ValueType>;
55
+ export declare function getApplicationStateIdentifier<IdentifierType extends ApplicationStateIdentifier<any>>(subStateIdMap: IdentifierType): IdentifierType;
39
56
  /**
40
57
  * Read the modification status for a specific identifier.
41
58
  *
@@ -43,7 +60,7 @@ export declare const getApplicationStateIdentifier: <SubStateIdStructure extends
43
60
  * @param modificationState - The modification map to read from.
44
61
  * @returns Whether the identifier is marked as modified.
45
62
  * */
46
- export declare const getApplicationStateModified: (identifier: ApplicationStateIdentifier, modificationState: ApplicationStateModificationState) => boolean;
63
+ export declare const getApplicationStateModified: (identifier: ApplicationStateIdentifier<any>, modificationState: ApplicationStateModificationState) => boolean;
47
64
  /**
48
65
  * Read the stored value for an identifier.
49
66
  *
@@ -51,7 +68,7 @@ export declare const getApplicationStateModified: (identifier: ApplicationStateI
51
68
  * @param applicationState - The application state map.
52
69
  * @returns The stored value, if any.
53
70
  * */
54
- export declare const getApplicationStateValue: (identifier: ApplicationStateIdentifier, applicationState: ApplicationState) => ApplicationStateValue;
71
+ export declare const getApplicationStateValue: <ValueType = ApplicationStateValue>(identifier: ApplicationStateIdentifier<ValueType>, applicationState: ApplicationState) => ValueType | undefined;
55
72
  /**
56
73
  * Set the modification status for an identifier.
57
74
  *
@@ -60,7 +77,7 @@ export declare const getApplicationStateValue: (identifier: ApplicationStateIden
60
77
  * @param modificationState - The current modification map.
61
78
  * @returns A new modification map with the updated flag.
62
79
  * */
63
- export declare const setApplicationStateModified: (identifier: ApplicationStateIdentifier, value: boolean, modificationState: ApplicationStateModificationState) => ApplicationStateModificationState;
80
+ export declare const setApplicationStateModified: (identifier: ApplicationStateIdentifier<any>, value: boolean, modificationState: ApplicationStateModificationState) => ApplicationStateModificationState;
64
81
  /**
65
82
  * Set the stored value for an identifier.
66
83
  *
@@ -69,15 +86,7 @@ export declare const setApplicationStateModified: (identifier: ApplicationStateI
69
86
  * @param applicationState - The current application state map.
70
87
  * @returns A new application state map with the updated value.
71
88
  * */
72
- export declare const setApplicationStateValue: (identifier: ApplicationStateIdentifier, value: ApplicationStateValue, applicationState: ApplicationState) => ApplicationState;
73
- /**
74
- * Resolve a structured map of identifiers into their current values.
75
- *
76
- * @param idStructure - Map of structure keys to identifiers.
77
- * @param applicationState - The application state map.
78
- * @returns An object of the same shape containing resolved values.
79
- * */
80
- export declare const getApplicationStateValueStructure: <ReturnStructureType extends Record<string, any>>(idStructure: Record<keyof ReturnStructureType, ApplicationStateIdentifier>, applicationState: ApplicationState) => ReturnStructureType;
89
+ export declare const setApplicationStateValue: <ValueType = ApplicationStateValue>(identifier: ApplicationStateIdentifier<ValueType>, value: ValueType, applicationState: ApplicationState) => ApplicationState;
81
90
  /**
82
91
  * Context state and updater hooks for application state.
83
92
  * */
@@ -93,11 +102,11 @@ export type ApplicationStateContextType = {
93
102
  /**
94
103
  * Replace the current application state map.
95
104
  * */
96
- onChange: (newValue: ApplicationState) => void;
105
+ onChange: Dispatch<SetStateAction<ApplicationState>>;
97
106
  /**
98
107
  * Replace the current modification state map.
99
108
  * */
100
- setModified: (newValue: ApplicationStateModificationState) => void;
109
+ setModified: Dispatch<SetStateAction<ApplicationStateModificationState>>;
101
110
  };
102
111
  /**
103
112
  * React context for application state and modification tracking.
@@ -106,7 +115,7 @@ export declare const ApplicationStateContext: import("react").Context<Applicatio
106
115
  /**
107
116
  * Used to access and update application state values.
108
117
  * */
109
- export type ApplicationStateValueController = {
118
+ export type ApplicationStateValueController<ValueType = ApplicationStateValue> = {
110
119
  /**
111
120
  * Whether the value is marked as modified.
112
121
  * */
@@ -114,13 +123,17 @@ export type ApplicationStateValueController = {
114
123
  /**
115
124
  * The current value for the identifier.
116
125
  * */
117
- value: ApplicationStateValue;
126
+ value: ValueType | undefined;
118
127
  /**
119
- * Update the current value.
128
+ * Update the current value with React `useState` semantics.
120
129
  *
121
- * @param value - The new value to store.
130
+ * The setter is intentionally stable so consumers can safely depend on it
131
+ * like a normal React state setter.
132
+ *
133
+ * @param value - The next value, or a function that derives it from the
134
+ * previous value.
122
135
  * */
123
- onChange: (value: ApplicationStateValue) => void;
136
+ onChange: ApplicationStateSetter<ValueType | undefined>;
124
137
  /**
125
138
  * Update the modified flag.
126
139
  *
@@ -134,27 +147,7 @@ export type ApplicationStateValueController = {
134
147
  * @param identifier - Identifier to read and update.
135
148
  * @returns Controller for the identifier value and modified flag.
136
149
  * */
137
- export declare const useApplicationStateValue: (identifier: ApplicationStateIdentifier) => ApplicationStateValueController;
138
- /**
139
- * A mapped structure of application state value controllers.
140
- * */
141
- export type ApplicationStateValueStructureController<StructureType extends Record<string, any>> = {
142
- /**
143
- * The resolved value structure.
144
- * */
145
- valueStructure: StructureType;
146
- /**
147
- * Per-field change handlers for the structure.
148
- * */
149
- onChangeStructure: Record<keyof StructureType, (newValue: any) => void>;
150
- };
151
- /**
152
- * Use an object that is a collection of application state value controllers.
153
- *
154
- * @param idStructure - Map of structure keys to identifiers.
155
- * @returns Controller with the resolved values and per-field change handlers.
156
- * */
157
- export declare const useApplicationStateValueStructure: <StructureType extends Record<string, any>>(idStructure: Record<keyof StructureType, ApplicationStateIdentifier>) => ApplicationStateValueStructureController<StructureType>;
150
+ export declare const useApplicationStateValue: <ValueType = ApplicationStateValue>(identifier: ApplicationStateIdentifier<ValueType>) => ApplicationStateValueController<ValueType>;
158
151
  /**
159
152
  * Props for ApplicationStateProvider.
160
153
  * */
@@ -165,3 +158,4 @@ export type ApplicationStateProviderProps = PropsWithChildren<{}>;
165
158
  * @param children - React children to render in the provider.
166
159
  * */
167
160
  export declare const ApplicationStateProvider: FC<ApplicationStateProviderProps>;
161
+ export {};
@@ -4,12 +4,12 @@
4
4
  * Loader hook for remote application state values. Calls a service endpoint,
5
5
  * tracks loading/error state, and populates ApplicationState via identifiers.
6
6
  */
7
- import { ApplicationStateIdentifier } from "./ApplicationState";
7
+ import { ApplicationStateIdentifier, type ApplicationStateValue, type ApplicationStateValueController } from "./ApplicationState";
8
8
  import { ServiceConfig } from "./Service";
9
9
  /**
10
10
  * Access and track the loading of an application state value.
11
11
  * */
12
- export type ApplicationStateLoader = {
12
+ export type ApplicationStateLoader<ValueType = ApplicationStateValue, ArgsType extends any[] = any[]> = ApplicationStateValueController<ValueType> & {
13
13
  /**
14
14
  * Whether the current request is in flight.
15
15
  * */
@@ -27,12 +27,12 @@ export type ApplicationStateLoader = {
27
27
  *
28
28
  * @param args - Arguments to send with the request.
29
29
  * */
30
- makeRemoteProcedureCall: (...args: any[]) => Promise<void>;
30
+ makeRemoteProcedureCall: (...args: ArgsType) => Promise<void>;
31
31
  };
32
32
  /**
33
33
  * The service, path and arguments to use for a remote procedure call.
34
34
  * */
35
- export type RemoteProcedureCall = {
35
+ export type RemoteProcedureCall<ArgsType extends any[] = any[]> = {
36
36
  /**
37
37
  * Configuration for the target service endpoint.
38
38
  * */
@@ -44,20 +44,20 @@ export type RemoteProcedureCall = {
44
44
  /**
45
45
  * Default args to send when the call auto-runs.
46
46
  * */
47
- args?: any[];
47
+ args?: ArgsType;
48
48
  };
49
49
  /**
50
50
  * The configuration for an application state loader.
51
51
  * */
52
- export type ApplicationStateLoaderConfig = {
52
+ export type ApplicationStateLoaderConfig<ValueType = ApplicationStateValue, ArgsType extends any[] = any[]> = {
53
53
  /**
54
54
  * Identifier for the value to update in application state.
55
55
  * */
56
- identifier: ApplicationStateIdentifier;
56
+ identifier: ApplicationStateIdentifier<ValueType>;
57
57
  /**
58
58
  * RPC target configuration and arguments.
59
59
  * */
60
- remoteProcedureCall: RemoteProcedureCall;
60
+ remoteProcedureCall: RemoteProcedureCall<ArgsType>;
61
61
  /**
62
62
  * Clear the application state value on error.
63
63
  *
@@ -80,7 +80,12 @@ export type ApplicationStateLoaderConfig = {
80
80
  /**
81
81
  * Load, track and access an application state value.
82
82
  *
83
+ * The returned object intentionally combines the remote-loading lifecycle with
84
+ * the same stable local controller contract as
85
+ * {@link useApplicationStateValue}. That keeps a loader-backed state value
86
+ * usable like normal React state once it has been identified.
87
+ *
83
88
  * @param config - Loader configuration for state identifier and RPC details.
84
89
  * @returns Loader controls and request state.
85
90
  * */
86
- export declare const useApplicationStateLoader: (config: ApplicationStateLoaderConfig) => ApplicationStateLoader;
91
+ export declare const useApplicationStateLoader: <ValueType = ApplicationStateValue, ArgsType extends any[] = any[]>(config: ApplicationStateLoaderConfig<ValueType, ArgsType>) => ApplicationStateLoader<ValueType, ArgsType>;
@@ -119,6 +119,11 @@ export type RouteContextType = {
119
119
  * Whether this route is the top-level router.
120
120
  */
121
121
  isTopLevel: boolean;
122
+ /**
123
+ * Absolute matched path used as the base for relative navigation at this
124
+ * route level.
125
+ */
126
+ adapterBasePath: string;
122
127
  /**
123
128
  * Adapter driving route updates.
124
129
  */
@@ -148,7 +153,7 @@ export declare const useRouteContext: () => RouteContextType;
148
153
  * @param adapter - RouteAdapter to normalize.
149
154
  * @returns Adapter with relative-aware `push` and `replace`.
150
155
  */
151
- export declare const wrapRouteAdapterWithPathResolver: (adapter: RouteAdapter) => RouteAdapter;
156
+ export declare const wrapRouteAdapterWithPathResolver: (adapter: RouteAdapter, getBasePath?: () => string) => RouteAdapter;
152
157
  /**
153
158
  * RouteProvider props.
154
159
  */
@@ -1,4 +1,4 @@
1
- import { parseTemplate, validateAreas } from './chunk-UD2ALOCE.js';
1
+ import { parseTemplate, validateAreas } from './chunk-K4R2PFNG.js';
2
2
 
3
3
  // src/app/utils/EasyLayout.tsx
4
4
  var getPascalCaseAreaName = (area) => {
@@ -552,7 +552,8 @@ var RouteContext = createContext({
552
552
  parentPath: "",
553
553
  parentPathInternal: "",
554
554
  params: {},
555
- isTopLevel: true
555
+ isTopLevel: true,
556
+ adapterBasePath: "/"
556
557
  });
557
558
  var {
558
559
  /**
@@ -565,27 +566,42 @@ var {
565
566
  Consumer: RouteContextConsumer
566
567
  } = RouteContext;
567
568
  var useRouteContext = () => useContext(RouteContext);
568
- var wrappedRouteAdapterCache = /* @__PURE__ */ new WeakMap();
569
- var wrapRouteAdapterWithPathResolver = (adapter) => {
569
+ var wrapRouteAdapterWithPathResolver = (adapter, getBasePath = () => adapter.getPath()) => {
570
570
  if (!adapter.push && !adapter.replace) {
571
571
  return adapter;
572
572
  }
573
- const cachedAdapter = wrappedRouteAdapterCache.get(adapter);
574
- if (cachedAdapter) {
575
- return cachedAdapter;
576
- }
577
573
  const wrappedAdapter = {
578
574
  ...adapter,
579
575
  push: adapter.push ? (path, title) => {
580
- adapter.push?.(resolveRouteAdapterPath(adapter.getPath(), path), title);
576
+ adapter.push?.(resolveRouteAdapterPath(getBasePath(), path), title);
581
577
  } : void 0,
582
578
  replace: adapter.replace ? (path, title) => {
583
- adapter.replace?.(resolveRouteAdapterPath(adapter.getPath(), path), title);
579
+ adapter.replace?.(resolveRouteAdapterPath(getBasePath(), path), title);
584
580
  } : void 0
585
581
  };
586
- wrappedRouteAdapterCache.set(adapter, wrappedAdapter);
587
582
  return wrappedAdapter;
588
583
  };
584
+ var getAdapterBasePath = (currentPath) => {
585
+ const normalizedPath = String(currentPath ?? "").trim();
586
+ if (normalizedPath === "") {
587
+ return "/";
588
+ }
589
+ const [pathOnly] = normalizedPath.split(/[?#]/, 1);
590
+ return pathOnly || "/";
591
+ };
592
+ var getMatchedRouteBasePath = (currentPath, matchedRoutePath) => {
593
+ const currentPathSegments = getPathArray(
594
+ getAdapterBasePath(currentPath),
595
+ PATH_DELIMITER,
596
+ true,
597
+ true,
598
+ false,
599
+ false
600
+ );
601
+ const matchedRouteSegments = getPathArray(matchedRoutePath);
602
+ const resolvedSegments = currentPathSegments.slice(0, matchedRouteSegments.length);
603
+ return resolvedSegments.length > 0 ? `/${resolvedSegments.join(PATH_DELIMITER)}` : "/";
604
+ };
589
605
  var getWindow2 = () => {
590
606
  if (typeof globalThis === "undefined") {
591
607
  return void 0;
@@ -645,6 +661,10 @@ var RouteProvider = ({
645
661
  const [currentPath, setCurrentPath] = useState(
646
662
  initialPath ?? normalizedAdapter.getPath()
647
663
  );
664
+ const adapterBasePath = useMemo(
665
+ () => getAdapterBasePath(currentPath),
666
+ [currentPath]
667
+ );
648
668
  useEffect(() => {
649
669
  return normalizedAdapter.subscribe((nextPath) => {
650
670
  setCurrentPath(nextPath);
@@ -657,9 +677,10 @@ var RouteProvider = ({
657
677
  parentPathInternal: "",
658
678
  params: {},
659
679
  isTopLevel: true,
680
+ adapterBasePath,
660
681
  adapter: normalizedAdapter
661
682
  }),
662
- [currentPath, normalizedAdapter]
683
+ [currentPath, adapterBasePath, normalizedAdapter]
663
684
  );
664
685
  return /* @__PURE__ */ jsx(RouteContextProvider, { value: contextValue, children });
665
686
  };
@@ -677,6 +698,7 @@ var RouteMatcher = ({
677
698
  parentPath = "",
678
699
  parentPathInternal = "",
679
700
  params: parentParams = {},
701
+ adapterBasePath: inheritedAdapterBasePath = "/",
680
702
  adapter
681
703
  } = useRouteContext();
682
704
  const targetCurrentPath = useMemo(
@@ -722,6 +744,14 @@ var RouteMatcher = ({
722
744
  }),
723
745
  [parentParams, matchedRoute]
724
746
  );
747
+ const matchedAdapterBasePath = useMemo(
748
+ () => matchedRoute ? getMatchedRouteBasePath(targetCurrentPath, matchedRoute.fullPath) : inheritedAdapterBasePath,
749
+ [targetCurrentPath, matchedRoute, inheritedAdapterBasePath]
750
+ );
751
+ const scopedAdapter = useMemo(
752
+ () => adapter ? wrapRouteAdapterWithPathResolver(adapter, () => matchedAdapterBasePath) : adapter,
753
+ [adapter, matchedAdapterBasePath]
754
+ );
725
755
  const newRouteContext = useMemo(
726
756
  () => ({
727
757
  currentWindowPath: targetCurrentPath,
@@ -729,9 +759,17 @@ var RouteMatcher = ({
729
759
  parentPathInternal: matchedRoute?.fullPath ?? parentPathInternal,
730
760
  params,
731
761
  isTopLevel: false,
732
- adapter
762
+ adapterBasePath: matchedAdapterBasePath,
763
+ adapter: scopedAdapter
733
764
  }),
734
- [targetCurrentPath, matchedRoute, parentPathInternal, params, adapter]
765
+ [
766
+ targetCurrentPath,
767
+ matchedRoute,
768
+ parentPathInternal,
769
+ params,
770
+ matchedAdapterBasePath,
771
+ scopedAdapter
772
+ ]
735
773
  );
736
774
  useEffect(() => {
737
775
  if (onParamsChange) {
package/native/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { computeTrackPixels } from '../chunk-TJFTWPXQ.js';
2
- import { createFormRenderer, createHistoryBackHandler, buildHistoryPath, AutoFormView, AutoForm, parseTemplate, validateAreas, computeAreaBounds, parseHistoryPath, createMemoryHistory, createBrowserRouteAdapter, Route, createRouteAdapterFromHistory } from '../chunk-UD2ALOCE.js';
2
+ import { createFormRenderer, createHistoryBackHandler, buildHistoryPath, AutoFormView, AutoForm, parseTemplate, validateAreas, computeAreaBounds, parseHistoryPath, createMemoryHistory, useRouteContext, createBrowserRouteAdapter, Route, createRouteAdapterFromHistory } from '../chunk-K4R2PFNG.js';
3
3
  import { ERROR_MESSAGE_CONSTANTS } from '../chunk-3HVYVX3S.js';
4
4
  import '../chunk-2JDOM6PB.js';
5
5
  import '../chunk-I2KLQ2HA.js';
@@ -675,6 +675,33 @@ var createNativeHistory = (options = {}) => {
675
675
  };
676
676
  };
677
677
  var createNativeBackHandler = (history) => createHistoryBackHandler(history);
678
+ var NavButton = ({
679
+ path,
680
+ replace = false,
681
+ onPress,
682
+ disabled,
683
+ children,
684
+ ...other
685
+ }) => {
686
+ const { adapter } = useRouteContext();
687
+ const onPressInternal = (event) => {
688
+ onPress?.(event);
689
+ if (disabled || event?.defaultPrevented) {
690
+ return;
691
+ }
692
+ const navigate = replace ? adapter?.replace : adapter?.push;
693
+ navigate?.(path);
694
+ };
695
+ return createElement(
696
+ Pressable,
697
+ {
698
+ ...other,
699
+ disabled,
700
+ onPress: onPressInternal
701
+ },
702
+ children
703
+ );
704
+ };
678
705
  var createNativeHardwareBackHandler = (adapter) => {
679
706
  return () => {
680
707
  if (adapter.canGoBack?.()) {
@@ -762,4 +789,4 @@ var Route2 = (props) => {
762
789
  ) : /* @__PURE__ */ jsx(Route, { ...routeProps });
763
790
  };
764
791
 
765
- export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, Route2 as Route, createNativeBackHandler, createNativeFormRenderer, createNativeHardwareBackHandler, createNativeHistory, createNativeRouteBackIntegration, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, registerNativeHardwareBackHandler, useNativeEasyLayout };
792
+ export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, Button, ErrorMessage, FieldWrapper, NativeEasyLayoutView, NavButton, Route2 as Route, createNativeBackHandler, createNativeFormRenderer, createNativeHardwareBackHandler, createNativeHistory, createNativeRouteBackIntegration, makeNativeEasyLayout, mapNativeURLToPath, nativeAutoField, nativeSuite, registerNativeHardwareBackHandler, useNativeEasyLayout };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Native intra-application navigation button.
5
+ */
6
+ import type { FC } from "react";
7
+ import { type PressableProps } from "react-native";
8
+ /**
9
+ * Props for {@link NavButton}.
10
+ */
11
+ export type NavButtonProps = PressableProps & {
12
+ /** Relative or absolute Voltra route path. */
13
+ path: string;
14
+ /** Use adapter `replace` instead of `push` when true. */
15
+ replace?: boolean;
16
+ };
17
+ /**
18
+ * Render a pressable control that navigates within the current Voltra app.
19
+ *
20
+ * Relative paths resolve from the route context where this component renders.
21
+ *
22
+ * @param props - Pressable props plus the destination route path.
23
+ * @returns Pressable element wired to the current route adapter.
24
+ */
25
+ export declare const NavButton: FC<NavButtonProps>;
@@ -13,6 +13,11 @@ export * from "./EasyLayout";
13
13
  * @group Layout and Navigation
14
14
  */
15
15
  export * from "./History";
16
+ /**
17
+ * @category native
18
+ * @group Layout and Navigation
19
+ */
20
+ export * from "./NavButton";
16
21
  /**
17
22
  * @category native
18
23
  * @group Layout and Navigation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@resistdesign/voltra",
3
- "version": "3.0.0-alpha.47",
3
+ "version": "3.0.0-alpha.49",
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
- import { createEasyLayout } from '../chunk-QXYUXJKM.js';
2
- import { createFormRenderer, AutoFormView, AutoForm, createBrowserRouteAdapter, Route } from '../chunk-UD2ALOCE.js';
1
+ import { createEasyLayout } from '../chunk-BSHQIRBV.js';
2
+ import { createFormRenderer, AutoFormView, AutoForm, useRouteContext, createBrowserRouteAdapter, Route } from '../chunk-K4R2PFNG.js';
3
3
  import { ERROR_MESSAGE_CONSTANTS } from '../chunk-3HVYVX3S.js';
4
- import '../chunk-2JDOM6PB.js';
4
+ import { resolveRouteAdapterPath } from '../chunk-2JDOM6PB.js';
5
5
  import { __export, __reExport } from '../chunk-I2KLQ2HA.js';
6
6
  import { createElement, useCallback, useRef } from 'react';
7
7
  import * as styledBase from 'styled-components';
@@ -648,6 +648,39 @@ var styledFactory = {
648
648
  var getEasyLayout = (extendFrom, areasExtendFrom, options = {}) => {
649
649
  return createEasyLayout(styledFactory, extendFrom, areasExtendFrom, options);
650
650
  };
651
+ var NavLink = ({
652
+ path,
653
+ replace = false,
654
+ onClick,
655
+ title,
656
+ children,
657
+ ...other
658
+ }) => {
659
+ const { adapter, adapterBasePath } = useRouteContext();
660
+ const href = resolveRouteAdapterPath(adapterBasePath, path);
661
+ const onClickInternal = (event) => {
662
+ onClick?.(event);
663
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) {
664
+ return;
665
+ }
666
+ const navigate = replace ? adapter?.replace : adapter?.push;
667
+ if (!navigate) {
668
+ return;
669
+ }
670
+ event.preventDefault();
671
+ navigate(path, title);
672
+ };
673
+ return createElement(
674
+ "a",
675
+ {
676
+ ...other,
677
+ href,
678
+ title,
679
+ onClick: onClickInternal
680
+ },
681
+ children
682
+ );
683
+ };
651
684
  var Route2 = (props) => {
652
685
  const hasMatcherProps = typeof props.path !== "undefined" || typeof props.exact !== "undefined" || typeof props.onParamsChange !== "undefined";
653
686
  const adapterRef = useRef(null);
@@ -665,4 +698,4 @@ var Route2 = (props) => {
665
698
  ) : /* @__PURE__ */ jsx(Route, { ...routeProps });
666
699
  };
667
700
 
668
- export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, ErrorMessage, FieldWrapper, Route2 as Route, createWebFormRenderer, getEasyLayout, webAutoField, webSuite };
701
+ export { ArrayContainer, ArrayItemWrapper, AutoField, AutoForm2 as AutoForm, AutoFormView2 as AutoFormView, ErrorMessage, FieldWrapper, NavLink, Route2 as Route, createWebFormRenderer, getEasyLayout, webAutoField, webSuite };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @packageDocumentation
3
+ *
4
+ * Web intra-application navigation link.
5
+ */
6
+ import type { AnchorHTMLAttributes, FC } from "react";
7
+ /**
8
+ * Props for {@link NavLink}.
9
+ */
10
+ export type NavLinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> & {
11
+ /** Relative or absolute Voltra route path. */
12
+ path: string;
13
+ /** Use adapter `replace` instead of `push` when true. */
14
+ replace?: boolean;
15
+ };
16
+ /**
17
+ * Render an anchor-like control that navigates within the current Voltra app.
18
+ *
19
+ * Relative paths resolve from the route context where this component renders.
20
+ *
21
+ * @param props - Anchor props plus the destination route path.
22
+ * @returns Anchor element wired to the current route adapter.
23
+ */
24
+ export declare const NavLink: FC<NavLinkProps>;
@@ -4,4 +4,5 @@
4
4
  * Web-only utilities.
5
5
  */
6
6
  export * from "./EasyLayout";
7
+ export * from "./NavLink";
7
8
  export * from "./Route";