@glyphjs/runtime 0.1.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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/index.cjs +1766 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +570 -0
- package/dist/index.d.ts +570 -0
- package/dist/index.js +1714 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1714 @@
|
|
|
1
|
+
import { createContext, useMemo, useContext, Component, useRef, useCallback, useState, useEffect } from 'react';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { componentSchemas } from '@glyphjs/schemas';
|
|
4
|
+
import DOMPurify from 'dompurify';
|
|
5
|
+
|
|
6
|
+
// src/create-runtime.tsx
|
|
7
|
+
|
|
8
|
+
// src/plugins/validate.ts
|
|
9
|
+
var UI_TYPE_PATTERN = /^ui:.+$/;
|
|
10
|
+
function validateComponentDefinition(definition) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
if (!definition.type) {
|
|
13
|
+
errors.push('Component definition must have a "type" field.');
|
|
14
|
+
} else if (!UI_TYPE_PATTERN.test(definition.type)) {
|
|
15
|
+
errors.push(
|
|
16
|
+
`Component type "${definition.type}" must match the "ui:*" format (e.g. "ui:chart", "ui:graph").`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
if (!definition.schema) {
|
|
20
|
+
errors.push('Component definition must have a "schema" field.');
|
|
21
|
+
} else {
|
|
22
|
+
if (typeof definition.schema.parse !== "function") {
|
|
23
|
+
errors.push(
|
|
24
|
+
'Component schema must have a "parse" method (expected a Zod-compatible schema).'
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
if (typeof definition.schema.safeParse !== "function") {
|
|
28
|
+
errors.push(
|
|
29
|
+
'Component schema must have a "safeParse" method (expected a Zod-compatible schema).'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (!definition.render) {
|
|
34
|
+
errors.push(
|
|
35
|
+
'Component definition must have a "render" field (function or class component).'
|
|
36
|
+
);
|
|
37
|
+
} else if (typeof definition.render !== "function") {
|
|
38
|
+
errors.push(
|
|
39
|
+
'Component "render" must be a function (functional component) or a class (class component).'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
valid: errors.length === 0,
|
|
44
|
+
errors
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/plugins/registry.ts
|
|
49
|
+
var PluginRegistry = class {
|
|
50
|
+
components = /* @__PURE__ */ new Map();
|
|
51
|
+
overrides = /* @__PURE__ */ new Map();
|
|
52
|
+
listeners = /* @__PURE__ */ new Set();
|
|
53
|
+
themeDefaults = {};
|
|
54
|
+
// ─── Registration ────────────────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Register a `ui:*` component plugin definition.
|
|
57
|
+
* Validates the definition first; throws if invalid.
|
|
58
|
+
* Merges any `themeDefaults` from the definition into
|
|
59
|
+
* the accumulated theme defaults map.
|
|
60
|
+
*
|
|
61
|
+
* @param definition - The component definition to register.
|
|
62
|
+
* @throws Error if the definition fails validation.
|
|
63
|
+
*/
|
|
64
|
+
registerComponent(definition) {
|
|
65
|
+
const result = validateComponentDefinition(definition);
|
|
66
|
+
if (!result.valid) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Invalid component definition for "${definition.type}":
|
|
69
|
+
- ${result.errors.join("\n - ")}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
this.components.set(definition.type, definition);
|
|
73
|
+
if (definition.themeDefaults) {
|
|
74
|
+
Object.assign(this.themeDefaults, definition.themeDefaults);
|
|
75
|
+
}
|
|
76
|
+
this.notify();
|
|
77
|
+
}
|
|
78
|
+
/** Bulk-register an array of component definitions. */
|
|
79
|
+
registerAll(definitions) {
|
|
80
|
+
for (const def of definitions) {
|
|
81
|
+
this.registerComponent(def);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ─── Overrides ───────────────────────────────────────────────
|
|
85
|
+
/** Set override renderers (keyed by block type). */
|
|
86
|
+
setOverrides(overrides) {
|
|
87
|
+
for (const [type, renderer] of Object.entries(overrides)) {
|
|
88
|
+
if (renderer) {
|
|
89
|
+
this.overrides.set(type, renderer);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this.notify();
|
|
93
|
+
}
|
|
94
|
+
// ─── Lookups ─────────────────────────────────────────────────
|
|
95
|
+
/** Get a registered `ui:*` component definition. */
|
|
96
|
+
getRenderer(blockType) {
|
|
97
|
+
return this.components.get(blockType);
|
|
98
|
+
}
|
|
99
|
+
/** Get an override renderer for any block type. */
|
|
100
|
+
getOverride(blockType) {
|
|
101
|
+
return this.overrides.get(blockType);
|
|
102
|
+
}
|
|
103
|
+
/** Check if a component type is registered. */
|
|
104
|
+
has(blockType) {
|
|
105
|
+
return this.components.has(blockType);
|
|
106
|
+
}
|
|
107
|
+
/** Get all registered component type names. */
|
|
108
|
+
getRegisteredTypes() {
|
|
109
|
+
return Array.from(this.components.keys());
|
|
110
|
+
}
|
|
111
|
+
// ─── Theme Defaults ──────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Returns the accumulated theme defaults from all registered
|
|
114
|
+
* component definitions. These can be merged into a GlyphTheme
|
|
115
|
+
* to provide sensible defaults for plugin-specific variables.
|
|
116
|
+
*/
|
|
117
|
+
getThemeDefaults() {
|
|
118
|
+
return { ...this.themeDefaults };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Merge accumulated plugin theme defaults into a GlyphTheme.
|
|
122
|
+
* Plugin defaults have lower priority than existing theme variables.
|
|
123
|
+
*
|
|
124
|
+
* @param theme - The base theme to merge defaults into.
|
|
125
|
+
* @returns A new GlyphTheme with plugin defaults applied under existing variables.
|
|
126
|
+
*/
|
|
127
|
+
mergeThemeDefaults(theme) {
|
|
128
|
+
return {
|
|
129
|
+
...theme,
|
|
130
|
+
variables: {
|
|
131
|
+
...this.themeDefaults,
|
|
132
|
+
...theme.variables
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ─── Change Notification ─────────────────────────────────────
|
|
137
|
+
/**
|
|
138
|
+
* Subscribe to registry changes.
|
|
139
|
+
*
|
|
140
|
+
* @param listener - Callback invoked whenever a component or override is registered.
|
|
141
|
+
* @returns An unsubscribe function that removes the listener.
|
|
142
|
+
*/
|
|
143
|
+
subscribe(listener) {
|
|
144
|
+
this.listeners.add(listener);
|
|
145
|
+
return () => {
|
|
146
|
+
this.listeners.delete(listener);
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
notify() {
|
|
150
|
+
for (const listener of this.listeners) {
|
|
151
|
+
listener();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/theme/light.ts
|
|
157
|
+
var lightTheme = {
|
|
158
|
+
name: "light",
|
|
159
|
+
variables: {
|
|
160
|
+
// Colors
|
|
161
|
+
"--glyph-bg": "#f4f6fa",
|
|
162
|
+
"--glyph-text": "#1a2035",
|
|
163
|
+
"--glyph-text-muted": "#6b7a94",
|
|
164
|
+
"--glyph-heading": "#0a0e1a",
|
|
165
|
+
"--glyph-link": "#0a9d7c",
|
|
166
|
+
"--glyph-link-hover": "#088a6c",
|
|
167
|
+
"--glyph-border": "#d0d8e4",
|
|
168
|
+
"--glyph-border-strong": "#a8b5c8",
|
|
169
|
+
"--glyph-surface": "#e8ecf3",
|
|
170
|
+
"--glyph-surface-raised": "#f4f6fa",
|
|
171
|
+
// Accent
|
|
172
|
+
"--glyph-accent": "#0a9d7c",
|
|
173
|
+
"--glyph-accent-hover": "#088a6c",
|
|
174
|
+
"--glyph-accent-subtle": "#e6f6f2",
|
|
175
|
+
"--glyph-accent-muted": "#b0ddd0",
|
|
176
|
+
// Code
|
|
177
|
+
"--glyph-code-bg": "#e8ecf3",
|
|
178
|
+
"--glyph-code-text": "#1a2035",
|
|
179
|
+
// Blockquote
|
|
180
|
+
"--glyph-blockquote-border": "#0a9d7c",
|
|
181
|
+
"--glyph-blockquote-bg": "#e6f6f2",
|
|
182
|
+
// Grid / Tooltip
|
|
183
|
+
"--glyph-grid": "#d0d8e4",
|
|
184
|
+
"--glyph-tooltip-bg": "rgba(10, 14, 26, 0.9)",
|
|
185
|
+
"--glyph-tooltip-text": "#f4f6fa",
|
|
186
|
+
// Callouts
|
|
187
|
+
"--glyph-callout-info-bg": "#e6f2fa",
|
|
188
|
+
"--glyph-callout-info-border": "#38bdf8",
|
|
189
|
+
"--glyph-callout-warning-bg": "#fef3e2",
|
|
190
|
+
"--glyph-callout-warning-border": "#fb923c",
|
|
191
|
+
"--glyph-callout-error-bg": "#fde8e8",
|
|
192
|
+
"--glyph-callout-error-border": "#f87171",
|
|
193
|
+
"--glyph-callout-tip-bg": "#e6f6f0",
|
|
194
|
+
"--glyph-callout-tip-border": "#22c55e",
|
|
195
|
+
// Spacing
|
|
196
|
+
"--glyph-spacing-xs": "0.25rem",
|
|
197
|
+
"--glyph-spacing-sm": "0.5rem",
|
|
198
|
+
"--glyph-spacing-md": "1rem",
|
|
199
|
+
"--glyph-spacing-lg": "1.5rem",
|
|
200
|
+
"--glyph-spacing-xl": "2rem",
|
|
201
|
+
// Typography
|
|
202
|
+
"--glyph-font-body": '"Inter", "Helvetica Neue", system-ui, sans-serif',
|
|
203
|
+
"--glyph-font-heading": '"Inter", "Helvetica Neue", system-ui, sans-serif',
|
|
204
|
+
"--glyph-font-mono": 'ui-monospace, "Cascadia Code", "Fira Code", monospace',
|
|
205
|
+
// Border radius
|
|
206
|
+
"--glyph-radius-sm": "0.375rem",
|
|
207
|
+
"--glyph-radius-md": "0.5rem",
|
|
208
|
+
"--glyph-radius-lg": "0.75rem",
|
|
209
|
+
// Effects
|
|
210
|
+
"--glyph-shadow-sm": "0 1px 3px rgba(0,0,0,0.1)",
|
|
211
|
+
"--glyph-shadow-md": "0 4px 12px rgba(0,0,0,0.15)",
|
|
212
|
+
"--glyph-shadow-lg": "0 8px 30px rgba(0,0,0,0.2)",
|
|
213
|
+
"--glyph-shadow-glow": "none",
|
|
214
|
+
"--glyph-text-shadow": "none",
|
|
215
|
+
"--glyph-backdrop": "none",
|
|
216
|
+
"--glyph-gradient-accent": "linear-gradient(135deg, #0a9d7c, #22c55e)",
|
|
217
|
+
"--glyph-transition": "0.2s ease",
|
|
218
|
+
"--glyph-opacity-muted": "0.7",
|
|
219
|
+
"--glyph-opacity-disabled": "0.4",
|
|
220
|
+
"--glyph-focus-ring": "0 0 0 2px #0a9d7c",
|
|
221
|
+
// SVG / Data Visualization
|
|
222
|
+
"--glyph-node-fill-opacity": "0.85",
|
|
223
|
+
"--glyph-node-radius": "3",
|
|
224
|
+
"--glyph-node-stroke-width": "1.5",
|
|
225
|
+
"--glyph-node-label-color": "#fff",
|
|
226
|
+
"--glyph-edge-color": "#a8b5c8",
|
|
227
|
+
"--glyph-icon-stroke": "#fff",
|
|
228
|
+
"--glyph-icon-stroke-width": "1.5"
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/theme/dark.ts
|
|
233
|
+
var darkTheme = {
|
|
234
|
+
name: "dark",
|
|
235
|
+
variables: {
|
|
236
|
+
// Colors
|
|
237
|
+
"--glyph-bg": "#0a0e1a",
|
|
238
|
+
"--glyph-text": "#d4dae3",
|
|
239
|
+
"--glyph-text-muted": "#6b7a94",
|
|
240
|
+
"--glyph-heading": "#edf0f5",
|
|
241
|
+
"--glyph-link": "#00d4aa",
|
|
242
|
+
"--glyph-link-hover": "#33e0be",
|
|
243
|
+
"--glyph-border": "#1a2035",
|
|
244
|
+
"--glyph-border-strong": "#2a3550",
|
|
245
|
+
"--glyph-surface": "#0f1526",
|
|
246
|
+
"--glyph-surface-raised": "#162038",
|
|
247
|
+
// Accent
|
|
248
|
+
"--glyph-accent": "#00d4aa",
|
|
249
|
+
"--glyph-accent-hover": "#33e0be",
|
|
250
|
+
"--glyph-accent-subtle": "#0a1a1a",
|
|
251
|
+
"--glyph-accent-muted": "#1a4a3a",
|
|
252
|
+
// Code
|
|
253
|
+
"--glyph-code-bg": "#0f1526",
|
|
254
|
+
"--glyph-code-text": "#d4dae3",
|
|
255
|
+
// Blockquote
|
|
256
|
+
"--glyph-blockquote-border": "#00d4aa",
|
|
257
|
+
"--glyph-blockquote-bg": "#0a1a1a",
|
|
258
|
+
// Grid / Tooltip
|
|
259
|
+
"--glyph-grid": "#1a2035",
|
|
260
|
+
"--glyph-tooltip-bg": "rgba(0, 0, 0, 0.9)",
|
|
261
|
+
"--glyph-tooltip-text": "#d4dae3",
|
|
262
|
+
// Callouts
|
|
263
|
+
"--glyph-callout-info-bg": "#0a1526",
|
|
264
|
+
"--glyph-callout-info-border": "#38bdf8",
|
|
265
|
+
"--glyph-callout-warning-bg": "#1a1608",
|
|
266
|
+
"--glyph-callout-warning-border": "#fb923c",
|
|
267
|
+
"--glyph-callout-error-bg": "#1f0e0e",
|
|
268
|
+
"--glyph-callout-error-border": "#f87171",
|
|
269
|
+
"--glyph-callout-tip-bg": "#0a1a14",
|
|
270
|
+
"--glyph-callout-tip-border": "#22c55e",
|
|
271
|
+
// Spacing (same as light — spacing is mode-independent)
|
|
272
|
+
"--glyph-spacing-xs": "0.25rem",
|
|
273
|
+
"--glyph-spacing-sm": "0.5rem",
|
|
274
|
+
"--glyph-spacing-md": "1rem",
|
|
275
|
+
"--glyph-spacing-lg": "1.5rem",
|
|
276
|
+
"--glyph-spacing-xl": "2rem",
|
|
277
|
+
// Typography (same as light — font stacks are mode-independent)
|
|
278
|
+
"--glyph-font-body": '"Inter", "Helvetica Neue", system-ui, sans-serif',
|
|
279
|
+
"--glyph-font-heading": '"Inter", "Helvetica Neue", system-ui, sans-serif',
|
|
280
|
+
"--glyph-font-mono": 'ui-monospace, "Cascadia Code", "Fira Code", monospace',
|
|
281
|
+
// Border radius
|
|
282
|
+
"--glyph-radius-sm": "0.375rem",
|
|
283
|
+
"--glyph-radius-md": "0.5rem",
|
|
284
|
+
"--glyph-radius-lg": "0.75rem",
|
|
285
|
+
// Effects
|
|
286
|
+
"--glyph-shadow-sm": "0 1px 3px rgba(0,0,0,0.4)",
|
|
287
|
+
"--glyph-shadow-md": "0 4px 12px rgba(0,0,0,0.5)",
|
|
288
|
+
"--glyph-shadow-lg": "0 8px 30px rgba(0,0,0,0.6)",
|
|
289
|
+
"--glyph-shadow-glow": "0 0 15px rgba(0,212,170,0.3)",
|
|
290
|
+
"--glyph-text-shadow": "none",
|
|
291
|
+
"--glyph-backdrop": "none",
|
|
292
|
+
"--glyph-gradient-accent": "linear-gradient(135deg, #00d4aa, #00e5ff)",
|
|
293
|
+
"--glyph-transition": "0.2s ease",
|
|
294
|
+
"--glyph-opacity-muted": "0.7",
|
|
295
|
+
"--glyph-opacity-disabled": "0.4",
|
|
296
|
+
"--glyph-focus-ring": "0 0 0 2px #00d4aa",
|
|
297
|
+
// SVG / Data Visualization
|
|
298
|
+
"--glyph-node-fill-opacity": "0.85",
|
|
299
|
+
"--glyph-node-radius": "3",
|
|
300
|
+
"--glyph-node-stroke-width": "1.5",
|
|
301
|
+
"--glyph-node-label-color": "#fff",
|
|
302
|
+
"--glyph-edge-color": "#6b7a94",
|
|
303
|
+
"--glyph-icon-stroke": "#fff",
|
|
304
|
+
"--glyph-icon-stroke-width": "1.5"
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/theme/resolve.ts
|
|
309
|
+
function resolveTheme(theme) {
|
|
310
|
+
if (!theme || theme === "light") {
|
|
311
|
+
return lightTheme;
|
|
312
|
+
}
|
|
313
|
+
if (theme === "dark") {
|
|
314
|
+
return darkTheme;
|
|
315
|
+
}
|
|
316
|
+
return theme;
|
|
317
|
+
}
|
|
318
|
+
function mergeThemeDefaults(theme, defaults) {
|
|
319
|
+
return {
|
|
320
|
+
...theme,
|
|
321
|
+
variables: { ...defaults, ...theme.variables }
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function createResolveVar(theme) {
|
|
325
|
+
return (varName) => theme.variables[varName] ?? "";
|
|
326
|
+
}
|
|
327
|
+
function isDarkTheme(theme) {
|
|
328
|
+
const bg = theme.variables["--glyph-bg"];
|
|
329
|
+
if (bg) {
|
|
330
|
+
const luminance = perceivedLuminance(bg);
|
|
331
|
+
if (luminance !== null) {
|
|
332
|
+
return luminance < 0.5;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return theme.name.toLowerCase().includes("dark");
|
|
336
|
+
}
|
|
337
|
+
function perceivedLuminance(hex) {
|
|
338
|
+
const trimmed = hex.trim();
|
|
339
|
+
if (!trimmed.startsWith("#")) return null;
|
|
340
|
+
let r;
|
|
341
|
+
let g;
|
|
342
|
+
let b;
|
|
343
|
+
if (trimmed.length === 4) {
|
|
344
|
+
const rChar = trimmed.charAt(1);
|
|
345
|
+
const gChar = trimmed.charAt(2);
|
|
346
|
+
const bChar = trimmed.charAt(3);
|
|
347
|
+
r = parseInt(rChar + rChar, 16);
|
|
348
|
+
g = parseInt(gChar + gChar, 16);
|
|
349
|
+
b = parseInt(bChar + bChar, 16);
|
|
350
|
+
} else if (trimmed.length === 7) {
|
|
351
|
+
r = parseInt(trimmed.slice(1, 3), 16);
|
|
352
|
+
g = parseInt(trimmed.slice(3, 5), 16);
|
|
353
|
+
b = parseInt(trimmed.slice(5, 7), 16);
|
|
354
|
+
} else {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
358
|
+
return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
|
|
359
|
+
}
|
|
360
|
+
var ThemeContext = createContext(null);
|
|
361
|
+
function ThemeProvider({
|
|
362
|
+
theme,
|
|
363
|
+
className,
|
|
364
|
+
style: consumerStyle,
|
|
365
|
+
children
|
|
366
|
+
}) {
|
|
367
|
+
const resolved = useMemo(() => resolveTheme(theme), [theme]);
|
|
368
|
+
const themeContext = useMemo(
|
|
369
|
+
() => ({
|
|
370
|
+
name: resolved.name,
|
|
371
|
+
resolveVar: createResolveVar(resolved),
|
|
372
|
+
isDark: isDarkTheme(resolved)
|
|
373
|
+
}),
|
|
374
|
+
[resolved]
|
|
375
|
+
);
|
|
376
|
+
const style = useMemo(
|
|
377
|
+
() => ({ ...resolved.variables, ...consumerStyle }),
|
|
378
|
+
[resolved, consumerStyle]
|
|
379
|
+
);
|
|
380
|
+
return /* @__PURE__ */ jsx(ThemeContext, { value: themeContext, children: /* @__PURE__ */ jsx("div", { "data-glyph-theme": resolved.name, className, style, children }) });
|
|
381
|
+
}
|
|
382
|
+
function useGlyphTheme() {
|
|
383
|
+
const ctx = useContext(ThemeContext);
|
|
384
|
+
if (!ctx) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
"useGlyphTheme() must be used within a <ThemeProvider> or <RuntimeProvider>. Did you forget to wrap your component tree?"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
return ctx;
|
|
390
|
+
}
|
|
391
|
+
var noop = () => {
|
|
392
|
+
};
|
|
393
|
+
var RuntimeContext = createContext(null);
|
|
394
|
+
function RuntimeProvider({
|
|
395
|
+
registry,
|
|
396
|
+
references,
|
|
397
|
+
theme,
|
|
398
|
+
className,
|
|
399
|
+
style: consumerStyle,
|
|
400
|
+
onDiagnostic,
|
|
401
|
+
onNavigate,
|
|
402
|
+
children
|
|
403
|
+
}) {
|
|
404
|
+
const resolvedThemeObject = useMemo(() => resolveTheme(theme), [theme]);
|
|
405
|
+
const resolvedTheme = useMemo(
|
|
406
|
+
() => ({
|
|
407
|
+
name: resolvedThemeObject.name,
|
|
408
|
+
resolveVar: createResolveVar(resolvedThemeObject),
|
|
409
|
+
isDark: isDarkTheme(resolvedThemeObject)
|
|
410
|
+
}),
|
|
411
|
+
[resolvedThemeObject]
|
|
412
|
+
);
|
|
413
|
+
const value = useMemo(
|
|
414
|
+
() => ({
|
|
415
|
+
registry,
|
|
416
|
+
references,
|
|
417
|
+
theme: resolvedTheme,
|
|
418
|
+
onDiagnostic: onDiagnostic ?? noop,
|
|
419
|
+
onNavigate: onNavigate ?? noop
|
|
420
|
+
}),
|
|
421
|
+
[registry, references, resolvedTheme, onDiagnostic, onNavigate]
|
|
422
|
+
);
|
|
423
|
+
const style = useMemo(
|
|
424
|
+
() => ({ ...resolvedThemeObject.variables, ...consumerStyle }),
|
|
425
|
+
[resolvedThemeObject, consumerStyle]
|
|
426
|
+
);
|
|
427
|
+
return /* @__PURE__ */ jsx(RuntimeContext, { value, children: /* @__PURE__ */ jsx(ThemeContext, { value: resolvedTheme, children: /* @__PURE__ */ jsx("div", { "data-glyph-theme": resolvedThemeObject.name, className, style, children }) }) });
|
|
428
|
+
}
|
|
429
|
+
function useRuntime() {
|
|
430
|
+
const ctx = useContext(RuntimeContext);
|
|
431
|
+
if (!ctx) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"useRuntime() must be used within a <RuntimeProvider>. Did you forget to use createGlyphRuntime()?"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return ctx;
|
|
437
|
+
}
|
|
438
|
+
function useReferences(blockId) {
|
|
439
|
+
const { references } = useRuntime();
|
|
440
|
+
return useMemo(() => {
|
|
441
|
+
const incoming = [];
|
|
442
|
+
const outgoing = [];
|
|
443
|
+
for (const ref of references) {
|
|
444
|
+
if (ref.sourceBlockId === blockId) {
|
|
445
|
+
outgoing.push(ref);
|
|
446
|
+
}
|
|
447
|
+
if (ref.targetBlockId === blockId) {
|
|
448
|
+
incoming.push(ref);
|
|
449
|
+
}
|
|
450
|
+
if (ref.bidirectional) {
|
|
451
|
+
if (ref.targetBlockId === blockId && ref.sourceBlockId !== blockId) {
|
|
452
|
+
outgoing.push(ref);
|
|
453
|
+
}
|
|
454
|
+
if (ref.sourceBlockId === blockId && ref.targetBlockId !== blockId) {
|
|
455
|
+
incoming.push(ref);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return { incomingRefs: incoming, outgoingRefs: outgoing };
|
|
460
|
+
}, [references, blockId]);
|
|
461
|
+
}
|
|
462
|
+
var ErrorBoundary = class extends Component {
|
|
463
|
+
constructor(props) {
|
|
464
|
+
super(props);
|
|
465
|
+
this.state = { hasError: false, error: null };
|
|
466
|
+
}
|
|
467
|
+
static getDerivedStateFromError(error) {
|
|
468
|
+
return { hasError: true, error };
|
|
469
|
+
}
|
|
470
|
+
componentDidCatch(error, info) {
|
|
471
|
+
const { blockId, blockType, onDiagnostic } = this.props;
|
|
472
|
+
onDiagnostic({
|
|
473
|
+
severity: "error",
|
|
474
|
+
code: "RUNTIME_RENDER_ERROR",
|
|
475
|
+
message: `Error rendering block "${blockId}" (type: ${blockType}): ${error.message}`,
|
|
476
|
+
source: "runtime",
|
|
477
|
+
details: {
|
|
478
|
+
blockId,
|
|
479
|
+
blockType,
|
|
480
|
+
errorMessage: error.message,
|
|
481
|
+
componentStack: info.componentStack
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
render() {
|
|
486
|
+
if (this.state.hasError) {
|
|
487
|
+
const { blockId, blockType } = this.props;
|
|
488
|
+
const { error } = this.state;
|
|
489
|
+
return /* @__PURE__ */ jsxs(
|
|
490
|
+
"div",
|
|
491
|
+
{
|
|
492
|
+
style: {
|
|
493
|
+
border: "1px solid #e53e3e",
|
|
494
|
+
borderRadius: "4px",
|
|
495
|
+
padding: "8px 12px",
|
|
496
|
+
margin: "4px 0",
|
|
497
|
+
backgroundColor: "#fff5f5",
|
|
498
|
+
fontSize: "13px",
|
|
499
|
+
fontFamily: "monospace"
|
|
500
|
+
},
|
|
501
|
+
children: [
|
|
502
|
+
/* @__PURE__ */ jsxs("div", { style: { color: "#e53e3e", fontWeight: 600 }, children: [
|
|
503
|
+
'Render error in block "',
|
|
504
|
+
blockId,
|
|
505
|
+
'" (',
|
|
506
|
+
blockType,
|
|
507
|
+
")"
|
|
508
|
+
] }),
|
|
509
|
+
error && /* @__PURE__ */ jsx("div", { style: { color: "#742a2a", marginTop: "4px" }, children: error.message })
|
|
510
|
+
]
|
|
511
|
+
}
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return this.props.children;
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
function FallbackRenderer({ block }) {
|
|
518
|
+
let dataPreview;
|
|
519
|
+
try {
|
|
520
|
+
dataPreview = JSON.stringify(block.data, null, 2);
|
|
521
|
+
} catch {
|
|
522
|
+
dataPreview = "[Unable to serialize block data]";
|
|
523
|
+
}
|
|
524
|
+
return /* @__PURE__ */ jsxs(
|
|
525
|
+
"div",
|
|
526
|
+
{
|
|
527
|
+
style: {
|
|
528
|
+
border: "1px dashed #a0aec0",
|
|
529
|
+
borderRadius: "4px",
|
|
530
|
+
padding: "8px 12px",
|
|
531
|
+
margin: "4px 0",
|
|
532
|
+
backgroundColor: "#f7fafc",
|
|
533
|
+
fontSize: "12px",
|
|
534
|
+
fontFamily: "monospace",
|
|
535
|
+
color: "#718096"
|
|
536
|
+
},
|
|
537
|
+
children: [
|
|
538
|
+
/* @__PURE__ */ jsxs("div", { style: { fontWeight: 600, marginBottom: "4px" }, children: [
|
|
539
|
+
"Unknown block type: ",
|
|
540
|
+
block.type
|
|
541
|
+
] }),
|
|
542
|
+
/* @__PURE__ */ jsx(
|
|
543
|
+
"pre",
|
|
544
|
+
{
|
|
545
|
+
style: {
|
|
546
|
+
margin: 0,
|
|
547
|
+
whiteSpace: "pre-wrap",
|
|
548
|
+
wordBreak: "break-word",
|
|
549
|
+
maxHeight: "200px",
|
|
550
|
+
overflow: "auto"
|
|
551
|
+
},
|
|
552
|
+
children: dataPreview
|
|
553
|
+
}
|
|
554
|
+
)
|
|
555
|
+
]
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
var HIGHLIGHT_DURATION_MS = 1500;
|
|
560
|
+
var SCROLL_BEHAVIOR = "smooth";
|
|
561
|
+
var BLOCK_SELECTOR_PREFIX = "data-glyph-block";
|
|
562
|
+
function useNavigation() {
|
|
563
|
+
const { onNavigate, references } = useRuntime();
|
|
564
|
+
const announcerRef = useRef(null);
|
|
565
|
+
const navigateTo = useCallback(
|
|
566
|
+
(blockId, ref) => {
|
|
567
|
+
const el = document.querySelector(
|
|
568
|
+
`[${BLOCK_SELECTOR_PREFIX}="${blockId}"]`
|
|
569
|
+
);
|
|
570
|
+
if (!el || !(el instanceof HTMLElement)) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
el.scrollIntoView({ behavior: SCROLL_BEHAVIOR, block: "center" });
|
|
574
|
+
el.setAttribute("data-glyph-highlight", "true");
|
|
575
|
+
setTimeout(() => {
|
|
576
|
+
el.removeAttribute("data-glyph-highlight");
|
|
577
|
+
}, HIGHLIGHT_DURATION_MS);
|
|
578
|
+
if (!el.hasAttribute("tabindex")) {
|
|
579
|
+
el.setAttribute("tabindex", "-1");
|
|
580
|
+
}
|
|
581
|
+
el.focus({ preventScroll: true });
|
|
582
|
+
announceNavigation(blockId, announcerRef);
|
|
583
|
+
if (ref) {
|
|
584
|
+
const targetBlock = {
|
|
585
|
+
id: blockId,
|
|
586
|
+
type: "paragraph",
|
|
587
|
+
data: {},
|
|
588
|
+
position: {
|
|
589
|
+
start: { line: 0, column: 0 },
|
|
590
|
+
end: { line: 0, column: 0 }
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
onNavigate(ref, targetBlock);
|
|
594
|
+
} else if (references.length > 0) {
|
|
595
|
+
const matchedRef = references.find(
|
|
596
|
+
(r) => r.targetBlockId === blockId || r.sourceBlockId === blockId
|
|
597
|
+
);
|
|
598
|
+
if (matchedRef) {
|
|
599
|
+
const targetBlock = {
|
|
600
|
+
id: blockId,
|
|
601
|
+
type: "paragraph",
|
|
602
|
+
data: {},
|
|
603
|
+
position: {
|
|
604
|
+
start: { line: 0, column: 0 },
|
|
605
|
+
end: { line: 0, column: 0 }
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
onNavigate(matchedRef, targetBlock);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
[onNavigate, references]
|
|
613
|
+
);
|
|
614
|
+
return { navigateTo };
|
|
615
|
+
}
|
|
616
|
+
function announceNavigation(blockId, ref) {
|
|
617
|
+
let announcer = ref.current;
|
|
618
|
+
if (!announcer) {
|
|
619
|
+
announcer = document.createElement("div");
|
|
620
|
+
announcer.setAttribute("aria-live", "polite");
|
|
621
|
+
announcer.setAttribute("aria-atomic", "true");
|
|
622
|
+
announcer.setAttribute("role", "status");
|
|
623
|
+
Object.assign(announcer.style, {
|
|
624
|
+
position: "absolute",
|
|
625
|
+
width: "1px",
|
|
626
|
+
height: "1px",
|
|
627
|
+
padding: "0",
|
|
628
|
+
margin: "-1px",
|
|
629
|
+
overflow: "hidden",
|
|
630
|
+
clip: "rect(0, 0, 0, 0)",
|
|
631
|
+
whiteSpace: "nowrap",
|
|
632
|
+
border: "0"
|
|
633
|
+
});
|
|
634
|
+
document.body.appendChild(announcer);
|
|
635
|
+
ref.current = announcer;
|
|
636
|
+
}
|
|
637
|
+
announcer.textContent = `Navigated to block ${blockId}`;
|
|
638
|
+
}
|
|
639
|
+
var GLYPH_LINK_PREFIX = "#glyph:";
|
|
640
|
+
function GlyphLink({
|
|
641
|
+
blockId,
|
|
642
|
+
title,
|
|
643
|
+
children
|
|
644
|
+
}) {
|
|
645
|
+
const { navigateTo } = useNavigation();
|
|
646
|
+
const handleClick = useCallback(
|
|
647
|
+
(e) => {
|
|
648
|
+
e.preventDefault();
|
|
649
|
+
navigateTo(blockId);
|
|
650
|
+
},
|
|
651
|
+
[navigateTo, blockId]
|
|
652
|
+
);
|
|
653
|
+
return /* @__PURE__ */ jsx(
|
|
654
|
+
"a",
|
|
655
|
+
{
|
|
656
|
+
href: `${GLYPH_LINK_PREFIX}${blockId}`,
|
|
657
|
+
title,
|
|
658
|
+
onClick: handleClick,
|
|
659
|
+
"data-glyph-ref": blockId,
|
|
660
|
+
role: "link",
|
|
661
|
+
children
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
function isGlyphLink(url) {
|
|
666
|
+
return url.startsWith(GLYPH_LINK_PREFIX);
|
|
667
|
+
}
|
|
668
|
+
function extractBlockId(url) {
|
|
669
|
+
return url.slice(GLYPH_LINK_PREFIX.length);
|
|
670
|
+
}
|
|
671
|
+
function renderInlineNode(node, index) {
|
|
672
|
+
switch (node.type) {
|
|
673
|
+
case "text":
|
|
674
|
+
return node.value;
|
|
675
|
+
case "strong":
|
|
676
|
+
return /* @__PURE__ */ jsx("strong", { children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: node.children }) }, index);
|
|
677
|
+
case "emphasis":
|
|
678
|
+
return /* @__PURE__ */ jsx("em", { children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: node.children }) }, index);
|
|
679
|
+
case "delete":
|
|
680
|
+
return /* @__PURE__ */ jsx("del", { children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: node.children }) }, index);
|
|
681
|
+
case "inlineCode":
|
|
682
|
+
return /* @__PURE__ */ jsx("code", { children: node.value }, index);
|
|
683
|
+
case "link":
|
|
684
|
+
if (isGlyphLink(node.url)) {
|
|
685
|
+
return /* @__PURE__ */ jsx(
|
|
686
|
+
GlyphLink,
|
|
687
|
+
{
|
|
688
|
+
blockId: extractBlockId(node.url),
|
|
689
|
+
title: node.title,
|
|
690
|
+
children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: node.children })
|
|
691
|
+
},
|
|
692
|
+
index
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
return /* @__PURE__ */ jsx("a", { href: node.url, title: node.title, children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: node.children }) }, index);
|
|
696
|
+
case "image":
|
|
697
|
+
return /* @__PURE__ */ jsx("img", { src: node.src, alt: node.alt, title: node.title }, index);
|
|
698
|
+
case "break":
|
|
699
|
+
return /* @__PURE__ */ jsx("br", {}, index);
|
|
700
|
+
default:
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
function InlineRenderer({ nodes }) {
|
|
705
|
+
return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node, i) => renderInlineNode(node, i)) });
|
|
706
|
+
}
|
|
707
|
+
function extractText(nodes) {
|
|
708
|
+
return nodes.map((node) => {
|
|
709
|
+
switch (node.type) {
|
|
710
|
+
case "text":
|
|
711
|
+
return node.value;
|
|
712
|
+
case "inlineCode":
|
|
713
|
+
return node.value;
|
|
714
|
+
case "strong":
|
|
715
|
+
case "emphasis":
|
|
716
|
+
case "delete":
|
|
717
|
+
case "link":
|
|
718
|
+
return extractText(node.children);
|
|
719
|
+
default:
|
|
720
|
+
return "";
|
|
721
|
+
}
|
|
722
|
+
}).join("");
|
|
723
|
+
}
|
|
724
|
+
function slugify(text) {
|
|
725
|
+
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
726
|
+
}
|
|
727
|
+
function GlyphHeading({ block }) {
|
|
728
|
+
const data = block.data;
|
|
729
|
+
const Tag = `h${data.depth}`;
|
|
730
|
+
const id = slugify(extractText(data.children));
|
|
731
|
+
return /* @__PURE__ */ jsx(Tag, { id, children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: data.children }) });
|
|
732
|
+
}
|
|
733
|
+
function GlyphParagraph({ block }) {
|
|
734
|
+
const data = block.data;
|
|
735
|
+
return /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: data.children }) });
|
|
736
|
+
}
|
|
737
|
+
function renderSubList(list) {
|
|
738
|
+
const items = list.items.map((item, i) => renderListItem(item, i));
|
|
739
|
+
if (list.ordered) {
|
|
740
|
+
return /* @__PURE__ */ jsx("ol", { start: list.start, children: items });
|
|
741
|
+
}
|
|
742
|
+
return /* @__PURE__ */ jsx("ul", { children: items });
|
|
743
|
+
}
|
|
744
|
+
function renderListItem(item, index) {
|
|
745
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
746
|
+
/* @__PURE__ */ jsx(InlineRenderer, { nodes: item.children }),
|
|
747
|
+
item.subList ? renderSubList(item.subList) : null
|
|
748
|
+
] }, index);
|
|
749
|
+
}
|
|
750
|
+
function GlyphList({ block }) {
|
|
751
|
+
const data = block.data;
|
|
752
|
+
return renderSubList(data);
|
|
753
|
+
}
|
|
754
|
+
function GlyphCodeBlock({ block }) {
|
|
755
|
+
const data = block.data;
|
|
756
|
+
const language = data.language ?? void 0;
|
|
757
|
+
return /* @__PURE__ */ jsx("pre", { "data-language": language, "aria-label": language ? `Code block (${language})` : "Code block", children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children: data.value }) });
|
|
758
|
+
}
|
|
759
|
+
function GlyphBlockquote({ block }) {
|
|
760
|
+
const data = block.data;
|
|
761
|
+
return /* @__PURE__ */ jsx("blockquote", { children: /* @__PURE__ */ jsx(InlineRenderer, { nodes: data.children }) });
|
|
762
|
+
}
|
|
763
|
+
function GlyphImage({ block }) {
|
|
764
|
+
const data = block.data;
|
|
765
|
+
return /* @__PURE__ */ jsxs("figure", { children: [
|
|
766
|
+
/* @__PURE__ */ jsx("img", { src: data.src, alt: data.alt ?? "", loading: "lazy" }),
|
|
767
|
+
data.title ? /* @__PURE__ */ jsx("figcaption", { children: data.title }) : null
|
|
768
|
+
] });
|
|
769
|
+
}
|
|
770
|
+
function GlyphThematicBreak(_props) {
|
|
771
|
+
return /* @__PURE__ */ jsx("hr", {});
|
|
772
|
+
}
|
|
773
|
+
function GlyphRawHtml({ block }) {
|
|
774
|
+
const data = block.data;
|
|
775
|
+
const clean = DOMPurify.sanitize(data.value);
|
|
776
|
+
return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: clean } });
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/renderers/index.ts
|
|
780
|
+
var builtInRenderers = {
|
|
781
|
+
heading: GlyphHeading,
|
|
782
|
+
paragraph: GlyphParagraph,
|
|
783
|
+
list: GlyphList,
|
|
784
|
+
code: GlyphCodeBlock,
|
|
785
|
+
blockquote: GlyphBlockquote,
|
|
786
|
+
image: GlyphImage,
|
|
787
|
+
"thematic-break": GlyphThematicBreak,
|
|
788
|
+
html: GlyphRawHtml
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// src/plugins/resolve-props.ts
|
|
792
|
+
function resolveComponentProps(block, definition, references, onNavigate, themeContext, layoutHints, containerContext) {
|
|
793
|
+
const parseResult = definition.schema.safeParse(block.data);
|
|
794
|
+
const data = parseResult.success ? parseResult.data : (() => {
|
|
795
|
+
const err = parseResult.error;
|
|
796
|
+
const issues = err != null && typeof err === "object" && "issues" in err && Array.isArray(err.issues) ? err.issues.map(
|
|
797
|
+
(i) => `${i.path.join(".")}: ${i.message}`
|
|
798
|
+
).join("; ") : "unknown error";
|
|
799
|
+
console.warn(
|
|
800
|
+
`[GlyphJS] Schema validation failed for block "${block.id}" (${block.type}). Falling back to raw data. Issues: ${issues}`
|
|
801
|
+
);
|
|
802
|
+
return block.data;
|
|
803
|
+
})();
|
|
804
|
+
const outgoingRefs = [];
|
|
805
|
+
const incomingRefs = [];
|
|
806
|
+
for (const ref of references) {
|
|
807
|
+
if (ref.sourceBlockId === block.id) {
|
|
808
|
+
outgoingRefs.push(ref);
|
|
809
|
+
}
|
|
810
|
+
if (ref.targetBlockId === block.id) {
|
|
811
|
+
incomingRefs.push(ref);
|
|
812
|
+
}
|
|
813
|
+
if (ref.bidirectional) {
|
|
814
|
+
if (ref.targetBlockId === block.id && ref.sourceBlockId !== block.id) {
|
|
815
|
+
outgoingRefs.push(ref);
|
|
816
|
+
}
|
|
817
|
+
if (ref.sourceBlockId === block.id && ref.targetBlockId !== block.id) {
|
|
818
|
+
incomingRefs.push(ref);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return {
|
|
823
|
+
data,
|
|
824
|
+
block,
|
|
825
|
+
outgoingRefs,
|
|
826
|
+
incomingRefs,
|
|
827
|
+
onNavigate,
|
|
828
|
+
theme: themeContext,
|
|
829
|
+
layout: layoutHints,
|
|
830
|
+
container: containerContext
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
var defaultConfig = {
|
|
834
|
+
enabled: true,
|
|
835
|
+
duration: 300,
|
|
836
|
+
easing: "ease-out",
|
|
837
|
+
staggerDelay: 50
|
|
838
|
+
};
|
|
839
|
+
var AnimationContext = createContext(defaultConfig);
|
|
840
|
+
function AnimationProvider({
|
|
841
|
+
config,
|
|
842
|
+
children
|
|
843
|
+
}) {
|
|
844
|
+
const state = useMemo(
|
|
845
|
+
() => ({
|
|
846
|
+
...defaultConfig,
|
|
847
|
+
...config
|
|
848
|
+
}),
|
|
849
|
+
[config]
|
|
850
|
+
);
|
|
851
|
+
return /* @__PURE__ */ jsx(AnimationContext, { value: state, children });
|
|
852
|
+
}
|
|
853
|
+
function useAnimation() {
|
|
854
|
+
return useContext(AnimationContext);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// src/animation/useBlockAnimation.ts
|
|
858
|
+
function usePrefersReducedMotion() {
|
|
859
|
+
const [prefersReduced, setPrefersReduced] = useState(false);
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
865
|
+
setPrefersReduced(mql.matches);
|
|
866
|
+
const handler = (e) => {
|
|
867
|
+
setPrefersReduced(e.matches);
|
|
868
|
+
};
|
|
869
|
+
mql.addEventListener("change", handler);
|
|
870
|
+
return () => {
|
|
871
|
+
mql.removeEventListener("change", handler);
|
|
872
|
+
};
|
|
873
|
+
}, []);
|
|
874
|
+
return prefersReduced;
|
|
875
|
+
}
|
|
876
|
+
function useBlockAnimation(index) {
|
|
877
|
+
const config = useAnimation();
|
|
878
|
+
const prefersReduced = usePrefersReducedMotion();
|
|
879
|
+
const ref = useRef(null);
|
|
880
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
881
|
+
const disabled = !config.enabled || prefersReduced;
|
|
882
|
+
useEffect(() => {
|
|
883
|
+
if (disabled) {
|
|
884
|
+
setIsVisible(true);
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
const element = ref.current;
|
|
888
|
+
if (!element) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
892
|
+
setIsVisible(true);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
const observer = new IntersectionObserver(
|
|
896
|
+
(entries) => {
|
|
897
|
+
const entry = entries[0];
|
|
898
|
+
if (entry && entry.isIntersecting) {
|
|
899
|
+
setIsVisible(true);
|
|
900
|
+
observer.unobserve(element);
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
{ threshold: 0.1 }
|
|
904
|
+
);
|
|
905
|
+
observer.observe(element);
|
|
906
|
+
return () => {
|
|
907
|
+
observer.disconnect();
|
|
908
|
+
};
|
|
909
|
+
}, [disabled]);
|
|
910
|
+
const style = useMemo(() => {
|
|
911
|
+
if (disabled) {
|
|
912
|
+
return {};
|
|
913
|
+
}
|
|
914
|
+
const delay = index * config.staggerDelay;
|
|
915
|
+
return isVisible ? {
|
|
916
|
+
opacity: 1,
|
|
917
|
+
transform: "translateY(0)",
|
|
918
|
+
transition: `opacity ${config.duration}ms ${config.easing} ${delay}ms, transform ${config.duration}ms ${config.easing} ${delay}ms`
|
|
919
|
+
} : {
|
|
920
|
+
opacity: 0,
|
|
921
|
+
transform: "translateY(10px)",
|
|
922
|
+
transition: `opacity ${config.duration}ms ${config.easing} ${delay}ms, transform ${config.duration}ms ${config.easing} ${delay}ms`
|
|
923
|
+
};
|
|
924
|
+
}, [disabled, isVisible, index, config.duration, config.easing, config.staggerDelay]);
|
|
925
|
+
return { ref, style, isVisible };
|
|
926
|
+
}
|
|
927
|
+
var containerStyle = {
|
|
928
|
+
display: "flex",
|
|
929
|
+
flexWrap: "wrap",
|
|
930
|
+
gap: "4px",
|
|
931
|
+
marginTop: "4px"
|
|
932
|
+
};
|
|
933
|
+
var badgeBaseStyle = {
|
|
934
|
+
display: "inline-flex",
|
|
935
|
+
alignItems: "center",
|
|
936
|
+
padding: "1px 6px",
|
|
937
|
+
borderRadius: "9999px",
|
|
938
|
+
fontSize: "11px",
|
|
939
|
+
lineHeight: "1.4",
|
|
940
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
941
|
+
cursor: "pointer",
|
|
942
|
+
border: "1px solid",
|
|
943
|
+
textDecoration: "none",
|
|
944
|
+
transition: "opacity 150ms ease"
|
|
945
|
+
};
|
|
946
|
+
var outgoingBadgeStyle = {
|
|
947
|
+
...badgeBaseStyle,
|
|
948
|
+
backgroundColor: "#eff6ff",
|
|
949
|
+
borderColor: "#bfdbfe",
|
|
950
|
+
color: "#1d4ed8"
|
|
951
|
+
};
|
|
952
|
+
var incomingBadgeStyle = {
|
|
953
|
+
...badgeBaseStyle,
|
|
954
|
+
backgroundColor: "#f0fdf4",
|
|
955
|
+
borderColor: "#bbf7d0",
|
|
956
|
+
color: "#15803d"
|
|
957
|
+
};
|
|
958
|
+
function ReferenceIndicator({
|
|
959
|
+
blockId
|
|
960
|
+
}) {
|
|
961
|
+
const { incomingRefs, outgoingRefs } = useReferences(blockId);
|
|
962
|
+
const { navigateTo } = useNavigation();
|
|
963
|
+
const handleClick = useCallback(
|
|
964
|
+
(targetBlockId, ref) => {
|
|
965
|
+
navigateTo(targetBlockId, ref);
|
|
966
|
+
},
|
|
967
|
+
[navigateTo]
|
|
968
|
+
);
|
|
969
|
+
if (outgoingRefs.length === 0 && incomingRefs.length === 0) {
|
|
970
|
+
return null;
|
|
971
|
+
}
|
|
972
|
+
return /* @__PURE__ */ jsxs(
|
|
973
|
+
"nav",
|
|
974
|
+
{
|
|
975
|
+
"aria-label": `References for block ${blockId}`,
|
|
976
|
+
style: containerStyle,
|
|
977
|
+
children: [
|
|
978
|
+
outgoingRefs.map((ref) => {
|
|
979
|
+
const targetId = ref.sourceBlockId === blockId ? ref.targetBlockId : ref.sourceBlockId;
|
|
980
|
+
const label = ref.label ?? targetId;
|
|
981
|
+
return /* @__PURE__ */ jsxs(
|
|
982
|
+
"button",
|
|
983
|
+
{
|
|
984
|
+
type: "button",
|
|
985
|
+
style: outgoingBadgeStyle,
|
|
986
|
+
onClick: () => handleClick(targetId, ref),
|
|
987
|
+
"aria-label": `Navigate to referenced block: ${label}`,
|
|
988
|
+
title: `Go to: ${label} (${ref.type})`,
|
|
989
|
+
children: [
|
|
990
|
+
"\u2192 ",
|
|
991
|
+
label
|
|
992
|
+
]
|
|
993
|
+
},
|
|
994
|
+
`out-${ref.id}`
|
|
995
|
+
);
|
|
996
|
+
}),
|
|
997
|
+
incomingRefs.map((ref) => {
|
|
998
|
+
const sourceId = ref.targetBlockId === blockId ? ref.sourceBlockId : ref.targetBlockId;
|
|
999
|
+
const label = ref.label ?? sourceId;
|
|
1000
|
+
return /* @__PURE__ */ jsxs(
|
|
1001
|
+
"button",
|
|
1002
|
+
{
|
|
1003
|
+
type: "button",
|
|
1004
|
+
style: incomingBadgeStyle,
|
|
1005
|
+
onClick: () => handleClick(sourceId, ref),
|
|
1006
|
+
"aria-label": `Navigate to referencing block: ${label}`,
|
|
1007
|
+
title: `Referenced by: ${label} (${ref.type})`,
|
|
1008
|
+
children: [
|
|
1009
|
+
"\u2190 ",
|
|
1010
|
+
label
|
|
1011
|
+
]
|
|
1012
|
+
},
|
|
1013
|
+
`in-${ref.id}`
|
|
1014
|
+
);
|
|
1015
|
+
})
|
|
1016
|
+
]
|
|
1017
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
function BlockDispatch({ block, layout, container }) {
|
|
1021
|
+
const { registry, references, theme, onNavigate } = useRuntime();
|
|
1022
|
+
const { incomingRefs, outgoingRefs } = useReferences(block.id);
|
|
1023
|
+
const hasRefs = incomingRefs.length > 0 || outgoingRefs.length > 0;
|
|
1024
|
+
if (block.type.startsWith("ui:")) {
|
|
1025
|
+
const componentName = block.type.slice(3);
|
|
1026
|
+
const schema = componentSchemas.get(componentName);
|
|
1027
|
+
if (schema) {
|
|
1028
|
+
const result = schema.safeParse(block.data);
|
|
1029
|
+
if (!result.success) {
|
|
1030
|
+
console.warn(
|
|
1031
|
+
`[GlyphJS] Schema validation failed for block "${block.id}" (${block.type}):`,
|
|
1032
|
+
result.error.issues
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
let content;
|
|
1038
|
+
const overrideDef = registry.getOverride(block.type);
|
|
1039
|
+
if (overrideDef) {
|
|
1040
|
+
const Override = overrideDef;
|
|
1041
|
+
content = /* @__PURE__ */ jsx(Override, { block, layout });
|
|
1042
|
+
} else {
|
|
1043
|
+
const definition = registry.getRenderer(block.type);
|
|
1044
|
+
if (definition) {
|
|
1045
|
+
const handleNavigate = (ref) => {
|
|
1046
|
+
onNavigate(ref, block);
|
|
1047
|
+
};
|
|
1048
|
+
const props = resolveComponentProps(
|
|
1049
|
+
block,
|
|
1050
|
+
definition,
|
|
1051
|
+
references,
|
|
1052
|
+
handleNavigate,
|
|
1053
|
+
theme,
|
|
1054
|
+
layout,
|
|
1055
|
+
container
|
|
1056
|
+
);
|
|
1057
|
+
const Renderer = definition.render;
|
|
1058
|
+
content = /* @__PURE__ */ jsx(Renderer, { ...props });
|
|
1059
|
+
} else {
|
|
1060
|
+
const BuiltIn = builtInRenderers[block.type];
|
|
1061
|
+
if (BuiltIn) {
|
|
1062
|
+
content = /* @__PURE__ */ jsx(BuiltIn, { block, layout });
|
|
1063
|
+
} else {
|
|
1064
|
+
content = /* @__PURE__ */ jsx(FallbackRenderer, { block });
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return /* @__PURE__ */ jsxs("div", { "data-glyph-block": block.id, children: [
|
|
1069
|
+
content,
|
|
1070
|
+
hasRefs && /* @__PURE__ */ jsx(ReferenceIndicator, { blockId: block.id })
|
|
1071
|
+
] });
|
|
1072
|
+
}
|
|
1073
|
+
function BlockRenderer({
|
|
1074
|
+
block,
|
|
1075
|
+
layout,
|
|
1076
|
+
index = 0,
|
|
1077
|
+
container
|
|
1078
|
+
}) {
|
|
1079
|
+
const { onDiagnostic } = useRuntime();
|
|
1080
|
+
const { ref, style } = useBlockAnimation(index);
|
|
1081
|
+
return /* @__PURE__ */ jsx("div", { ref, style, "data-glyph-block-anim": block.id, children: /* @__PURE__ */ jsx(ErrorBoundary, { blockId: block.id, blockType: block.type, onDiagnostic, children: /* @__PURE__ */ jsx(BlockDispatch, { block, layout, container }) }) });
|
|
1082
|
+
}
|
|
1083
|
+
var defaultLayout = {
|
|
1084
|
+
mode: "document",
|
|
1085
|
+
spacing: "normal"
|
|
1086
|
+
};
|
|
1087
|
+
var LayoutContext = createContext(defaultLayout);
|
|
1088
|
+
function LayoutProvider({
|
|
1089
|
+
layout,
|
|
1090
|
+
children
|
|
1091
|
+
}) {
|
|
1092
|
+
return /* @__PURE__ */ jsx(LayoutContext, { value: layout, children });
|
|
1093
|
+
}
|
|
1094
|
+
function useLayout() {
|
|
1095
|
+
return useContext(LayoutContext);
|
|
1096
|
+
}
|
|
1097
|
+
var spacingMap = {
|
|
1098
|
+
compact: "0.5rem",
|
|
1099
|
+
normal: "1rem",
|
|
1100
|
+
relaxed: "2rem"
|
|
1101
|
+
};
|
|
1102
|
+
function DocumentLayout({
|
|
1103
|
+
blocks,
|
|
1104
|
+
layout,
|
|
1105
|
+
renderBlock
|
|
1106
|
+
}) {
|
|
1107
|
+
const gap = spacingMap[layout.spacing ?? "normal"];
|
|
1108
|
+
const containerStyle2 = {
|
|
1109
|
+
maxWidth: layout.maxWidth ?? "none",
|
|
1110
|
+
margin: "0 auto",
|
|
1111
|
+
display: "flex",
|
|
1112
|
+
flexDirection: "column",
|
|
1113
|
+
gap
|
|
1114
|
+
};
|
|
1115
|
+
return /* @__PURE__ */ jsx("div", { style: containerStyle2, "data-glyph-layout": "document", children: blocks.map((block, index) => /* @__PURE__ */ jsx("div", { children: renderBlock(block, index) }, block.id)) });
|
|
1116
|
+
}
|
|
1117
|
+
function DashboardLayout({
|
|
1118
|
+
blocks,
|
|
1119
|
+
layout,
|
|
1120
|
+
renderBlock
|
|
1121
|
+
}) {
|
|
1122
|
+
const columns = layout.columns ?? 2;
|
|
1123
|
+
const containerStyle2 = {
|
|
1124
|
+
display: "grid",
|
|
1125
|
+
gridTemplateColumns: `repeat(${String(columns)}, 1fr)`,
|
|
1126
|
+
gap: "1rem"
|
|
1127
|
+
};
|
|
1128
|
+
return /* @__PURE__ */ jsx("div", { style: containerStyle2, "data-glyph-layout": "dashboard", children: blocks.map((block, index) => {
|
|
1129
|
+
const override = layout.blockLayout?.[block.id];
|
|
1130
|
+
const cellStyle = {};
|
|
1131
|
+
if (override) {
|
|
1132
|
+
if (override.gridColumn) {
|
|
1133
|
+
cellStyle.gridColumn = override.gridColumn;
|
|
1134
|
+
}
|
|
1135
|
+
if (override.gridRow) {
|
|
1136
|
+
cellStyle.gridRow = override.gridRow;
|
|
1137
|
+
}
|
|
1138
|
+
if (override.span) {
|
|
1139
|
+
cellStyle.gridColumn = `span ${String(override.span)}`;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return /* @__PURE__ */ jsx("div", { style: cellStyle, children: renderBlock(block, index) }, block.id);
|
|
1143
|
+
}) });
|
|
1144
|
+
}
|
|
1145
|
+
function PresentationLayout({
|
|
1146
|
+
blocks,
|
|
1147
|
+
renderBlock
|
|
1148
|
+
}) {
|
|
1149
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
1150
|
+
const total = blocks.length;
|
|
1151
|
+
const goNext = useCallback(() => {
|
|
1152
|
+
setCurrentIndex((i) => Math.min(i + 1, total - 1));
|
|
1153
|
+
}, [total]);
|
|
1154
|
+
const goPrev = useCallback(() => {
|
|
1155
|
+
setCurrentIndex((i) => Math.max(i - 1, 0));
|
|
1156
|
+
}, []);
|
|
1157
|
+
useEffect(() => {
|
|
1158
|
+
function handleKeyDown(e) {
|
|
1159
|
+
switch (e.key) {
|
|
1160
|
+
case "ArrowRight":
|
|
1161
|
+
case "ArrowDown":
|
|
1162
|
+
case " ":
|
|
1163
|
+
e.preventDefault();
|
|
1164
|
+
goNext();
|
|
1165
|
+
break;
|
|
1166
|
+
case "ArrowLeft":
|
|
1167
|
+
case "ArrowUp":
|
|
1168
|
+
e.preventDefault();
|
|
1169
|
+
goPrev();
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
1174
|
+
return () => {
|
|
1175
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
1176
|
+
};
|
|
1177
|
+
}, [goNext, goPrev]);
|
|
1178
|
+
if (total === 0) {
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
const currentBlock = blocks[currentIndex];
|
|
1182
|
+
const containerStyle2 = {
|
|
1183
|
+
position: "relative",
|
|
1184
|
+
width: "100vw",
|
|
1185
|
+
height: "100vh",
|
|
1186
|
+
overflow: "hidden"
|
|
1187
|
+
};
|
|
1188
|
+
const slideStyle = {
|
|
1189
|
+
width: "100%",
|
|
1190
|
+
height: "100%",
|
|
1191
|
+
display: "flex",
|
|
1192
|
+
alignItems: "center",
|
|
1193
|
+
justifyContent: "center",
|
|
1194
|
+
padding: "2rem",
|
|
1195
|
+
boxSizing: "border-box"
|
|
1196
|
+
};
|
|
1197
|
+
const indicatorStyle = {
|
|
1198
|
+
position: "absolute",
|
|
1199
|
+
bottom: "1rem",
|
|
1200
|
+
right: "1rem",
|
|
1201
|
+
fontSize: "0.875rem",
|
|
1202
|
+
color: "#718096",
|
|
1203
|
+
fontFamily: "sans-serif",
|
|
1204
|
+
userSelect: "none"
|
|
1205
|
+
};
|
|
1206
|
+
return /* @__PURE__ */ jsxs("div", { style: containerStyle2, "data-glyph-layout": "presentation", children: [
|
|
1207
|
+
/* @__PURE__ */ jsx("div", { style: slideStyle, children: currentBlock ? renderBlock(currentBlock, currentIndex) : null }),
|
|
1208
|
+
/* @__PURE__ */ jsxs("div", { style: indicatorStyle, children: [
|
|
1209
|
+
String(currentIndex + 1),
|
|
1210
|
+
" / ",
|
|
1211
|
+
String(total)
|
|
1212
|
+
] })
|
|
1213
|
+
] });
|
|
1214
|
+
}
|
|
1215
|
+
var severityColors = {
|
|
1216
|
+
error: "#dc2626",
|
|
1217
|
+
warning: "#d97706",
|
|
1218
|
+
info: "#2563eb"
|
|
1219
|
+
};
|
|
1220
|
+
var severityBackgrounds = {
|
|
1221
|
+
error: "#fef2f2",
|
|
1222
|
+
warning: "#fffbeb",
|
|
1223
|
+
info: "#eff6ff"
|
|
1224
|
+
};
|
|
1225
|
+
var severityIcons = {
|
|
1226
|
+
error: "\u2716",
|
|
1227
|
+
// heavy multiplication sign
|
|
1228
|
+
warning: "\u26A0",
|
|
1229
|
+
// warning sign
|
|
1230
|
+
info: "\u2139"
|
|
1231
|
+
// information source
|
|
1232
|
+
};
|
|
1233
|
+
function formatPosition(diagnostic) {
|
|
1234
|
+
if (!diagnostic.position) return "";
|
|
1235
|
+
const { start } = diagnostic.position;
|
|
1236
|
+
return `${String(start.line)}:${String(start.column)}`;
|
|
1237
|
+
}
|
|
1238
|
+
var overlayStyle = {
|
|
1239
|
+
position: "fixed",
|
|
1240
|
+
bottom: "16px",
|
|
1241
|
+
right: "16px",
|
|
1242
|
+
maxWidth: "480px",
|
|
1243
|
+
maxHeight: "60vh",
|
|
1244
|
+
overflowY: "auto",
|
|
1245
|
+
backgroundColor: "#ffffff",
|
|
1246
|
+
border: "1px solid #e5e7eb",
|
|
1247
|
+
borderRadius: "8px",
|
|
1248
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
|
|
1249
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
1250
|
+
fontSize: "13px",
|
|
1251
|
+
zIndex: 9999
|
|
1252
|
+
};
|
|
1253
|
+
var headerStyle = {
|
|
1254
|
+
display: "flex",
|
|
1255
|
+
alignItems: "center",
|
|
1256
|
+
justifyContent: "space-between",
|
|
1257
|
+
padding: "10px 14px",
|
|
1258
|
+
borderBottom: "1px solid #e5e7eb",
|
|
1259
|
+
backgroundColor: "#f9fafb",
|
|
1260
|
+
borderRadius: "8px 8px 0 0"
|
|
1261
|
+
};
|
|
1262
|
+
var closeButtonStyle = {
|
|
1263
|
+
background: "none",
|
|
1264
|
+
border: "none",
|
|
1265
|
+
cursor: "pointer",
|
|
1266
|
+
fontSize: "16px",
|
|
1267
|
+
color: "#6b7280",
|
|
1268
|
+
padding: "2px 6px",
|
|
1269
|
+
borderRadius: "4px",
|
|
1270
|
+
lineHeight: 1
|
|
1271
|
+
};
|
|
1272
|
+
var itemStyle = {
|
|
1273
|
+
padding: "8px 14px",
|
|
1274
|
+
borderBottom: "1px solid #f3f4f6"
|
|
1275
|
+
};
|
|
1276
|
+
var itemHeaderStyle = {
|
|
1277
|
+
display: "flex",
|
|
1278
|
+
alignItems: "center",
|
|
1279
|
+
gap: "6px"
|
|
1280
|
+
};
|
|
1281
|
+
var codeStyle = {
|
|
1282
|
+
color: "#6b7280",
|
|
1283
|
+
fontSize: "11px"
|
|
1284
|
+
};
|
|
1285
|
+
var messageStyle = {
|
|
1286
|
+
marginTop: "2px",
|
|
1287
|
+
color: "#1f2937",
|
|
1288
|
+
lineHeight: "1.4"
|
|
1289
|
+
};
|
|
1290
|
+
var positionStyle = {
|
|
1291
|
+
color: "#9ca3af",
|
|
1292
|
+
fontSize: "11px",
|
|
1293
|
+
marginTop: "2px"
|
|
1294
|
+
};
|
|
1295
|
+
function DiagnosticsOverlay({
|
|
1296
|
+
diagnostics
|
|
1297
|
+
}) {
|
|
1298
|
+
const [dismissed, setDismissed] = useState(false);
|
|
1299
|
+
useEffect(() => {
|
|
1300
|
+
setDismissed(false);
|
|
1301
|
+
}, [diagnostics]);
|
|
1302
|
+
const handleDismiss = useCallback(() => {
|
|
1303
|
+
setDismissed(true);
|
|
1304
|
+
}, []);
|
|
1305
|
+
useEffect(() => {
|
|
1306
|
+
function handleKeyDown(e) {
|
|
1307
|
+
if (e.key === "Escape") {
|
|
1308
|
+
setDismissed(true);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1312
|
+
return () => {
|
|
1313
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
1314
|
+
};
|
|
1315
|
+
}, []);
|
|
1316
|
+
const summary = useMemo(() => {
|
|
1317
|
+
let errors = 0;
|
|
1318
|
+
let warnings = 0;
|
|
1319
|
+
let infos = 0;
|
|
1320
|
+
for (const d of diagnostics) {
|
|
1321
|
+
if (d.severity === "error") errors++;
|
|
1322
|
+
else if (d.severity === "warning") warnings++;
|
|
1323
|
+
else infos++;
|
|
1324
|
+
}
|
|
1325
|
+
const parts = [];
|
|
1326
|
+
if (errors > 0) parts.push(`${String(errors)} error${errors > 1 ? "s" : ""}`);
|
|
1327
|
+
if (warnings > 0)
|
|
1328
|
+
parts.push(`${String(warnings)} warning${warnings > 1 ? "s" : ""}`);
|
|
1329
|
+
if (infos > 0) parts.push(`${String(infos)} info`);
|
|
1330
|
+
return parts.join(", ");
|
|
1331
|
+
}, [diagnostics]);
|
|
1332
|
+
if (diagnostics.length === 0 || dismissed) {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
return /* @__PURE__ */ jsxs(
|
|
1336
|
+
"div",
|
|
1337
|
+
{
|
|
1338
|
+
style: overlayStyle,
|
|
1339
|
+
"data-glyph-diagnostics-overlay": true,
|
|
1340
|
+
role: "complementary",
|
|
1341
|
+
"aria-label": "Diagnostics overlay",
|
|
1342
|
+
children: [
|
|
1343
|
+
/* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
|
|
1344
|
+
/* @__PURE__ */ jsx("span", { style: { fontWeight: 600, color: "#1f2937" }, children: summary }),
|
|
1345
|
+
/* @__PURE__ */ jsx(
|
|
1346
|
+
"button",
|
|
1347
|
+
{
|
|
1348
|
+
type: "button",
|
|
1349
|
+
style: closeButtonStyle,
|
|
1350
|
+
onClick: handleDismiss,
|
|
1351
|
+
"aria-label": "Dismiss diagnostics",
|
|
1352
|
+
children: "\u2715"
|
|
1353
|
+
}
|
|
1354
|
+
)
|
|
1355
|
+
] }),
|
|
1356
|
+
/* @__PURE__ */ jsx("div", { children: diagnostics.map((diagnostic, index) => {
|
|
1357
|
+
const color = severityColors[diagnostic.severity];
|
|
1358
|
+
const bg = severityBackgrounds[diagnostic.severity];
|
|
1359
|
+
const icon = severityIcons[diagnostic.severity];
|
|
1360
|
+
const pos = formatPosition(diagnostic);
|
|
1361
|
+
return /* @__PURE__ */ jsxs(
|
|
1362
|
+
"div",
|
|
1363
|
+
{
|
|
1364
|
+
style: { ...itemStyle, backgroundColor: bg },
|
|
1365
|
+
children: [
|
|
1366
|
+
/* @__PURE__ */ jsxs("div", { style: itemHeaderStyle, children: [
|
|
1367
|
+
/* @__PURE__ */ jsx("span", { style: { color, fontWeight: 700 }, children: icon }),
|
|
1368
|
+
/* @__PURE__ */ jsx("span", { style: { color, fontWeight: 600 }, children: diagnostic.severity }),
|
|
1369
|
+
diagnostic.code && /* @__PURE__ */ jsxs("span", { style: codeStyle, children: [
|
|
1370
|
+
"[",
|
|
1371
|
+
diagnostic.code,
|
|
1372
|
+
"]"
|
|
1373
|
+
] })
|
|
1374
|
+
] }),
|
|
1375
|
+
/* @__PURE__ */ jsx("div", { style: messageStyle, children: diagnostic.message }),
|
|
1376
|
+
pos && /* @__PURE__ */ jsxs("div", { style: positionStyle, children: [
|
|
1377
|
+
"at ",
|
|
1378
|
+
pos
|
|
1379
|
+
] })
|
|
1380
|
+
]
|
|
1381
|
+
},
|
|
1382
|
+
`${diagnostic.code}-${String(index)}`
|
|
1383
|
+
);
|
|
1384
|
+
}) })
|
|
1385
|
+
]
|
|
1386
|
+
}
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
var severityColors2 = {
|
|
1390
|
+
error: "#dc2626",
|
|
1391
|
+
warning: "#d97706",
|
|
1392
|
+
info: "#2563eb"
|
|
1393
|
+
};
|
|
1394
|
+
var severityBackgrounds2 = {
|
|
1395
|
+
error: "#fef2f2",
|
|
1396
|
+
warning: "#fffbeb",
|
|
1397
|
+
info: "#eff6ff"
|
|
1398
|
+
};
|
|
1399
|
+
var severityIcons2 = {
|
|
1400
|
+
error: "\u2716",
|
|
1401
|
+
warning: "\u26A0",
|
|
1402
|
+
info: "\u2139"
|
|
1403
|
+
};
|
|
1404
|
+
function highestSeverity(diagnostics) {
|
|
1405
|
+
let has = "info";
|
|
1406
|
+
for (const d of diagnostics) {
|
|
1407
|
+
if (d.severity === "error") return "error";
|
|
1408
|
+
if (d.severity === "warning") has = "warning";
|
|
1409
|
+
}
|
|
1410
|
+
return has;
|
|
1411
|
+
}
|
|
1412
|
+
function formatPosition2(diagnostic) {
|
|
1413
|
+
if (!diagnostic.position) return "";
|
|
1414
|
+
const { start } = diagnostic.position;
|
|
1415
|
+
return `${String(start.line)}:${String(start.column)}`;
|
|
1416
|
+
}
|
|
1417
|
+
var wrapperStyle = {
|
|
1418
|
+
position: "relative",
|
|
1419
|
+
display: "inline-block"
|
|
1420
|
+
};
|
|
1421
|
+
var detailsStyle = {
|
|
1422
|
+
position: "absolute",
|
|
1423
|
+
top: "100%",
|
|
1424
|
+
left: 0,
|
|
1425
|
+
marginTop: "4px",
|
|
1426
|
+
minWidth: "280px",
|
|
1427
|
+
maxWidth: "400px",
|
|
1428
|
+
backgroundColor: "#ffffff",
|
|
1429
|
+
border: "1px solid #e5e7eb",
|
|
1430
|
+
borderRadius: "6px",
|
|
1431
|
+
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.12)",
|
|
1432
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
|
|
1433
|
+
fontSize: "12px",
|
|
1434
|
+
zIndex: 9998,
|
|
1435
|
+
overflow: "hidden"
|
|
1436
|
+
};
|
|
1437
|
+
var detailItemStyle = {
|
|
1438
|
+
padding: "6px 10px",
|
|
1439
|
+
borderBottom: "1px solid #f3f4f6"
|
|
1440
|
+
};
|
|
1441
|
+
function BlockDiagnosticIndicator({
|
|
1442
|
+
diagnostics
|
|
1443
|
+
}) {
|
|
1444
|
+
const [expanded, setExpanded] = useState(false);
|
|
1445
|
+
const toggle = useCallback(() => {
|
|
1446
|
+
setExpanded((prev) => !prev);
|
|
1447
|
+
}, []);
|
|
1448
|
+
if (diagnostics.length === 0) {
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
const severity = highestSeverity(diagnostics);
|
|
1452
|
+
const color = severityColors2[severity];
|
|
1453
|
+
const icon = severityIcons2[severity];
|
|
1454
|
+
const badgeStyle = {
|
|
1455
|
+
display: "inline-flex",
|
|
1456
|
+
alignItems: "center",
|
|
1457
|
+
justifyContent: "center",
|
|
1458
|
+
width: "20px",
|
|
1459
|
+
height: "20px",
|
|
1460
|
+
borderRadius: "50%",
|
|
1461
|
+
backgroundColor: color,
|
|
1462
|
+
color: "#ffffff",
|
|
1463
|
+
fontSize: "11px",
|
|
1464
|
+
fontWeight: 700,
|
|
1465
|
+
cursor: "pointer",
|
|
1466
|
+
border: "none",
|
|
1467
|
+
lineHeight: 1,
|
|
1468
|
+
padding: 0
|
|
1469
|
+
};
|
|
1470
|
+
return /* @__PURE__ */ jsxs("span", { style: wrapperStyle, "data-glyph-diagnostic-indicator": true, children: [
|
|
1471
|
+
/* @__PURE__ */ jsx(
|
|
1472
|
+
"button",
|
|
1473
|
+
{
|
|
1474
|
+
type: "button",
|
|
1475
|
+
style: badgeStyle,
|
|
1476
|
+
onClick: toggle,
|
|
1477
|
+
"aria-label": `${String(diagnostics.length)} diagnostic${diagnostics.length > 1 ? "s" : ""}`,
|
|
1478
|
+
"aria-expanded": expanded,
|
|
1479
|
+
title: `${String(diagnostics.length)} diagnostic${diagnostics.length > 1 ? "s" : ""}`,
|
|
1480
|
+
children: icon
|
|
1481
|
+
}
|
|
1482
|
+
),
|
|
1483
|
+
expanded && /* @__PURE__ */ jsx("div", { style: detailsStyle, role: "tooltip", children: diagnostics.map((diagnostic, index) => {
|
|
1484
|
+
const dColor = severityColors2[diagnostic.severity];
|
|
1485
|
+
const bg = severityBackgrounds2[diagnostic.severity];
|
|
1486
|
+
const dIcon = severityIcons2[diagnostic.severity];
|
|
1487
|
+
const pos = formatPosition2(diagnostic);
|
|
1488
|
+
return /* @__PURE__ */ jsxs(
|
|
1489
|
+
"div",
|
|
1490
|
+
{
|
|
1491
|
+
style: { ...detailItemStyle, backgroundColor: bg },
|
|
1492
|
+
children: [
|
|
1493
|
+
/* @__PURE__ */ jsxs(
|
|
1494
|
+
"div",
|
|
1495
|
+
{
|
|
1496
|
+
style: {
|
|
1497
|
+
display: "flex",
|
|
1498
|
+
alignItems: "center",
|
|
1499
|
+
gap: "4px"
|
|
1500
|
+
},
|
|
1501
|
+
children: [
|
|
1502
|
+
/* @__PURE__ */ jsx("span", { style: { color: dColor, fontWeight: 700 }, children: dIcon }),
|
|
1503
|
+
/* @__PURE__ */ jsx("span", { style: { color: dColor, fontWeight: 600 }, children: diagnostic.severity }),
|
|
1504
|
+
diagnostic.code && /* @__PURE__ */ jsxs("span", { style: { color: "#6b7280", fontSize: "10px" }, children: [
|
|
1505
|
+
"[",
|
|
1506
|
+
diagnostic.code,
|
|
1507
|
+
"]"
|
|
1508
|
+
] })
|
|
1509
|
+
]
|
|
1510
|
+
}
|
|
1511
|
+
),
|
|
1512
|
+
/* @__PURE__ */ jsx(
|
|
1513
|
+
"div",
|
|
1514
|
+
{
|
|
1515
|
+
style: {
|
|
1516
|
+
color: "#1f2937",
|
|
1517
|
+
marginTop: "2px",
|
|
1518
|
+
lineHeight: "1.4"
|
|
1519
|
+
},
|
|
1520
|
+
children: diagnostic.message
|
|
1521
|
+
}
|
|
1522
|
+
),
|
|
1523
|
+
pos && /* @__PURE__ */ jsxs("div", { style: { color: "#9ca3af", fontSize: "10px", marginTop: "2px" }, children: [
|
|
1524
|
+
"at ",
|
|
1525
|
+
pos
|
|
1526
|
+
] })
|
|
1527
|
+
]
|
|
1528
|
+
},
|
|
1529
|
+
`${diagnostic.code}-${String(index)}`
|
|
1530
|
+
);
|
|
1531
|
+
}) })
|
|
1532
|
+
] });
|
|
1533
|
+
}
|
|
1534
|
+
function ContainerMeasure({ children, onMeasure }) {
|
|
1535
|
+
const ref = useRef(null);
|
|
1536
|
+
useEffect(() => {
|
|
1537
|
+
const el = ref.current;
|
|
1538
|
+
if (!el) return;
|
|
1539
|
+
const observer = new ResizeObserver((entries) => {
|
|
1540
|
+
for (const entry of entries) {
|
|
1541
|
+
if (entry.contentRect.width > 0) {
|
|
1542
|
+
onMeasure(entry.contentRect.width);
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
onMeasure(el.clientWidth);
|
|
1547
|
+
observer.observe(el);
|
|
1548
|
+
return () => observer.disconnect();
|
|
1549
|
+
}, [onMeasure]);
|
|
1550
|
+
return /* @__PURE__ */ jsx("div", { ref, style: { width: "100%" }, children });
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/container/breakpoints.ts
|
|
1554
|
+
var COMPACT_UP = 500;
|
|
1555
|
+
var COMPACT_DOWN = 484;
|
|
1556
|
+
var WIDE_UP = 900;
|
|
1557
|
+
var WIDE_DOWN = 884;
|
|
1558
|
+
function resolveTier(width, previous) {
|
|
1559
|
+
if (width === 0) return "wide";
|
|
1560
|
+
switch (previous) {
|
|
1561
|
+
case "compact":
|
|
1562
|
+
if (width >= WIDE_UP) return "wide";
|
|
1563
|
+
if (width >= COMPACT_UP) return "standard";
|
|
1564
|
+
return "compact";
|
|
1565
|
+
case "standard":
|
|
1566
|
+
if (width >= WIDE_UP) return "wide";
|
|
1567
|
+
if (width < COMPACT_DOWN) return "compact";
|
|
1568
|
+
return "standard";
|
|
1569
|
+
case "wide":
|
|
1570
|
+
if (width < COMPACT_DOWN) return "compact";
|
|
1571
|
+
if (width < WIDE_DOWN) return "standard";
|
|
1572
|
+
return "wide";
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
function GlyphDocument({
|
|
1576
|
+
ir,
|
|
1577
|
+
className,
|
|
1578
|
+
animation,
|
|
1579
|
+
diagnostics
|
|
1580
|
+
}) {
|
|
1581
|
+
const { layout, blocks } = ir;
|
|
1582
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
1583
|
+
const tierRef = useRef("wide");
|
|
1584
|
+
const containerTier = useMemo(() => {
|
|
1585
|
+
const next = resolveTier(containerWidth, tierRef.current);
|
|
1586
|
+
tierRef.current = next;
|
|
1587
|
+
return next;
|
|
1588
|
+
}, [containerWidth]);
|
|
1589
|
+
const container = useMemo(
|
|
1590
|
+
() => ({ width: containerWidth, tier: containerTier }),
|
|
1591
|
+
[containerWidth, containerTier]
|
|
1592
|
+
);
|
|
1593
|
+
const renderBlock = useCallback(
|
|
1594
|
+
(block, index) => /* @__PURE__ */ jsx(
|
|
1595
|
+
BlockRenderer,
|
|
1596
|
+
{
|
|
1597
|
+
block,
|
|
1598
|
+
layout,
|
|
1599
|
+
index,
|
|
1600
|
+
container
|
|
1601
|
+
},
|
|
1602
|
+
block.id
|
|
1603
|
+
),
|
|
1604
|
+
[layout, container]
|
|
1605
|
+
);
|
|
1606
|
+
let content;
|
|
1607
|
+
switch (layout.mode) {
|
|
1608
|
+
case "dashboard":
|
|
1609
|
+
content = /* @__PURE__ */ jsx(DashboardLayout, { blocks, layout, renderBlock });
|
|
1610
|
+
break;
|
|
1611
|
+
case "presentation":
|
|
1612
|
+
content = /* @__PURE__ */ jsx(PresentationLayout, { blocks, renderBlock });
|
|
1613
|
+
break;
|
|
1614
|
+
case "document":
|
|
1615
|
+
default:
|
|
1616
|
+
content = /* @__PURE__ */ jsx(DocumentLayout, { blocks, layout, renderBlock });
|
|
1617
|
+
break;
|
|
1618
|
+
}
|
|
1619
|
+
return /* @__PURE__ */ jsx(AnimationProvider, { config: animation, children: /* @__PURE__ */ jsx(LayoutProvider, { layout, children: /* @__PURE__ */ jsx(ContainerMeasure, { onMeasure: setContainerWidth, children: /* @__PURE__ */ jsxs("div", { className, "data-glyph-document": ir.id, children: [
|
|
1620
|
+
content,
|
|
1621
|
+
diagnostics && diagnostics.length > 0 && /* @__PURE__ */ jsx(DiagnosticsOverlay, { diagnostics })
|
|
1622
|
+
] }) }) }) });
|
|
1623
|
+
}
|
|
1624
|
+
function createGlyphRuntime(config) {
|
|
1625
|
+
const registry = new PluginRegistry();
|
|
1626
|
+
if (config.components) {
|
|
1627
|
+
registry.registerAll(config.components);
|
|
1628
|
+
}
|
|
1629
|
+
if (config.overrides) {
|
|
1630
|
+
registry.setOverrides(config.overrides);
|
|
1631
|
+
}
|
|
1632
|
+
let currentTheme = config.theme;
|
|
1633
|
+
let registryVersion = 0;
|
|
1634
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1635
|
+
function notify() {
|
|
1636
|
+
for (const listener of listeners) {
|
|
1637
|
+
listener();
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
function WrappedDocument({ ir, className }) {
|
|
1641
|
+
const [theme, setThemeState] = useState(currentTheme);
|
|
1642
|
+
const [, setVersion] = useState(registryVersion);
|
|
1643
|
+
const forceUpdate = useCallback(() => {
|
|
1644
|
+
setThemeState(currentTheme);
|
|
1645
|
+
setVersion(registryVersion);
|
|
1646
|
+
}, []);
|
|
1647
|
+
useMemo(() => {
|
|
1648
|
+
listeners.add(forceUpdate);
|
|
1649
|
+
return () => {
|
|
1650
|
+
listeners.delete(forceUpdate);
|
|
1651
|
+
};
|
|
1652
|
+
}, [forceUpdate]);
|
|
1653
|
+
return /* @__PURE__ */ jsx(
|
|
1654
|
+
RuntimeProvider,
|
|
1655
|
+
{
|
|
1656
|
+
registry,
|
|
1657
|
+
references: ir.references,
|
|
1658
|
+
theme,
|
|
1659
|
+
onDiagnostic: config.onDiagnostic,
|
|
1660
|
+
onNavigate: config.onNavigate,
|
|
1661
|
+
children: /* @__PURE__ */ jsx(GlyphDocument, { ir, className, animation: config.animation })
|
|
1662
|
+
}
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
return {
|
|
1666
|
+
GlyphDocument: WrappedDocument,
|
|
1667
|
+
registerComponent(definition) {
|
|
1668
|
+
registry.registerComponent(definition);
|
|
1669
|
+
registryVersion++;
|
|
1670
|
+
notify();
|
|
1671
|
+
},
|
|
1672
|
+
setTheme(theme) {
|
|
1673
|
+
currentTheme = theme;
|
|
1674
|
+
notify();
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function useIsClient() {
|
|
1679
|
+
const [isClient, setIsClient] = useState(false);
|
|
1680
|
+
useEffect(() => {
|
|
1681
|
+
setIsClient(true);
|
|
1682
|
+
}, []);
|
|
1683
|
+
return isClient;
|
|
1684
|
+
}
|
|
1685
|
+
function SSRPlaceholder({
|
|
1686
|
+
width = "100%",
|
|
1687
|
+
height = 300,
|
|
1688
|
+
className,
|
|
1689
|
+
children
|
|
1690
|
+
}) {
|
|
1691
|
+
const isClient = useIsClient();
|
|
1692
|
+
if (!isClient) {
|
|
1693
|
+
const style = {
|
|
1694
|
+
width,
|
|
1695
|
+
height,
|
|
1696
|
+
// Subtle background so the reserved space is visible in the layout
|
|
1697
|
+
backgroundColor: "var(--glyph-ssr-placeholder-bg, #f0f0f0)"
|
|
1698
|
+
};
|
|
1699
|
+
return /* @__PURE__ */ jsx(
|
|
1700
|
+
"div",
|
|
1701
|
+
{
|
|
1702
|
+
className,
|
|
1703
|
+
style,
|
|
1704
|
+
"data-ssr-placeholder": "true",
|
|
1705
|
+
"aria-hidden": "true"
|
|
1706
|
+
}
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
export { AnimationContext, AnimationProvider, BlockDiagnosticIndicator, BlockRenderer, PluginRegistry as ComponentRegistry, ContainerMeasure, DashboardLayout, DiagnosticsOverlay, DocumentLayout, ErrorBoundary, FallbackRenderer, GlyphBlockquote, GlyphCodeBlock, GlyphDocument, GlyphHeading, GlyphImage, GlyphList, GlyphParagraph, GlyphRawHtml, GlyphThematicBreak, InlineRenderer, LayoutProvider, PluginRegistry, PresentationLayout, ReferenceIndicator, RuntimeProvider, SSRPlaceholder, ThemeProvider, builtInRenderers, createGlyphRuntime, createResolveVar, darkTheme, isDarkTheme, lightTheme, mergeThemeDefaults, resolveComponentProps, resolveTheme, resolveTier, useAnimation, useBlockAnimation, useGlyphTheme, useIsClient, useLayout, useNavigation, useReferences, useRuntime, validateComponentDefinition };
|
|
1713
|
+
//# sourceMappingURL=index.js.map
|
|
1714
|
+
//# sourceMappingURL=index.js.map
|