@declarion/react 0.26.4 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/{ActionRunner-B2e3Igpd.js → ActionRunner-DtUDeH4F.js} +595 -579
- package/dist-lib/{DraggableChildrenTable-BUUUPzI3.js → DraggableChildrenTable-D4WoV6ck.js} +5 -5
- package/dist-lib/{Grid-Bjq2IPUx.js → Grid-CsK_TuK_.js} +2 -2
- package/dist-lib/{MarkdownRenderer-Be0nUKB1.js → MarkdownRenderer-CwlNQju5.js} +1 -1
- package/dist-lib/components/layout/Layout.d.ts +4 -0
- package/dist-lib/components/shared/PageHeader.d.ts +1 -1
- package/dist-lib/declarion-react.css +1 -1
- package/dist-lib/embed/EmbedHistoryTracker.d.ts +1 -0
- package/dist-lib/embed/embed-history.d.ts +19 -0
- package/dist-lib/embed/index.d.ts +3 -1
- package/dist-lib/embed/useEmbedNavigate.d.ts +12 -0
- package/dist-lib/hooks/useActionData.d.ts +61 -0
- package/dist-lib/hooks/useCursorEntityData.d.ts +13 -0
- package/dist-lib/hooks/useEntityData.d.ts +8 -0
- package/dist-lib/hooks/useEntityItem.d.ts +8 -0
- package/dist-lib/hooks/usePageState.d.ts +32 -0
- package/dist-lib/hooks/usePageStateRuntime.d.ts +27 -0
- package/dist-lib/hooks/usePageStateTtl.d.ts +11 -0
- package/dist-lib/hooks/useRegisterScreenTab.d.ts +11 -0
- package/dist-lib/hooks/useScreenBack.d.ts +21 -0
- package/dist-lib/hooks/useScreenTab.d.ts +10 -0
- package/dist-lib/hooks/useTabNavigate.d.ts +9 -0
- package/dist-lib/hooks/useTabSearchMirror.d.ts +1 -0
- package/dist-lib/index.d.ts +19 -4
- package/dist-lib/index.js +4696 -4028
- package/dist-lib/lib/canonical-url.d.ts +27 -0
- package/dist-lib/lib/screen-back.d.ts +72 -0
- package/dist-lib/lib/scroll-restoration.d.ts +90 -0
- package/dist-lib/stores/page-state.d.ts +61 -0
- package/dist-lib/stores/tabs.d.ts +34 -0
- package/dist-lib/testing.js +7 -7
- package/dist-lib/{value-D7fe0TEL.js → value-CNUbJNK-.js} +97 -97
- package/package.json +13 -13
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The minimal location shape canonicalUrl needs (a router `Location` satisfies
|
|
2
|
+
* it; tests can pass a bare object). */
|
|
3
|
+
export interface CanonicalLocation {
|
|
4
|
+
pathname: string;
|
|
5
|
+
search?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Build the canonical URL key for a location: normalized pathname + the
|
|
9
|
+
* view-defining query, sorted by key, with the embed transport params removed.
|
|
10
|
+
*
|
|
11
|
+
* Sorting makes `?a=1&b=2` and `?b=2&a=1` one key. Removing `EMBED_PARAM_KEYS`
|
|
12
|
+
* makes the same screen one key whether or not the embed params ride along, so
|
|
13
|
+
* a retained page is shared between the embedded and non-embedded view of the
|
|
14
|
+
* same URL. Default query values are NOT collapsed (which params have a default
|
|
15
|
+
* is screen-specific and collapsing the wrong one would silently merge or fork
|
|
16
|
+
* pages); two URLs that differ only by an explicit-vs-implicit default are
|
|
17
|
+
* treated as distinct, which is safe (at worst slightly less sharing).
|
|
18
|
+
*/
|
|
19
|
+
export declare function canonicalUrl(location: CanonicalLocation): string;
|
|
20
|
+
/**
|
|
21
|
+
* The SCREEN-scoped key: the normalized pathname alone, ignoring the query.
|
|
22
|
+
* Coarser than `canonicalUrl` - it does NOT fork when filters/sort/columns
|
|
23
|
+
* change. Used for deep state that must persist ACROSS in-screen query changes
|
|
24
|
+
* (row selection, pruned to the visible result set) rather than resetting per
|
|
25
|
+
* filtered view. Two filter views of the same screen share one screen key.
|
|
26
|
+
*/
|
|
27
|
+
export declare function canonicalPath(location: CanonicalLocation): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ReturnTarget, type TabItem } from "../stores/tabs";
|
|
2
|
+
/** Drop a leading `?` so a search string matches the canonical `TabItem.search`
|
|
3
|
+
* / `ReturnTarget.search` form (stored WITHOUT `?`). Empty in -> empty out. */
|
|
4
|
+
export declare function stripLeadingQuestion(search: string | undefined | null): string;
|
|
5
|
+
/** Re-attach the leading `?` for the router/`navigate` form, or `undefined`
|
|
6
|
+
* when there is no query. The inverse of `stripLeadingQuestion`. */
|
|
7
|
+
export declare function toRouterSearch(search: string | undefined): string | undefined;
|
|
8
|
+
/** The current location at navigate time (canonical: `search` may carry `?`,
|
|
9
|
+
* `hash` carries `#`). Sourced from `window.location` at the click. */
|
|
10
|
+
export interface CaptureLocation {
|
|
11
|
+
pathname: string;
|
|
12
|
+
search?: string;
|
|
13
|
+
hash?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the return target to stamp on the DESTINATION tab when navigating from
|
|
17
|
+
* `current` to `destinationPathname`. Returns `undefined` (no capture) when:
|
|
18
|
+
* - the current path is not a same-origin in-app route,
|
|
19
|
+
* - the current path is an excluded (auth) route,
|
|
20
|
+
* - the destination shares the current PATHNAME (self guard, Invariant 7) -
|
|
21
|
+
* a same-screen, different-query link must not self-stamp a Back.
|
|
22
|
+
*
|
|
23
|
+
* `originTab` supplies metadata (id/title/icon/screen) for switching to or
|
|
24
|
+
* reopening the origin; only the current location drives pathname/search/hash.
|
|
25
|
+
*/
|
|
26
|
+
export declare function captureReturnTarget(current: CaptureLocation, destinationPathname: string, originTab?: Pick<TabItem, "id" | "title" | "icon" | "screen">): ReturnTarget | undefined;
|
|
27
|
+
/** The Back visibility intent, derived from a tab's `returnTo` alone (no tab
|
|
28
|
+
* list needed). `hasOriginBack` drives an origin Back affordance;
|
|
29
|
+
* `allowFallbackBack` permits a screen's static/parent fallback (false ONLY
|
|
30
|
+
* for a menu-cleared `null`). */
|
|
31
|
+
export interface BackVisibility {
|
|
32
|
+
hasOriginBack: boolean;
|
|
33
|
+
allowFallbackBack: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Interpret the tri-state `returnTo` for Back VISIBILITY:
|
|
37
|
+
* - embed enabled -> never an origin Back; fallback allowed (embed has no tabs),
|
|
38
|
+
* - `null` (menu-cleared) -> no origin Back AND no fallback (a menu-opened
|
|
39
|
+
* screen has nowhere to return),
|
|
40
|
+
* - a valid origin object -> origin Back,
|
|
41
|
+
* - `undefined` / invalid object (deep-link) -> no origin Back, fallback allowed.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveBackVisibility(returnTo: ReturnTarget | null | undefined, embedEnabled: boolean): BackVisibility;
|
|
44
|
+
/** The resolved Back destination. `live` = the origin tab is still open (switch
|
|
45
|
+
* to it); `reopen` = the origin tab was closed (reopen it). Both navigate to
|
|
46
|
+
* the CAPTURED snapshot (pathname+search+hash). */
|
|
47
|
+
export interface ResolvedBack {
|
|
48
|
+
kind: "live" | "reopen";
|
|
49
|
+
tabId?: string;
|
|
50
|
+
pathname: string;
|
|
51
|
+
search?: string;
|
|
52
|
+
hash?: string;
|
|
53
|
+
title?: string;
|
|
54
|
+
icon?: string;
|
|
55
|
+
screen?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the Back DESTINATION from a tab's `returnTo` and the live tab list.
|
|
59
|
+
* Returns `null` when there is no valid origin (the caller then uses its
|
|
60
|
+
* fallback). The origin tab (live by `tabId`, else by `pathname`) decides only
|
|
61
|
+
* SWITCH vs REOPEN; the destination URL is always the CAPTURED snapshot. Embed
|
|
62
|
+
* yields `null` (Back is fallback-only in embed; no tab ops).
|
|
63
|
+
*
|
|
64
|
+
* Divergence from the plan's Invariant 5 (reasoned): the plan preferred the
|
|
65
|
+
* live tab's CURRENT search over the captured one, premised on every origin
|
|
66
|
+
* screen mirroring its URL into the tab store. That premise is false for custom
|
|
67
|
+
* screens (Compare, dashboards) with no per-page mirror, so preferring live
|
|
68
|
+
* search loses URL-encoded state (Business Case 3). The captured `returnTo` is
|
|
69
|
+
* exact at drill time ("where I came from"), so it is the single source of
|
|
70
|
+
* truth for the destination URL - no per-screen search mirror needed.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveBackTarget(returnTo: ReturnTarget | null | undefined, tabs: TabItem[], embedEnabled: boolean): ResolvedBack | null;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** The default deep-state bag key under which a screen's scroll anchor is
|
|
2
|
+
* stored. A screen with one scroll container uses this; a screen with several
|
|
3
|
+
* passes distinct keys. */
|
|
4
|
+
export declare const SCROLL_STATE_KEY = "scroll";
|
|
5
|
+
/**
|
|
6
|
+
* A saved scroll position. `key` is the stable key of the row at the top of the
|
|
7
|
+
* viewport (empty when the container had no rows / is non-virtualized); `delta`
|
|
8
|
+
* is the sub-row pixel offset; `offset` is the raw scroll offset kept as a
|
|
9
|
+
* last-resort fallback when the anchor row is gone.
|
|
10
|
+
*/
|
|
11
|
+
export interface ScrollAnchor {
|
|
12
|
+
key: string;
|
|
13
|
+
delta: number;
|
|
14
|
+
offset: number;
|
|
15
|
+
}
|
|
16
|
+
/** The minimal virtualizer surface the seam needs. A
|
|
17
|
+
* `Virtualizer<HTMLElement, Element>` is structurally assignable; a test can
|
|
18
|
+
* pass a fake. */
|
|
19
|
+
export interface ScrollableVirtualizer {
|
|
20
|
+
scrollOffset: number | null;
|
|
21
|
+
getVirtualItemForOffset: (offset: number) => {
|
|
22
|
+
index: number;
|
|
23
|
+
start: number;
|
|
24
|
+
} | undefined;
|
|
25
|
+
getOffsetForIndex: (index: number, align?: "start") => readonly [number, unknown] | undefined;
|
|
26
|
+
getTotalSize: () => number;
|
|
27
|
+
scrollToOffset: (offset: number, opts?: {
|
|
28
|
+
align?: "start";
|
|
29
|
+
behavior?: "auto";
|
|
30
|
+
}) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Capture the scroll anchor at `scrollOffset`. `topItem` is the virtual item at
|
|
34
|
+
* the top of the viewport (`getVirtualItemForOffset(scrollOffset)`); `rowKeys`
|
|
35
|
+
* maps its index to a stable row key. Returns an anchor with an empty `key`
|
|
36
|
+
* (raw-offset-only) when there is no top item or no key for it. Pure.
|
|
37
|
+
*/
|
|
38
|
+
export declare function captureScrollAnchor(scrollOffset: number, topItem: {
|
|
39
|
+
index: number;
|
|
40
|
+
start: number;
|
|
41
|
+
} | undefined, rowKeys: string[]): ScrollAnchor;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the scroll offset to restore to, from a saved `anchor`, the CURRENT
|
|
44
|
+
* `rowKeys`, an `offsetForIndex` accessor (the virtualizer's measured start of a
|
|
45
|
+
* row), and the current `totalSize` (for clamping). Pure and testable.
|
|
46
|
+
*
|
|
47
|
+
* Fallback chain (Decision #17): anchor row found -> its measured start + delta;
|
|
48
|
+
* anchor row gone (deleted / filtered out) -> clamped raw offset; unusable
|
|
49
|
+
* offset -> top (0). Returns null when there is nothing saved.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveScrollOffset(anchor: ScrollAnchor | undefined, rowKeys: string[], offsetForIndex: (index: number) => number | undefined, totalSize: number): number | null;
|
|
52
|
+
/** Read the saved raw scroll offset for the current page ONCE, for seeding a
|
|
53
|
+
* virtualizer's `initialOffset` (read-once at init). Returns 0 when none /
|
|
54
|
+
* retention off. The full anchor re-restore happens after rows measure. */
|
|
55
|
+
export declare function useSavedScrollOffset(stateKey?: string): number;
|
|
56
|
+
/**
|
|
57
|
+
* Bind scroll save + anchor-row restore for a VIRTUALIZED list to the retained
|
|
58
|
+
* page keyed by the live canonical URL.
|
|
59
|
+
*
|
|
60
|
+
* - Restore: once per pageKey, after `rowKeys` first become non-empty, scrolls
|
|
61
|
+
* to `resolveScrollOffset(...)` (anchor row -> clamped raw -> top).
|
|
62
|
+
* - Save: a throttled scroll listener captures the top-of-viewport anchor and
|
|
63
|
+
* writes it imperatively (no re-render); the final position flushes on
|
|
64
|
+
* unmount / pageKey change to the DEPARTING view's own URL (tracked via a
|
|
65
|
+
* per-render ref).
|
|
66
|
+
*
|
|
67
|
+
* `enabled` (default true) lets a caller gate the seam; when false it is inert.
|
|
68
|
+
*/
|
|
69
|
+
export declare function useVirtualScrollRestoration(opts: {
|
|
70
|
+
virtualizer: ScrollableVirtualizer | null;
|
|
71
|
+
scrollElementRef: {
|
|
72
|
+
current: HTMLElement | null;
|
|
73
|
+
};
|
|
74
|
+
rowKeys: string[];
|
|
75
|
+
stateKey?: string;
|
|
76
|
+
enabled?: boolean;
|
|
77
|
+
}): void;
|
|
78
|
+
/**
|
|
79
|
+
* Scroll save + restore for a PLAIN (non-virtualized) scroll container - the
|
|
80
|
+
* detail page. Stores the raw `scrollTop` (no anchor rows to key by) under the
|
|
81
|
+
* live canonical URL; restores after first layout; saves throttled-on-scroll +
|
|
82
|
+
* on unmount flush.
|
|
83
|
+
*/
|
|
84
|
+
export declare function usePlainScrollRestoration(opts: {
|
|
85
|
+
scrollElementRef: {
|
|
86
|
+
current: HTMLElement | null;
|
|
87
|
+
};
|
|
88
|
+
stateKey?: string;
|
|
89
|
+
enabled?: boolean;
|
|
90
|
+
}): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { QueryClient, QueryKey } from "@tanstack/react-query";
|
|
2
|
+
/** Default retention window before the `ui_page_state_ttl_ms` parameter
|
|
3
|
+
* resolves (and the fallback when it is absent). 30 minutes - long enough to
|
|
4
|
+
* switch among tabs all day instantly, bounded enough to keep memory sane.
|
|
5
|
+
* Mirrors the YAML default in core_parameters.yaml; both must agree. */
|
|
6
|
+
export declare const DEFAULT_PAGE_STATE_TTL_MS: number;
|
|
7
|
+
/** Hard upper bound on retained-page entries. Bounds total memory and is the
|
|
8
|
+
* ONLY bound in embed mode, which has no tab-close signal at all. Generous
|
|
9
|
+
* relative to `ui_tabs_max_open` (a user rarely revisits more than this many
|
|
10
|
+
* distinct URLs within one TTL window); the least-recently-active entry is
|
|
11
|
+
* evicted first when the cap is exceeded. */
|
|
12
|
+
export declare const PAGE_STATE_MAX_ENTRIES = 64;
|
|
13
|
+
/** One retained page's deep state. `bag` holds small JSON-serializable deep
|
|
14
|
+
* values; `queryKeys` references the screen's PRIMARY React Query reads so an
|
|
15
|
+
* eviction can release the cached data alongside the deep state. */
|
|
16
|
+
export interface PageStateEntry {
|
|
17
|
+
bag: Record<string, unknown>;
|
|
18
|
+
lastActiveAt: number;
|
|
19
|
+
queryKeys: QueryKey[];
|
|
20
|
+
}
|
|
21
|
+
interface PageStateStore {
|
|
22
|
+
/** pageKey (canonical URL) -> retained-page deep state. */
|
|
23
|
+
entries: Record<string, PageStateEntry>;
|
|
24
|
+
/** Active retention window. `<= 0` disables retention. Set by the runtime
|
|
25
|
+
* from the resolved `ui_page_state_ttl_ms`; defaults to the 30-min fallback
|
|
26
|
+
* so retention works before the parameter loads. */
|
|
27
|
+
ttlMs: number;
|
|
28
|
+
/** Set the retention window. `<= 0` disables retention and immediately clears
|
|
29
|
+
* every entry (with linked `removeQueries`), so a runtime toggle to "off"
|
|
30
|
+
* frees memory at once. */
|
|
31
|
+
setTtl: (ttlMs: number) => void;
|
|
32
|
+
/** Write a deep value under `pageKey`, refreshing `lastActiveAt` and
|
|
33
|
+
* enforcing the LRU cap. No-op when retention is disabled. */
|
|
34
|
+
set: (pageKey: string, key: string, value: unknown) => void;
|
|
35
|
+
/** Read a deep value, or `undefined` when absent / reaped / retention off. */
|
|
36
|
+
read: (pageKey: string, key: string) => unknown;
|
|
37
|
+
/** Refresh `lastActiveAt` for a still-mounted view so it is not reaped while
|
|
38
|
+
* active. No-op when the entry is absent or retention is disabled. */
|
|
39
|
+
touch: (pageKey: string) => void;
|
|
40
|
+
/** Track one of the screen's PRIMARY React Query keys so eviction releases
|
|
41
|
+
* the cached data. Deduplicated. No-op when retention is disabled. */
|
|
42
|
+
registerQueryKey: (pageKey: string, queryKey: QueryKey) => void;
|
|
43
|
+
/** Explicitly evict one retained page NOW: drop its deep state AND
|
|
44
|
+
* `removeQueries` its tracked keys. Idempotent; works even when retention is
|
|
45
|
+
* disabled (used by per-screen kill policies, e.g. a deleted detail record). */
|
|
46
|
+
kill: (pageKey: string) => void;
|
|
47
|
+
/** Reap every entry stale past the active TTL (`now - lastActiveAt > ttlMs`),
|
|
48
|
+
* releasing data + deep state together. When retention is disabled, evicts
|
|
49
|
+
* all. `now` is injected for testability. */
|
|
50
|
+
sweep: (now: number) => void;
|
|
51
|
+
/** Drop EVERY retained page NOW (releasing all tracked queries) without
|
|
52
|
+
* changing the TTL - the auth-scope reset. Called on logout so a different
|
|
53
|
+
* user signing in within the same JS session cannot restore the prior user's
|
|
54
|
+
* deep state (selection, scroll, custom-screen values). */
|
|
55
|
+
clear: () => void;
|
|
56
|
+
}
|
|
57
|
+
/** Register the app's QueryClient so retained-page eviction can release the
|
|
58
|
+
* tracked entity-read queries. Called once at app mount. */
|
|
59
|
+
export declare function setPageStateQueryClient(client: QueryClient | null): void;
|
|
60
|
+
export declare const usePageStateStore: import("zustand").UseBoundStore<import("zustand").StoreApi<PageStateStore>>;
|
|
61
|
+
export {};
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
export type TabMode = "view" | "edit" | "create";
|
|
2
2
|
export type TabCloseScope = "current" | "others" | "right" | "all";
|
|
3
|
+
/**
|
|
4
|
+
* Origin-aware Back return target. The CANONICAL stored shape: `pathname` is an
|
|
5
|
+
* in-app path (no origin, starts `/` not `//`), `search` is the query string
|
|
6
|
+
* WITHOUT a leading `?` (matches `TabItem.search`), `hash` carries its leading
|
|
7
|
+
* `#`. Converted to the `?`-prefixed router form only at the navigate boundary.
|
|
8
|
+
*
|
|
9
|
+
* `hash` lives here but NOT on `TabItem` (which has no `hash` field): a closed
|
|
10
|
+
* origin tab with a hash is reopened via pathname+search, and the hash is
|
|
11
|
+
* applied only in the `navigate()` call. `tabId`/`title`/`icon`/`screen` are
|
|
12
|
+
* hints for switching to (or reopening) the origin tab. Never stores secrets,
|
|
13
|
+
* external URLs, or row data. See docs/plans/2026-06-29-origin-aware-back-navigation.md.
|
|
14
|
+
*/
|
|
15
|
+
export interface ReturnTarget {
|
|
16
|
+
pathname: string;
|
|
17
|
+
search?: string;
|
|
18
|
+
hash?: string;
|
|
19
|
+
tabId?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
icon?: string;
|
|
22
|
+
screen?: string;
|
|
23
|
+
}
|
|
3
24
|
export interface TabItem {
|
|
4
25
|
id: string;
|
|
5
26
|
pathname: string;
|
|
@@ -14,6 +35,7 @@ export interface TabItem {
|
|
|
14
35
|
record?: Record<string, unknown>;
|
|
15
36
|
mode?: TabMode;
|
|
16
37
|
dirty?: boolean;
|
|
38
|
+
returnTo?: ReturnTarget | null;
|
|
17
39
|
}
|
|
18
40
|
interface TabState {
|
|
19
41
|
tabs: TabItem[];
|
|
@@ -26,12 +48,24 @@ interface TabState {
|
|
|
26
48
|
findTabByPathname: (pathname: string) => TabItem | undefined;
|
|
27
49
|
updateTabTitle: (id: string, title: string) => void;
|
|
28
50
|
updateTabSearch: (id: string, search: string) => void;
|
|
51
|
+
setReturnTarget: (id: string, rt: ReturnTarget | null) => void;
|
|
52
|
+
commitTabNavigation: (id: string, patch: {
|
|
53
|
+
search: string;
|
|
54
|
+
returnTo?: ReturnTarget | null;
|
|
55
|
+
}) => void;
|
|
29
56
|
setDirty: (id: string, dirty: boolean) => void;
|
|
30
57
|
setMode: (id: string, mode: TabMode) => void;
|
|
31
58
|
updateTab: (id: string, patch: Partial<TabItem>) => void;
|
|
32
59
|
setScope: (tenantId: string | null, userId: string | null) => void;
|
|
33
60
|
hydrate: () => void;
|
|
34
61
|
}
|
|
62
|
+
/** True for an auth/chrome route that must never create a tab or be captured
|
|
63
|
+
* as a Back origin. Shared by `openTab` and the origin-capture path. */
|
|
64
|
+
export declare function isExcludedRoute(pathname: string): boolean;
|
|
65
|
+
/** A same-origin in-app path: starts `/` but not `//` (the latter is a
|
|
66
|
+
* protocol-relative external URL). The single guard reused on capture, on
|
|
67
|
+
* snapshot load (localStorage is hostile), and in the tenant-landing peek. */
|
|
68
|
+
export declare function isSameOriginPath(pathname: unknown): pathname is string;
|
|
35
69
|
export declare function tabIdsForCloseScope(tabs: TabItem[], activeTabId: string | null, scope: TabCloseScope, targetId?: string): string[];
|
|
36
70
|
export declare function peekTenantLandingPath(tenantId: string, userId: string): string | null;
|
|
37
71
|
export declare const useTabStore: import("zustand").UseBoundStore<import("zustand").StoreApi<TabState>>;
|
package/dist-lib/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as e, X as t, Y as n, Z as r, mt as i, t as a } from "./ActionRunner-DtUDeH4F.js";
|
|
2
2
|
import { useState as o } from "react";
|
|
3
3
|
import { MemoryRouter as s } from "react-router-dom";
|
|
4
4
|
import { QueryClient as c, QueryClientProvider as l } from "@tanstack/react-query";
|
|
@@ -87,7 +87,7 @@ function x(e) {
|
|
|
87
87
|
}
|
|
88
88
|
function S(i) {
|
|
89
89
|
let a = i.entityData ?? {};
|
|
90
|
-
|
|
90
|
+
r((e, t) => {
|
|
91
91
|
let n = a[e], r = x(n), i = r.meta?.per_page ?? 50, o = r.meta?.total ?? r.rows.length;
|
|
92
92
|
return t.count === "only" ? {
|
|
93
93
|
data: [],
|
|
@@ -109,19 +109,19 @@ function S(i) {
|
|
|
109
109
|
};
|
|
110
110
|
});
|
|
111
111
|
let o = i.entityItems ?? {};
|
|
112
|
-
|
|
113
|
-
let r = typeof n == "string" ? n :
|
|
114
|
-
return { data: o[
|
|
112
|
+
t((t, n) => {
|
|
113
|
+
let r = typeof n == "string" ? n : e(n);
|
|
114
|
+
return { data: o[t]?.[r] };
|
|
115
115
|
});
|
|
116
116
|
let s = i.actionResults ?? {};
|
|
117
|
-
|
|
117
|
+
n(async (e, t) => {
|
|
118
118
|
if (!(e in s)) throw Error(`renderWithDeclarion: no action result registered for "${e}" (seed it via actionResults)`);
|
|
119
119
|
let n = s[e];
|
|
120
120
|
return y(typeof n == "function" ? await n(t) : n);
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
function C() {
|
|
124
|
-
|
|
124
|
+
r(null), t(null), n(null), i.setState({
|
|
125
125
|
user: null,
|
|
126
126
|
isAuthenticated: !1,
|
|
127
127
|
activeTenant: null,
|
|
@@ -1,68 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ft as e, It as t, zt as n } from "./ActionRunner-DtUDeH4F.js";
|
|
2
2
|
import { useCallback as r, useEffect as i, useRef as a, useState as o } from "react";
|
|
3
|
-
//#region src/embed/file-src.ts
|
|
4
|
-
var s = "/api/files";
|
|
5
|
-
function c(e) {
|
|
6
|
-
if (typeof e != "object" || !e) return !1;
|
|
7
|
-
let t = e;
|
|
8
|
-
return typeof t.url == "string" && typeof t.proxy == "boolean";
|
|
9
|
-
}
|
|
10
|
-
function l(e, t) {
|
|
11
|
-
let n = encodeURIComponent(e);
|
|
12
|
-
return typeof t == "object" ? `${s}/${n}/derived/${encodeURIComponent(t.derivation)}/url` : `${s}/${n}/url`;
|
|
13
|
-
}
|
|
14
|
-
var u = class extends Error {
|
|
15
|
-
fileId;
|
|
16
|
-
key;
|
|
17
|
-
constructor(e, t) {
|
|
18
|
-
super(`derivation "${t}" of file ${e} is still generating`), this.fileId = e, this.key = t, this.name = "DerivationPendingError";
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
function d(e, t) {
|
|
22
|
-
return {
|
|
23
|
-
method: "GET",
|
|
24
|
-
credentials: "omit",
|
|
25
|
-
headers: { Authorization: `Bearer ${e}` },
|
|
26
|
-
signal: t
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
function f(e) {
|
|
30
|
-
return `${e}${e.includes("?") ? "&" : "?"}disposition=attachment`;
|
|
31
|
-
}
|
|
32
|
-
async function p(e, r) {
|
|
33
|
-
let i = await t(), a = await fetch(e, d(i, r));
|
|
34
|
-
if (a.status !== 401) return a;
|
|
35
|
-
n();
|
|
36
|
-
let o = await t();
|
|
37
|
-
return fetch(e, d(o, r));
|
|
38
|
-
}
|
|
39
|
-
async function m(t, n, r) {
|
|
40
|
-
if (!e()) throw Error("resolveEmbedFileSrc called outside embed token mode");
|
|
41
|
-
let i = await p(l(t, n), r);
|
|
42
|
-
if (i.status === 202 && typeof n == "object") throw new u(t, n.derivation);
|
|
43
|
-
if (!i.ok) throw Error(`embed file url request failed: ${i.status} ${i.statusText}`);
|
|
44
|
-
let a = await i.json();
|
|
45
|
-
if (!c(a)) throw Error("embed file url response is malformed");
|
|
46
|
-
if (!a.proxy) return {
|
|
47
|
-
url: a.url,
|
|
48
|
-
kind: "direct"
|
|
49
|
-
};
|
|
50
|
-
if (!a.url.startsWith("/api/files/")) throw Error(`embed file proxy: refusing non-files-route URL ${JSON.stringify(a.url)}`);
|
|
51
|
-
let o = await p(n === "download" ? f(a.url) : a.url, r);
|
|
52
|
-
if (!o.ok) throw Error(`embed file proxy fetch failed: ${o.status} ${o.statusText}`);
|
|
53
|
-
let s = await o.blob();
|
|
54
|
-
return {
|
|
55
|
-
url: URL.createObjectURL(s),
|
|
56
|
-
kind: "blob"
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
//#endregion
|
|
60
3
|
//#region src/components/file-widgets/helpers.ts
|
|
61
|
-
var
|
|
62
|
-
function
|
|
4
|
+
var s = ["thumb_200", "thumb_300"];
|
|
5
|
+
function c() {
|
|
63
6
|
return typeof navigator > "u" ? ["en"] : (navigator.languages?.length ? navigator.languages : [navigator.language]).filter((e) => typeof e == "string" && e.length > 0);
|
|
64
7
|
}
|
|
65
|
-
function
|
|
8
|
+
function l(e, t) {
|
|
66
9
|
for (let n of t) {
|
|
67
10
|
if (e[n]) return e[n];
|
|
68
11
|
let t = n.split("-")[0];
|
|
@@ -70,17 +13,17 @@ function _(e, t) {
|
|
|
70
13
|
}
|
|
71
14
|
for (let t of Object.values(e)) if (typeof t == "string" && t.length > 0) return t;
|
|
72
15
|
}
|
|
73
|
-
function
|
|
16
|
+
function u(e, t, n) {
|
|
74
17
|
if (t) return t;
|
|
75
18
|
let r = e?.metadata?.alt_text;
|
|
76
19
|
if (typeof r == "string" && r.length > 0) return r;
|
|
77
20
|
if (r && typeof r == "object" && !Array.isArray(r)) {
|
|
78
|
-
let e =
|
|
21
|
+
let e = l(Object.fromEntries(Object.entries(r).filter((e) => e[0].length > 0 && typeof e[1] == "string")), c());
|
|
79
22
|
if (e) return e;
|
|
80
23
|
}
|
|
81
24
|
return n;
|
|
82
25
|
}
|
|
83
|
-
var
|
|
26
|
+
var d = /* @__PURE__ */ new Set([
|
|
84
27
|
"image/png",
|
|
85
28
|
"image/jpeg",
|
|
86
29
|
"image/webp",
|
|
@@ -88,30 +31,30 @@ var y = new Set([
|
|
|
88
31
|
"image/avif",
|
|
89
32
|
"application/pdf"
|
|
90
33
|
]);
|
|
91
|
-
function
|
|
34
|
+
function f(e) {
|
|
92
35
|
let t = e.content_type?.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
93
|
-
return
|
|
36
|
+
return d.has(t);
|
|
94
37
|
}
|
|
95
|
-
function
|
|
38
|
+
function p(e) {
|
|
96
39
|
return e.url ?? e.presigned_url;
|
|
97
40
|
}
|
|
98
|
-
function
|
|
99
|
-
return e.url ?
|
|
41
|
+
function m(e) {
|
|
42
|
+
return e.url ? _(e.url, "attachment") : e.presigned_url;
|
|
100
43
|
}
|
|
101
|
-
function
|
|
44
|
+
function h(e, t) {
|
|
102
45
|
if (!e) return;
|
|
103
46
|
let n = e.derivations ?? {};
|
|
104
47
|
if (t && n[t]) return n[t];
|
|
105
|
-
for (let e of
|
|
48
|
+
for (let e of s) if (n[e]) return n[e];
|
|
106
49
|
return e.url ?? e.presigned_url;
|
|
107
50
|
}
|
|
108
|
-
function
|
|
51
|
+
function g(e, t) {
|
|
109
52
|
let n = e?.derivations ?? {};
|
|
110
53
|
if (t && n[t]) return { derivation: t };
|
|
111
|
-
for (let e of
|
|
54
|
+
for (let e of s) if (n[e]) return { derivation: e };
|
|
112
55
|
return "preview";
|
|
113
56
|
}
|
|
114
|
-
function
|
|
57
|
+
function _(e, t) {
|
|
115
58
|
try {
|
|
116
59
|
let n = new URL(e, typeof window < "u" ? window.location.origin : "http://localhost");
|
|
117
60
|
return n.searchParams.set("disposition", t), /^https?:\/\//i.test(e) ? n.toString() : `${n.pathname}${n.search}${n.hash}`;
|
|
@@ -119,7 +62,7 @@ function T(e, t) {
|
|
|
119
62
|
return `${e}${e.includes("?") ? "&" : "?"}disposition=${t}`;
|
|
120
63
|
}
|
|
121
64
|
}
|
|
122
|
-
function
|
|
65
|
+
function v(e) {
|
|
123
66
|
let t = e?.metadata?.focal_point;
|
|
124
67
|
if (!t || typeof t != "object" || Array.isArray(t)) return null;
|
|
125
68
|
let n = t, r = typeof n.x == "number" ? n.x : NaN, i = typeof n.y == "number" ? n.y : NaN;
|
|
@@ -128,10 +71,10 @@ function E(e) {
|
|
|
128
71
|
y: i
|
|
129
72
|
};
|
|
130
73
|
}
|
|
131
|
-
function
|
|
74
|
+
function y(e) {
|
|
132
75
|
if (e) return `${e.x * 100}% ${e.y * 100}%`;
|
|
133
76
|
}
|
|
134
|
-
function
|
|
77
|
+
function b(e, t) {
|
|
135
78
|
return {
|
|
136
79
|
...e,
|
|
137
80
|
metadata: {
|
|
@@ -141,6 +84,63 @@ function O(e, t) {
|
|
|
141
84
|
};
|
|
142
85
|
}
|
|
143
86
|
//#endregion
|
|
87
|
+
//#region src/embed/file-src.ts
|
|
88
|
+
var x = "/api/files";
|
|
89
|
+
function S(e) {
|
|
90
|
+
if (typeof e != "object" || !e) return !1;
|
|
91
|
+
let t = e;
|
|
92
|
+
return typeof t.url == "string" && typeof t.proxy == "boolean";
|
|
93
|
+
}
|
|
94
|
+
function C(e, t) {
|
|
95
|
+
let n = encodeURIComponent(e);
|
|
96
|
+
return typeof t == "object" ? `${x}/${n}/derived/${encodeURIComponent(t.derivation)}/url` : `${x}/${n}/url`;
|
|
97
|
+
}
|
|
98
|
+
var w = class extends Error {
|
|
99
|
+
fileId;
|
|
100
|
+
key;
|
|
101
|
+
constructor(e, t) {
|
|
102
|
+
super(`derivation "${t}" of file ${e} is still generating`), this.fileId = e, this.key = t, this.name = "DerivationPendingError";
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
function T(e, t) {
|
|
106
|
+
return {
|
|
107
|
+
method: "GET",
|
|
108
|
+
credentials: "omit",
|
|
109
|
+
headers: { Authorization: `Bearer ${e}` },
|
|
110
|
+
signal: t
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function E(e) {
|
|
114
|
+
return `${e}${e.includes("?") ? "&" : "?"}disposition=attachment`;
|
|
115
|
+
}
|
|
116
|
+
async function D(n, r) {
|
|
117
|
+
let i = await e(), a = await fetch(n, T(i, r));
|
|
118
|
+
if (a.status !== 401) return a;
|
|
119
|
+
t();
|
|
120
|
+
let o = await e();
|
|
121
|
+
return fetch(n, T(o, r));
|
|
122
|
+
}
|
|
123
|
+
async function O(e, t, r) {
|
|
124
|
+
if (!n()) throw Error("resolveEmbedFileSrc called outside embed token mode");
|
|
125
|
+
let i = await D(C(e, t), r);
|
|
126
|
+
if (i.status === 202 && typeof t == "object") throw new w(e, t.derivation);
|
|
127
|
+
if (!i.ok) throw Error(`embed file url request failed: ${i.status} ${i.statusText}`);
|
|
128
|
+
let a = await i.json();
|
|
129
|
+
if (!S(a)) throw Error("embed file url response is malformed");
|
|
130
|
+
if (!a.proxy) return {
|
|
131
|
+
url: a.url,
|
|
132
|
+
kind: "direct"
|
|
133
|
+
};
|
|
134
|
+
if (!a.url.startsWith("/api/files/")) throw Error(`embed file proxy: refusing non-files-route URL ${JSON.stringify(a.url)}`);
|
|
135
|
+
let o = await D(t === "download" ? E(a.url) : a.url, r);
|
|
136
|
+
if (!o.ok) throw Error(`embed file proxy fetch failed: ${o.status} ${o.statusText}`);
|
|
137
|
+
let s = await o.blob();
|
|
138
|
+
return {
|
|
139
|
+
url: URL.createObjectURL(s),
|
|
140
|
+
kind: "blob"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
144
|
//#region src/components/file-widgets/useEmbedFileSrc.ts
|
|
145
145
|
function k(e) {
|
|
146
146
|
return typeof e == "object" ? `derivation:${e.derivation}` : e;
|
|
@@ -148,50 +148,50 @@ function k(e) {
|
|
|
148
148
|
function A(e) {
|
|
149
149
|
return e.startsWith("derivation:") ? { derivation: e.slice(11) } : e === "download" ? "download" : "preview";
|
|
150
150
|
}
|
|
151
|
-
function j(
|
|
152
|
-
let a = k(
|
|
151
|
+
function j(e, t, r) {
|
|
152
|
+
let a = k(t), [s, c] = o(() => n() ? void 0 : r);
|
|
153
153
|
return i(() => {
|
|
154
|
-
|
|
154
|
+
n() || c(r);
|
|
155
155
|
}, [r]), i(() => {
|
|
156
|
-
if (!
|
|
157
|
-
if (!
|
|
156
|
+
if (!n()) return;
|
|
157
|
+
if (!e) {
|
|
158
158
|
c(void 0);
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
|
-
let
|
|
162
|
-
return c(void 0),
|
|
161
|
+
let t = null, r = !1, i = new AbortController();
|
|
162
|
+
return c(void 0), O(e, A(a), i.signal).then((e) => {
|
|
163
163
|
if (r) {
|
|
164
164
|
e.kind === "blob" && URL.revokeObjectURL(e.url);
|
|
165
165
|
return;
|
|
166
166
|
}
|
|
167
|
-
e.kind === "blob" && (
|
|
167
|
+
e.kind === "blob" && (t = e.url), c(e.url);
|
|
168
168
|
}).catch((e) => {
|
|
169
169
|
if (!r) {
|
|
170
|
-
if (e instanceof
|
|
170
|
+
if (e instanceof w) {
|
|
171
171
|
c(void 0);
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
174
|
e instanceof DOMException && e.name === "AbortError" || (console.error("[declarion-embed] file src resolution failed:", e), c(void 0));
|
|
175
175
|
}
|
|
176
176
|
}), () => {
|
|
177
|
-
r = !0, i.abort(),
|
|
177
|
+
r = !0, i.abort(), t && URL.revokeObjectURL(t);
|
|
178
178
|
};
|
|
179
|
-
}, [
|
|
179
|
+
}, [e, a]), s;
|
|
180
180
|
}
|
|
181
181
|
function M() {
|
|
182
|
-
let
|
|
182
|
+
let e = a([]);
|
|
183
183
|
return i(() => () => {
|
|
184
|
-
for (let
|
|
185
|
-
|
|
186
|
-
}, []), { open: r((
|
|
187
|
-
if (!
|
|
184
|
+
for (let t of e.current) URL.revokeObjectURL(t);
|
|
185
|
+
e.current = [];
|
|
186
|
+
}, []), { open: r((t, r, i) => {
|
|
187
|
+
if (!n()) {
|
|
188
188
|
i && window.open(i, "_blank", "noopener");
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
t && O(t, r).then((t) => {
|
|
192
|
+
t.kind === "blob" && e.current.push(t.url), window.open(t.url, "_blank", "noopener");
|
|
193
193
|
}).catch((e) => {
|
|
194
|
-
e instanceof
|
|
194
|
+
e instanceof w || console.error("[declarion-embed] file open failed:", e);
|
|
195
195
|
});
|
|
196
196
|
}, []) };
|
|
197
197
|
}
|
|
@@ -207,4 +207,4 @@ function F(e) {
|
|
|
207
207
|
return e ? Array.isArray(e) ? e.filter(N) : N(e) ? [e] : [] : [];
|
|
208
208
|
}
|
|
209
209
|
//#endregion
|
|
210
|
-
export {
|
|
210
|
+
export { f as a, h as c, v as d, u as f, j as i, g as l, P as n, m as o, b as p, M as r, y as s, F as t, p as u };
|