@powerportalspro/react 5.0.0-beta.1
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/dist/index.cjs +2333 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2524 -0
- package/dist/index.d.ts +2524 -0
- package/dist/index.js +2263 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,2524 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { components, TableMetadata as TableMetadata$1, ViewMetadata, CurrentUserInfo, Transport, AuthClient, PowerPortalsProClient, LoginRequest, LoginResponse, SwitchIdentityResponse, PowerPortalsProError, TableRecord, RetrieveRecordsResponse, GridDataRequest, GridDataResponse, GridFilterBase, ChartAggregation, ColumnSort, ResolvedColumn, TableSecurityPermission, ChartData, OrganizationRequest } from '@powerportalspro/core';
|
|
4
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
type EnvironmentFileSettings$2 = components['schemas']['EnvironmentFileSettings'];
|
|
7
|
+
/**
|
|
8
|
+
* Client-side cache for table metadata. Mirrors Blazor's
|
|
9
|
+
* `ITableMetadataCache` interface (in `PowerPortalsPro.Common.Services`).
|
|
10
|
+
*
|
|
11
|
+
* The default implementation lives in-memory (Map) with in-flight-promise
|
|
12
|
+
* dedup so N parallel `getAsync` calls for the same table share one
|
|
13
|
+
* server round-trip. A localStorage layer can be stacked on top by
|
|
14
|
+
* supplying a custom implementation through
|
|
15
|
+
* {@link PowerPortalsProProviderProps.tableMetadataCache}.
|
|
16
|
+
*
|
|
17
|
+
* Why this exists: the inline-editable grid mounts one `<RecordContext>`
|
|
18
|
+
* per visible row when the user toggles edit mode on. Each RC would
|
|
19
|
+
* otherwise fire its own `retrieveTableMetadataAsync` round-trip —
|
|
20
|
+
* thundering-herd against the same endpoint that produces visibly
|
|
21
|
+
* staggered row rendering as each fetch's response lands. The cache
|
|
22
|
+
* collapses that to a single fetch shared across all consumers.
|
|
23
|
+
*/
|
|
24
|
+
interface TableMetadataCache {
|
|
25
|
+
/**
|
|
26
|
+
* Returns the cached metadata for `tableLogicalName`, fetching it from
|
|
27
|
+
* the server on a miss. Concurrent calls for the same table dedupe
|
|
28
|
+
* onto a single in-flight promise. Returns `null` only when the table
|
|
29
|
+
* itself doesn't exist on the server (404).
|
|
30
|
+
*/
|
|
31
|
+
getAsync(tableLogicalName: string): Promise<TableMetadata$1 | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Manually evicts an entry. Use when consumer code knows the server-
|
|
34
|
+
* side metadata has changed (rare — typical case is letting natural
|
|
35
|
+
* page refresh handle it).
|
|
36
|
+
*/
|
|
37
|
+
invalidate(tableLogicalName: string): void;
|
|
38
|
+
/** Drops every cached entry. */
|
|
39
|
+
clear(): void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Client-side cache for Dataverse view metadata. Same shape and
|
|
43
|
+
* rationale as {@link TableMetadataCache} — mirrors Blazor's
|
|
44
|
+
* `IViewMetadataCache` (in `PowerPortalsPro.Common.Services`).
|
|
45
|
+
*
|
|
46
|
+
* Keyed by view id (Dataverse GUID, hyphenated, no braces). The default
|
|
47
|
+
* implementation is in-memory with in-flight-promise dedup.
|
|
48
|
+
*/
|
|
49
|
+
interface ViewMetadataCache {
|
|
50
|
+
/**
|
|
51
|
+
* Returns the cached metadata for `viewId`, fetching it from the
|
|
52
|
+
* server on a miss. Concurrent calls dedupe onto one in-flight
|
|
53
|
+
* promise. Returns `null` only when the view doesn't exist (404).
|
|
54
|
+
*/
|
|
55
|
+
getAsync(viewId: string): Promise<ViewMetadata | null>;
|
|
56
|
+
/** Manually evicts an entry. */
|
|
57
|
+
invalidate(viewId: string): void;
|
|
58
|
+
/** Drops every cached entry. */
|
|
59
|
+
clear(): void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Client-side cache for the per-table list of available views (powers
|
|
63
|
+
* grid view-pickers and default-view resolution). Same in-memory +
|
|
64
|
+
* in-flight-promise-dedup shape as {@link TableMetadataCache}. Keyed by
|
|
65
|
+
* table logical name.
|
|
66
|
+
*
|
|
67
|
+
* Avoids the case where two grids on the same page targeting the same
|
|
68
|
+
* table each fetch the views list independently. Server-side caching
|
|
69
|
+
* already collapses these to one Dataverse call per server lifetime;
|
|
70
|
+
* the client cache collapses them to one HTTP call per browser session.
|
|
71
|
+
*/
|
|
72
|
+
interface ViewsForTableCache {
|
|
73
|
+
/**
|
|
74
|
+
* Returns the cached views list for `tableLogicalName`, fetching from
|
|
75
|
+
* the server on a miss. Returns an empty array (not `null`) when the
|
|
76
|
+
* table exists but has no views — distinguishes "no views" from "table
|
|
77
|
+
* doesn't exist" (the latter raises through the underlying client).
|
|
78
|
+
*/
|
|
79
|
+
getAsync(tableLogicalName: string): Promise<readonly ViewMetadata[]>;
|
|
80
|
+
/** Manually evicts an entry. */
|
|
81
|
+
invalidate(tableLogicalName: string): void;
|
|
82
|
+
/** Drops every cached entry. */
|
|
83
|
+
clear(): void;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Singleton cache for the org-wide environment file settings (blocked
|
|
87
|
+
* extensions, max upload size). Unlike the keyed caches, there's only
|
|
88
|
+
* one `EnvironmentFileSettings` per environment — no key parameter.
|
|
89
|
+
*
|
|
90
|
+
* Avoids the case where multiple `<FileGrid>`s on the same page each
|
|
91
|
+
* fire their own `getEnvironmentFileSettingsAsync` request. The server
|
|
92
|
+
* already caches the response, but the client-side cache eliminates
|
|
93
|
+
* the duplicate HTTP round-trips entirely.
|
|
94
|
+
*/
|
|
95
|
+
interface EnvironmentFileSettingsCache {
|
|
96
|
+
/**
|
|
97
|
+
* Returns the cached settings, fetching from the server on a miss.
|
|
98
|
+
* Concurrent calls share a single in-flight promise. Returns `null`
|
|
99
|
+
* only when the server explicitly returns no settings (rare —
|
|
100
|
+
* typically the endpoint always returns a value).
|
|
101
|
+
*/
|
|
102
|
+
getAsync(): Promise<EnvironmentFileSettings$2 | null>;
|
|
103
|
+
/**
|
|
104
|
+
* Drops the cached value. The next `getAsync` re-fetches from the
|
|
105
|
+
* server.
|
|
106
|
+
*/
|
|
107
|
+
invalidate(): void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Status values for the {@link AuthState} state machine.
|
|
112
|
+
*
|
|
113
|
+
* Hybrid shape — bare string-literal union as the type (clean narrowing) plus a
|
|
114
|
+
* const-object export for consumers who prefer refactor-safe constants. Both
|
|
115
|
+
* compare against the same runtime string, so `auth.status === 'loading'` and
|
|
116
|
+
* `auth.status === AuthStatus.Loading` are equivalent. Matches the
|
|
117
|
+
* auto-generated `LoginResult`-style pattern from `@powerportalspro/core`.
|
|
118
|
+
*/
|
|
119
|
+
declare const AuthStatus: {
|
|
120
|
+
readonly Loading: "loading";
|
|
121
|
+
readonly Anonymous: "anonymous";
|
|
122
|
+
readonly Authenticated: "authenticated";
|
|
123
|
+
};
|
|
124
|
+
type AuthStatus = (typeof AuthStatus)[keyof typeof AuthStatus];
|
|
125
|
+
/**
|
|
126
|
+
* Discriminated state union exposed by {@link useAuth}. `loading` is the initial state
|
|
127
|
+
* during the first `/api/auth/me` call on mount; once that resolves, the state is
|
|
128
|
+
* either `anonymous` or `authenticated` for the rest of the app's lifetime (until
|
|
129
|
+
* `login()` / `logout()` actions transition it).
|
|
130
|
+
*
|
|
131
|
+
* The `authenticated` branch carries the {@link CurrentUserInfo} the server returned —
|
|
132
|
+
* the auto-generated DTO from `@powerportalspro/core`, no narrowing applied. Fields
|
|
133
|
+
* like `roles` arrive as `string[] | undefined` per the wire contract; consumers
|
|
134
|
+
* coalesce to `[]` at the read site if they want a non-null array.
|
|
135
|
+
*/
|
|
136
|
+
type AuthState = {
|
|
137
|
+
readonly status: 'loading';
|
|
138
|
+
} | {
|
|
139
|
+
readonly status: 'anonymous';
|
|
140
|
+
} | {
|
|
141
|
+
readonly status: 'authenticated';
|
|
142
|
+
readonly user: CurrentUserInfo;
|
|
143
|
+
};
|
|
144
|
+
interface PowerPortalsProContextValue {
|
|
145
|
+
/** The shared {@link Transport} both clients are constructed against. Exposed for advanced use. */
|
|
146
|
+
readonly transport: Transport;
|
|
147
|
+
/** {@link AuthClient} instance — same one returned by `usePowerPortalsPro().auth`. */
|
|
148
|
+
readonly auth: AuthClient;
|
|
149
|
+
/** {@link PowerPortalsProClient} instance — same one returned by `usePowerPortalsPro().ppp`. */
|
|
150
|
+
readonly ppp: PowerPortalsProClient;
|
|
151
|
+
/**
|
|
152
|
+
* Client-side cache for Dataverse table metadata. Reads dedupe
|
|
153
|
+
* concurrent fetches and persist resolved values for the lifetime of
|
|
154
|
+
* the `PowerPortalsProClient`. See {@link TableMetadataCache}.
|
|
155
|
+
*/
|
|
156
|
+
readonly tableMetadataCache: TableMetadataCache;
|
|
157
|
+
/**
|
|
158
|
+
* Client-side cache for Dataverse view metadata. Same shape as
|
|
159
|
+
* {@link tableMetadataCache}, keyed by view id. See {@link ViewMetadataCache}.
|
|
160
|
+
*/
|
|
161
|
+
readonly viewMetadataCache: ViewMetadataCache;
|
|
162
|
+
/**
|
|
163
|
+
* Client-side cache for the per-table views list (powers grid view-
|
|
164
|
+
* pickers). See {@link ViewsForTableCache}.
|
|
165
|
+
*/
|
|
166
|
+
readonly viewsForTableCache: ViewsForTableCache;
|
|
167
|
+
/**
|
|
168
|
+
* Client-side cache for the singleton environment file settings
|
|
169
|
+
* (blocked extensions, max upload size). See
|
|
170
|
+
* {@link EnvironmentFileSettingsCache}.
|
|
171
|
+
*/
|
|
172
|
+
readonly environmentFileSettingsCache: EnvironmentFileSettingsCache;
|
|
173
|
+
/** Current auth state. {@link useAuth} unpacks this directly. */
|
|
174
|
+
readonly authState: AuthState;
|
|
175
|
+
/** Refreshes the auth state by re-calling `/api/auth/me`. */
|
|
176
|
+
readonly refreshAuth: () => Promise<void>;
|
|
177
|
+
/** Submits credentials. On success the auth state transitions to `authenticated` automatically. */
|
|
178
|
+
readonly login: (request: LoginRequest) => Promise<LoginResponse>;
|
|
179
|
+
/** Clears the auth cookie and transitions the auth state to `anonymous`. */
|
|
180
|
+
readonly logout: () => Promise<void>;
|
|
181
|
+
/**
|
|
182
|
+
* Swaps the auth cookie for the user's alt-identity sibling (the
|
|
183
|
+
* contact↔systemuser pair stamped via `PortalClaimTypes.AltIdentity*`).
|
|
184
|
+
* On `SwitchIdentityResult.Switched` the in-memory auth state is refreshed
|
|
185
|
+
* from `/api/auth/me`; other outcomes leave it unchanged so the caller can
|
|
186
|
+
* surface a message to the user. The endpoint takes no parameters — the
|
|
187
|
+
* alt id is read from the cookie's claims server-side.
|
|
188
|
+
*/
|
|
189
|
+
readonly switchIdentity: () => Promise<SwitchIdentityResponse>;
|
|
190
|
+
}
|
|
191
|
+
declare const PowerPortalsProContext: react.Context<PowerPortalsProContextValue | null>;
|
|
192
|
+
|
|
193
|
+
interface PowerPortalsProProviderProps {
|
|
194
|
+
children: ReactNode;
|
|
195
|
+
/**
|
|
196
|
+
* Optional pre-constructed {@link Transport} for the AuthClient and PowerPortalsProClient
|
|
197
|
+
* to share. Pass one to set a non-default `baseUrl`, inject a custom `fetch`
|
|
198
|
+
* implementation for tests, etc. When omitted, a default transport is constructed —
|
|
199
|
+
* same-origin, browser fetch, cookie credentials included.
|
|
200
|
+
*
|
|
201
|
+
* The provider holds a stable reference to the transport for its lifetime — passing
|
|
202
|
+
* a new instance on every render is wasteful, so memoize it at the call site or
|
|
203
|
+
* construct it at module scope. Changes to this prop after first render are ignored.
|
|
204
|
+
*/
|
|
205
|
+
transport?: Transport;
|
|
206
|
+
/**
|
|
207
|
+
* Single override JSON layered on top of the framework defaults. Use this
|
|
208
|
+
* when the consumer app has one additional strings file (e.g.
|
|
209
|
+
* `/localization/app.en.json`) with app-specific strings or English
|
|
210
|
+
* overrides. The framework defaults remain the base.
|
|
211
|
+
*
|
|
212
|
+
* Pass `localizationUrl={false}` to opt out of the auto-mounted localizer
|
|
213
|
+
* entirely (e.g. when adapting i18next via your own `<LocalizerProvider>`).
|
|
214
|
+
*/
|
|
215
|
+
localizationUrl?: string | false;
|
|
216
|
+
/**
|
|
217
|
+
* Multiple override JSONs layered on top of the framework defaults, in
|
|
218
|
+
* order. Later URLs override earlier ones for matching keys. Use this when
|
|
219
|
+
* the consumer app has more than one strings file (e.g. one per page,
|
|
220
|
+
* one per feature module).
|
|
221
|
+
*
|
|
222
|
+
* Combined with `localizationUrl`, both are appended to the framework
|
|
223
|
+
* defaults — `localizationUrl` first, then `localizationUrls`.
|
|
224
|
+
*/
|
|
225
|
+
localizationUrls?: readonly string[];
|
|
226
|
+
/**
|
|
227
|
+
* Baseline localization prefixes the auto-mounted
|
|
228
|
+
* `<DefaultLocalizerProvider>` should eagerly load alongside the
|
|
229
|
+
* default bundle. Forwards directly to that provider's `prefixes`
|
|
230
|
+
* prop — see its doc for the full semantics. Ignored when
|
|
231
|
+
* `localizationUrl={false}` (consumer is wiring their own
|
|
232
|
+
* localizer adapter).
|
|
233
|
+
*
|
|
234
|
+
* Typical use: declare baseline tables / views the entire app
|
|
235
|
+
* touches (header lookups, navigation chrome) so every
|
|
236
|
+
* `<LocalizationBoundary>` inherits them for free.
|
|
237
|
+
*/
|
|
238
|
+
localizationPrefixes?: readonly string[];
|
|
239
|
+
/**
|
|
240
|
+
* Custom {@link TableMetadataCache} implementation. When omitted, the
|
|
241
|
+
* provider mounts an in-memory cache with in-flight-promise dedup.
|
|
242
|
+
* Override with a localStorage-backed implementation for cross-session
|
|
243
|
+
* persistence, or a mock for tests.
|
|
244
|
+
*/
|
|
245
|
+
tableMetadataCache?: TableMetadataCache;
|
|
246
|
+
/**
|
|
247
|
+
* Custom {@link ViewMetadataCache} implementation. Same defaulting
|
|
248
|
+
* behavior as {@link tableMetadataCache}.
|
|
249
|
+
*/
|
|
250
|
+
viewMetadataCache?: ViewMetadataCache;
|
|
251
|
+
/**
|
|
252
|
+
* Custom {@link ViewsForTableCache} implementation. Same defaulting
|
|
253
|
+
* behavior as {@link tableMetadataCache}.
|
|
254
|
+
*/
|
|
255
|
+
viewsForTableCache?: ViewsForTableCache;
|
|
256
|
+
/**
|
|
257
|
+
* Custom {@link EnvironmentFileSettingsCache} implementation. Same
|
|
258
|
+
* defaulting behavior as {@link tableMetadataCache}.
|
|
259
|
+
*/
|
|
260
|
+
environmentFileSettingsCache?: EnvironmentFileSettingsCache;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Top-level provider for `@powerportalspro/react`. Constructs an {@link AuthClient}
|
|
264
|
+
* and a {@link PowerPortalsProClient} (sharing one {@link Transport}) and manages the
|
|
265
|
+
* auth state machine — `loading` until the first `/api/auth/me` response, then
|
|
266
|
+
* `anonymous` or `authenticated` for the rest of the session.
|
|
267
|
+
*
|
|
268
|
+
* Wrap the app once near the root:
|
|
269
|
+
* ```tsx
|
|
270
|
+
* <PowerPortalsProProvider>
|
|
271
|
+
* <App />
|
|
272
|
+
* </PowerPortalsProProvider>
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* Hooks ({@link useAuth}, {@link usePowerPortalsPro}, {@link useRecord}, …) read from
|
|
276
|
+
* this provider's context.
|
|
277
|
+
*/
|
|
278
|
+
declare function PowerPortalsProProvider({ children, transport, localizationUrl, localizationUrls, localizationPrefixes, tableMetadataCache, viewMetadataCache, viewsForTableCache, environmentFileSettingsCache, }: PowerPortalsProProviderProps): react_jsx_runtime.JSX.Element;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Returns the {@link AuthClient}, {@link PowerPortalsProClient}, and underlying
|
|
282
|
+
* {@link Transport} from the nearest {@link PowerPortalsProProvider}. Use this when
|
|
283
|
+
* you need to call a method that doesn't have a dedicated hook (the higher-level
|
|
284
|
+
* `useAuth` / `useRecord` / `useFetchRecords` etc. cover the common cases).
|
|
285
|
+
*
|
|
286
|
+
* Throws if there's no provider above the calling component.
|
|
287
|
+
*/
|
|
288
|
+
declare function usePowerPortalsPro(): PowerPortalsProContextValue;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Auth state + actions, surfaced as a discriminated union spread. Compare against
|
|
292
|
+
* the bare string literals or against {@link AuthStatus} constants — both are
|
|
293
|
+
* type-narrowed equivalently:
|
|
294
|
+
*
|
|
295
|
+
* ```tsx
|
|
296
|
+
* import { AuthStatus, useAuth } from '@powerportalspro/react';
|
|
297
|
+
*
|
|
298
|
+
* const auth = useAuth();
|
|
299
|
+
* if (auth.status === AuthStatus.Loading) return <Spinner />;
|
|
300
|
+
* if (auth.status === AuthStatus.Anonymous) return <SignInButton onClick={() => auth.login(...)} />;
|
|
301
|
+
* if (auth.status === AuthStatus.Authenticated) return <span>Hi, {auth.user.email}</span>;
|
|
302
|
+
* ```
|
|
303
|
+
*
|
|
304
|
+
* Behind the scenes the state lives on the {@link PowerPortalsProProvider} and
|
|
305
|
+
* propagates via context, so all `useAuth` calls in the tree see the same value
|
|
306
|
+
* and re-render on transitions.
|
|
307
|
+
*
|
|
308
|
+
* `login` mirrors {@link AuthClient.loginAsync} — same request/response shapes —
|
|
309
|
+
* but additionally re-fetches `/api/auth/me` on `Success` so the auth state
|
|
310
|
+
* transitions to `authenticated` without an extra round-trip from the consumer.
|
|
311
|
+
*
|
|
312
|
+
* `logout` mirrors {@link AuthClient.logoutAsync} and transitions the state to
|
|
313
|
+
* `anonymous` immediately on success.
|
|
314
|
+
*/
|
|
315
|
+
type UseAuthResult = AuthState & {
|
|
316
|
+
/** Re-runs `/api/auth/me` and updates the state. Call after side effects that may have changed the user (server-driven role updates, contact-record edits, etc.). */
|
|
317
|
+
refresh: () => Promise<void>;
|
|
318
|
+
/** Submits credentials. On `LoginResult.Success`, the state transitions to `authenticated`. */
|
|
319
|
+
login: (request: LoginRequest) => Promise<LoginResponse>;
|
|
320
|
+
/** Clears the auth cookie and transitions the state to `anonymous`. */
|
|
321
|
+
logout: () => Promise<void>;
|
|
322
|
+
/**
|
|
323
|
+
* Swaps the auth cookie for the user's alt-identity sibling. Returns the
|
|
324
|
+
* server's `SwitchIdentityResponse` so the caller can surface non-success
|
|
325
|
+
* outcomes (`NoAltIdentity`, `AltIdentityNotFound`, `NotAuthenticated`).
|
|
326
|
+
* On `Switched`, the in-memory auth state is refreshed automatically.
|
|
327
|
+
*/
|
|
328
|
+
switchIdentity: () => Promise<SwitchIdentityResponse>;
|
|
329
|
+
};
|
|
330
|
+
declare function useAuth(): UseAuthResult;
|
|
331
|
+
|
|
332
|
+
interface AuthorizedViewProps {
|
|
333
|
+
/**
|
|
334
|
+
* Children rendered when the principal is authenticated (and, if {@link roles}
|
|
335
|
+
* is supplied, holds at least one of the listed roles).
|
|
336
|
+
*/
|
|
337
|
+
children: ReactNode;
|
|
338
|
+
/**
|
|
339
|
+
* Optional role gate. When supplied, the principal must hold AT LEAST ONE of
|
|
340
|
+
* the listed roles for the children to render — matching Blazor's
|
|
341
|
+
* `<AuthorizeView Roles="Admin,Manager">` (comma-separated = OR).
|
|
342
|
+
*
|
|
343
|
+
* Roles arrive on `auth.user.roles` from `/api/auth/me`. Per-role claims are
|
|
344
|
+
* only emitted for systemuser-backed principals — contacts don't have roles —
|
|
345
|
+
* so a role gate also implicitly limits the surface to staff users.
|
|
346
|
+
*/
|
|
347
|
+
roles?: string | readonly string[];
|
|
348
|
+
/**
|
|
349
|
+
* Rendered when the principal is anonymous, the auth state is still loading,
|
|
350
|
+
* or the role check fails. Defaults to `null` (renders nothing).
|
|
351
|
+
*/
|
|
352
|
+
fallback?: ReactNode;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Headless auth gate — React equivalent of Blazor's `<AuthorizeView>`. Renders
|
|
356
|
+
* {@link AuthorizedViewProps.children} only when the principal is authenticated;
|
|
357
|
+
* if {@link AuthorizedViewProps.roles} is supplied, the principal must hold at
|
|
358
|
+
* least one of the listed roles. Otherwise renders {@link AuthorizedViewProps.fallback}
|
|
359
|
+
* (default `null`).
|
|
360
|
+
*
|
|
361
|
+
* Loading is treated as "not yet authorized" — fallback renders during the
|
|
362
|
+
* initial `/api/auth/me` round-trip so consumers don't briefly flash the
|
|
363
|
+
* authorized UI before the cookie principal is hydrated.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```tsx
|
|
367
|
+
* <AuthorizedView>
|
|
368
|
+
* <Link to="/Account/Manage">Manage account</Link>
|
|
369
|
+
* </AuthorizedView>
|
|
370
|
+
*
|
|
371
|
+
* <AuthorizedView roles="SystemAdmin">
|
|
372
|
+
* <Link to="/SiteAdmin">Site admin</Link>
|
|
373
|
+
* </AuthorizedView>
|
|
374
|
+
*
|
|
375
|
+
* <AuthorizedView roles={["Admin", "Manager"]} fallback={<span>Restricted</span>}>
|
|
376
|
+
* <SensitiveTools />
|
|
377
|
+
* </AuthorizedView>
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
declare function AuthorizedView({ children, roles, fallback }: AuthorizedViewProps): react_jsx_runtime.JSX.Element;
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Status values for query hooks ({@link useRecord}, {@link useFetchRecords},
|
|
384
|
+
* {@link useTableMetadata}, …) and {@link RecordContext}.
|
|
385
|
+
*
|
|
386
|
+
* Hybrid shape — bare string-literal union as the type for clean narrowing in
|
|
387
|
+
* `if (q.status === 'success')`-style branching, plus a const-object export so
|
|
388
|
+
* consumers who prefer the refactor-safe form can write `q.status === QueryStatus.Success`.
|
|
389
|
+
* Both compare against the same runtime string. Matches the auto-generated
|
|
390
|
+
* `LoginResult`-style pattern from `@powerportalspro/core`.
|
|
391
|
+
*/
|
|
392
|
+
declare const QueryStatus: {
|
|
393
|
+
readonly Loading: "loading";
|
|
394
|
+
readonly Success: "success";
|
|
395
|
+
readonly Error: "error";
|
|
396
|
+
};
|
|
397
|
+
type QueryStatus = (typeof QueryStatus)[keyof typeof QueryStatus];
|
|
398
|
+
/**
|
|
399
|
+
* Discriminated state surface returned by every query hook. `data` and `error` are
|
|
400
|
+
* always present as fields (so consumers can read them at any time) but only carry
|
|
401
|
+
* meaningful values when `status` matches. `refetch()` re-runs the query with the
|
|
402
|
+
* current dependency values, regardless of current state.
|
|
403
|
+
*
|
|
404
|
+
* Loosely mirrors react-query's API shape so consumers familiar with that library
|
|
405
|
+
* find the contract familiar — but with no caching, no dedup, no shared cache between
|
|
406
|
+
* components. Each hook call owns its own fetch lifecycle.
|
|
407
|
+
*
|
|
408
|
+
* **`status` vs. `isFetching`** — `status` describes the *outcome* (what's
|
|
409
|
+
* in `data`): `'loading'` = no data yet, `'success'` = data is populated,
|
|
410
|
+
* `'error'` = the most recent fetch threw. `isFetching` describes the
|
|
411
|
+
* *current request* — true whenever a network call is in flight,
|
|
412
|
+
* regardless of whether prior data is still being shown. With
|
|
413
|
+
* `keepPreviousData` enabled (see {@link useAsyncResource}), a deps-change
|
|
414
|
+
* triggered refetch keeps `status='success'` and the prior `data`
|
|
415
|
+
* populated while `isFetching` flips to true, so consumers can render a
|
|
416
|
+
* scrim overlay instead of wiping the UI.
|
|
417
|
+
*/
|
|
418
|
+
interface QueryResult<T> {
|
|
419
|
+
readonly status: QueryStatus;
|
|
420
|
+
readonly data: T | undefined;
|
|
421
|
+
readonly error: PowerPortalsProError | undefined;
|
|
422
|
+
/**
|
|
423
|
+
* True whenever a network request is currently in flight, including
|
|
424
|
+
* refetches triggered by deps changes or explicit `refetch()` calls.
|
|
425
|
+
* Independent of `status`: when `keepPreviousData` is enabled, the hook
|
|
426
|
+
* can be `status='success'` (prior data still on screen) AND
|
|
427
|
+
* `isFetching=true` (new request running) at the same time.
|
|
428
|
+
*/
|
|
429
|
+
readonly isFetching: boolean;
|
|
430
|
+
/**
|
|
431
|
+
* Re-runs the query immediately, bypassing the deps comparison. Returns a
|
|
432
|
+
* promise that resolves when the next fetch settles successfully (or
|
|
433
|
+
* rejects if the fetch errors). Awaiting is optional — callers that just
|
|
434
|
+
* want to trigger a refresh can fire-and-forget.
|
|
435
|
+
*
|
|
436
|
+
* If the request is superseded by another deps change or another
|
|
437
|
+
* `refetch()` call before it settles, the returned promise resolves anyway
|
|
438
|
+
* (with the stale-but-final state of this `refetch` call's outcome) — that
|
|
439
|
+
* matches the "kick + observe" semantics callers want for a save-then-refresh
|
|
440
|
+
* flow, where the new data landing is what matters, not which trigger
|
|
441
|
+
* caused it.
|
|
442
|
+
*/
|
|
443
|
+
readonly refetch: () => Promise<void>;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
interface UseRecordOptions {
|
|
447
|
+
/** Column logical names to project. Omit for the table's full visible column set. */
|
|
448
|
+
columns?: readonly string[];
|
|
449
|
+
/** When false, the hook stays in the idle (success / data: undefined) state and skips fetching. Defaults to true. */
|
|
450
|
+
enabled?: boolean;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Fetches a single Dataverse record by table + id. Re-runs when `tableLogicalName`,
|
|
454
|
+
* `recordId`, or the joined column list changes; aborts in-flight requests on unmount.
|
|
455
|
+
*
|
|
456
|
+
* The `data` field is the raw `TableRecord` envelope (id, tableName, properties,
|
|
457
|
+
* permissions, formattedValues). Drill into `data.properties[columnName]` for typed
|
|
458
|
+
* column values; `data.formattedValues[columnName]` for the display string Dataverse
|
|
459
|
+
* supplied (already formatted in the user's locale).
|
|
460
|
+
*
|
|
461
|
+
* Permission enforcement happens server-side — an unauthorized read surfaces as a
|
|
462
|
+
* `UnauthorizedAccessError` in `error`, not a partial record.
|
|
463
|
+
*/
|
|
464
|
+
declare function useRecord(tableLogicalName: string, recordId: string, options?: UseRecordOptions): QueryResult<TableRecord>;
|
|
465
|
+
|
|
466
|
+
interface UseFetchRecordsOptions {
|
|
467
|
+
/** When false, the hook stays in the idle (success / data: undefined) state and skips fetching. Defaults to true. */
|
|
468
|
+
enabled?: boolean;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Runs a FetchXML query. Re-runs when the `fetchXml` string changes; aborts
|
|
472
|
+
* in-flight requests on unmount. The query string is the FetchXML XML literal —
|
|
473
|
+
* the same format Dataverse natively accepts.
|
|
474
|
+
*
|
|
475
|
+
* `data.records` is the matched record list; `data.pagingCookie` and friends drive
|
|
476
|
+
* the standard FetchXML paging UX. Sorting, filtering, and link-entity joins are all
|
|
477
|
+
* encoded in the FetchXML — for grid scenarios a higher-level hook (in
|
|
478
|
+
* `@powerportalspro/react-fluent`'s eventual `MainGrid`) will manage the FetchXML
|
|
479
|
+
* lifecycle around view changes.
|
|
480
|
+
*/
|
|
481
|
+
declare function useFetchRecords(fetchXml: string, options?: UseFetchRecordsOptions): QueryResult<RetrieveRecordsResponse>;
|
|
482
|
+
|
|
483
|
+
interface UseGridDataOptions {
|
|
484
|
+
/** When false, the hook stays in the idle (success / data: undefined) state and skips fetching. Defaults to true. */
|
|
485
|
+
enabled?: boolean;
|
|
486
|
+
/**
|
|
487
|
+
* When true, deps-change refetches (new page, new sort, new search text)
|
|
488
|
+
* hold the prior successful response on screen while the next request is
|
|
489
|
+
* in flight — `status` stays `'success'`, `data` stays populated, and
|
|
490
|
+
* `isFetching` flips to true so consumers can scrim the UI. Defaults to
|
|
491
|
+
* false, matching the strict state-machine behavior the other hooks use.
|
|
492
|
+
* Grids and other long-lived data surfaces will typically want this on.
|
|
493
|
+
*/
|
|
494
|
+
keepPreviousData?: boolean;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Grid-data counterpart to {@link useFetchRecords}. Calls the server's
|
|
498
|
+
* `POST /api/grids/data` endpoint, which composes the final FetchXML via the
|
|
499
|
+
* shared `IFetchXmlQueryComposer` — applying search across the view's
|
|
500
|
+
* searchable columns, replacing sorts, AND-merging filters, etc. — so the
|
|
501
|
+
* search / sort / paging semantics match the Blazor grid exactly.
|
|
502
|
+
*
|
|
503
|
+
* Response includes both the row data (`tableRecords`, `pagingInfo`) and a
|
|
504
|
+
* server-resolved `columns` array — display names already localized, types
|
|
505
|
+
* for cell dispatch, sortability flags, widths. Grid consumers render
|
|
506
|
+
* straight from the response without a separate table-metadata round-trip.
|
|
507
|
+
*
|
|
508
|
+
* Re-runs whenever any field of the `request` changes (compared via JSON
|
|
509
|
+
* serialization — cheap, and the request is a small flat object). Aborts
|
|
510
|
+
* in-flight requests on unmount or when the request changes.
|
|
511
|
+
*
|
|
512
|
+
* Exactly one of `request.viewId` or `request.fetchXml` must be supplied;
|
|
513
|
+
* sending both or neither produces a 400 from the server.
|
|
514
|
+
*/
|
|
515
|
+
declare function useGridData(request: GridDataRequest, options?: UseGridDataOptions): QueryResult<GridDataResponse>;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Soft cross-filter — a chart slice the user clicked but didn't drill
|
|
519
|
+
* through. Drives styling on consumers (chart: emphasize matching
|
|
520
|
+
* slice + dim others; grid: dim non-matching rows) WITHOUT changing
|
|
521
|
+
* the server-side query. Distinct from the hard filter
|
|
522
|
+
* ({@link ViewDataSourceProps.filter}) which IS sent to the server
|
|
523
|
+
* and re-fetches both row page and aggregations.
|
|
524
|
+
*/
|
|
525
|
+
interface ViewDataSourceHighlight {
|
|
526
|
+
/** Column the user clicked into — the slice's group-by column. */
|
|
527
|
+
readonly column: string;
|
|
528
|
+
/** Raw column value to match. Compared by strict equality. */
|
|
529
|
+
readonly value: unknown;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Options accepted by {@link useViewDataSource}. Split into two roles:
|
|
533
|
+
*
|
|
534
|
+
* - **Props** (forwarded straight into every request) — `viewId` /
|
|
535
|
+
* `fetchXml` source, `staticFilters` (always-applied, never cleared
|
|
536
|
+
* by mutators — the SubGrid relationship-filter case), `aggregations`
|
|
537
|
+
* spec, behavior knobs (`enabled`, `keepPreviousData`, `debounceMs`).
|
|
538
|
+
* - **Initial state** (seeds once on mount, mutators take over) —
|
|
539
|
+
* `initialFilter`, `initialSort`, `initialSearch`, `initialPage`,
|
|
540
|
+
* `initialPageSize`, `initialHighlight`.
|
|
541
|
+
*
|
|
542
|
+
* The same datasource instance feeds a `<MainGrid>` and a
|
|
543
|
+
* `<DataverseChart>` (or two of either) — they all read the same row
|
|
544
|
+
* page + aggregations, so filter changes from any view land
|
|
545
|
+
* everywhere in one round-trip.
|
|
546
|
+
*/
|
|
547
|
+
interface UseViewDataSourceOptions {
|
|
548
|
+
/**
|
|
549
|
+
* Stored Dataverse view to query. Mutually exclusive with
|
|
550
|
+
* {@link fetchXml}. Reading the view's columns drives the
|
|
551
|
+
* search-column-type dispatch.
|
|
552
|
+
*/
|
|
553
|
+
viewId?: string;
|
|
554
|
+
/**
|
|
555
|
+
* Caller-supplied FetchXML to query. Mutually exclusive with
|
|
556
|
+
* {@link viewId}. Useful for custom views.
|
|
557
|
+
*/
|
|
558
|
+
fetchXml?: string;
|
|
559
|
+
/**
|
|
560
|
+
* Filters that the datasource never mutates — always sent verbatim
|
|
561
|
+
* with every request. The primary use case is `<SubGrid>` passing a
|
|
562
|
+
* `RelationshipFilter` to scope rows to a parent record; the
|
|
563
|
+
* datasource's user-driven {@link ViewDataSource.filter} state
|
|
564
|
+
* layers on top.
|
|
565
|
+
*/
|
|
566
|
+
staticFilters?: readonly GridFilterBase[];
|
|
567
|
+
/**
|
|
568
|
+
* Optional aggregations to compute alongside the row page. Each
|
|
569
|
+
* entry becomes one entry in `GridDataRequest.Aggregations`; the
|
|
570
|
+
* server returns one `ChartData` per entry in
|
|
571
|
+
* {@link ViewDataSource.aggregationResults}, same order.
|
|
572
|
+
* <c>undefined</c> / empty → no chart pass; the response's
|
|
573
|
+
* `aggregations` slot is `null`. Treated as a prop (changes
|
|
574
|
+
* re-fetch) — if you need it to be mutable state inside the
|
|
575
|
+
* datasource, lift it to the consumer.
|
|
576
|
+
*/
|
|
577
|
+
aggregations?: readonly ChartAggregation[];
|
|
578
|
+
/** Seed for {@link ViewDataSource.filter}. Defaults to empty. */
|
|
579
|
+
initialFilter?: readonly GridFilterBase[];
|
|
580
|
+
/** Seed for {@link ViewDataSource.sort}. Defaults to empty. */
|
|
581
|
+
initialSort?: readonly ColumnSort[];
|
|
582
|
+
/** Seed for {@link ViewDataSource.search}. Defaults to empty string. */
|
|
583
|
+
initialSearch?: string;
|
|
584
|
+
/** Seed for {@link ViewDataSource.page}. Defaults to 1. */
|
|
585
|
+
initialPage?: number;
|
|
586
|
+
/** Seed for {@link ViewDataSource.pageSize}. Defaults to 50. */
|
|
587
|
+
initialPageSize?: number;
|
|
588
|
+
/** Seed for {@link ViewDataSource.highlight}. Defaults to undefined. */
|
|
589
|
+
initialHighlight?: ViewDataSourceHighlight;
|
|
590
|
+
/**
|
|
591
|
+
* Debounce window in ms for the actual server request. State setter
|
|
592
|
+
* calls land synchronously (consumers reading `ds.search` see the
|
|
593
|
+
* latest typed value), but the network fetch fires once the
|
|
594
|
+
* debounce settles. Defaults to 300ms; pass 0 to disable.
|
|
595
|
+
*/
|
|
596
|
+
debounceMs?: number;
|
|
597
|
+
/** When false, no fetch fires. Defaults to true. */
|
|
598
|
+
enabled?: boolean;
|
|
599
|
+
/**
|
|
600
|
+
* Keep the prior successful response on screen during deps-change
|
|
601
|
+
* refetches. Defaults to true — grids and charts typically want
|
|
602
|
+
* scrim-during-refetch over wiping the UI.
|
|
603
|
+
*/
|
|
604
|
+
keepPreviousData?: boolean;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* The reactive view of one query, shared by every consumer (grid,
|
|
608
|
+
* chart, side-panel, etc.) wired to the same {@link useViewDataSource}
|
|
609
|
+
* call. All consumers see the same row page + aggregations because
|
|
610
|
+
* the server returns them in a single response (atomically consistent
|
|
611
|
+
* — no race where the chart shows aggregations for an old filter
|
|
612
|
+
* while the grid shows rows for the new one).
|
|
613
|
+
*
|
|
614
|
+
* Reads (`filter` / `sort` / `search` / `rows` / `aggregationResults`)
|
|
615
|
+
* are snapshots at the time the hook ran. Mutators
|
|
616
|
+
* (`setFilter` / `setSearch` / `setHighlight` / etc.) trigger
|
|
617
|
+
* re-renders of the hook owner, which propagates to all consumers.
|
|
618
|
+
*
|
|
619
|
+
* Stable identity per snapshot — the returned object only changes
|
|
620
|
+
* reference when one of its fields actually changes. Safe to pass
|
|
621
|
+
* through `React.memo`'d component trees.
|
|
622
|
+
*/
|
|
623
|
+
interface ViewDataSource {
|
|
624
|
+
/**
|
|
625
|
+
* User-driven filter applied on top of {@link UseViewDataSourceOptions.staticFilters}.
|
|
626
|
+
* Always shipped as an AND-merge with the view's base filter.
|
|
627
|
+
*/
|
|
628
|
+
readonly filter: readonly GridFilterBase[];
|
|
629
|
+
readonly sort: readonly ColumnSort[];
|
|
630
|
+
readonly search: string;
|
|
631
|
+
readonly page: number;
|
|
632
|
+
readonly pageSize: number;
|
|
633
|
+
/**
|
|
634
|
+
* Soft cross-filter for chart slice clicks — drives client-side
|
|
635
|
+
* styling on consumers (chart slice emphasis, grid row dimming)
|
|
636
|
+
* without changing the server query. Set to `undefined` to clear.
|
|
637
|
+
* Distinct from {@link filter}, which IS a hard filter.
|
|
638
|
+
*/
|
|
639
|
+
readonly highlight: ViewDataSourceHighlight | undefined;
|
|
640
|
+
readonly aggregations: readonly ChartAggregation[];
|
|
641
|
+
readonly status: QueryStatus;
|
|
642
|
+
readonly rows: readonly TableRecord[];
|
|
643
|
+
readonly totalCount: number | string | null | undefined;
|
|
644
|
+
readonly columns: readonly ResolvedColumn[];
|
|
645
|
+
/**
|
|
646
|
+
* Current user's combined table-level `TableSecurityPermission` mask for
|
|
647
|
+
* the view's table — comes from the server response's
|
|
648
|
+
* `GridDataResponse.tablePermissions`. Mirrors the per-row
|
|
649
|
+
* permissions but at table scope, so toolbar buttons (`<MainGrid>`'s
|
|
650
|
+
* New / Delete and consumer-built equivalents) can gate off one
|
|
651
|
+
* authoritative value instead of sampling row[0] as a proxy.
|
|
652
|
+
* `undefined` until the first response lands.
|
|
653
|
+
*/
|
|
654
|
+
readonly tablePermissions: TableSecurityPermission | undefined;
|
|
655
|
+
/**
|
|
656
|
+
* One {@link ChartData} per requested aggregation, in request order.
|
|
657
|
+
* Empty when no aggregations were requested OR when the response
|
|
658
|
+
* hasn't landed yet — consumers can render an empty / spinner state
|
|
659
|
+
* uniformly.
|
|
660
|
+
*/
|
|
661
|
+
readonly aggregationResults: readonly ChartData[];
|
|
662
|
+
readonly error: PowerPortalsProError | undefined;
|
|
663
|
+
/** True whenever a fetch is in flight (including debounced refetches). */
|
|
664
|
+
readonly isFetching: boolean;
|
|
665
|
+
/**
|
|
666
|
+
* True only when the in-flight request actually carries the
|
|
667
|
+
* `aggregations` spec — i.e., when the chart's data is about to
|
|
668
|
+
* change. The skip-on-sort optimization above means that a column-
|
|
669
|
+
* header click on a sibling grid fires a row-page-only refetch the
|
|
670
|
+
* server processes without re-running aggregations, and this flag
|
|
671
|
+
* stays false through that. Chart consumers ({@link DataverseChart})
|
|
672
|
+
* gate their loading overlay on this so a sort-only refetch doesn't
|
|
673
|
+
* flash a spinner over an unchanged chart.
|
|
674
|
+
*
|
|
675
|
+
* Always false when {@link aggregations} is empty (no chart data
|
|
676
|
+
* being requested at all).
|
|
677
|
+
*/
|
|
678
|
+
readonly isFetchingAggregations: boolean;
|
|
679
|
+
setFilter(filter: readonly GridFilterBase[]): void;
|
|
680
|
+
setSort(sort: readonly ColumnSort[]): void;
|
|
681
|
+
setSearch(search: string): void;
|
|
682
|
+
setPage(page: number): void;
|
|
683
|
+
setPageSize(pageSize: number): void;
|
|
684
|
+
setHighlight(highlight: ViewDataSourceHighlight | undefined): void;
|
|
685
|
+
/** Re-fires the current request immediately (skipping the debounce). */
|
|
686
|
+
refresh(): Promise<void>;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Shared {@link GridDataRequest} + chart-spec state for grid+chart
|
|
690
|
+
* scenarios. Hosts the filter / sort / search / page / aggregations
|
|
691
|
+
* once for any combination of `<MainGrid dataSource={ds}>` and
|
|
692
|
+
* `<DataverseChart dataSource={ds}>` consumers — one server request
|
|
693
|
+
* keeps the row page and the chart aggregations atomically consistent.
|
|
694
|
+
*
|
|
695
|
+
* Standalone usage (no chart) still works: omit the `aggregations`
|
|
696
|
+
* option and the datasource is just a hoisted-state grid hook.
|
|
697
|
+
*
|
|
698
|
+
* **Typical usage:**
|
|
699
|
+
*
|
|
700
|
+
* ```tsx
|
|
701
|
+
* function OpportunitiesPage() {
|
|
702
|
+
* const ds = useViewDataSource({
|
|
703
|
+
* viewId: openOpportunitiesViewId,
|
|
704
|
+
* aggregations: [
|
|
705
|
+
* { groupBy: { column: 'stepname' },
|
|
706
|
+
* aggregateColumn: 'estimatedvalue',
|
|
707
|
+
* aggregate: AggregateType.Sum },
|
|
708
|
+
* ],
|
|
709
|
+
* });
|
|
710
|
+
* return (
|
|
711
|
+
* <>
|
|
712
|
+
* <DataverseChart dataSource={ds} type="bar" />
|
|
713
|
+
* <MainGrid dataSource={ds} />
|
|
714
|
+
* </>
|
|
715
|
+
* );
|
|
716
|
+
* }
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
declare function useViewDataSource(options: UseViewDataSourceOptions): ViewDataSource;
|
|
720
|
+
|
|
721
|
+
interface UseTableMetadataOptions {
|
|
722
|
+
enabled?: boolean;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Fetches the column + relationship metadata for one Dataverse table.
|
|
726
|
+
* Routed through the {@link TableMetadataCache} on the provider — first
|
|
727
|
+
* call kicks off a server round-trip, concurrent calls dedupe onto the
|
|
728
|
+
* same in-flight promise, and resolved values are reused for the
|
|
729
|
+
* lifetime of the `PowerPortalsProClient`. Critical for the inline-
|
|
730
|
+
* editable grid, where N visible rows each mount their own
|
|
731
|
+
* `<RecordContext>` and would otherwise thunder N parallel
|
|
732
|
+
* `retrieveTableMetadataAsync` requests at toggle time.
|
|
733
|
+
*
|
|
734
|
+
* Coerces the cache's `null` "table not found" return to `undefined` in
|
|
735
|
+
* the query result so downstream consumers can treat metadata absence
|
|
736
|
+
* uniformly via `data == null`.
|
|
737
|
+
*/
|
|
738
|
+
declare function useTableMetadata(tableLogicalName: string, options?: UseTableMetadataOptions): QueryResult<TableMetadata$1>;
|
|
739
|
+
|
|
740
|
+
interface UseTablePermissionsOptions {
|
|
741
|
+
enabled?: boolean;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Fetches the current user's combined table-level `TableSecurityPermission`
|
|
745
|
+
* mask for one Dataverse table. Mirrors the server-side
|
|
746
|
+
* `ITablePermissionCache.GetPermissionForUserAsync` lookup the Blazor
|
|
747
|
+
* grid buttons already use directly via DI — exposed here so React
|
|
748
|
+
* consumers can drive the same "Can the user create / delete on this
|
|
749
|
+
* table?" gates without firing a grid query.
|
|
750
|
+
*
|
|
751
|
+
* When a `<MainGrid>` / `<SubGrid>` is on the page the grid response
|
|
752
|
+
* already carries the same value as `GridDataResponse.tablePermissions`
|
|
753
|
+
* and toolbar buttons read it from there; reach for this hook when
|
|
754
|
+
* you're rendering UI outside a grid (a custom toolbar, conditional
|
|
755
|
+
* navigation, a "permission required" tooltip) and don't want to fire
|
|
756
|
+
* a grid query just to read the permission bits.
|
|
757
|
+
*
|
|
758
|
+
* Returns `data: TableSecurityPermission` once the fetch resolves. The
|
|
759
|
+
* caller compares via bit math, e.g.
|
|
760
|
+
* `(data & TableSecurityPermission.Create) === TableSecurityPermission.Create`.
|
|
761
|
+
*
|
|
762
|
+
* Empty `tableLogicalName` returns `data: TableSecurityPermission.None` (0)
|
|
763
|
+
* without firing a server round-trip — matches the server-side cache
|
|
764
|
+
* which returns `None` for null/empty table names.
|
|
765
|
+
*/
|
|
766
|
+
declare function useTablePermissions(tableLogicalName: string, options?: UseTablePermissionsOptions): QueryResult<TableSecurityPermission>;
|
|
767
|
+
|
|
768
|
+
interface UseViewMetadataOptions {
|
|
769
|
+
enabled?: boolean;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Fetches metadata for one Dataverse view by its id (a GUID string).
|
|
773
|
+
* Routed through the {@link ViewMetadataCache} on the provider — first
|
|
774
|
+
* call hits the server, concurrent calls dedupe, resolved values are
|
|
775
|
+
* reused for the lifetime of the `PowerPortalsProClient`. Use
|
|
776
|
+
* {@link useViewsForTable} when you want every view for a table instead.
|
|
777
|
+
*/
|
|
778
|
+
declare function useViewMetadata(viewId: string, options?: UseViewMetadataOptions): QueryResult<ViewMetadata>;
|
|
779
|
+
|
|
780
|
+
interface UseViewsForTableOptions {
|
|
781
|
+
enabled?: boolean;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Lists every view for a given table. Drives view-pickers and default-view
|
|
785
|
+
* resolution. Routed through {@link ViewsForTableCache} so two grids on
|
|
786
|
+
* the same page targeting the same table share one fetch.
|
|
787
|
+
*/
|
|
788
|
+
declare function useViewsForTable(tableLogicalName: string, options?: UseViewsForTableOptions): QueryResult<readonly ViewMetadata[]>;
|
|
789
|
+
|
|
790
|
+
type EnvironmentFileSettings$1 = components['schemas']['EnvironmentFileSettings'];
|
|
791
|
+
interface UseEnvironmentFileSettingsOptions {
|
|
792
|
+
enabled?: boolean;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Fetches the environment-wide file upload settings (blocked extension
|
|
796
|
+
* list, organization-level max upload size). Mirrors the Blazor
|
|
797
|
+
* <c>IPowerPortalsProService.GetEnvironmentFileSettingsAsync</c> call.
|
|
798
|
+
*
|
|
799
|
+
* Routed through {@link EnvironmentFileSettingsCache} on the provider —
|
|
800
|
+
* concurrent callers (e.g. multiple `<FileGrid>`s on the same page)
|
|
801
|
+
* share a single fetch, and the resolved value is reused across re-
|
|
802
|
+
* mounts for the lifetime of the `PowerPortalsProClient`. The cap
|
|
803
|
+
* reported here is the org-wide ceiling — the effective per-upload cap
|
|
804
|
+
* also has to account for per-column <c>maxSizeInKB</c> from
|
|
805
|
+
* {@link useTableMetadata}.
|
|
806
|
+
*/
|
|
807
|
+
declare function useEnvironmentFileSettings(options?: UseEnvironmentFileSettingsOptions): QueryResult<EnvironmentFileSettings$1>;
|
|
808
|
+
|
|
809
|
+
interface UseSupportedLocalesOptions {
|
|
810
|
+
/**
|
|
811
|
+
* Gates the fetch. Defaults to `true` — the manifest is tiny (a
|
|
812
|
+
* handful of locale codes) and effectively free to fetch. Set
|
|
813
|
+
* `false` only when the consumer wants to suppress the call until
|
|
814
|
+
* some later condition (e.g. only fetch after auth resolves).
|
|
815
|
+
*/
|
|
816
|
+
enabled?: boolean;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Returns the framework's configured supported UI locales. Reads from
|
|
820
|
+
* the {@link LocalizationManifestContext} populated by
|
|
821
|
+
* `DefaultLocalizerProvider`'s tier-1 manifest fetch — so call sites
|
|
822
|
+
* (typically `<LanguageDropdown>`) reuse the already-loaded data
|
|
823
|
+
* instead of issuing their own `/localizations/version` round-trip
|
|
824
|
+
* every time the hook mounts. The previous implementation re-fetched
|
|
825
|
+
* on each mount, which surfaced as one extra `/localizations/version`
|
|
826
|
+
* request on every page navigation in apps where the dropdown sits
|
|
827
|
+
* inside route-level chrome.
|
|
828
|
+
*
|
|
829
|
+
* Fallback path: when no `DefaultLocalizerProvider` is mounted (rare —
|
|
830
|
+
* consumers who wired their own i18next setup via
|
|
831
|
+
* `localizationUrl={false}`), the hook still issues its own fetch so
|
|
832
|
+
* the API works in isolation. The fallback uses the same
|
|
833
|
+
* `useAsyncResource` shape it always did, so the {@link QueryResult}
|
|
834
|
+
* surface is unchanged.
|
|
835
|
+
*
|
|
836
|
+
* The Blazor side gets this server-side via
|
|
837
|
+
* `IOptions<RequestLocalizationOptions>.SupportedUICultures`; in
|
|
838
|
+
* React the same configuration is exposed through the localization
|
|
839
|
+
* manifest endpoint at `/localizations/version`. Order matches the
|
|
840
|
+
* server's configured preference, default culture first.
|
|
841
|
+
*/
|
|
842
|
+
declare function useSupportedLocales(options?: UseSupportedLocalesOptions): QueryResult<readonly string[]>;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Reads the value of a URL query-string parameter and re-renders when
|
|
846
|
+
* it changes (via browser back / forward, programmatic
|
|
847
|
+
* `history.pushState` / `replaceState`, or hash-only navigation).
|
|
848
|
+
* Router-agnostic — uses the platform `URLSearchParams` API directly
|
|
849
|
+
* so consumers don't have to pull in a specific router's hook.
|
|
850
|
+
*
|
|
851
|
+
* Returns `null` when the parameter is absent. SSR-safe — returns
|
|
852
|
+
* `null` when `window` is undefined.
|
|
853
|
+
*
|
|
854
|
+
* **Detecting in-app navigation:** `popstate` covers back / forward
|
|
855
|
+
* but the History API has no built-in event for `pushState` /
|
|
856
|
+
* `replaceState` calls. We patch both at module load to dispatch a
|
|
857
|
+
* synthetic `pushstate` / `replacestate` event, so consumers using
|
|
858
|
+
* any router (react-router, TanStack Router, Next.js, hand-rolled)
|
|
859
|
+
* get correct re-renders without taking a router dependency.
|
|
860
|
+
*
|
|
861
|
+
* Multiple components subscribing to the same parameter all re-read
|
|
862
|
+
* synchronously — each call sites runs its own `URLSearchParams`
|
|
863
|
+
* parse, which is cheap.
|
|
864
|
+
*/
|
|
865
|
+
declare function useQueryParam(name: string): string | null;
|
|
866
|
+
|
|
867
|
+
type EnvironmentFileSettings = components['schemas']['EnvironmentFileSettings'];
|
|
868
|
+
/**
|
|
869
|
+
* Default in-memory implementation of {@link TableMetadataCache}. Wraps
|
|
870
|
+
* a `PowerPortalsProClient.retrieveTableMetadataAsync` call with Map-based
|
|
871
|
+
* dedup. One instance per `PowerPortalsProClient` — the
|
|
872
|
+
* `PowerPortalsProProvider` constructs it.
|
|
873
|
+
*/
|
|
874
|
+
declare class InMemoryTableMetadataCache implements TableMetadataCache {
|
|
875
|
+
private readonly cache;
|
|
876
|
+
constructor(ppp: PowerPortalsProClient);
|
|
877
|
+
getAsync(tableLogicalName: string): Promise<TableMetadata$1 | null>;
|
|
878
|
+
invalidate(tableLogicalName: string): void;
|
|
879
|
+
clear(): void;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Default in-memory implementation of {@link ViewMetadataCache}. Same
|
|
883
|
+
* shape as {@link InMemoryTableMetadataCache}, keyed by view id.
|
|
884
|
+
*/
|
|
885
|
+
declare class InMemoryViewMetadataCache implements ViewMetadataCache {
|
|
886
|
+
private readonly cache;
|
|
887
|
+
constructor(ppp: PowerPortalsProClient);
|
|
888
|
+
getAsync(viewId: string): Promise<ViewMetadata | null>;
|
|
889
|
+
invalidate(viewId: string): void;
|
|
890
|
+
clear(): void;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Default in-memory implementation of {@link EnvironmentFileSettingsCache}.
|
|
894
|
+
* Singleton-shaped: no key, just one stored value + one in-flight promise.
|
|
895
|
+
*
|
|
896
|
+
* Implemented as a thin wrapper around the keyed {@link InMemoryCache}
|
|
897
|
+
* with a fixed sentinel key, so the dedup / fetch / error-eviction
|
|
898
|
+
* behaviors stay identical to the metadata caches without re-implementing
|
|
899
|
+
* any of that logic.
|
|
900
|
+
*/
|
|
901
|
+
declare class InMemoryEnvironmentFileSettingsCache implements EnvironmentFileSettingsCache {
|
|
902
|
+
private static readonly KEY;
|
|
903
|
+
private readonly cache;
|
|
904
|
+
constructor(ppp: PowerPortalsProClient);
|
|
905
|
+
getAsync(): Promise<EnvironmentFileSettings | null>;
|
|
906
|
+
invalidate(): void;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Default in-memory implementation of {@link ViewsForTableCache}. Wraps
|
|
910
|
+
* the per-table views-list endpoint with the same in-memory + in-flight
|
|
911
|
+
* dedup machinery the metadata caches use. Coerces a `null` fetch result
|
|
912
|
+
* (table not found / 404) to an empty list so consumers can treat the
|
|
913
|
+
* absence uniformly without branching on null.
|
|
914
|
+
*/
|
|
915
|
+
declare class InMemoryViewsForTableCache implements ViewsForTableCache {
|
|
916
|
+
private readonly cache;
|
|
917
|
+
constructor(ppp: PowerPortalsProClient);
|
|
918
|
+
getAsync(tableLogicalName: string): Promise<readonly ViewMetadata[]>;
|
|
919
|
+
invalidate(tableLogicalName: string): void;
|
|
920
|
+
clear(): void;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Per-descendant registration the {@link MainContext} keeps for save/refresh
|
|
925
|
+
* coordination + dirty aggregation. Each child component (typically a
|
|
926
|
+
* {@link RecordContext}, a `MainGrid`/`SubGrid`, or a many-to-many editor)
|
|
927
|
+
* calls {@link MainContextValue.registerDescendant} on mount and the returned
|
|
928
|
+
* unregister function on unmount.
|
|
929
|
+
*
|
|
930
|
+
* The parent uses the registry to orchestrate save / refresh / validate /
|
|
931
|
+
* aggregate `IsDirty`. Save is transactional: the parent collects
|
|
932
|
+
* {@link getRequests} across every descendant and ships them in a single
|
|
933
|
+
* `executeMultiple` round-trip.
|
|
934
|
+
*
|
|
935
|
+
* `isDirty` is a **getter** (`() => boolean`) — the parent calls it every
|
|
936
|
+
* time it recomputes the aggregate. Descendants should back it with a
|
|
937
|
+
* ref so it reads the latest value without re-registering. When the
|
|
938
|
+
* value flips, the descendant calls
|
|
939
|
+
* {@link MainContextValue.notifyDescendantDirtyChanged} to prompt the
|
|
940
|
+
* parent to re-read; this is materially cheaper than tearing down and
|
|
941
|
+
* re-creating the registration on every transition, which used to
|
|
942
|
+
* cascade re-renders through every {@link MainContext} consumer and
|
|
943
|
+
* dropped focus on the first keystroke in inline-edit cells.
|
|
944
|
+
*/
|
|
945
|
+
interface MainContextDescendant {
|
|
946
|
+
/**
|
|
947
|
+
* Returns all pending Dataverse operations for this descendant subtree.
|
|
948
|
+
* Returns an empty array when nothing is dirty. The parent collects these
|
|
949
|
+
* across every descendant and ships them in one transactional
|
|
950
|
+
* `executeMultiple` batch. Descendants that themselves contain nested
|
|
951
|
+
* descendants (e.g. a {@link RecordContext} hosting a `SubGrid`) should
|
|
952
|
+
* include the nested requests in the returned array so the entire subtree
|
|
953
|
+
* lands in a single batch.
|
|
954
|
+
*/
|
|
955
|
+
readonly getRequests: () => OrganizationRequest[];
|
|
956
|
+
/**
|
|
957
|
+
* Returns `true` when the descendant subtree is in a state suitable for
|
|
958
|
+
* save. The parent calls this before collecting {@link getRequests};
|
|
959
|
+
* returning `false` aborts the save without firing any server-side
|
|
960
|
+
* operations.
|
|
961
|
+
*
|
|
962
|
+
* For descendants whose validation lives in a parallel system (e.g.
|
|
963
|
+
* {@link RecordContext} which routes per-column validation through
|
|
964
|
+
* `ValidationContext`), this can simply return `true` and let the
|
|
965
|
+
* dedicated validation surface drive the abort decision separately.
|
|
966
|
+
*/
|
|
967
|
+
readonly validate: () => boolean | Promise<boolean>;
|
|
968
|
+
/** Reloads this descendant subtree from the server. */
|
|
969
|
+
readonly refresh: () => Promise<void>;
|
|
970
|
+
/**
|
|
971
|
+
* Discards every pending edit in this descendant subtree, reverting back
|
|
972
|
+
* to the loaded baseline WITHOUT re-fetching from the server. The "Cancel"
|
|
973
|
+
* / "Discard" primitive.
|
|
974
|
+
*
|
|
975
|
+
* Implementations should clear their own pending queue (`pendingChanges`
|
|
976
|
+
* for `RecordContext`, the queued row updates / creates / associates /
|
|
977
|
+
* disassociates for `MainGrid`, the pending adds / removes for
|
|
978
|
+
* `<ManyToManyLookupEdit>`, etc.) AND cascade to any nested descendants
|
|
979
|
+
* so the entire subtree is clean afterward.
|
|
980
|
+
*/
|
|
981
|
+
readonly resetState: () => void;
|
|
982
|
+
/**
|
|
983
|
+
* Getter the parent calls when recomputing aggregate dirty state.
|
|
984
|
+
* Back it with a ref so the latest value is visible without
|
|
985
|
+
* re-registering; call
|
|
986
|
+
* {@link MainContextValue.notifyDescendantDirtyChanged} whenever the
|
|
987
|
+
* underlying value transitions so the parent knows to re-read.
|
|
988
|
+
*/
|
|
989
|
+
readonly isDirty: () => boolean;
|
|
990
|
+
}
|
|
991
|
+
interface MainContextValue {
|
|
992
|
+
/**
|
|
993
|
+
* Transactionally saves the entire descendant subtree:
|
|
994
|
+
*
|
|
995
|
+
* 1. Calls {@link MainContextProps.onBeforeSave} (if wired). Throwing
|
|
996
|
+
* aborts the save before any server call.
|
|
997
|
+
* 2. Runs {@link validate} across descendants; returns early without
|
|
998
|
+
* saving when any descendant reports invalid.
|
|
999
|
+
* 3. Collects {@link MainContextDescendant.getRequests} from every
|
|
1000
|
+
* registered descendant.
|
|
1001
|
+
* 4. If non-empty, ships the combined array in one
|
|
1002
|
+
* `executeMultipleAsync` round-trip so the operations land
|
|
1003
|
+
* transactionally on the server.
|
|
1004
|
+
* 5. Calls {@link refreshAll} so the local state picks up server-applied
|
|
1005
|
+
* side-effects (computed columns, plugin defaults, the modifiedon
|
|
1006
|
+
* timestamp, etc.).
|
|
1007
|
+
*
|
|
1008
|
+
* Throws on the first server-side failure; later steps are skipped.
|
|
1009
|
+
*/
|
|
1010
|
+
readonly saveAll: () => Promise<void>;
|
|
1011
|
+
/** Refreshes every registered descendant. */
|
|
1012
|
+
readonly refreshAll: () => Promise<void>;
|
|
1013
|
+
/**
|
|
1014
|
+
* Discards every pending edit in every registered descendant — the
|
|
1015
|
+
* "Cancel" / "Discard" primitive that returns the entire subtree to its
|
|
1016
|
+
* loaded baseline WITHOUT re-fetching from the server.
|
|
1017
|
+
*
|
|
1018
|
+
* Synchronous: descendants clear their local state in their own
|
|
1019
|
+
* `setState` calls. Doesn't itself await anything.
|
|
1020
|
+
*/
|
|
1021
|
+
readonly resetState: () => void;
|
|
1022
|
+
/**
|
|
1023
|
+
* Runs {@link MainContextDescendant.validate} across every registered
|
|
1024
|
+
* descendant and returns `false` if any of them invalidates. Awaitable
|
|
1025
|
+
* so descendants can perform async validation (e.g. async column
|
|
1026
|
+
* validators routed through `ValidationContext.validateAll`).
|
|
1027
|
+
*/
|
|
1028
|
+
readonly validate: () => Promise<boolean>;
|
|
1029
|
+
/**
|
|
1030
|
+
* Aggregates every pending Dataverse operation across the descendant
|
|
1031
|
+
* subtree without firing them. Returns an empty array when nothing is
|
|
1032
|
+
* dirty. Useful for previewing the payload {@link saveAll} would ship
|
|
1033
|
+
* (audit log, dry-run UI, custom transactional flow that wants to
|
|
1034
|
+
* append extra requests before calling
|
|
1035
|
+
* `IPowerPortalsPro.executeMultipleAsync` directly).
|
|
1036
|
+
*
|
|
1037
|
+
* Mirrors Blazor's public `MainContext.GetRequests()` — same walk over
|
|
1038
|
+
* registered grids / child contexts / M2M editors, same shape.
|
|
1039
|
+
* Consumers that just want to save should call {@link saveAll}; this
|
|
1040
|
+
* is the escape hatch for the rarer dry-run / merge-with-extras case.
|
|
1041
|
+
*/
|
|
1042
|
+
readonly getRequests: () => OrganizationRequest[];
|
|
1043
|
+
/**
|
|
1044
|
+
* Adds `descendant` to the registry and returns an unregister function. Each
|
|
1045
|
+
* descendant should call this once on mount and call the returned function
|
|
1046
|
+
* on unmount, typically inside a single `useEffect` whose deps DON'T include
|
|
1047
|
+
* the descendant's local `isDirty` — instead, signal dirty transitions via
|
|
1048
|
+
* {@link notifyDescendantDirtyChanged}. The descendant's
|
|
1049
|
+
* {@link MainContextDescendant.isDirty} getter is what the parent reads.
|
|
1050
|
+
*/
|
|
1051
|
+
readonly registerDescendant: (descendant: MainContextDescendant) => () => void;
|
|
1052
|
+
/**
|
|
1053
|
+
* Called by a descendant when its
|
|
1054
|
+
* {@link MainContextDescendant.isDirty} return value transitions.
|
|
1055
|
+
* Prompts the parent to recompute its aggregate without forcing the
|
|
1056
|
+
* descendant to unregister + re-register itself. The recommended
|
|
1057
|
+
* idiom is a `useEffect` keyed on the local `isDirty` value.
|
|
1058
|
+
*/
|
|
1059
|
+
readonly notifyDescendantDirtyChanged: () => void;
|
|
1060
|
+
/**
|
|
1061
|
+
* Aggregated dirty state — `true` if any registered descendant currently
|
|
1062
|
+
* reports dirty. Flips reactively as descendants register/unregister/notify;
|
|
1063
|
+
* consumers subscribed via {@link useMainContext} re-render on transitions.
|
|
1064
|
+
*/
|
|
1065
|
+
readonly isDirty: boolean;
|
|
1066
|
+
/**
|
|
1067
|
+
* Resolved value of {@link MainContextProps.warnOnUnsavedChanges} for this
|
|
1068
|
+
* context — `true` means the auto unsaved-changes warning is active; `false`
|
|
1069
|
+
* means it's suppressed. Children read this to inherit the effective value
|
|
1070
|
+
* when their own prop is omitted.
|
|
1071
|
+
*/
|
|
1072
|
+
readonly warnOnUnsavedChanges: boolean;
|
|
1073
|
+
/**
|
|
1074
|
+
* Resolved value of {@link MainContextProps.forceSuccessfulValidationBeforeSave}.
|
|
1075
|
+
* When `true` (the default), save flows in this subtree gate on
|
|
1076
|
+
* validation passing — `SaveContextButton`, the dialog buttons inside
|
|
1077
|
+
* `<OpenRecordGridButton>` / `<NewRecordGridButton>`, and
|
|
1078
|
+
* {@link saveAll} all abort when any registered column validator reports
|
|
1079
|
+
* an error. When `false`, those gates are bypassed and the save proceeds
|
|
1080
|
+
* regardless. The server's own column validation (Dataverse required-on-
|
|
1081
|
+
* update / type checks) still applies.
|
|
1082
|
+
*
|
|
1083
|
+
* Children read this to inherit the effective value when their own prop
|
|
1084
|
+
* is omitted. Per-button overrides (e.g. on `<OpenRecordGridButton>`)
|
|
1085
|
+
* take precedence over the cascaded value for the scope of that button.
|
|
1086
|
+
*/
|
|
1087
|
+
readonly forceSuccessfulValidationBeforeSave: boolean;
|
|
1088
|
+
}
|
|
1089
|
+
interface MainContextProps {
|
|
1090
|
+
children: ReactNode;
|
|
1091
|
+
/**
|
|
1092
|
+
* Optional pre-save hook. Throw (or return a rejected promise) to cancel
|
|
1093
|
+
* the save — subsequent descendants are not saved.
|
|
1094
|
+
*/
|
|
1095
|
+
onBeforeSave?: () => void | Promise<void>;
|
|
1096
|
+
/**
|
|
1097
|
+
* Auto-register a browser `beforeunload` warning when this context (or any
|
|
1098
|
+
* descendant) is dirty.
|
|
1099
|
+
*
|
|
1100
|
+
* Resolution rules:
|
|
1101
|
+
* - `true` / `false` — explicit override at this level.
|
|
1102
|
+
* - `undefined` (default) — inherit from the parent
|
|
1103
|
+
* {@link MainContext} / {@link RecordContext}, falling back to `true` at
|
|
1104
|
+
* the root. Set `false` on the outer context of e.g. a demo site to
|
|
1105
|
+
* suppress the prompt across the whole subtree without touching every
|
|
1106
|
+
* nested context.
|
|
1107
|
+
*
|
|
1108
|
+
* Only the **top-most** MainContext / RecordContext on the page actually
|
|
1109
|
+
* registers the listener — when contexts nest, the inner ones skip
|
|
1110
|
+
* registration to avoid duplicate `beforeunload` handlers (the browser would
|
|
1111
|
+
* still only prompt once, but the duplicates are wasteful). Soft navigation
|
|
1112
|
+
* (e.g. `react-router` `<Link>` clicks) is NOT covered — that's
|
|
1113
|
+
* router-specific; pair with the router's blocker hook plus
|
|
1114
|
+
* {@link useWarnAboutUnsavedChanges}.
|
|
1115
|
+
*/
|
|
1116
|
+
warnOnUnsavedChanges?: boolean;
|
|
1117
|
+
/**
|
|
1118
|
+
* When `true` (default), descendants' save flows abort if any registered
|
|
1119
|
+
* column validator reports an error — covers `<SaveContextButton>`, the
|
|
1120
|
+
* dialog buttons on `<OpenRecordGridButton>` / `<NewRecordGridButton>`,
|
|
1121
|
+
* and {@link MainContextValue.saveAll}'s pre-flight. When `false`, those
|
|
1122
|
+
* gates are bypassed and the save proceeds regardless. Useful for draft-
|
|
1123
|
+
* style "save what you have" flows or when the consumer wants to defer
|
|
1124
|
+
* validation to a downstream review step.
|
|
1125
|
+
*
|
|
1126
|
+
* Resolution rules (same as {@link warnOnUnsavedChanges}):
|
|
1127
|
+
* - `true` / `false` — explicit override at this level.
|
|
1128
|
+
* - `undefined` (default) — inherit from the parent
|
|
1129
|
+
* {@link MainContext} / {@link RecordContext}, falling back to `true`
|
|
1130
|
+
* at the root.
|
|
1131
|
+
*/
|
|
1132
|
+
forceSuccessfulValidationBeforeSave?: boolean;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* A context root that coordinates save and refresh across descendant components
|
|
1136
|
+
* ({@link RecordContext}, future SubGrid / many-to-many editors). Headless:
|
|
1137
|
+
* provides state and orchestration via React context, no rendering of its own
|
|
1138
|
+
* beyond `children`.
|
|
1139
|
+
*
|
|
1140
|
+
* Nesting is supported — a child MainContext doesn't replace its parent's
|
|
1141
|
+
* registry; it's just a separate root for its own subtree. Descendants always
|
|
1142
|
+
* register with their nearest ancestor.
|
|
1143
|
+
*
|
|
1144
|
+
* Deferred surface (and how to layer it on later):
|
|
1145
|
+
* - **Dirty tracking.** Blazor's MainContext aggregates `isDirty` across children
|
|
1146
|
+
* via `EditContext`. React doesn't have a built-in form layer; consumers bring
|
|
1147
|
+
* their own (react-hook-form, formik, vanilla state). When the dirty story
|
|
1148
|
+
* solidifies, an `isDirty: () => boolean` getter on `MainContextDescendant`
|
|
1149
|
+
* plus an aggregate getter on `MainContextValue` is the natural extension.
|
|
1150
|
+
* - **Unsaved-changes warning** dialog on navigation. Driven by dirty tracking.
|
|
1151
|
+
* - **Validation orchestration**. Same — needs the form layer.
|
|
1152
|
+
* - **Persistent state across server↔WASM**. N/A in pure React.
|
|
1153
|
+
*/
|
|
1154
|
+
declare function MainContext({ children, onBeforeSave, warnOnUnsavedChanges, forceSuccessfulValidationBeforeSave, }: MainContextProps): react_jsx_runtime.JSX.Element;
|
|
1155
|
+
/**
|
|
1156
|
+
* Returns the nearest ancestor {@link MainContext} value, or `null` if there is
|
|
1157
|
+
* none. Components like {@link RecordContext} use this to optionally register
|
|
1158
|
+
* themselves for save/refresh coordination — `null` is the standalone case where
|
|
1159
|
+
* the component manages its own save without a coordinator.
|
|
1160
|
+
*/
|
|
1161
|
+
declare function useMainContext(): MainContextValue | null;
|
|
1162
|
+
/**
|
|
1163
|
+
* Provides an existing {@link MainContextValue} to a subtree. Used by
|
|
1164
|
+
* {@link RecordContext} to expose itself as a `MainContext` for nested
|
|
1165
|
+
* descendants (mirroring Blazor's `RecordContext : MainContext` inheritance) —
|
|
1166
|
+
* keeps the raw `MainContextContext` internal while still letting framework
|
|
1167
|
+
* components compose multiple `MainContext` levels.
|
|
1168
|
+
*
|
|
1169
|
+
* Not generally needed by application code — use `<MainContext>` instead, which
|
|
1170
|
+
* builds the value internally.
|
|
1171
|
+
*/
|
|
1172
|
+
declare function MainContextProvider({ value, children, }: {
|
|
1173
|
+
value: MainContextValue;
|
|
1174
|
+
children: ReactNode;
|
|
1175
|
+
}): react_jsx_runtime.JSX.Element;
|
|
1176
|
+
|
|
1177
|
+
type ColumnValueBase = components['schemas']['ColumnValueBase'];
|
|
1178
|
+
type TableMetadata = components['schemas']['TableMetadata'];
|
|
1179
|
+
interface RecordContextValue {
|
|
1180
|
+
/** Discriminator for the load lifecycle — same {@link QueryStatus} the query hooks use. */
|
|
1181
|
+
readonly status: QueryStatus;
|
|
1182
|
+
/** Most recent record from the server (or undefined while loading / on error). */
|
|
1183
|
+
readonly record: TableRecord | undefined;
|
|
1184
|
+
/** Last error thrown by the load. Undefined on success. */
|
|
1185
|
+
readonly error: PowerPortalsProError | undefined;
|
|
1186
|
+
/**
|
|
1187
|
+
* Re-fetches this record from the server (replacing any local mutations) and
|
|
1188
|
+
* cascades to every nested descendant's `refresh()`.
|
|
1189
|
+
*/
|
|
1190
|
+
readonly reload: () => Promise<void>;
|
|
1191
|
+
/**
|
|
1192
|
+
* Persists this record AND any nested descendants (grids, M2M editors, child
|
|
1193
|
+
* `<RecordContext>`s) — `RecordContext` IS-A `MainContext`, so `save()` here
|
|
1194
|
+
* is the same as `MainContext.saveAll()` would be at this level.
|
|
1195
|
+
*
|
|
1196
|
+
* Order: this record's PATCH first, then each registered descendant's `save()`
|
|
1197
|
+
* in registration order. Each descendant decides its own dirty-skip; saving a
|
|
1198
|
+
* clean RecordContext is a no-op for the record itself but still cascades to
|
|
1199
|
+
* nested descendants in case they have their own pending changes.
|
|
1200
|
+
*
|
|
1201
|
+
* Calls {@link RecordContextProps.onBeforeSave} (when provided) before issuing
|
|
1202
|
+
* the PATCH; throwing from that callback cancels the entire save (no descendants
|
|
1203
|
+
* run either). When inside an outer `<MainContext>`, this is also what gets
|
|
1204
|
+
* wired into the outer `MainContext.saveAll()` for this subtree.
|
|
1205
|
+
*/
|
|
1206
|
+
readonly save: () => Promise<void>;
|
|
1207
|
+
/**
|
|
1208
|
+
* Aggregates every pending Dataverse operation across this record AND
|
|
1209
|
+
* every nested descendant — the own `UpdateRequest` / `CreateRequest`
|
|
1210
|
+
* (when dirty) followed by each nested descendant's `getRequests()`, in
|
|
1211
|
+
* registration order. Returns an empty array when nothing is dirty.
|
|
1212
|
+
*
|
|
1213
|
+
* Mirrors Blazor's public `RecordContext.GetRequests()` — same shape, same
|
|
1214
|
+
* traversal. Use for previewing the payload {@link save} would ship,
|
|
1215
|
+
* for dry-run UIs, or to merge the records into a custom
|
|
1216
|
+
* `executeMultiple` batch alongside consumer-supplied requests.
|
|
1217
|
+
* Consumers that just want to persist should call {@link save}.
|
|
1218
|
+
*/
|
|
1219
|
+
readonly getRequests: () => OrganizationRequest[];
|
|
1220
|
+
/**
|
|
1221
|
+
* Discards every pending edit on this record AND in every nested
|
|
1222
|
+
* descendant, reverting back to the loaded baseline WITHOUT re-fetching
|
|
1223
|
+
* from the server. The "Cancel" / "Discard" primitive — same surface
|
|
1224
|
+
* as `MainContextValue.resetState`, also drops the record's own
|
|
1225
|
+
* baseline so it re-loads clean.
|
|
1226
|
+
*
|
|
1227
|
+
* Validation errors tracked against the dropped pending values are
|
|
1228
|
+
* also cleared. Synchronous: descendants do their own `setState` calls.
|
|
1229
|
+
*/
|
|
1230
|
+
readonly resetState: () => void;
|
|
1231
|
+
/**
|
|
1232
|
+
* Immutably updates one column on the in-memory record. Triggers a re-render of all
|
|
1233
|
+
* descendants reading the record (typed-column hooks bind to this from
|
|
1234
|
+
* `@powerportalspro/react-fluent`'s edit components and similar). Pass `null` or
|
|
1235
|
+
* `undefined` to clear the column. Marks the column dirty in {@link dirtyColumns}
|
|
1236
|
+
* and sets `record.isDirty = true`.
|
|
1237
|
+
*
|
|
1238
|
+
* No-op if no record has loaded yet — children should gate input on `status === 'success'`.
|
|
1239
|
+
*/
|
|
1240
|
+
readonly setColumnValue: (columnName: string, value: ColumnValueBase | null | undefined) => void;
|
|
1241
|
+
/**
|
|
1242
|
+
* Patches the loaded baseline with a fresh value for one column WITHOUT
|
|
1243
|
+
* marking the column dirty. Use for late-arriving server-side data — file
|
|
1244
|
+
* binaries lazy-loaded by `<ImageEdit>`/`<FileEdit>`, async-resolved
|
|
1245
|
+
* formatted values, secondary fetches that fill in fields the initial
|
|
1246
|
+
* record query didn't populate. NOT for user edits — those go through
|
|
1247
|
+
* {@link setColumnValue}.
|
|
1248
|
+
*
|
|
1249
|
+
* If the column already has a pending edit, the hydration is skipped to
|
|
1250
|
+
* avoid silently clobbering the user's in-progress change. This
|
|
1251
|
+
* distinguishes "load filled this in" from "user typed this" — the
|
|
1252
|
+
* former sets the baseline, the latter sets a pending value compared
|
|
1253
|
+
* against the baseline for dirty calculation.
|
|
1254
|
+
*
|
|
1255
|
+
* No-op if no record has loaded yet.
|
|
1256
|
+
*/
|
|
1257
|
+
readonly hydrateColumnValue: (columnName: string, value: ColumnValueBase | null) => void;
|
|
1258
|
+
/**
|
|
1259
|
+
* Table metadata for the parent record's table — column types, required levels,
|
|
1260
|
+
* isValidForCreate/Update flags, etc. Loaded in parallel with the record itself.
|
|
1261
|
+
*
|
|
1262
|
+
* Independent of `status`: this is `undefined` while metadata is loading, on
|
|
1263
|
+
* metadata fetch failure, or for tables that have no metadata. The record's own
|
|
1264
|
+
* `status` reflects only the record query — metadata is best-effort, edit
|
|
1265
|
+
* components fall back to consumer-passed props when it's absent. This keeps
|
|
1266
|
+
* forms rendering as soon as the record is ready instead of blocking on a
|
|
1267
|
+
* second round-trip.
|
|
1268
|
+
*
|
|
1269
|
+
* Use {@link useColumnMetadata} from `@powerportalspro/react` to look up a
|
|
1270
|
+
* single column's entry.
|
|
1271
|
+
*/
|
|
1272
|
+
readonly tableMetadata: TableMetadata | undefined;
|
|
1273
|
+
/**
|
|
1274
|
+
* `true` when at least one column on this record has been modified, OR when
|
|
1275
|
+
* any nested descendant (child `<RecordContext>`, grid, M2M editor) reports
|
|
1276
|
+
* dirty.
|
|
1277
|
+
*
|
|
1278
|
+
* Use this for Save-button enablement, navigation-warning gates, or any UI
|
|
1279
|
+
* that should react to the existence of unsaved changes anywhere in the
|
|
1280
|
+
* record's subtree. For a more focused check (just this record's columns,
|
|
1281
|
+
* ignoring nested descendants), inspect {@link dirtyColumns} directly.
|
|
1282
|
+
*/
|
|
1283
|
+
readonly isDirty: boolean;
|
|
1284
|
+
/**
|
|
1285
|
+
* The set of column logical names that have been modified since the record
|
|
1286
|
+
* loaded or was last saved. Cleared on successful {@link save} and when a
|
|
1287
|
+
* fresh record lands from {@link reload}.
|
|
1288
|
+
*
|
|
1289
|
+
* Useful for per-field dirty indicators (a small marker next to a label),
|
|
1290
|
+
* for save filtering (only ship changed columns), or for diff-style review
|
|
1291
|
+
* UIs that show what's about to be persisted.
|
|
1292
|
+
*/
|
|
1293
|
+
readonly dirtyColumns: ReadonlySet<string>;
|
|
1294
|
+
/**
|
|
1295
|
+
* Stable id this RecordContext uses when calling {@link OverlayService.show}
|
|
1296
|
+
* during load. Pass to a scoped overlay container (e.g.
|
|
1297
|
+
* `<FluentOverlayTarget targetId={ctx.overlayTargetId}>`) inside the
|
|
1298
|
+
* RecordContext subtree to render the loading spinner localized to that
|
|
1299
|
+
* region rather than full-screen.
|
|
1300
|
+
*
|
|
1301
|
+
* Generated once per RecordContext instance via React's `useId`. If no
|
|
1302
|
+
* `<OverlayService>` is mounted, the show calls no-op silently.
|
|
1303
|
+
*/
|
|
1304
|
+
readonly overlayTargetId: string;
|
|
1305
|
+
/**
|
|
1306
|
+
* Runs the context-level {@link RecordContextProps.onBeforeDelete}
|
|
1307
|
+
* callback (when configured), surfacing any thrown error to the
|
|
1308
|
+
* caller so the delete can be cancelled. No-op when no callback is
|
|
1309
|
+
* wired. Delete-style buttons should `await` this BEFORE issuing
|
|
1310
|
+
* their own pre-delete prompts or the actual `deleteRecordAsync`
|
|
1311
|
+
* call. Parallels how `MainContext.saveAll` invokes
|
|
1312
|
+
* {@link MainContextProps.onBeforeSave}.
|
|
1313
|
+
*/
|
|
1314
|
+
readonly runBeforeDelete: () => Promise<void>;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* `RecordContext` extends {@link MainContextProps} because every `RecordContext`
|
|
1318
|
+
* conceptually IS-A `MainContext` — Blazor's `RecordContext.razor.cs` literally
|
|
1319
|
+
* inherits from `MainContext.razor.cs` and `override`s `IsDirty` to add the
|
|
1320
|
+
* record's own dirty state on top of the base aggregation. Inheriting the props
|
|
1321
|
+
* means `onBeforeSave` (and any future cross-cutting `MainContextProps`) is
|
|
1322
|
+
* available on a standalone `<RecordContext>` without a wrapping `<MainContext>`.
|
|
1323
|
+
*/
|
|
1324
|
+
interface RecordContextProps extends MainContextProps {
|
|
1325
|
+
/** Logical name of the Dataverse table (e.g. `"account"`, `"contact"`). */
|
|
1326
|
+
table: string;
|
|
1327
|
+
/**
|
|
1328
|
+
* Primary id of the record to edit. Omit (or pass empty / `Guid.Empty`)
|
|
1329
|
+
* to enter **create mode**: no fetch fires, the in-memory record starts
|
|
1330
|
+
* either empty or from {@link initialRecord}, and {@link save} emits a
|
|
1331
|
+
* `CreateRequest` instead of an `UpdateRequest` on commit.
|
|
1332
|
+
*/
|
|
1333
|
+
id?: string;
|
|
1334
|
+
/**
|
|
1335
|
+
* Optional pre-populated record to start from in **create mode** (when
|
|
1336
|
+
* {@link id} is omitted). Use to seed default values — typical case is
|
|
1337
|
+
* a SubGrid-hosted new-record dialog pre-filling the parent foreign-key
|
|
1338
|
+
* column (`parentcustomerid`, `contact_customer_accounts` etc.). Ignored
|
|
1339
|
+
* in edit mode — when `id` is set the server-loaded record is the
|
|
1340
|
+
* baseline. The `tableName` on `initialRecord` should match {@link table};
|
|
1341
|
+
* `id` will be ignored either way (a create starts without one).
|
|
1342
|
+
*/
|
|
1343
|
+
initialRecord?: TableRecord;
|
|
1344
|
+
/**
|
|
1345
|
+
* Pre-loaded record to use as the baseline instead of fetching from the
|
|
1346
|
+
* server. When provided in edit mode (with {@link id} set), the network
|
|
1347
|
+
* fetch is skipped entirely and the supplied record becomes the
|
|
1348
|
+
* `loadedRecord` baseline — `status` immediately reads `'success'`.
|
|
1349
|
+
*
|
|
1350
|
+
* The intended consumer is the inline-editable grid: each row already
|
|
1351
|
+
* has its record in hand from the grid's `useGridData` fetch, so per-
|
|
1352
|
+
* row RecordContexts would otherwise issue N redundant `retrieveRecord`
|
|
1353
|
+
* round-trips just to re-fetch what's already on screen. Passing
|
|
1354
|
+
* `loadedRecord={row}` short-circuits that.
|
|
1355
|
+
*
|
|
1356
|
+
* Identity change re-baselines: a fresh reference is treated like a
|
|
1357
|
+
* fresh load, which seeds `loadedRecord` and resets `pendingChanges`
|
|
1358
|
+
* (mirroring the success-effect's behavior on a real refetch). Pass a
|
|
1359
|
+
* stable / memoized reference if you want pending edits to persist
|
|
1360
|
+
* across re-renders.
|
|
1361
|
+
*
|
|
1362
|
+
* Ignored in create mode ({@link id} omitted) — {@link initialRecord}
|
|
1363
|
+
* is the create-mode baseline.
|
|
1364
|
+
*/
|
|
1365
|
+
loadedRecord?: TableRecord;
|
|
1366
|
+
/**
|
|
1367
|
+
* Pending column changes to apply on top of the server-loaded record.
|
|
1368
|
+
* Used by the dialog-based Edit flow when reopening a row that already
|
|
1369
|
+
* has queued updates from a prior session — the queued values become
|
|
1370
|
+
* the dialog's initial pendingChanges, so reverting to the server value
|
|
1371
|
+
* cleanly drops the column from `pendingChanges` (matches the loaded
|
|
1372
|
+
* baseline → removed) and the eventual save can detect "no net change"
|
|
1373
|
+
* by inspecting `dirtyColumns`.
|
|
1374
|
+
*
|
|
1375
|
+
* Applied once when the load lands; further user edits drive the
|
|
1376
|
+
* pending map normally via `setColumnValue`. Identity change after the
|
|
1377
|
+
* initial apply re-applies (rare — typically the prop is stable for
|
|
1378
|
+
* the lifetime of a dialog instance).
|
|
1379
|
+
*/
|
|
1380
|
+
initialPendingChanges?: ReadonlyMap<string, ColumnValueBase | null>;
|
|
1381
|
+
/** Optional column allow-list. Omit for the table's full visible column set. */
|
|
1382
|
+
columns?: readonly string[];
|
|
1383
|
+
/** Fired once after a successful initial load (and again after any subsequent reload). */
|
|
1384
|
+
onRecordLoaded?: (record: TableRecord) => void;
|
|
1385
|
+
/**
|
|
1386
|
+
* Name of the URL query-string parameter to read the record's id
|
|
1387
|
+
* from. When set, the parameter's current value overrides
|
|
1388
|
+
* {@link id} — useful for routes shaped like
|
|
1389
|
+
* `/accounts/edit?accountId=...` where the consumer would
|
|
1390
|
+
* otherwise wire `useSearchParams` + pass to `id` by hand.
|
|
1391
|
+
*
|
|
1392
|
+
* Router-agnostic — uses `useQueryParam` under the hood which
|
|
1393
|
+
* reads from `window.location.search` and subscribes to back /
|
|
1394
|
+
* forward + programmatic `pushState` / `replaceState` so any
|
|
1395
|
+
* router's navigation triggers an id re-read.
|
|
1396
|
+
*
|
|
1397
|
+
* When the parameter is absent (or empty / Guid.Empty), the
|
|
1398
|
+
* RecordContext enters create mode — same semantic as omitting
|
|
1399
|
+
* the `id` prop entirely.
|
|
1400
|
+
*/
|
|
1401
|
+
queryParameterName?: string;
|
|
1402
|
+
/**
|
|
1403
|
+
* Cancellable hook fired before this record is deleted. Throw from
|
|
1404
|
+
* the callback to abort — the delete request never goes out and
|
|
1405
|
+
* subsequent `onDeleted` hooks don't run either.
|
|
1406
|
+
*
|
|
1407
|
+
* Surfaces as `useRecordContext().onBeforeDelete` for delete-style
|
|
1408
|
+
* buttons (the framework `<DeleteContextButton>`, or consumer-built
|
|
1409
|
+
* delete actions) to consult before issuing `deleteRecordAsync`.
|
|
1410
|
+
* Per-button `onBeforeDelete` props chain after this — context first
|
|
1411
|
+
* (record-scoped policy), button second (action-specific).
|
|
1412
|
+
*
|
|
1413
|
+
* Use this for context-level deletion gates: "deleted records can't
|
|
1414
|
+
* have active child records" / "only owners can delete" / etc.
|
|
1415
|
+
* Throwing with a meaningful error message is fine — the framework
|
|
1416
|
+
* delete button routes thrown errors through its `onError` path or
|
|
1417
|
+
* the dialog service's error display.
|
|
1418
|
+
*/
|
|
1419
|
+
onBeforeDelete?: () => void | Promise<void>;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Loads a single Dataverse record and provides it to descendants via React
|
|
1423
|
+
* context (the analog of Blazor's cascading value). Children call
|
|
1424
|
+
* {@link useRecordContext} to read the record + status.
|
|
1425
|
+
*
|
|
1426
|
+
* Also acts as a `MainContext` for its own subtree — nested grids, M2M editors,
|
|
1427
|
+
* and child `<RecordContext>`s register with this `RecordContext` instead of
|
|
1428
|
+
* skipping past it to a further-up `<MainContext>`. The aggregated
|
|
1429
|
+
* `isDirty`/`saveAll` exposed to a wrapping outer `<MainContext>` includes both
|
|
1430
|
+
* this record's own pending edits AND every nested descendant's state, so a
|
|
1431
|
+
* single outer `<SaveButton>` covers everything beneath it.
|
|
1432
|
+
*
|
|
1433
|
+
* Auto-registers with a parent {@link MainContext} (when present) so
|
|
1434
|
+
* {@link MainContext.saveAll} / {@link MainContext.refreshAll} include this record.
|
|
1435
|
+
* Without a parent, `save()` and `reload()` are still callable directly from
|
|
1436
|
+
* descendants via the context.
|
|
1437
|
+
*
|
|
1438
|
+
* What's deferred from Blazor's RecordContext (the full version is ~700 lines —
|
|
1439
|
+
* this is the headless coordination skeleton):
|
|
1440
|
+
* - **New-record creation** (id absent → empty TableRecord). Will need a
|
|
1441
|
+
* `mode: "edit" | "create"` discriminator and dispatching `createRecordAsync`
|
|
1442
|
+
* instead of `updateRecordAsync` from `save()`.
|
|
1443
|
+
* - **Dirty tracking + EditContext-style validation**. Tied to the form layer.
|
|
1444
|
+
* - **`OnBeforeSave` / `OnBeforeDelete`** cancellable callbacks at this level
|
|
1445
|
+
* (MainContext already has `onBeforeSave`).
|
|
1446
|
+
* - **Delete handler** with confirmation flow.
|
|
1447
|
+
* - **URL query-parameter reading** (Blazor's `QueryParameterName`). Consumers can
|
|
1448
|
+
* pull the id from `useSearchParams()` themselves and pass it as the `id` prop.
|
|
1449
|
+
* - **PersistentComponentState** for server→WASM bridging. N/A in pure React.
|
|
1450
|
+
*/
|
|
1451
|
+
/**
|
|
1452
|
+
* Wrapper that auto-mounts a {@link ValidationProvider} around the inner
|
|
1453
|
+
* record-context logic. The split lets the inner component call
|
|
1454
|
+
* `useValidationContext()` (e.g. to clear errors on reload) — that hook
|
|
1455
|
+
* resolves the provider this wrapper just rendered.
|
|
1456
|
+
*
|
|
1457
|
+
* The two-component shape is invisible to consumers — `<RecordContext>` is
|
|
1458
|
+
* the only public surface.
|
|
1459
|
+
*/
|
|
1460
|
+
declare function RecordContext(props: RecordContextProps): react_jsx_runtime.JSX.Element;
|
|
1461
|
+
/**
|
|
1462
|
+
* Reads the nearest ancestor {@link RecordContext}'s value. Throws when called
|
|
1463
|
+
* outside one — same diagnostic pattern as `useAuth`.
|
|
1464
|
+
*
|
|
1465
|
+
* @example
|
|
1466
|
+
* ```tsx
|
|
1467
|
+
* import { QueryStatus, useRecordContext } from '@powerportalspro/react';
|
|
1468
|
+
*
|
|
1469
|
+
* function ContactDetails() {
|
|
1470
|
+
* const { status, record } = useRecordContext();
|
|
1471
|
+
* if (status === QueryStatus.Loading) return <Spinner />;
|
|
1472
|
+
* if (status === QueryStatus.Error) return <ErrorBanner />;
|
|
1473
|
+
* return <h1>{record!.formattedValues?.name}</h1>;
|
|
1474
|
+
* }
|
|
1475
|
+
* ```
|
|
1476
|
+
*/
|
|
1477
|
+
declare function useRecordContext(): RecordContextValue;
|
|
1478
|
+
/**
|
|
1479
|
+
* Non-throwing variant of {@link useRecordContext} — returns the nearest
|
|
1480
|
+
* `<RecordContext>`'s value when one is present, otherwise `null`. Useful
|
|
1481
|
+
* for components that work either inside or outside a record context
|
|
1482
|
+
* (e.g. `<SubGrid>` picks up the cascading record when one is available
|
|
1483
|
+
* but accepts an explicit `record` prop as an alternative).
|
|
1484
|
+
*
|
|
1485
|
+
* Prefer {@link useRecordContext} when the component absolutely requires
|
|
1486
|
+
* the context — the loud throw catches wiring bugs early; this hook is
|
|
1487
|
+
* for the genuinely optional case.
|
|
1488
|
+
*/
|
|
1489
|
+
declare function useRecordContextOptional(): RecordContextValue | null;
|
|
1490
|
+
|
|
1491
|
+
interface LookupRecordContextProps {
|
|
1492
|
+
/**
|
|
1493
|
+
* Logical name of the lookup column on the parent `<RecordContext>`'s
|
|
1494
|
+
* record. The column must resolve to a `LookupValue` (typed
|
|
1495
|
+
* `$type: 6`) — the component reads the lookup's `tableName` + `value`
|
|
1496
|
+
* and spawns a nested `<RecordContext>` for the referenced record.
|
|
1497
|
+
*
|
|
1498
|
+
* Required. Mirrors Blazor's `<LookupRecordContext ColumnName="...">`
|
|
1499
|
+
* `ColumnName` parameter.
|
|
1500
|
+
*/
|
|
1501
|
+
columnName: string;
|
|
1502
|
+
/**
|
|
1503
|
+
* Optional pre-save cancellable hook forwarded to the nested
|
|
1504
|
+
* `<RecordContext>`. Throwing (or returning a rejected promise) from
|
|
1505
|
+
* this callback aborts the save before any server call — the same
|
|
1506
|
+
* semantic as `<RecordContext onBeforeSave={…}>` directly. Mirrors
|
|
1507
|
+
* Blazor's `LookupRecordContext.OnBeforeSave` cancellable hook.
|
|
1508
|
+
*/
|
|
1509
|
+
onBeforeSave?: () => void | Promise<void>;
|
|
1510
|
+
/**
|
|
1511
|
+
* Children rendered inside the nested context. Typical contents:
|
|
1512
|
+
* edit components (`<TextEdit>`, `<NumberEdit>`, etc.) bound to
|
|
1513
|
+
* columns on the LOOKED-UP record's table — e.g. an
|
|
1514
|
+
* `<TextEdit columnName="firstname" />` inside a
|
|
1515
|
+
* `<LookupRecordContext columnName="primarycontactid">` on an account
|
|
1516
|
+
* form edits the linked contact's first name.
|
|
1517
|
+
*/
|
|
1518
|
+
children?: ReactNode;
|
|
1519
|
+
/**
|
|
1520
|
+
* When `false`, renders nothing. Defaults to `true`. Useful for
|
|
1521
|
+
* conditionally hiding the section without unmounting the
|
|
1522
|
+
* declaration. Mirrors `IsVisible` on the other framework
|
|
1523
|
+
* components.
|
|
1524
|
+
*/
|
|
1525
|
+
isVisible?: boolean;
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Branches off into a nested `<RecordContext>` for the record referenced
|
|
1529
|
+
* by a lookup column on the surrounding `<RecordContext>`. Mirrors
|
|
1530
|
+
* Blazor's `<LookupRecordContext>` — a thin wrapper that resolves the
|
|
1531
|
+
* lookup value, gates on its presence, and spawns the nested context.
|
|
1532
|
+
*
|
|
1533
|
+
* Edit components inside the lookup context bind to columns on the
|
|
1534
|
+
* LOOKED-UP record's table, not the parent's. Changes flow through the
|
|
1535
|
+
* surrounding save cascade automatically via the descendant-registry
|
|
1536
|
+
* plumbing every `<RecordContext>` already wires up — the nested record
|
|
1537
|
+
* registers with the outermost `<MainContext>` (or
|
|
1538
|
+
* `<RecordContext>`-as-MainContext) and its pending edits join the
|
|
1539
|
+
* single transactional `executeMultiple` batch on save.
|
|
1540
|
+
*
|
|
1541
|
+
* **Conditional render (matches Blazor's lazy pattern):** the nested
|
|
1542
|
+
* `<RecordContext>` is only instantiated when the lookup actually has
|
|
1543
|
+
* a value (non-null + non-empty-GUID + has `tableName`). Empty lookups
|
|
1544
|
+
* render nothing — avoids spinning up nested contexts, EditContext
|
|
1545
|
+
* validators, descendant registrations, etc. for every empty lookup
|
|
1546
|
+
* column on a form. The Blazor source carries an explicit perf note on
|
|
1547
|
+
* this: ~10 lookups × per-render lifecycle overhead added up to a
|
|
1548
|
+
* measurable freeze before the conditional render was added.
|
|
1549
|
+
*
|
|
1550
|
+
* ```tsx
|
|
1551
|
+
* <RecordContext table="account" id={accountId}>
|
|
1552
|
+
* <Section>
|
|
1553
|
+
* <SectionColumn header={<Text>Account</Text>}>
|
|
1554
|
+
* <TextEdit columnName="name" />
|
|
1555
|
+
* <TextEdit columnName="accountnumber" />
|
|
1556
|
+
* </SectionColumn>
|
|
1557
|
+
*
|
|
1558
|
+
* <SectionColumn header={<Text>Primary contact</Text>}>
|
|
1559
|
+
* <LookupRecordContext columnName="primarycontactid">
|
|
1560
|
+
* {/* These edits bind to the linked CONTACT, not the account *\/}
|
|
1561
|
+
* <TextEdit columnName="firstname" />
|
|
1562
|
+
* <TextEdit columnName="lastname" />
|
|
1563
|
+
* <TextEdit columnName="emailaddress1" type="email" />
|
|
1564
|
+
* </LookupRecordContext>
|
|
1565
|
+
* </SectionColumn>
|
|
1566
|
+
* </Section>
|
|
1567
|
+
* </RecordContext>
|
|
1568
|
+
* ```
|
|
1569
|
+
*
|
|
1570
|
+
* Renders `null` when:
|
|
1571
|
+
* - the surrounding `<RecordContext>` hasn't loaded yet,
|
|
1572
|
+
* - the parent column doesn't carry a `LookupValue`,
|
|
1573
|
+
* - the lookup is the empty GUID (Dataverse's "no value" sentinel), or
|
|
1574
|
+
* - the lookup is missing its `tableName`.
|
|
1575
|
+
*
|
|
1576
|
+
* `<LookupRecordContext>` deliberately does NOT mirror Blazor's
|
|
1577
|
+
* `BaseColumnEdit` inheritance — the parent's column read-write loop
|
|
1578
|
+
* is owned by `useLookupColumn(columnName)` for consumers who want to
|
|
1579
|
+
* change the lookup value programmatically; `<LookupRecordContext>` is
|
|
1580
|
+
* pure read-then-branch.
|
|
1581
|
+
*/
|
|
1582
|
+
declare function LookupRecordContext({ columnName, onBeforeSave, children, isVisible, }: LookupRecordContextProps): react_jsx_runtime.JSX.Element | null;
|
|
1583
|
+
|
|
1584
|
+
type ColumnMetadataBase = components['schemas']['ColumnMetadataBase'];
|
|
1585
|
+
/**
|
|
1586
|
+
* Returns the column metadata for the given column name from the parent
|
|
1587
|
+
* {@link RecordContext}'s table metadata. Returns `undefined` when:
|
|
1588
|
+
* - the table metadata hasn't loaded yet (the request is still in flight),
|
|
1589
|
+
* - the table metadata fetch failed,
|
|
1590
|
+
* - the column doesn't exist on the table.
|
|
1591
|
+
*
|
|
1592
|
+
* Edit components use this to auto-resolve defaults (`maxLength`, `min`,
|
|
1593
|
+
* `max`, `precision`, `required`, `readOnly`) when consumer-passed props are
|
|
1594
|
+
* absent. Pass-through props always win — same convention as Blazor's
|
|
1595
|
+
* `BaseColumnEdit.OnParametersSetAsync` (which only fills metadata-derived
|
|
1596
|
+
* defaults when the consumer hasn't set them explicitly).
|
|
1597
|
+
*
|
|
1598
|
+
* Throws if not used inside a `<RecordContext>`, since column metadata only
|
|
1599
|
+
* makes sense relative to a record's table.
|
|
1600
|
+
*/
|
|
1601
|
+
declare function useColumnMetadata(columnName: string): ColumnMetadataBase | undefined;
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Headless localizer contract. The framework calls `t(key)` to fetch a
|
|
1605
|
+
* localized string; consumers can plug in any implementation that satisfies
|
|
1606
|
+
* this interface — the bundled default ({@link createLocalizer}) reads from a
|
|
1607
|
+
* flat key→string map, but an i18next-backed adapter, a hardcoded English
|
|
1608
|
+
* map for tests, or a server-driven RPC localizer all work the same way.
|
|
1609
|
+
*
|
|
1610
|
+
* Args use positional indexing (`args[0]` → `{0}` in the template) to match
|
|
1611
|
+
* the existing Blazor `IStringLocalizer` JSON format — `.NET String.Format`
|
|
1612
|
+
* placeholders. Consumers using a named-args lib (e.g. i18next's `{{name}}`)
|
|
1613
|
+
* adapt at the boundary.
|
|
1614
|
+
*/
|
|
1615
|
+
interface Localizer {
|
|
1616
|
+
t(key: string, args?: readonly unknown[]): string;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Reads the nearest {@link Localizer}. Throws when called outside a provider —
|
|
1620
|
+
* mirrors `useDialogService`'s strict behavior. The framework auto-mounts a
|
|
1621
|
+
* default `<DefaultLocalizerProvider>` inside `<PowerPortalsProProvider>`, so
|
|
1622
|
+
* this only fails when neither is present (typically a misconfigured test).
|
|
1623
|
+
*/
|
|
1624
|
+
declare function useLocalizer(): Localizer;
|
|
1625
|
+
/**
|
|
1626
|
+
* Sugar over `useLocalizer().t` — returns the bound `t` function directly so
|
|
1627
|
+
* components can write `const t = useT();` and then `t('app.buttons.save.label')`.
|
|
1628
|
+
*/
|
|
1629
|
+
declare function useT(): Localizer['t'];
|
|
1630
|
+
/**
|
|
1631
|
+
* Returns a {@link Localizer.t} function with every key automatically
|
|
1632
|
+
* prepended with `{prefix}.`. React analog of Blazor's
|
|
1633
|
+
* `IPrefixedStringLocalizer` / `IStringLocalizer<T>` — use when a
|
|
1634
|
+
* component consumes strings from a known namespace (typically another
|
|
1635
|
+
* framework's class-scoped keys) and you don't want to repeat the
|
|
1636
|
+
* prefix at every call site.
|
|
1637
|
+
*
|
|
1638
|
+
* Pure sugar over `useT()` — relies on the same auto-mounted
|
|
1639
|
+
* `<DefaultLocalizerProvider>` and inherits all of its behavior
|
|
1640
|
+
* (cache, locale tracking, missing-key warnings). The bundle still
|
|
1641
|
+
* needs the full unprefixed key; this just saves the caller from
|
|
1642
|
+
* writing it out each time.
|
|
1643
|
+
*
|
|
1644
|
+
* @example
|
|
1645
|
+
* ```tsx
|
|
1646
|
+
* const t = usePrefixedT(
|
|
1647
|
+
* 'components.PowerPortalsPro.Web.Blazor.FluentUI.Components.Theme.ThemeColorSelector',
|
|
1648
|
+
* );
|
|
1649
|
+
* t('OfficeColor.0');
|
|
1650
|
+
* // → looks up
|
|
1651
|
+
* // 'components.PowerPortalsPro.Web.Blazor.FluentUI.Components.Theme.ThemeColorSelector.OfficeColor.0'
|
|
1652
|
+
* ```
|
|
1653
|
+
*
|
|
1654
|
+
* The returned function is stable per `prefix` value (memoized via
|
|
1655
|
+
* `useCallback`), so it's safe to reference from effect dependency
|
|
1656
|
+
* arrays. Passing a fresh literal each render reuses the same identity
|
|
1657
|
+
* as long as the string is identical.
|
|
1658
|
+
*/
|
|
1659
|
+
declare function usePrefixedT(prefix: string): Localizer['t'];
|
|
1660
|
+
/**
|
|
1661
|
+
* Imperative contract for queueing per-resource localization bundles. The
|
|
1662
|
+
* default localizer provider sets one of these on the
|
|
1663
|
+
* {@link LocalizationPrefetcherContext}; custom adapters set via
|
|
1664
|
+
* `<LocalizerProvider value={…}>` don't have to provide one — the public
|
|
1665
|
+
* hooks degrade gracefully (treat everything as "loaded") when the
|
|
1666
|
+
* prefetcher is absent.
|
|
1667
|
+
*/
|
|
1668
|
+
interface LocalizationPrefetcher {
|
|
1669
|
+
/**
|
|
1670
|
+
* Queue the supplied prefixes for fetch via the existing debounce / batch
|
|
1671
|
+
* mechanism. Prefixes already fetched (or in flight) are silently skipped
|
|
1672
|
+
* — repeated calls with the same prefixes don't generate redundant network
|
|
1673
|
+
* traffic. Returns immediately; the actual fetch happens after the next
|
|
1674
|
+
* flush tick.
|
|
1675
|
+
*/
|
|
1676
|
+
prefetch(prefixes: readonly string[]): void;
|
|
1677
|
+
/**
|
|
1678
|
+
* Returns `true` once every supplied prefix has been *settled* — fetched
|
|
1679
|
+
* successfully OR a failed attempt has resolved. Failures count as settled
|
|
1680
|
+
* deliberately: a flaky bundle endpoint shouldn't strand consumers waiting
|
|
1681
|
+
* forever. Per-fetch errors are logged via `console.warn` by the
|
|
1682
|
+
* underlying flush so they remain debuggable.
|
|
1683
|
+
*
|
|
1684
|
+
* Prefixes not covered by a per-resource bundle (anything outside
|
|
1685
|
+
* `tables.{name}` / `views.{viewId}` shape) are treated as already loaded
|
|
1686
|
+
* — those keys live in the default bundle that loads on app boot.
|
|
1687
|
+
*/
|
|
1688
|
+
isLoaded(prefixes: readonly string[]): boolean;
|
|
1689
|
+
/**
|
|
1690
|
+
* Subscribe to settled-set changes. The listener fires synchronously once
|
|
1691
|
+
* per flush, after the settled set is updated. Used by
|
|
1692
|
+
* {@link useLocalizationLoaded} to re-render subscribers; advanced
|
|
1693
|
+
* consumers can hook in directly. Returns an unsubscribe function.
|
|
1694
|
+
*/
|
|
1695
|
+
subscribe(listener: () => void): () => void;
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Reads the active {@link LocalizationPrefetcher}. Returns `null` when no
|
|
1699
|
+
* provider is mounted (custom-adapter setups). Most consumers should use
|
|
1700
|
+
* the higher-level {@link useLocalization} / {@link useLocalizationLoaded}
|
|
1701
|
+
* hooks; this is the raw escape hatch.
|
|
1702
|
+
*/
|
|
1703
|
+
declare function useLocalizationPrefetcher(): LocalizationPrefetcher | null;
|
|
1704
|
+
/**
|
|
1705
|
+
* Declares localization prefixes the calling component (or its subtree)
|
|
1706
|
+
* needs. The framework queues them via the existing debounced fetch
|
|
1707
|
+
* pipeline — by the time the user wanders into a component that calls
|
|
1708
|
+
* `t('tables.account.label')`, the fetch is either complete or in flight,
|
|
1709
|
+
* collapsing the brief flash of raw keys.
|
|
1710
|
+
*
|
|
1711
|
+
* Fire-and-forget — does not block render or expose loading state. Pair
|
|
1712
|
+
* with `<LocalizationBoundary>` (from `@powerportalspro/react-fluent`) or
|
|
1713
|
+
* {@link useLocalizationLoaded} when you need to hold off rendering until
|
|
1714
|
+
* the strings are ready.
|
|
1715
|
+
*
|
|
1716
|
+
* Accepts prefix or full-key shapes:
|
|
1717
|
+
* - `tables.{tablename}` (token) → loads `tables.<tablename>.*` strings
|
|
1718
|
+
* - `tables.account.label` (key) → coerced to `tables.account`
|
|
1719
|
+
* - `views.{viewId}` (token) → loads `views.<viewId>.*` strings
|
|
1720
|
+
* - `tables.account.views.{viewId}.foo` (key) → coerced to `views.<viewId>`
|
|
1721
|
+
* - Anything else (`app.*`, `components.*`) → silently dropped; covered
|
|
1722
|
+
* by the default bundle loaded on app boot.
|
|
1723
|
+
*
|
|
1724
|
+
* Re-fires only when `prefixes` changes by VALUE — passing a fresh array
|
|
1725
|
+
* with the same content each render doesn't requeue. Already-fetched
|
|
1726
|
+
* prefixes are deduped at the prefetcher layer regardless, so calling
|
|
1727
|
+
* `useLocalization(['tables.account'])` from two components on the same
|
|
1728
|
+
* page produces one network round-trip total.
|
|
1729
|
+
*
|
|
1730
|
+
* No-op when no `<DefaultLocalizerProvider>` ancestor is mounted (consumer
|
|
1731
|
+
* is on a custom localizer adapter).
|
|
1732
|
+
*
|
|
1733
|
+
* @example
|
|
1734
|
+
* ```tsx
|
|
1735
|
+
* function MyAccountPage() {
|
|
1736
|
+
* useLocalization(['tables.account', 'tables.contact']);
|
|
1737
|
+
* return <Form>...</Form>;
|
|
1738
|
+
* }
|
|
1739
|
+
* ```
|
|
1740
|
+
*/
|
|
1741
|
+
declare function useLocalization(prefixes: readonly string[]): void;
|
|
1742
|
+
/**
|
|
1743
|
+
* Reactive boolean — `true` once every prefix in the array has been settled
|
|
1744
|
+
* (fetched or failed). Re-renders the calling component when the state
|
|
1745
|
+
* flips. Treats a missing prefetcher (custom-adapter consumer) as "always
|
|
1746
|
+
* loaded" so dependent components degrade gracefully.
|
|
1747
|
+
*
|
|
1748
|
+
* Powers `<LocalizationBoundary>` and is exported for consumers that want
|
|
1749
|
+
* to drive their own gating UX — e.g. a loading row inside a grid rather
|
|
1750
|
+
* than holding off the whole subtree.
|
|
1751
|
+
*
|
|
1752
|
+
* @example
|
|
1753
|
+
* ```tsx
|
|
1754
|
+
* function MyForm() {
|
|
1755
|
+
* const ready = useLocalizationLoaded(['tables.account']);
|
|
1756
|
+
* if (!ready) return <Spinner />;
|
|
1757
|
+
* return <Form>...</Form>;
|
|
1758
|
+
* }
|
|
1759
|
+
* ```
|
|
1760
|
+
*/
|
|
1761
|
+
declare function useLocalizationLoaded(prefixes: readonly string[]): boolean;
|
|
1762
|
+
/**
|
|
1763
|
+
* Provides an explicit {@link Localizer} to the subtree. Used internally by
|
|
1764
|
+
* {@link DefaultLocalizerProvider}; consumers wanting to swap in i18next or
|
|
1765
|
+
* any other implementation wrap their tree with this and pass their adapter
|
|
1766
|
+
* as `value`.
|
|
1767
|
+
*/
|
|
1768
|
+
declare function LocalizerProvider({ value, children, }: {
|
|
1769
|
+
value: Localizer;
|
|
1770
|
+
children: ReactNode;
|
|
1771
|
+
}): react_jsx_runtime.JSX.Element;
|
|
1772
|
+
/**
|
|
1773
|
+
* Flattens a nested object (Blazor's localization JSON shape) into a flat
|
|
1774
|
+
* `key.path → value` map. Drops non-string leaves (no use for them in the
|
|
1775
|
+
* localizer's lookup).
|
|
1776
|
+
*/
|
|
1777
|
+
declare function flattenStrings(obj: Record<string, unknown>, prefix?: string): Record<string, string>;
|
|
1778
|
+
/**
|
|
1779
|
+
* Substitutes `{0}`, `{1}`, etc. with the corresponding positional arg.
|
|
1780
|
+
* Strips `.NET`-style format specifiers (`{0:N0}`, `{1:yyyy-MM-dd}`) since
|
|
1781
|
+
* those are .NET-only and don't translate to JS without a custom formatter
|
|
1782
|
+
* pipeline. Consumers needing locale-aware number / date display should
|
|
1783
|
+
* format their values via `Intl.NumberFormat` / `Intl.DateTimeFormat` before
|
|
1784
|
+
* passing them to `t()`.
|
|
1785
|
+
*
|
|
1786
|
+
* Out-of-range placeholders are left as-is (visible in the rendered string)
|
|
1787
|
+
* so the missing arg surfaces during dev.
|
|
1788
|
+
*/
|
|
1789
|
+
declare function interpolate(template: string, args?: readonly unknown[]): string;
|
|
1790
|
+
/**
|
|
1791
|
+
* Optional behavior knobs for {@link createLocalizer}.
|
|
1792
|
+
*/
|
|
1793
|
+
interface CreateLocalizerOptions {
|
|
1794
|
+
/**
|
|
1795
|
+
* Called once per missing key the first time it's looked up. Use to wire
|
|
1796
|
+
* dev-mode warnings, telemetry, or to fail loudly during tests. The hook
|
|
1797
|
+
* is invoked AFTER the localizer falls back to returning the raw key.
|
|
1798
|
+
*
|
|
1799
|
+
* Each key is reported at most once per localizer instance — repeat
|
|
1800
|
+
* lookups for the same missing key don't fire it again. This keeps a
|
|
1801
|
+
* deeply-rendered missing key from spamming the console.
|
|
1802
|
+
*/
|
|
1803
|
+
onMissingKey?: (key: string) => void;
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Builds a {@link Localizer} backed by a flat string map. Missing keys
|
|
1807
|
+
* return the key itself (visible fallback for missing strings — easier to
|
|
1808
|
+
* spot than silently showing nothing). Pass `onMissingKey` to get a hook
|
|
1809
|
+
* that fires once per missing key.
|
|
1810
|
+
*/
|
|
1811
|
+
declare function createLocalizer(strings: Record<string, string>, options?: CreateLocalizerOptions): Localizer;
|
|
1812
|
+
/**
|
|
1813
|
+
* Substitutes the `{locale}` placeholder in a URL template. URLs without
|
|
1814
|
+
* the placeholder pass through unchanged — useful for static-locale
|
|
1815
|
+
* deployments where every locale uses the same URL.
|
|
1816
|
+
*/
|
|
1817
|
+
declare function resolveLocalizationUrl(template: string, locale: string): string;
|
|
1818
|
+
/**
|
|
1819
|
+
* Cookie name ASP.NET Core's `CookieRequestCultureProvider` uses to persist
|
|
1820
|
+
* the active culture. Same cookie the Blazor stack sets via the
|
|
1821
|
+
* <c>/Culture/{culture}</c> endpoint, so reading/writing it here keeps the
|
|
1822
|
+
* React and Blazor stacks in sync when both are hosted side-by-side.
|
|
1823
|
+
*/
|
|
1824
|
+
declare const ASP_NET_CULTURE_COOKIE = ".AspNetCore.Culture";
|
|
1825
|
+
/**
|
|
1826
|
+
* Reads the ASP.NET Core culture cookie. The cookie value follows the
|
|
1827
|
+
* `c={culture}|uic={uiCulture}` shape (URL-encoded) — we parse the `c`
|
|
1828
|
+
* segment. Returns `null` when the cookie isn't set or can't be parsed.
|
|
1829
|
+
*
|
|
1830
|
+
* Safe to call in non-browser environments (returns `null`); both `document`
|
|
1831
|
+
* and `document.cookie` are guarded.
|
|
1832
|
+
*/
|
|
1833
|
+
declare function getAspNetCultureCookie(): string | null;
|
|
1834
|
+
/**
|
|
1835
|
+
* Writes the ASP.NET Core culture cookie with both `c` and `uic` segments
|
|
1836
|
+
* set to the same value. Mirrors what the server-side
|
|
1837
|
+
* <c>/Culture/{culture}</c> endpoint sets, so a React-side locale change
|
|
1838
|
+
* immediately affects subsequent server-rendered Blazor pages on the same
|
|
1839
|
+
* origin (and vice versa).
|
|
1840
|
+
*
|
|
1841
|
+
* Defaults to `path=/`, 1-year expiry, `SameSite=Lax`. Safe to call in
|
|
1842
|
+
* non-browser environments (no-op).
|
|
1843
|
+
*/
|
|
1844
|
+
declare function setAspNetCultureCookie(culture: string, options?: {
|
|
1845
|
+
path?: string;
|
|
1846
|
+
maxAgeSeconds?: number;
|
|
1847
|
+
}): void;
|
|
1848
|
+
/**
|
|
1849
|
+
* Default regex matching a locale code at the start of `window.location.pathname` —
|
|
1850
|
+
* shapes like `/en`, `/en/`, `/en-US/page`, `/fr-CA/path/x`. Captures the
|
|
1851
|
+
* locale code in group 1. Only 2-letter primary tags + optional 2–4-letter
|
|
1852
|
+
* subtag (script / region) are recognized; arbitrary path segments don't
|
|
1853
|
+
* trigger a false match.
|
|
1854
|
+
*/
|
|
1855
|
+
declare const DEFAULT_URL_LOCALE_PATTERN: RegExp;
|
|
1856
|
+
/**
|
|
1857
|
+
* Reads the locale from the URL path's first segment using
|
|
1858
|
+
* {@link DEFAULT_URL_LOCALE_PATTERN} (override via `pattern`). Returns the
|
|
1859
|
+
* matched locale or `null` when the URL doesn't carry one.
|
|
1860
|
+
*
|
|
1861
|
+
* Safe to call in non-browser environments (returns `null`).
|
|
1862
|
+
*/
|
|
1863
|
+
declare function getLocaleFromUrlPath(pattern?: RegExp): string | null;
|
|
1864
|
+
/**
|
|
1865
|
+
* Resolves the active locale from common sources, in order of precedence:
|
|
1866
|
+
*
|
|
1867
|
+
* 1. URL path segment (`/en/...`) — picked up via {@link getLocaleFromUrlPath}
|
|
1868
|
+
* when `matchUrl` is `true` (default).
|
|
1869
|
+
* 2. ASP.NET culture cookie (`.AspNetCore.Culture`) — picked up via
|
|
1870
|
+
* {@link getAspNetCultureCookie}.
|
|
1871
|
+
* 3. `navigator.language` — when `matchBrowser` is `true` (default), strips
|
|
1872
|
+
* the region (e.g. `en-US` → `en`) for compatibility with bundle locale
|
|
1873
|
+
* codes which are typically primary-tag only.
|
|
1874
|
+
* 4. The supplied `fallback` (default `'en'`).
|
|
1875
|
+
*
|
|
1876
|
+
* Used by `<LocaleProvider>` for auto-detection; also exported for consumer
|
|
1877
|
+
* code that wants the resolved locale without the provider context.
|
|
1878
|
+
*/
|
|
1879
|
+
declare function detectLocale(options?: {
|
|
1880
|
+
matchUrl?: boolean;
|
|
1881
|
+
urlPattern?: RegExp;
|
|
1882
|
+
matchBrowser?: boolean;
|
|
1883
|
+
fallback?: string;
|
|
1884
|
+
}): string;
|
|
1885
|
+
interface LocaleState {
|
|
1886
|
+
/** Current locale code (e.g. `'en'`, `'es'`, `'fr-CA'`). */
|
|
1887
|
+
readonly locale: string;
|
|
1888
|
+
/** Replace the active locale. Triggers re-fetch of localization JSONs. */
|
|
1889
|
+
readonly setLocale: (locale: string) => void;
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Reads the active locale state, falling back to `{ locale: 'en' }` when no
|
|
1893
|
+
* `<LocaleProvider>` is mounted. Apps that don't need locale switching can
|
|
1894
|
+
* skip the provider — the framework treats the default locale as `'en'`.
|
|
1895
|
+
*/
|
|
1896
|
+
declare function useLocale(): LocaleState;
|
|
1897
|
+
interface LocaleProviderProps {
|
|
1898
|
+
/**
|
|
1899
|
+
* Initial locale on mount. Used as the FALLBACK when auto-detection is
|
|
1900
|
+
* enabled (the default) and finds no URL / cookie / browser hint, OR as
|
|
1901
|
+
* the literal initial value when `autoDetect={false}`. Defaults to `'en'`.
|
|
1902
|
+
*/
|
|
1903
|
+
initialLocale?: string;
|
|
1904
|
+
/**
|
|
1905
|
+
* Auto-detect the active locale via {@link detectLocale} on mount. Default
|
|
1906
|
+
* `true`. When enabled, the resolution order is:
|
|
1907
|
+
* URL path → ASP.NET culture cookie → `navigator.language` → `initialLocale`.
|
|
1908
|
+
* Pass `false` to start from `initialLocale` ignoring the environment.
|
|
1909
|
+
*/
|
|
1910
|
+
autoDetect?: boolean;
|
|
1911
|
+
/**
|
|
1912
|
+
* Override the URL pattern used by `autoDetect`. Defaults to
|
|
1913
|
+
* {@link DEFAULT_URL_LOCALE_PATTERN}. Set to a custom RegExp when your
|
|
1914
|
+
* routes use a non-standard locale-segment shape, or pass `null` to
|
|
1915
|
+
* disable URL-based detection (only cookie + browser will be checked).
|
|
1916
|
+
*/
|
|
1917
|
+
urlPattern?: RegExp | null;
|
|
1918
|
+
/**
|
|
1919
|
+
* Persist locale changes to the ASP.NET culture cookie (the same cookie
|
|
1920
|
+
* Blazor's locale switcher writes via `/Culture/{culture}`). Default
|
|
1921
|
+
* `true` — keeps the React + Blazor stacks in sync when both render
|
|
1922
|
+
* pages on the same origin. Pass `false` to manage persistence yourself
|
|
1923
|
+
* (e.g. via the `onLocaleChange` callback).
|
|
1924
|
+
*/
|
|
1925
|
+
persistToCookie?: boolean;
|
|
1926
|
+
/**
|
|
1927
|
+
* Optional controlled locale. When provided, the component is controlled
|
|
1928
|
+
* and `setLocale` calls are forwarded via {@link onLocaleChange}. When
|
|
1929
|
+
* omitted, the component is uncontrolled with internal state.
|
|
1930
|
+
*/
|
|
1931
|
+
locale?: string;
|
|
1932
|
+
/**
|
|
1933
|
+
* Called when `setLocale` is invoked. Required when {@link locale} is
|
|
1934
|
+
* provided (controlled mode); ignored otherwise.
|
|
1935
|
+
*/
|
|
1936
|
+
onLocaleChange?: (locale: string) => void;
|
|
1937
|
+
children: ReactNode;
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Provides the active locale + a setter to the subtree. Descendants read via
|
|
1941
|
+
* {@link useLocale}; the {@link DefaultLocalizerProvider} subscribes
|
|
1942
|
+
* automatically and re-fetches its JSONs when the locale changes.
|
|
1943
|
+
*
|
|
1944
|
+
* Supports both controlled (`locale` + `onLocaleChange` props) and
|
|
1945
|
+
* uncontrolled (`initialLocale` only, internal state) modes — same pattern
|
|
1946
|
+
* as Fluent UI's controlled inputs.
|
|
1947
|
+
*
|
|
1948
|
+
* Example:
|
|
1949
|
+
* ```tsx
|
|
1950
|
+
* <LocaleProvider initialLocale="en">
|
|
1951
|
+
* <PowerPortalsProProvider>
|
|
1952
|
+
* <App /> {/* useLocale() inside returns { locale: 'en', setLocale: ... } *\/}
|
|
1953
|
+
* </PowerPortalsProProvider>
|
|
1954
|
+
* </LocaleProvider>
|
|
1955
|
+
* ```
|
|
1956
|
+
*/
|
|
1957
|
+
declare function LocaleProvider({ initialLocale, autoDetect, urlPattern, persistToCookie, locale: controlledLocale, onLocaleChange, children, }: LocaleProviderProps): react_jsx_runtime.JSX.Element;
|
|
1958
|
+
interface DefaultLocalizerProviderProps {
|
|
1959
|
+
/**
|
|
1960
|
+
* Optional additional URL templates to fetch + layer on top of the
|
|
1961
|
+
* server bundle. Each may contain `{locale}` substituted with the active
|
|
1962
|
+
* locale. Layered later-wins: bundle first, then each URL in order.
|
|
1963
|
+
*
|
|
1964
|
+
* Most apps leave this empty — the server bundle (framework defaults +
|
|
1965
|
+
* app overrides + Dataverse-metadata-derived strings outside `tables.*`)
|
|
1966
|
+
* already covers the common case. Use this for client-side overrides or
|
|
1967
|
+
* page-specific files the consumer doesn't want round-tripping through
|
|
1968
|
+
* the server.
|
|
1969
|
+
*/
|
|
1970
|
+
urls?: readonly string[];
|
|
1971
|
+
/**
|
|
1972
|
+
* @deprecated Use {@link urls} instead. Kept for backwards compatibility —
|
|
1973
|
+
* when set, treated as `urls: [url]`.
|
|
1974
|
+
*/
|
|
1975
|
+
url?: string;
|
|
1976
|
+
/**
|
|
1977
|
+
* Called once per missing key the first time it's looked up via `t(key)`.
|
|
1978
|
+
* Defaults to a `console.warn` so missing strings surface during dev.
|
|
1979
|
+
* Pass `() => undefined` to silence; pass a custom function to route the
|
|
1980
|
+
* warnings to telemetry.
|
|
1981
|
+
*
|
|
1982
|
+
* Fires whether or not the on-demand endpoint fetch succeeds — the hook is
|
|
1983
|
+
* about "this key wasn't in the static cache when first seen", not "this
|
|
1984
|
+
* key is unresolvable globally".
|
|
1985
|
+
*/
|
|
1986
|
+
onMissingKey?: (key: string) => void;
|
|
1987
|
+
/**
|
|
1988
|
+
* Baseline localization prefixes the provider should eagerly load
|
|
1989
|
+
* alongside the default bundle on mount. Same shape as the prop on
|
|
1990
|
+
* {@link LocalizationBoundary} — `tables.{name}` / `views.{viewId}`
|
|
1991
|
+
* tokens (or full keys, coerced). The framework queues them through
|
|
1992
|
+
* the standard prefetch pipeline once the default bundle has
|
|
1993
|
+
* resolved; descendant `<LocalizationBoundary>` components
|
|
1994
|
+
* automatically wait for them as part of their readiness check, so
|
|
1995
|
+
* baseline strings the whole app needs (e.g. tables that appear in
|
|
1996
|
+
* the navigation chrome) flow into the cache before any boundary
|
|
1997
|
+
* mounts its children.
|
|
1998
|
+
*
|
|
1999
|
+
* Prefixes the default bundle already covers (`app.*`,
|
|
2000
|
+
* `components.*`) are silently dropped — they're loaded as part of
|
|
2001
|
+
* Tier 1 regardless.
|
|
2002
|
+
*
|
|
2003
|
+
* Changing this prop after mount is supported: new prefixes are
|
|
2004
|
+
* queued on the next render; removed prefixes stay cached (we don't
|
|
2005
|
+
* actively evict strings — they just won't be re-fetched on locale
|
|
2006
|
+
* change). Most apps set this once at the root and never change it.
|
|
2007
|
+
*/
|
|
2008
|
+
prefixes?: readonly string[];
|
|
2009
|
+
children: ReactNode;
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Auto-loads localized strings and provides a {@link Localizer} to the
|
|
2013
|
+
* subtree using a two-tier strategy:
|
|
2014
|
+
*
|
|
2015
|
+
* **Tier 1 — manifest + default bundle (eager, on mount + locale change).**
|
|
2016
|
+
*
|
|
2017
|
+
* Fetches the manifest from `/localizations/version`, then the default
|
|
2018
|
+
* bundle from `/localizations/default/{locale}.{thumbprint}.json`. The
|
|
2019
|
+
* thumbprint segment makes each release's URL unique so the browser can
|
|
2020
|
+
* cache it `immutable`. The default bundle contains everything outside
|
|
2021
|
+
* `tables.*` / `choices.*`. Optional client-side overlay URLs (the `urls`
|
|
2022
|
+
* prop) are fetched in parallel and layered later-wins on top.
|
|
2023
|
+
*
|
|
2024
|
+
* **Tier 2 — per-resource bundle fetches (lazy, on cache miss).**
|
|
2025
|
+
*
|
|
2026
|
+
* When `t(key)` is called and the key isn't in the static cache, the key
|
|
2027
|
+
* is coerced to its owning bundle token (`tables.{name}` or `views.{viewId}`)
|
|
2028
|
+
* and queued for a debounced batched fetch (~75 ms). The flush fires one
|
|
2029
|
+
* GET per distinct token in parallel, each landing under a per-resource
|
|
2030
|
+
* thumbprinted URL (`/localizations/tables/{name}/{locale}.{thumb}.json`,
|
|
2031
|
+
* `/localizations/views/{viewId}/{locale}.{thumb}.json`). Subsequent
|
|
2032
|
+
* navigations to a page declaring the same token reuse the browser-cached
|
|
2033
|
+
* response with zero network traffic. Source-generator-emitted tokens (via
|
|
2034
|
+
* `LocalizationManifest`) flow through the same path — one fetch per
|
|
2035
|
+
* declared token, on first hit, then cached.
|
|
2036
|
+
*
|
|
2037
|
+
* **Render strategy — non-blocking.** Children render immediately with a
|
|
2038
|
+
* key-fallback localizer (every `t(key)` returns the key as plain text);
|
|
2039
|
+
* the provider swaps in real strings once the manifest + bundle resolve.
|
|
2040
|
+
* Cache misses appear as raw keys briefly until the per-resource fetch
|
|
2041
|
+
* resolves and triggers a re-render.
|
|
2042
|
+
*
|
|
2043
|
+
* **Locale changes** clear the cache, cancel any in-flight fetch, re-fetch
|
|
2044
|
+
* the manifest + bundle at the new locale, and start fresh.
|
|
2045
|
+
*
|
|
2046
|
+
* Auto-mounted by `<PowerPortalsProProvider>`. Wrap with a custom
|
|
2047
|
+
* `<LocalizerProvider>` and pass `localizationUrl={false}` on the provider
|
|
2048
|
+
* to opt out entirely (e.g. when adapting i18next).
|
|
2049
|
+
*/
|
|
2050
|
+
declare function DefaultLocalizerProvider({ urls, url, onMissingKey, prefixes, children, }: DefaultLocalizerProviderProps): react_jsx_runtime.JSX.Element;
|
|
2051
|
+
|
|
2052
|
+
/**
|
|
2053
|
+
* Outcome of a dialog interaction. `cancelled` is `true` when the user
|
|
2054
|
+
* dismissed the dialog (clicked the secondary button on a confirmation,
|
|
2055
|
+
* pressed Escape, clicked the backdrop, or otherwise rejected the action).
|
|
2056
|
+
*
|
|
2057
|
+
* Mirrors Blazor's `DialogResult` shape — kept as an object rather than a
|
|
2058
|
+
* naked boolean so we can extend with `data?` etc. when custom-content
|
|
2059
|
+
* dialogs land without a wire-breaking change.
|
|
2060
|
+
*/
|
|
2061
|
+
interface DialogResult {
|
|
2062
|
+
/** True when the user dismissed/cancelled, false when they confirmed. */
|
|
2063
|
+
readonly cancelled: boolean;
|
|
2064
|
+
}
|
|
2065
|
+
/** Common shape for all single-button dialogs (success / warning / error / info). */
|
|
2066
|
+
interface DialogOptions {
|
|
2067
|
+
/** Dialog title. When omitted, the implementation supplies a sensible default for the variant. */
|
|
2068
|
+
title?: string;
|
|
2069
|
+
/** Label for the primary (and only) action button. Defaults to `"OK"`. */
|
|
2070
|
+
primaryText?: string;
|
|
2071
|
+
}
|
|
2072
|
+
/** Options for a confirmation dialog (two action buttons). */
|
|
2073
|
+
interface ConfirmationDialogOptions extends DialogOptions {
|
|
2074
|
+
/** Label for the primary (affirmative) button. Defaults to `"Yes"`. */
|
|
2075
|
+
primaryText?: string;
|
|
2076
|
+
/** Label for the secondary (negative) button. Defaults to `"No"`. */
|
|
2077
|
+
secondaryText?: string;
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Headless dialog service contract — equivalent to Blazor's `IDialogService`.
|
|
2081
|
+
* The framework calls these methods to surface success/warning/error/info
|
|
2082
|
+
* messages and confirmation prompts; consumers provide the concrete
|
|
2083
|
+
* implementation via {@link DialogServiceProvider} (typically the Fluent UI
|
|
2084
|
+
* implementation that ships with `@powerportalspro/react-fluent`, but any
|
|
2085
|
+
* dialog UI works as long as it satisfies this contract).
|
|
2086
|
+
*
|
|
2087
|
+
* **Message format.** The `message` argument is treated as HTML markup —
|
|
2088
|
+
* matching Blazor's `Microsoft.FluentUI.AspNetCore.Components.IDialogService`,
|
|
2089
|
+
* which renders messages via `MarkupString`. Tags like `<br/>`, `<strong>`,
|
|
2090
|
+
* `<em>` work; framework localization strings (e.g.
|
|
2091
|
+
* `app.messages.unsaved-changes`) rely on this. Don't pass unsanitized user
|
|
2092
|
+
* input — same constraint as Blazor.
|
|
2093
|
+
*
|
|
2094
|
+
* Each method returns a {@link DialogResult} so the same shape covers both
|
|
2095
|
+
* single-button (always `{ cancelled: false }` once dismissed) and
|
|
2096
|
+
* confirmation dialogs.
|
|
2097
|
+
*/
|
|
2098
|
+
interface DialogService {
|
|
2099
|
+
showSuccessAsync(message: string, options?: DialogOptions): Promise<DialogResult>;
|
|
2100
|
+
showWarningAsync(message: string, options?: DialogOptions): Promise<DialogResult>;
|
|
2101
|
+
showErrorAsync(message: string, options?: DialogOptions): Promise<DialogResult>;
|
|
2102
|
+
showInfoAsync(message: string, options?: DialogOptions): Promise<DialogResult>;
|
|
2103
|
+
showConfirmationAsync(message: string, options?: ConfirmationDialogOptions): Promise<DialogResult>;
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Reads the nearest ancestor {@link DialogServiceProvider}. Throws when called
|
|
2107
|
+
* outside one — the framework relies on the dialog service for unsaved-changes
|
|
2108
|
+
* warnings, error reporting, etc.; throwing surfaces missing-provider bugs at
|
|
2109
|
+
* mount time instead of producing silent no-op dialogs.
|
|
2110
|
+
*/
|
|
2111
|
+
declare function useDialogService(): DialogService;
|
|
2112
|
+
/**
|
|
2113
|
+
* Provides a {@link DialogService} implementation to the subtree. UI libraries
|
|
2114
|
+
* that ship a `DialogService` (e.g. `@powerportalspro/react-fluent`'s
|
|
2115
|
+
* `<FluentDialogProvider>`) wrap their tree with this internally.
|
|
2116
|
+
*/
|
|
2117
|
+
declare function DialogServiceProvider({ value, children, }: {
|
|
2118
|
+
value: DialogService;
|
|
2119
|
+
children: ReactNode;
|
|
2120
|
+
}): react_jsx_runtime.JSX.Element;
|
|
2121
|
+
/**
|
|
2122
|
+
* Prompts the user about unsaved changes. Returns `true` if the user wants to
|
|
2123
|
+
* continue (discarding their edits), `false` if they want to cancel and keep
|
|
2124
|
+
* editing. Mirrors Blazor's `IPowerPortalsProDialogService.WarnAboutUnsavedChangesAsync`.
|
|
2125
|
+
*
|
|
2126
|
+
* Used by the framework before navigation, refresh, and any other operation
|
|
2127
|
+
* that would discard pending edits. Application code can also call it directly
|
|
2128
|
+
* for custom flows.
|
|
2129
|
+
*
|
|
2130
|
+
* Strings come from the supplied {@link Localizer} (default English from
|
|
2131
|
+
* the framework's bundled JSON). Keys: `app.messages.unsaved-changes` (body),
|
|
2132
|
+
* `app.messages.unsaved-changes-title` (title), `app.buttons.yes.label` /
|
|
2133
|
+
* `app.buttons.no.label` (button labels).
|
|
2134
|
+
*/
|
|
2135
|
+
declare function warnAboutUnsavedChangesAsync(dialog: DialogService, localizer: Localizer): Promise<boolean>;
|
|
2136
|
+
/**
|
|
2137
|
+
* Hook variant of {@link warnAboutUnsavedChangesAsync}. Returns a stable
|
|
2138
|
+
* function bound to the current dialog service + localizer from context.
|
|
2139
|
+
*/
|
|
2140
|
+
declare function useWarnAboutUnsavedChanges(): () => Promise<boolean>;
|
|
2141
|
+
|
|
2142
|
+
/** Options passed to {@link OverlayService.show}. */
|
|
2143
|
+
interface OverlayOptions {
|
|
2144
|
+
/**
|
|
2145
|
+
* Target id to scope the overlay. Undefined / empty = global (full-screen);
|
|
2146
|
+
* any other value routes to a matching scoped `<OverlayTarget>` registered
|
|
2147
|
+
* with the same id.
|
|
2148
|
+
*/
|
|
2149
|
+
targetId?: string | undefined;
|
|
2150
|
+
/** Text to display alongside the spinner (e.g. `"Loading..."`, `"Saving..."`). */
|
|
2151
|
+
description?: string | undefined;
|
|
2152
|
+
/** Whether to show a spinner. Defaults to `true`. */
|
|
2153
|
+
showProgress?: boolean | undefined;
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* A live overlay handle returned by {@link OverlayService.show}. Pass it to
|
|
2157
|
+
* {@link OverlayService.hide} to dismiss exactly that overlay (matters when
|
|
2158
|
+
* concurrent show calls are stacked on the same target).
|
|
2159
|
+
*/
|
|
2160
|
+
interface OverlayInstance extends OverlayOptions {
|
|
2161
|
+
/** Stable id assigned by the service at show time. */
|
|
2162
|
+
readonly id: string;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Headless overlay service contract — equivalent to Blazor's
|
|
2166
|
+
* `IOverlayService`. Maintains a per-target stack of overlay instances; the
|
|
2167
|
+
* topmost instance for a target is what renders. Concrete implementations
|
|
2168
|
+
* (e.g. `<FluentOverlayProvider>` from `@powerportalspro/react-fluent`)
|
|
2169
|
+
* provide the actual UI.
|
|
2170
|
+
*
|
|
2171
|
+
* Reads happen via the {@link useOverlayForTarget} hook — that's how
|
|
2172
|
+
* components subscribe to "what overlay should I currently render". The
|
|
2173
|
+
* service interface itself is write-only (show / hide / hideAll).
|
|
2174
|
+
*/
|
|
2175
|
+
interface OverlayService {
|
|
2176
|
+
/** Push an overlay onto its target's stack. Returns the handle to dismiss it. */
|
|
2177
|
+
show(options?: OverlayOptions): OverlayInstance;
|
|
2178
|
+
/** Remove a specific overlay from its target's stack. No-op if already removed. */
|
|
2179
|
+
hide(overlay: OverlayInstance): void;
|
|
2180
|
+
/** Clear every overlay on every target. */
|
|
2181
|
+
hideAll(): void;
|
|
2182
|
+
}
|
|
2183
|
+
interface OverlayContextValue {
|
|
2184
|
+
readonly service: OverlayService;
|
|
2185
|
+
/**
|
|
2186
|
+
* Per-target stacks. Keyed by `targetId ?? ''` (empty string = global).
|
|
2187
|
+
* Last item in each stack is the topmost / currently-rendered overlay.
|
|
2188
|
+
* Mutated only via the service; consumers read via {@link useOverlayForTarget}.
|
|
2189
|
+
*/
|
|
2190
|
+
readonly overlaysByTarget: ReadonlyMap<string, readonly OverlayInstance[]>;
|
|
2191
|
+
}
|
|
2192
|
+
/** Returns the nearest {@link OverlayService}, or `null` when no provider is mounted. */
|
|
2193
|
+
declare function useOverlayService(): OverlayService | null;
|
|
2194
|
+
/**
|
|
2195
|
+
* Returns the topmost overlay currently active for the given target id (or
|
|
2196
|
+
* `null` if there is none). Subscribers re-render when their target's stack
|
|
2197
|
+
* changes — this is how overlay-rendering components like
|
|
2198
|
+
* `<FluentOverlayProvider>` and `<FluentOverlayTarget>` know what to draw.
|
|
2199
|
+
*/
|
|
2200
|
+
declare function useOverlayForTarget(targetId?: string): OverlayInstance | null;
|
|
2201
|
+
/**
|
|
2202
|
+
* Provides an externally-built {@link OverlayService} + state to the subtree.
|
|
2203
|
+
* Real implementations like `<FluentOverlayProvider>` use this internally;
|
|
2204
|
+
* tests can use it to inject a mock service.
|
|
2205
|
+
*/
|
|
2206
|
+
declare function OverlayServiceProvider({ value, children, }: {
|
|
2207
|
+
value: OverlayContextValue;
|
|
2208
|
+
children: ReactNode;
|
|
2209
|
+
}): react_jsx_runtime.JSX.Element;
|
|
2210
|
+
/**
|
|
2211
|
+
* Hook that builds a state-backed {@link OverlayService} + the overlays-by-target
|
|
2212
|
+
* map needed for the {@link OverlayServiceProvider} value. Used by overlay
|
|
2213
|
+
* provider components — wraps children with `<OverlayServiceProvider value={...}>`
|
|
2214
|
+
* and renders the actual overlay UI based on the returned `overlaysByTarget`.
|
|
2215
|
+
*/
|
|
2216
|
+
declare function useOverlayServiceState(): OverlayContextValue;
|
|
2217
|
+
/**
|
|
2218
|
+
* Run an async operation while showing an overlay. Hides on success or
|
|
2219
|
+
* failure (the overlay is dismissed in `finally`, so failures don't leave a
|
|
2220
|
+
* stuck spinner). Mirrors the try/finally pattern Blazor's
|
|
2221
|
+
* `MainContext.SaveAsync` uses around `_overlayService.ShowOverlayAsync` /
|
|
2222
|
+
* `HideOverlayAsync`.
|
|
2223
|
+
*/
|
|
2224
|
+
declare function runWithOverlayAsync<T>(service: OverlayService | null, options: OverlayOptions, operation: () => Promise<T>): Promise<T>;
|
|
2225
|
+
/**
|
|
2226
|
+
* Hook variant of {@link runWithOverlayAsync}. Returns a stable function bound
|
|
2227
|
+
* to the current overlay service. No-op (just runs the op) when no service is
|
|
2228
|
+
* present.
|
|
2229
|
+
*/
|
|
2230
|
+
declare function useRunWithOverlay(): <T>(options: OverlayOptions, operation: () => Promise<T>) => Promise<T>;
|
|
2231
|
+
|
|
2232
|
+
/**
|
|
2233
|
+
* Standalone hook that registers a `beforeunload` handler while the nearest
|
|
2234
|
+
* `MainContext` / `RecordContext` reports `isDirty`.
|
|
2235
|
+
*
|
|
2236
|
+
* **You usually don't need this.** Both `<MainContext>` and `<RecordContext>`
|
|
2237
|
+
* register the warning automatically when they're the top-most context on
|
|
2238
|
+
* the page (controlled via the `warnOnUnsavedChanges` prop, default `true`).
|
|
2239
|
+
* This hook is exported for cases where you want to wire the warning to a
|
|
2240
|
+
* dirty signal that doesn't come from a MainContext (e.g. a `react-hook-form`
|
|
2241
|
+
* `formState.isDirty`) or from a non-top-most context.
|
|
2242
|
+
*
|
|
2243
|
+
* **What this covers:** browser-level navigation — closing the tab, hitting
|
|
2244
|
+
* the back button, refreshing the page, typing a new URL. The browser's
|
|
2245
|
+
* native prompt fires; the message it shows is browser-controlled (you
|
|
2246
|
+
* cannot customize it — security restriction since Chrome 60+).
|
|
2247
|
+
*
|
|
2248
|
+
* **What this does NOT cover:** in-app route changes via `react-router`'s
|
|
2249
|
+
* `<Link>` or `navigate()`. Those don't trigger `beforeunload` because the
|
|
2250
|
+
* page never actually unloads. For soft-nav blocking, use
|
|
2251
|
+
* {@link useUnsavedChangesGuard} from any router's blocker callback (see
|
|
2252
|
+
* its JSDoc for canonical patterns for react-router, TanStack Router,
|
|
2253
|
+
* Wouter, etc.).
|
|
2254
|
+
*/
|
|
2255
|
+
declare function useUnsavedChangesWarning(): void;
|
|
2256
|
+
/**
|
|
2257
|
+
* Headless controller for building a router blocker that prompts the user
|
|
2258
|
+
* before discarding unsaved edits. Bundles the two primitives any soft-nav
|
|
2259
|
+
* guard needs:
|
|
2260
|
+
*
|
|
2261
|
+
* - `isAnyDirty` — reactive boolean tracking whether any registered
|
|
2262
|
+
* top-most {@link MainContext} / {@link RecordContext} on the page is
|
|
2263
|
+
* dirty (read-through to {@link useIsAnyDirty}).
|
|
2264
|
+
* - `warn` — async function that opens the framework confirmation dialog
|
|
2265
|
+
* and resolves `true` if the user confirmed discarding the edits,
|
|
2266
|
+
* `false` if they cancelled (read-through to
|
|
2267
|
+
* {@link useWarnAboutUnsavedChanges}).
|
|
2268
|
+
*
|
|
2269
|
+
* Router-agnostic — this package has zero router dependencies. Pair it
|
|
2270
|
+
* with whichever router blocker your app uses:
|
|
2271
|
+
*
|
|
2272
|
+
* **react-router-dom (data router):** use the pre-built
|
|
2273
|
+
* `<RouterUnsavedChangesGuard>` from `@powerportalspro/react-fluent` — it
|
|
2274
|
+
* wraps this hook + react-router's `useBlocker`. Or roll your own:
|
|
2275
|
+
*
|
|
2276
|
+
* ```tsx
|
|
2277
|
+
* import { useBlocker } from 'react-router-dom';
|
|
2278
|
+
* function MyGuard() {
|
|
2279
|
+
* const guard = useUnsavedChangesGuard();
|
|
2280
|
+
* const blocker = useBlocker(
|
|
2281
|
+
* ({ currentLocation, nextLocation }) =>
|
|
2282
|
+
* guard.isAnyDirty &&
|
|
2283
|
+
* currentLocation.pathname !== nextLocation.pathname,
|
|
2284
|
+
* );
|
|
2285
|
+
* useEffect(() => {
|
|
2286
|
+
* if (blocker.state !== 'blocked') return;
|
|
2287
|
+
* let cancelled = false;
|
|
2288
|
+
* (async () => {
|
|
2289
|
+
* const proceed = await guard.warn();
|
|
2290
|
+
* if (cancelled) return;
|
|
2291
|
+
* if (proceed) blocker.proceed();
|
|
2292
|
+
* else blocker.reset();
|
|
2293
|
+
* })();
|
|
2294
|
+
* return () => { cancelled = true; };
|
|
2295
|
+
* }, [blocker, guard]);
|
|
2296
|
+
* return null;
|
|
2297
|
+
* }
|
|
2298
|
+
* ```
|
|
2299
|
+
*
|
|
2300
|
+
* **TanStack Router:** use `useBlocker({ blockerFn })`. The blocker fn
|
|
2301
|
+
* returns `true` to allow the navigation, `false` to block. Pump the
|
|
2302
|
+
* `warn` result through inverted:
|
|
2303
|
+
*
|
|
2304
|
+
* ```tsx
|
|
2305
|
+
* import { useBlocker } from '@tanstack/react-router';
|
|
2306
|
+
* function MyGuard() {
|
|
2307
|
+
* const guard = useUnsavedChangesGuard();
|
|
2308
|
+
* useBlocker({
|
|
2309
|
+
* blockerFn: async () => {
|
|
2310
|
+
* if (!guard.isAnyDirty) return true; // allow
|
|
2311
|
+
* return await guard.warn(); // user choice
|
|
2312
|
+
* },
|
|
2313
|
+
* });
|
|
2314
|
+
* return null;
|
|
2315
|
+
* }
|
|
2316
|
+
* ```
|
|
2317
|
+
*
|
|
2318
|
+
* **Wouter:** doesn't ship a blocker API. Hand-roll by intercepting
|
|
2319
|
+
* `<Link>` clicks at the app shell — call `event.preventDefault()` and
|
|
2320
|
+
* route through `guard.warn()` before calling `navigate(to)`.
|
|
2321
|
+
*
|
|
2322
|
+
* **Next.js App Router:** `router.events` is gone in the App Router; the
|
|
2323
|
+
* community pattern is to intercept `<Link>` clicks similar to Wouter.
|
|
2324
|
+
* Same flow with `guard.warn()`.
|
|
2325
|
+
*
|
|
2326
|
+
* Same-pathname navigations (search-param tweaks, `replace: true` to the
|
|
2327
|
+
* current URL) should not be blocked — the user isn't actually leaving
|
|
2328
|
+
* the page. The react-router-dom guard above filters those out via the
|
|
2329
|
+
* `currentLocation.pathname !== nextLocation.pathname` check; the TanStack
|
|
2330
|
+
* blocker fn can do the equivalent using its passed-in location args.
|
|
2331
|
+
*/
|
|
2332
|
+
interface UnsavedChangesGuardController {
|
|
2333
|
+
/**
|
|
2334
|
+
* Reactive — `true` when at least one top-most
|
|
2335
|
+
* {@link MainContext} / {@link RecordContext} registered with the page's
|
|
2336
|
+
* {@link UnsavedChangesRegistry} reports dirty. Subscribes via
|
|
2337
|
+
* {@link useSyncExternalStore} so it re-renders the calling component
|
|
2338
|
+
* exactly when the aggregate flips.
|
|
2339
|
+
*/
|
|
2340
|
+
readonly isAnyDirty: boolean;
|
|
2341
|
+
/**
|
|
2342
|
+
* Opens the framework's "discard your changes?" confirmation dialog
|
|
2343
|
+
* and resolves `true` if the user confirmed proceeding (discarding
|
|
2344
|
+
* pending edits), `false` if they cancelled (staying on the page).
|
|
2345
|
+
* Stable identity across renders when the underlying dialog service
|
|
2346
|
+
* / localizer haven't changed, so it's safe to put in effect deps.
|
|
2347
|
+
*/
|
|
2348
|
+
readonly warn: () => Promise<boolean>;
|
|
2349
|
+
}
|
|
2350
|
+
declare function useUnsavedChangesGuard(): UnsavedChangesGuardController;
|
|
2351
|
+
|
|
2352
|
+
/**
|
|
2353
|
+
* Source of unsaved-changes state on the page. Each topmost
|
|
2354
|
+
* {@link MainContext} / {@link RecordContext} registers one of these so a
|
|
2355
|
+
* page-wide guard (browser beforeunload, router-blocker) can answer "is
|
|
2356
|
+
* anything on this page dirty?" without having to walk every framework
|
|
2357
|
+
* subtree itself.
|
|
2358
|
+
*
|
|
2359
|
+
* Nested contexts don't register — their dirty state already bubbles up
|
|
2360
|
+
* to the topmost MainContext through the existing
|
|
2361
|
+
* {@link MainContextValue.registerDescendant} plumbing, so registering
|
|
2362
|
+
* nested contexts here would double-count.
|
|
2363
|
+
*/
|
|
2364
|
+
interface UnsavedChangesReporter {
|
|
2365
|
+
/**
|
|
2366
|
+
* Snapshot getter — invoked synchronously by the registry on every
|
|
2367
|
+
* subscriber notification. Read-through to the reporter's live
|
|
2368
|
+
* `isDirty` so subscribers see the latest aggregate without needing to
|
|
2369
|
+
* re-register on every state flip.
|
|
2370
|
+
*/
|
|
2371
|
+
readonly isDirty: () => boolean;
|
|
2372
|
+
}
|
|
2373
|
+
/**
|
|
2374
|
+
* App-wide registry of {@link UnsavedChangesReporter}s. Owned by
|
|
2375
|
+
* {@link PowerPortalsProProvider}; consumed by:
|
|
2376
|
+
*
|
|
2377
|
+
* - The auto-registration effect inside topmost
|
|
2378
|
+
* {@link MainContext} / {@link RecordContext}, which adds a reporter on
|
|
2379
|
+
* mount + removes it on unmount.
|
|
2380
|
+
* - `<RouterUnsavedChangesGuard>` (in `@powerportalspro/react-fluent`),
|
|
2381
|
+
* which subscribes via {@link useIsAnyDirty} and gates `useBlocker` on
|
|
2382
|
+
* the aggregate.
|
|
2383
|
+
*
|
|
2384
|
+
* Router-agnostic by design — the registry knows nothing about
|
|
2385
|
+
* react-router, history, or navigation events. That coupling lives in
|
|
2386
|
+
* the optional UI package so consumers using a different router (or
|
|
2387
|
+
* none at all) aren't forced into a dependency they don't need.
|
|
2388
|
+
*/
|
|
2389
|
+
interface UnsavedChangesRegistry {
|
|
2390
|
+
/**
|
|
2391
|
+
* Add a reporter to the registry. Returns the deregister function;
|
|
2392
|
+
* call it in the same effect's cleanup so the reporter doesn't outlive
|
|
2393
|
+
* the component that owns it.
|
|
2394
|
+
*/
|
|
2395
|
+
readonly register: (reporter: UnsavedChangesReporter) => () => void;
|
|
2396
|
+
/**
|
|
2397
|
+
* Subscribe to "the set of dirty reporters might have changed" events.
|
|
2398
|
+
* Fires when reporters are added or removed, AND when a registered
|
|
2399
|
+
* reporter's wrapping {@link MainContext}'s `isDirty` flips (the
|
|
2400
|
+
* reporter's `isDirty` getter closes over the live value).
|
|
2401
|
+
*
|
|
2402
|
+
* Shape compatible with {@link useSyncExternalStore} so the consuming
|
|
2403
|
+
* hook ({@link useIsAnyDirty}) can use it directly.
|
|
2404
|
+
*/
|
|
2405
|
+
readonly subscribe: (listener: () => void) => () => void;
|
|
2406
|
+
/**
|
|
2407
|
+
* Walks every registered reporter and returns `true` as soon as one
|
|
2408
|
+
* reports dirty. Used as `getSnapshot` for {@link useSyncExternalStore}
|
|
2409
|
+
* and called imperatively from the beforeunload handler.
|
|
2410
|
+
*/
|
|
2411
|
+
readonly isAnyDirty: () => boolean;
|
|
2412
|
+
/**
|
|
2413
|
+
* Forces a notification fire — used by topmost MainContexts after
|
|
2414
|
+
* their internal `isDirty` aggregate changes, so subscribers re-read
|
|
2415
|
+
* the registry's `isAnyDirty()` and pick up the new value. Without
|
|
2416
|
+
* this, the registry would only notify on register/deregister and
|
|
2417
|
+
* stay silent through the user's actual edits.
|
|
2418
|
+
*/
|
|
2419
|
+
readonly notifyDirtyChanged: () => void;
|
|
2420
|
+
}
|
|
2421
|
+
interface UnsavedChangesRegistryProviderProps {
|
|
2422
|
+
children: ReactNode;
|
|
2423
|
+
}
|
|
2424
|
+
/**
|
|
2425
|
+
* Mounts an {@link UnsavedChangesRegistry} for its subtree. Auto-mounted
|
|
2426
|
+
* by {@link PowerPortalsProProvider}; consumers don't normally render it
|
|
2427
|
+
* directly.
|
|
2428
|
+
*/
|
|
2429
|
+
declare function UnsavedChangesRegistryProvider({ children }: UnsavedChangesRegistryProviderProps): react_jsx_runtime.JSX.Element;
|
|
2430
|
+
/**
|
|
2431
|
+
* Returns the nearest ancestor {@link UnsavedChangesRegistry}, or `null`
|
|
2432
|
+
* when none is mounted. Framework components ({@link MainContext}) use
|
|
2433
|
+
* this to opt into reporting their dirty state; guard components
|
|
2434
|
+
* ({@code RouterUnsavedChangesGuard}) use {@link useIsAnyDirty} which
|
|
2435
|
+
* already null-checks internally.
|
|
2436
|
+
*/
|
|
2437
|
+
declare function useUnsavedChangesRegistry(): UnsavedChangesRegistry | null;
|
|
2438
|
+
/**
|
|
2439
|
+
* Reactive read of "is anything on this page dirty?" — wraps
|
|
2440
|
+
* {@link useSyncExternalStore} against the surrounding
|
|
2441
|
+
* {@link UnsavedChangesRegistry}. Returns `false` when no registry is
|
|
2442
|
+
* mounted (e.g. a tree that hasn't been wrapped in
|
|
2443
|
+
* {@link PowerPortalsProProvider} yet) so guard components can default
|
|
2444
|
+
* to "don't block" rather than throw.
|
|
2445
|
+
*
|
|
2446
|
+
* Use this from any component that wants to react to page-wide unsaved
|
|
2447
|
+
* state — typically the router-blocker guard, but also custom dirty
|
|
2448
|
+
* indicators in app chrome.
|
|
2449
|
+
*/
|
|
2450
|
+
declare function useIsAnyDirty(): boolean;
|
|
2451
|
+
|
|
2452
|
+
/**
|
|
2453
|
+
* A single column's validator. Returns the list of error messages produced by
|
|
2454
|
+
* the current value (empty list = valid). Sync or async — async validators
|
|
2455
|
+
* run sequentially during {@link ValidationContextValue.validateAll}.
|
|
2456
|
+
*
|
|
2457
|
+
* Implementations must read whatever state they need (current value, required
|
|
2458
|
+
* flag, metadata) at call time — typically from refs — because the validator
|
|
2459
|
+
* is invoked from outside the registering component's render cycle.
|
|
2460
|
+
*/
|
|
2461
|
+
type ColumnValidator = () => string[] | Promise<string[]>;
|
|
2462
|
+
/**
|
|
2463
|
+
* Per-form-scope validation registry. One instance per logical form scope
|
|
2464
|
+
* (auto-mounted by `<RecordContext>`); edit components below it register
|
|
2465
|
+
* their validators and read back any errors.
|
|
2466
|
+
*
|
|
2467
|
+
* Reads happen via {@link useColumnValidationErrors} — that's how edit
|
|
2468
|
+
* components subscribe to "do I have errors right now?" without re-rendering
|
|
2469
|
+
* on every other column's transition.
|
|
2470
|
+
*/
|
|
2471
|
+
interface ValidationContextValue {
|
|
2472
|
+
/**
|
|
2473
|
+
* Register a column's validator. Returns the unregister function. The
|
|
2474
|
+
* recommended idiom is a `useEffect` whose cleanup unregisters; on column
|
|
2475
|
+
* rename or unmount the registration is replaced cleanly.
|
|
2476
|
+
*/
|
|
2477
|
+
readonly register: (columnName: string, validator: ColumnValidator) => () => void;
|
|
2478
|
+
/** Run a single column's validator and update its error state. */
|
|
2479
|
+
readonly validateColumn: (columnName: string) => Promise<void>;
|
|
2480
|
+
/**
|
|
2481
|
+
* Run every registered validator and update all error states. Returns
|
|
2482
|
+
* `true` when nothing failed. Used by save flows (e.g. `SaveContextButton`)
|
|
2483
|
+
* to gate a save on overall validity.
|
|
2484
|
+
*/
|
|
2485
|
+
readonly validateAll: () => Promise<boolean>;
|
|
2486
|
+
/** Drop all errors. Useful when the underlying record reloads. */
|
|
2487
|
+
readonly clearErrors: () => void;
|
|
2488
|
+
/**
|
|
2489
|
+
* Aggregated validity — `false` if any registered column currently has
|
|
2490
|
+
* errors. Reactive; consumers subscribed via `useValidationContext` re-render
|
|
2491
|
+
* on transitions.
|
|
2492
|
+
*/
|
|
2493
|
+
readonly isValid: boolean;
|
|
2494
|
+
/**
|
|
2495
|
+
* Current errors keyed by column logical name. Re-rendered subscribers see
|
|
2496
|
+
* the latest snapshot. Use {@link useColumnValidationErrors} for a
|
|
2497
|
+
* column-scoped read that doesn't churn on unrelated transitions.
|
|
2498
|
+
*/
|
|
2499
|
+
readonly errors: ReadonlyMap<string, readonly string[]>;
|
|
2500
|
+
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Returns the nearest {@link ValidationContextValue}, or `null` when no
|
|
2503
|
+
* provider is mounted. Validation is optional — a form rendered outside a
|
|
2504
|
+
* `<RecordContext>` (or anywhere without an explicit `<ValidationProvider>`)
|
|
2505
|
+
* just doesn't run validators.
|
|
2506
|
+
*/
|
|
2507
|
+
declare function useValidationContext(): ValidationContextValue | null;
|
|
2508
|
+
/**
|
|
2509
|
+
* Returns the current error messages for a single column (empty array if
|
|
2510
|
+
* valid; `undefined` if no validation provider is mounted). Edit components
|
|
2511
|
+
* call this to drive their inline error display.
|
|
2512
|
+
*/
|
|
2513
|
+
declare function useColumnValidationErrors(columnName: string): readonly string[] | undefined;
|
|
2514
|
+
/**
|
|
2515
|
+
* Mounts a fresh validation scope for descendants. `<RecordContext>`
|
|
2516
|
+
* auto-mounts one — manual use is for edge cases (e.g. a non-record form).
|
|
2517
|
+
*/
|
|
2518
|
+
declare function ValidationProvider({ children }: {
|
|
2519
|
+
children: ReactNode;
|
|
2520
|
+
}): react_jsx_runtime.JSX.Element;
|
|
2521
|
+
|
|
2522
|
+
declare const VERSION = "0.1.0";
|
|
2523
|
+
|
|
2524
|
+
export { ASP_NET_CULTURE_COOKIE, type AuthState, AuthStatus, AuthorizedView, type AuthorizedViewProps, type ColumnValidator, type ConfirmationDialogOptions, type CreateLocalizerOptions, DEFAULT_URL_LOCALE_PATTERN, DefaultLocalizerProvider, type DefaultLocalizerProviderProps, type DialogOptions, type DialogResult, type DialogService, DialogServiceProvider, type EnvironmentFileSettingsCache, InMemoryEnvironmentFileSettingsCache, InMemoryTableMetadataCache, InMemoryViewMetadataCache, InMemoryViewsForTableCache, LocaleProvider, type LocaleProviderProps, type LocaleState, type LocalizationPrefetcher, type Localizer, LocalizerProvider, LookupRecordContext, type LookupRecordContextProps, MainContext, type MainContextDescendant, type MainContextProps, MainContextProvider, type MainContextValue, type OverlayInstance, type OverlayOptions, type OverlayService, OverlayServiceProvider, PowerPortalsProContext, type PowerPortalsProContextValue, PowerPortalsProProvider, type PowerPortalsProProviderProps, type QueryResult, QueryStatus, RecordContext, type RecordContextProps, type RecordContextValue, type TableMetadataCache, type UnsavedChangesGuardController, type UnsavedChangesRegistry, UnsavedChangesRegistryProvider, type UnsavedChangesRegistryProviderProps, type UnsavedChangesReporter, type UseAuthResult, type UseEnvironmentFileSettingsOptions, type UseFetchRecordsOptions, type UseGridDataOptions, type UseRecordOptions, type UseSupportedLocalesOptions, type UseTableMetadataOptions, type UseTablePermissionsOptions, type UseViewDataSourceOptions, type UseViewMetadataOptions, type UseViewsForTableOptions, VERSION, type ValidationContextValue, ValidationProvider, type ViewDataSource, type ViewDataSourceHighlight, type ViewMetadataCache, type ViewsForTableCache, createLocalizer, detectLocale, flattenStrings, getAspNetCultureCookie, getLocaleFromUrlPath, interpolate, resolveLocalizationUrl, runWithOverlayAsync, setAspNetCultureCookie, useAuth, useColumnMetadata, useColumnValidationErrors, useDialogService, useEnvironmentFileSettings, useFetchRecords, useGridData, useIsAnyDirty, useLocale, useLocalization, useLocalizationLoaded, useLocalizationPrefetcher, useLocalizer, useMainContext, useOverlayForTarget, useOverlayService, useOverlayServiceState, usePowerPortalsPro, usePrefixedT, useQueryParam, useRecord, useRecordContext, useRecordContextOptional, useRunWithOverlay, useSupportedLocales, useT, useTableMetadata, useTablePermissions, useUnsavedChangesGuard, useUnsavedChangesRegistry, useUnsavedChangesWarning, useValidationContext, useViewDataSource, useViewMetadata, useViewsForTable, useWarnAboutUnsavedChanges, warnAboutUnsavedChangesAsync };
|