@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.
@@ -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 };