@hansenexus/hud 0.3.0 → 0.4.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/README.md +186 -4
- package/dist/access-CNvJOwKM.d.ts +54 -0
- package/dist/capabilities-CjulXFQX.js +189 -0
- package/dist/{define-hud-BHQR8Lbv.d.ts → define-hud-BBzsSl8b.d.ts} +6 -1
- package/dist/env-DfxpPfs9.js +28 -0
- package/dist/env-vEjyJfPW.d.ts +17 -0
- package/dist/force-state-BaqWsmrf.js +153 -0
- package/dist/force-state-CCqoTfVB.d.ts +23 -0
- package/dist/handler-WmE4Lu7l.d.ts +39 -0
- package/dist/index-BYsdS-7d.d.ts +55 -0
- package/dist/index.d.ts +6 -36
- package/dist/index.js +119 -202
- package/dist/marker-C66LxOhJ.d.ts +9 -0
- package/dist/marker-CMrvxPmS.d.ts +12 -0
- package/dist/marker-DjeGvL-F.js +9 -0
- package/dist/marker-uZYPOJ4M.js +12 -0
- package/dist/markers.d.ts +3 -0
- package/dist/markers.js +3 -0
- package/dist/next.d.ts +3 -18
- package/dist/next.js +3 -28
- package/dist/payload-CLKDVZWl.d.ts +68 -0
- package/dist/plugins/access/server.d.ts +122 -0
- package/dist/plugins/access/server.js +339 -0
- package/dist/plugins/access.d.ts +41 -0
- package/dist/plugins/access.js +250 -0
- package/dist/plugins/agent/server.d.ts +129 -0
- package/dist/plugins/agent/server.js +429 -0
- package/dist/plugins/agent.d.ts +37 -0
- package/dist/plugins/agent.js +411 -0
- package/dist/plugins/design.d.ts +125 -0
- package/dist/plugins/design.js +619 -0
- package/dist/plugins/observe.d.ts +64 -0
- package/dist/plugins/observe.js +617 -0
- package/dist/plugins/ops/server.d.ts +82 -0
- package/dist/plugins/ops/server.js +380 -0
- package/dist/plugins/ops.d.ts +26 -0
- package/dist/plugins/ops.js +362 -0
- package/dist/plugins/vitals.js +3 -86
- package/dist/{plugins-1QmTmZyL.d.ts → plugins-CQ_0w1ot.d.ts} +39 -1
- package/dist/{plugins-CzUiTn_r.js → plugins-DVy_vaYr.js} +3 -1
- package/dist/review.d.ts +181 -0
- package/dist/review.js +1017 -0
- package/dist/selector-NSYSe7AZ.js +28 -0
- package/dist/server.d.ts +3 -39
- package/dist/server.js +1 -1
- package/dist/shortcut-U6ftVY06.js +222 -0
- package/dist/state-boundary.d.ts +26 -0
- package/dist/state-boundary.js +61 -0
- package/dist/styles--slk86ZQ.js +332 -0
- package/dist/types-DADBRiGk.d.ts +50 -0
- package/dist/types-QJUajwWq.js +35 -0
- package/dist/types-f3jlNxzw.d.ts +92 -0
- package/dist/vitals-panel-Dvc5dnR-.js +87 -0
- package/package.json +23 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/core/grabber/selector.ts
|
|
2
|
+
/**
|
|
3
|
+
* `nth-of-type` path from `root` down to `el`. Ported from hn-monorepo
|
|
4
|
+
* `review-kit/anchor`, where console's design comments and the old HUD
|
|
5
|
+
* grabber shared it.
|
|
6
|
+
*/
|
|
7
|
+
function buildAnchorSelector(el, root) {
|
|
8
|
+
const segments = [];
|
|
9
|
+
let node = el;
|
|
10
|
+
while (node && node !== root) {
|
|
11
|
+
const tag = node.tagName.toLowerCase();
|
|
12
|
+
const parent = node.parentElement;
|
|
13
|
+
if (!parent) break;
|
|
14
|
+
const siblings = [...parent.children].filter((c) => c.tagName === node?.tagName);
|
|
15
|
+
const index = siblings.indexOf(node) + 1;
|
|
16
|
+
segments.unshift(siblings.length > 1 ? `${tag}:nth-of-type(${index})` : tag);
|
|
17
|
+
node = parent;
|
|
18
|
+
}
|
|
19
|
+
return segments.join(" > ");
|
|
20
|
+
}
|
|
21
|
+
/** Human label such as `button “Termin buchen”`. */
|
|
22
|
+
function anchorLabelFor(el) {
|
|
23
|
+
const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
|
|
24
|
+
const tag = el.tagName.toLowerCase();
|
|
25
|
+
return text ? `${tag} “${text.slice(0, 32)}${text.length > 32 ? "…" : ""}”` : tag;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { buildAnchorSelector as n, anchorLabelFor as t };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,39 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Decides whether a request may reach a plugin route. Return a `Response` to
|
|
5
|
-
* refuse it (404, 401, 403 ...) or `null` to let it through. The `access`
|
|
6
|
-
* plugin supplies one for deployed tiers.
|
|
7
|
-
*/
|
|
8
|
-
type HudGuard = (request: Request, capability: HudCapability | undefined) => Response | null | Promise<Response | null>;
|
|
9
|
-
interface HudHandlerOptions {
|
|
10
|
-
/** Path the catch-all route is mounted at. Default `/api/hud`. */
|
|
11
|
-
basePath?: string;
|
|
12
|
-
/** Default: `developmentOnly`, which answers 404 unless `NODE_ENV` is `development`. */
|
|
13
|
-
guard?: HudGuard;
|
|
14
|
-
/** Drops plugins server side, like `defineHud({ can })` does client side. Default: grant all. */
|
|
15
|
-
can?: (capability: HudCapability) => boolean;
|
|
16
|
-
}
|
|
17
|
-
type HudRequestHandler = (request: Request) => Promise<Response>;
|
|
18
|
-
/** One function per method, so a Next route can `export const { GET, POST } = createHudHandler(...)`. */
|
|
19
|
-
type HudHandler = {
|
|
20
|
-
handle: HudRequestHandler;
|
|
21
|
-
} & Record<HudHttpMethod, HudRequestHandler>;
|
|
22
|
-
/**
|
|
23
|
-
* The default guard. Core assumes development, so a deployed build that
|
|
24
|
-
* mounts the route without an `access` guard serves nothing and reveals
|
|
25
|
-
* nothing about which plugins exist.
|
|
26
|
-
*/
|
|
27
|
-
export declare const developmentOnly: HudGuard;
|
|
28
|
-
/**
|
|
29
|
-
* Serves every plugin's routes from one catch-all route
|
|
30
|
-
* (`app/api/hud/[...hud]/route.ts` in Next). A request to
|
|
31
|
-
* `<basePath>/<plugin id>/<path>` reaches that plugin's `routes[path][method]`;
|
|
32
|
-
* unknown plugin or path answers 404, a known path with another method 405.
|
|
33
|
-
*
|
|
34
|
-
* Pass the server halves (`@hansenexus/hud/plugins/<id>/server`): they carry
|
|
35
|
-
* `routes` and no React.
|
|
36
|
-
*/
|
|
37
|
-
export declare function createHudHandler(plugins: readonly HudPlugin[], options?: HudHandlerOptions): HudHandler;
|
|
38
|
-
//#endregion
|
|
39
|
-
export { type HudGuard, type HudHandler, type HudHandlerOptions, type HudHttpMethod, type HudPlugin, type HudRequestHandler, type HudRouteContext, type HudRouteHandler, type HudRoutes, definePlugin };
|
|
1
|
+
import { a as HudHttpMethod, d as HudRoutes, l as HudRouteContext, o as HudPlugin, p as definePlugin, u as HudRouteHandler } from "./plugins-CQ_0w1ot.js";
|
|
2
|
+
import { a as createHudHandler, i as HudRequestHandler, n as HudHandler, o as developmentOnly, r as HudHandlerOptions, t as HudGuard } from "./handler-WmE4Lu7l.js";
|
|
3
|
+
export { type HudGuard, type HudHandler, type HudHandlerOptions, type HudHttpMethod, type HudPlugin, type HudRequestHandler, type HudRouteContext, type HudRouteHandler, type HudRoutes, createHudHandler, definePlugin, developmentOnly };
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as resolvePlugins, i as definePlugin, t as HUD_HTTP_METHODS } from "./plugins-
|
|
1
|
+
import { a as resolvePlugins, i as definePlugin, t as HUD_HTTP_METHODS } from "./plugins-DVy_vaYr.js";
|
|
2
2
|
//#region src/server/handler.ts
|
|
3
3
|
function json(status, body, headers) {
|
|
4
4
|
const res = Response.json(body, { status });
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { n as buildAnchorSelector, t as anchorLabelFor } from "./selector-NSYSe7AZ.js";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
//#region src/core/grabber/store.ts
|
|
4
|
+
const IDLE = {
|
|
5
|
+
grabbing: false,
|
|
6
|
+
payload: null
|
|
7
|
+
};
|
|
8
|
+
function createGrabber() {
|
|
9
|
+
let state = IDLE;
|
|
10
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
11
|
+
const set = (next) => {
|
|
12
|
+
if (next.grabbing === state.grabbing && next.payload === state.payload) return;
|
|
13
|
+
state = next;
|
|
14
|
+
for (const listener of listeners) listener();
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
start: () => set({
|
|
18
|
+
...state,
|
|
19
|
+
grabbing: true
|
|
20
|
+
}),
|
|
21
|
+
cancel: () => set({
|
|
22
|
+
...state,
|
|
23
|
+
grabbing: false
|
|
24
|
+
}),
|
|
25
|
+
clear: () => set({
|
|
26
|
+
...state,
|
|
27
|
+
payload: null
|
|
28
|
+
}),
|
|
29
|
+
complete: (payload) => set({
|
|
30
|
+
grabbing: false,
|
|
31
|
+
payload
|
|
32
|
+
}),
|
|
33
|
+
getState: () => state,
|
|
34
|
+
subscribe: (listener) => {
|
|
35
|
+
listeners.add(listener);
|
|
36
|
+
return () => listeners.delete(listener);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Subscribes a component to a HUD's grab state: `useGrab(ctx.grab)`. */
|
|
41
|
+
function useGrab(grab) {
|
|
42
|
+
return useSyncExternalStore(grab.subscribe, grab.getState, grab.getState);
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/core/grabber/inspect.ts
|
|
46
|
+
const MAX_OWNERS = 5;
|
|
47
|
+
/** React's internal fiber key on a DOM node: `__reactFiber$<random>`. */
|
|
48
|
+
function fiberOf(el) {
|
|
49
|
+
for (const key of Object.keys(el)) if (key.startsWith("__reactFiber$")) {
|
|
50
|
+
const fiber = el[key];
|
|
51
|
+
return fiber && typeof fiber === "object" ? fiber : null;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/** Display name of a component fiber's type; null for host elements, fragments, providers. */
|
|
56
|
+
function componentName(type) {
|
|
57
|
+
if (typeof type === "function") {
|
|
58
|
+
const fn = type;
|
|
59
|
+
const name = typeof fn.displayName === "string" ? fn.displayName : fn.name;
|
|
60
|
+
return typeof name === "string" && name ? name : null;
|
|
61
|
+
}
|
|
62
|
+
if (type && typeof type === "object") {
|
|
63
|
+
const wrapper = type;
|
|
64
|
+
if (typeof wrapper.displayName === "string" && wrapper.displayName) return wrapper.displayName;
|
|
65
|
+
return componentName(wrapper.render) ?? componentName(wrapper.type);
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/** Internal frames between the caller and React: the JSX runtime, React itself, bundler glue. */
|
|
70
|
+
const INTERNAL_FRAME = /react-stack-top-frame|jsx-?dev|jsx-runtime|\/react(-dom)?[./@]|node_modules/;
|
|
71
|
+
/**
|
|
72
|
+
* Turns a frame location such as `http://localhost:5173/src/site.tsx?t=1:12:5`
|
|
73
|
+
* or `webpack-internal:///(app-pages-browser)/./src/hero.tsx:8:3` into
|
|
74
|
+
* `src/site.tsx:12:5`.
|
|
75
|
+
*/
|
|
76
|
+
function cleanSourceLocation(location) {
|
|
77
|
+
const match = /^(.*?):(\d+)(?::(\d+))?$/.exec(location.trim());
|
|
78
|
+
if (!match) return location;
|
|
79
|
+
const [, rawPath = "", line, column] = match;
|
|
80
|
+
let path = rawPath.replace(/[?#].*$/, "");
|
|
81
|
+
const bundled = /^webpack-internal:\/\/\/(?:\([^)]*\)\/)?(.*)$/.exec(path);
|
|
82
|
+
const url = /^[a-z][a-z0-9+.-]*:\/\/[^/]*\/(.*)$/i.exec(path);
|
|
83
|
+
path = bundled?.[1] ?? url?.[1] ?? path;
|
|
84
|
+
path = path.replace(/^\.\//, "").replace(/^\/?@fs\//, "/");
|
|
85
|
+
if (!path) path = rawPath;
|
|
86
|
+
return column ? `${path}:${line}:${column}` : `${path}:${line}`;
|
|
87
|
+
}
|
|
88
|
+
/** First non-internal frame of a V8 or SpiderMonkey/JSC stack. */
|
|
89
|
+
function sourceFromStack(stack) {
|
|
90
|
+
for (const line of stack.split("\n").slice(1)) {
|
|
91
|
+
if (INTERNAL_FRAME.test(line)) continue;
|
|
92
|
+
const v8 = /^\s*at (?:.*? \()?(.+?)\)?$/.exec(line);
|
|
93
|
+
const gecko = /@(.+)$/.exec(line);
|
|
94
|
+
const location = v8?.[1] ?? gecko?.[1];
|
|
95
|
+
if (location && /:\d+(:\d+)?$/.test(location)) return cleanSourceLocation(location);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function sourceOf(fiber) {
|
|
99
|
+
const legacy = fiber._debugSource;
|
|
100
|
+
if (legacy?.fileName && legacy.lineNumber) return cleanSourceLocation(`${legacy.fileName}:${legacy.lineNumber}${legacy.columnNumber ? `:${legacy.columnNumber}` : ""}`);
|
|
101
|
+
const stack = fiber._debugStack?.stack;
|
|
102
|
+
return typeof stack === "string" ? sourceFromStack(stack) : void 0;
|
|
103
|
+
}
|
|
104
|
+
function inspectComponent(el) {
|
|
105
|
+
const host = fiberOf(el);
|
|
106
|
+
if (!host) return null;
|
|
107
|
+
const names = [];
|
|
108
|
+
let source = sourceOf(host);
|
|
109
|
+
for (let fiber = host.return; fiber && names.length <= MAX_OWNERS; fiber = fiber.return) {
|
|
110
|
+
const name = componentName(fiber.type);
|
|
111
|
+
if (!name) continue;
|
|
112
|
+
names.push(name);
|
|
113
|
+
source ??= sourceOf(fiber);
|
|
114
|
+
}
|
|
115
|
+
const [name, ...owners] = names;
|
|
116
|
+
if (!name) return null;
|
|
117
|
+
return source ? {
|
|
118
|
+
name,
|
|
119
|
+
owners,
|
|
120
|
+
source
|
|
121
|
+
} : {
|
|
122
|
+
name,
|
|
123
|
+
owners
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/core/grabber/payload.ts
|
|
128
|
+
function pct(part, whole) {
|
|
129
|
+
return whole > 0 ? Number((part / whole * 100).toFixed(1)) : 0;
|
|
130
|
+
}
|
|
131
|
+
function buildGrabAnchor(el) {
|
|
132
|
+
const doc = el.ownerDocument;
|
|
133
|
+
const win = doc.defaultView;
|
|
134
|
+
const width = win?.innerWidth ?? 0;
|
|
135
|
+
const height = win?.innerHeight ?? 0;
|
|
136
|
+
const r = el.getBoundingClientRect();
|
|
137
|
+
return {
|
|
138
|
+
selector: buildAnchorSelector(el, doc.body),
|
|
139
|
+
label: anchorLabelFor(el),
|
|
140
|
+
tag: el.tagName.toLowerCase(),
|
|
141
|
+
rect: {
|
|
142
|
+
x: pct(r.left, width),
|
|
143
|
+
y: pct(r.top, height),
|
|
144
|
+
width: pct(r.width, width),
|
|
145
|
+
height: pct(r.height, height)
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildGrabPayload(el, { app }) {
|
|
150
|
+
const win = el.ownerDocument.defaultView;
|
|
151
|
+
return {
|
|
152
|
+
app,
|
|
153
|
+
url: win?.location.href ?? "",
|
|
154
|
+
route: win?.location.pathname ?? "/",
|
|
155
|
+
anchor: buildGrabAnchor(el),
|
|
156
|
+
component: inspectComponent(el),
|
|
157
|
+
viewport: {
|
|
158
|
+
width: win?.innerWidth ?? 0,
|
|
159
|
+
height: win?.innerHeight ?? 0
|
|
160
|
+
},
|
|
161
|
+
grabbedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** The picker's hover label: component and source when React reports them, the tag otherwise. */
|
|
165
|
+
function describeElement(el) {
|
|
166
|
+
const tag = el.tagName.toLowerCase();
|
|
167
|
+
const component = inspectComponent(el);
|
|
168
|
+
if (!component) return tag;
|
|
169
|
+
const file = component.source?.split("/").pop()?.replace(/(:\d+)+$/, "");
|
|
170
|
+
return `<${component.name}> ${tag}${file ? ` · ${file}` : ""}`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Scores manifest entries against the route and anchor label. Pure token
|
|
174
|
+
* overlap: a hint for the agent, not a resolution. Ported from the old HUD's
|
|
175
|
+
* `grabber/payload.ts`.
|
|
176
|
+
*/
|
|
177
|
+
function matchComponentHints(hints, route, label, max = 5) {
|
|
178
|
+
if (!hints?.length) return [];
|
|
179
|
+
const tokens = `${route} ${label}`.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2);
|
|
180
|
+
if (tokens.length === 0) return hints.slice(0, max);
|
|
181
|
+
return hints.map((hint) => {
|
|
182
|
+
const haystack = `${hint.name} ${hint.path}`.toLowerCase();
|
|
183
|
+
return {
|
|
184
|
+
hint,
|
|
185
|
+
score: tokens.reduce((n, token) => haystack.includes(token) ? n + 1 : n, 0)
|
|
186
|
+
};
|
|
187
|
+
}).filter((h) => h.score > 0).sort((a, b) => b.score - a.score).slice(0, max).map((h) => h.hint);
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/shell/commands/shortcut.ts
|
|
191
|
+
function parseShortcut(spec) {
|
|
192
|
+
const parts = spec.toLowerCase().split("+");
|
|
193
|
+
return {
|
|
194
|
+
key: parts.at(-1) ?? "",
|
|
195
|
+
mod: parts.includes("mod"),
|
|
196
|
+
shift: parts.includes("shift"),
|
|
197
|
+
alt: parts.includes("alt")
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function isApplePlatform() {
|
|
201
|
+
return typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent);
|
|
202
|
+
}
|
|
203
|
+
function matchesShortcut(event, shortcut, apple) {
|
|
204
|
+
if ((apple ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey) !== shortcut.mod) return false;
|
|
205
|
+
if (event.shiftKey !== shortcut.shift || event.altKey !== shortcut.alt) return false;
|
|
206
|
+
if (event.key.toLowerCase() === shortcut.key) return true;
|
|
207
|
+
if (/^[a-z]$/.test(shortcut.key)) return event.code === `Key${shortcut.key.toUpperCase()}`;
|
|
208
|
+
if (/^\d$/.test(shortcut.key)) return event.code === `Digit${shortcut.key}`;
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
function formatShortcut(shortcut, apple) {
|
|
212
|
+
const key = shortcut.key.toUpperCase();
|
|
213
|
+
if (apple) return `${shortcut.mod ? "⌘" : ""}${shortcut.alt ? "⌥" : ""}${shortcut.shift ? "⇧" : ""}${key}`;
|
|
214
|
+
return [
|
|
215
|
+
shortcut.mod && "Ctrl",
|
|
216
|
+
shortcut.alt && "Alt",
|
|
217
|
+
shortcut.shift && "Shift",
|
|
218
|
+
key
|
|
219
|
+
].filter(Boolean).join("+");
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { buildGrabPayload as a, createGrabber as c, parseShortcut as i, useGrab as l, isApplePlatform as n, describeElement as o, matchesShortcut as r, matchComponentHints as s, formatShortcut as t };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { i as ForcedState } from "./force-state-CCqoTfVB.js";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
//#region src/plugins/design/state-boundary.d.ts
|
|
4
|
+
export interface StateBoundaryProps {
|
|
5
|
+
/** Stable, page-unique id; the HUD States panel lists boundaries by it. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Renders the boundary; `forced` is the state to show instead of the real one, or null. */
|
|
8
|
+
children: (forced: ForcedState | null) => ReactNode;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Registers a loading/empty/error boundary with the HUD States panel
|
|
12
|
+
* (`forceState()` from `@hansenexus/hud/plugins/design`) and `?__state=`.
|
|
13
|
+
* Wrap the part of a page that branches on a query result:
|
|
14
|
+
*
|
|
15
|
+
* ```tsx
|
|
16
|
+
* <StateBoundary id="blog-post">
|
|
17
|
+
* {(forced) => forced === "loading" || post === undefined ? <Skeleton /> : …}
|
|
18
|
+
* </StateBoundary>
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Production-safe to import: outside development it renders `children(null)`
|
|
22
|
+
* and nothing else.
|
|
23
|
+
*/
|
|
24
|
+
export declare function StateBoundary({ id, children }: StateBoundaryProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
|
|
25
|
+
//#endregion
|
|
26
|
+
export type { ForcedState };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { a as getForceStateSnapshot, c as subscribeForceState, o as getForceStateStore, s as resolveForcedState } from "./force-state-BaqWsmrf.js";
|
|
2
|
+
import { createContext, useContext, useEffect, useSyncExternalStore } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
//#region src/plugins/design/state-boundary.tsx
|
|
5
|
+
/**
|
|
6
|
+
* Build-time gate. A literal once the consumer's bundler inlines NODE_ENV, so
|
|
7
|
+
* a production build folds `StateBoundary` to `children(null)` and drops
|
|
8
|
+
* everything below, the store included: no production code path reads
|
|
9
|
+
* `?__state=`.
|
|
10
|
+
*/
|
|
11
|
+
const FORCE_STATE_ENABLED = process.env.NODE_ENV === "development";
|
|
12
|
+
/**
|
|
13
|
+
* Registers a loading/empty/error boundary with the HUD States panel
|
|
14
|
+
* (`forceState()` from `@hansenexus/hud/plugins/design`) and `?__state=`.
|
|
15
|
+
* Wrap the part of a page that branches on a query result:
|
|
16
|
+
*
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <StateBoundary id="blog-post">
|
|
19
|
+
* {(forced) => forced === "loading" || post === undefined ? <Skeleton /> : …}
|
|
20
|
+
* </StateBoundary>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Production-safe to import: outside development it renders `children(null)`
|
|
24
|
+
* and nothing else.
|
|
25
|
+
*/
|
|
26
|
+
function StateBoundary({ id, children }) {
|
|
27
|
+
if (!FORCE_STATE_ENABLED) return children(null);
|
|
28
|
+
return /* @__PURE__ */ jsx(DevStateBoundary, {
|
|
29
|
+
id,
|
|
30
|
+
children
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** The enclosing boundary's id, so the store knows which boundaries are top-level. */
|
|
34
|
+
const ParentBoundary = /* @__PURE__ */ createContext(null);
|
|
35
|
+
const SERVER_SNAPSHOT = {
|
|
36
|
+
boundaries: [],
|
|
37
|
+
overrides: {},
|
|
38
|
+
param: null
|
|
39
|
+
};
|
|
40
|
+
function getServerSnapshot() {
|
|
41
|
+
return SERVER_SNAPSHOT;
|
|
42
|
+
}
|
|
43
|
+
function DevStateBoundary({ id, children }) {
|
|
44
|
+
const parentId = useContext(ParentBoundary);
|
|
45
|
+
const snapshot = useSyncExternalStore(subscribeForceState, getForceStateSnapshot, getServerSnapshot);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const store = getForceStateStore();
|
|
48
|
+
store.syncSearch(window.location.search);
|
|
49
|
+
return store.register(id, parentId);
|
|
50
|
+
}, [id, parentId]);
|
|
51
|
+
const forced = resolveForcedState(snapshot, {
|
|
52
|
+
id,
|
|
53
|
+
parentId
|
|
54
|
+
});
|
|
55
|
+
return /* @__PURE__ */ jsx(ParentBoundary.Provider, {
|
|
56
|
+
value: id,
|
|
57
|
+
children: children(forced)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { StateBoundary };
|