@omniharmonic/openspace-renderer 0.0.0-stage → 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.
@@ -0,0 +1,798 @@
1
+ import { B as BehaviorName } from './base-css-CCRQ8Nzw.js';
2
+ export { a as BASE_CSS, b as BEHAVIORS, c as BEHAVIOR_BUDGETS, d as BEHAVIOR_LOADER, e as behaviorsInHtml, i as isBehaviorName } from './base-css-CCRQ8Nzw.js';
3
+ import * as react from 'react';
4
+ import { ReactNode, AnchorHTMLAttributes } from 'react';
5
+ import { AnyBlockDefinition, BlockDefinition, FieldsShape, Block, Section, RichTextDoc, SiteDocument, Page, Responsive, Align, BlockStyle, Announcement, Site, ColorTheme, Popup as Popup$1, CollectionItem, CollectionConfig, ImageValue, ReferenceValue, HeaderVariant, Header as Header$1, NavItem, Styles, FocalPoint, AspectRatio, ImageTreatment, StackLayout, GridLayout, GridRect, SectionStyle, Link as Link$1, SocialProfile, TypePreset, SectionBackground } from '@omniharmonic/openspace-schema';
6
+ export { fallbackMobileRects } from '@omniharmonic/openspace-schema';
7
+ import { CompiledStyles } from '@omniharmonic/openspace-theme';
8
+
9
+ /**
10
+ * The image resolution contract (arch §4.3). site-kit walks a page, resolves every image through
11
+ * Astro's `getImage()` and passes the result in; `<Img>` reads it. Keys are the `src` exactly as
12
+ * stored in content (`media/2026/10/hero.jpg` or an https URL).
13
+ */
14
+ interface ResolvedImageSource {
15
+ /** MIME type, e.g. `image/avif`, `image/webp`. */
16
+ type: string;
17
+ srcset: string;
18
+ }
19
+ interface ResolvedImage {
20
+ /** Fallback URL (JPEG/PNG) for `<img src>`. */
21
+ src: string;
22
+ /** Fallback-format srcset for `<img srcset>`. */
23
+ srcset?: string;
24
+ /** Modern formats, best first, rendered as `<source>`s. */
25
+ sources?: ResolvedImageSource[];
26
+ /** Intrinsic dimensions (always emitted: no layout shift). */
27
+ width: number;
28
+ height: number;
29
+ /** Tiny blurred placeholder data URI (< 300 bytes). */
30
+ placeholder?: string;
31
+ }
32
+ type ImageMap = Readonly<Record<string, ResolvedImage>>;
33
+ /** Every image `src` referenced by a JSON value (pages, site, blocks). */
34
+ declare function collectImageSrcs(value: unknown, out?: Set<string>): Set<string>;
35
+
36
+ /**
37
+ * A block definition whose `render` returns React nodes. Schema keeps `render` generic
38
+ * (`(props) => unknown`) so it has no React dependency; the renderer narrows it here.
39
+ */
40
+ type RendererBlockDefinition = BlockDefinition<any, any, ReactNode>;
41
+ /** The minimal registry surface the renderer needs (schema's `BlockRegistry` satisfies it). */
42
+ interface RendererBlockRegistry {
43
+ get(type: string): AnyBlockDefinition | undefined;
44
+ }
45
+ /**
46
+ * Register block definitions with the renderer. Re-registering a type replaces it (hot reload,
47
+ * custom blocks overriding library blocks).
48
+ */
49
+ declare function registerBlocks(defs: readonly (AnyBlockDefinition | BlockDefinition<FieldsShape, string, ReactNode>)[]): void;
50
+ /** Look up a registered block definition. */
51
+ declare function getBlock(type: string): RendererBlockDefinition | undefined;
52
+ /** All registered definitions. */
53
+ declare function listBlocks(): RendererBlockDefinition[];
54
+ /** Remove one type, or every registration when called without arguments (tests). */
55
+ declare function unregisterBlocks(...types: string[]): void;
56
+ /** The module-level registry as a {@link RendererBlockRegistry}. */
57
+ declare const globalRegistry: RendererBlockRegistry;
58
+
59
+ /**
60
+ * Render modes.
61
+ * - `production`: the published site (site-kit build). Broken references render nothing.
62
+ * - `preview`: Hub dev preview / share links. Like production, plus visible notices for broken
63
+ * blocks and links, and always `noindex`.
64
+ * - `editor`: Studio canvas. Like preview, plus no entrance animations and editor hooks.
65
+ */
66
+ type RenderMode = "production" | "preview" | "editor";
67
+ interface RenderContextValue {
68
+ /** The whole site; optional so primitives (`<Img>`, `<Link>`) can render standalone. */
69
+ doc: SiteDocument | undefined;
70
+ /** The page being rendered, if any (used for same-page anchors). */
71
+ page: Page | undefined;
72
+ mode: RenderMode;
73
+ /** Site base path without a trailing slash: `""` or `/maya-yoga`. */
74
+ basePath: string;
75
+ /** Hub origin used by forms, newsletter, cart and analytics. */
76
+ hubUrl: string | undefined;
77
+ }
78
+ declare const RenderContext: react.Context<RenderContextValue>;
79
+ declare function useRenderContext(): RenderContextValue;
80
+ /** Resolved image map (see {@link ImageMap}); `<Img>` reads it. */
81
+ declare const ImageResolverContext: react.Context<Readonly<Record<string, ResolvedImage>>>;
82
+ declare function useImageMap(): ImageMap;
83
+ /** Optional block registry override (defaults to the module-level registry). */
84
+ declare const BlockRegistryContext: react.Context<RendererBlockRegistry | undefined>;
85
+ /**
86
+ * Hook point for Studio. The renderer always emits `data-os-id` on sections and blocks; in the
87
+ * canvas, Studio can additionally wrap each rendered section/block (selection outlines,
88
+ * drag handles) without the renderer knowing anything about the editor.
89
+ */
90
+ interface EditorHooks {
91
+ wrapSection?: (args: {
92
+ section: Section;
93
+ children: ReactNode;
94
+ }) => ReactNode;
95
+ wrapBlock?: (args: {
96
+ block: Block;
97
+ section: Section | undefined;
98
+ children: ReactNode;
99
+ }) => ReactNode;
100
+ /** Rendered in place of unknown/invalid blocks (defaults to a small notice). */
101
+ renderMissingBlock?: (args: {
102
+ block: Block;
103
+ reason: string;
104
+ }) => ReactNode;
105
+ /**
106
+ * Called by `<RichText>` for a rich-text value that is one of the current block's fields, so
107
+ * Studio can swap in an inline editor. `children` is what `<RichText>` would have rendered
108
+ * (null for an empty document); return it unchanged to keep the normal output.
109
+ */
110
+ renderRichText?: (args: RichTextHookArgs) => ReactNode;
111
+ }
112
+ interface RichTextHookArgs {
113
+ block: Block;
114
+ /** Dot path of the field inside `block.fields`, e.g. `content` or `items.2.body`. */
115
+ fieldPath: string;
116
+ doc: RichTextDoc | null | undefined;
117
+ /** Classes `<RichText>` puts on its root next to `os-rich`. */
118
+ className: string | undefined;
119
+ /** Root element `<RichText>` renders. */
120
+ as: "div" | "section" | "article";
121
+ children: ReactNode;
122
+ }
123
+ declare const EditorContext: react.Context<EditorHooks | undefined>;
124
+ declare function useEditor(): EditorHooks | undefined;
125
+ /** The block being rendered and its validated fields (lets `<RichText>` find its field path). */
126
+ declare const BlockFieldsContext: react.Context<{
127
+ block: Block;
128
+ fields: Record<string, unknown>;
129
+ } | undefined>;
130
+ /**
131
+ * Where `value` sits inside `fields`, by identity (`items.1.body`). Render functions pass field
132
+ * values straight through, so the object `<RichText>` receives is the one in `fields`.
133
+ */
134
+ declare function fieldPathOf(fields: Record<string, unknown>, value: unknown, depth?: number): string | undefined;
135
+ /** The section currently being rendered (blocks use it for context, e.g. color theme). */
136
+ declare const SectionContext: react.Context<{
137
+ id: `sec_${string}`;
138
+ layout: {
139
+ mode: "stack";
140
+ columns: {
141
+ d: number[];
142
+ t?: number[] | undefined;
143
+ m?: number[] | undefined;
144
+ };
145
+ order?: {
146
+ t?: number[] | undefined;
147
+ m?: number[] | undefined;
148
+ } | undefined;
149
+ align?: "start" | "center" | "end" | "stretch" | undefined;
150
+ gap?: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | undefined;
151
+ width?: "full" | "narrow" | "content" | "wide" | undefined;
152
+ } | {
153
+ mode: "grid";
154
+ rows: {
155
+ d: number;
156
+ t?: number | undefined;
157
+ m?: number | undefined;
158
+ };
159
+ items: Record<`blk_${string}`, {
160
+ d: {
161
+ x: number;
162
+ y: number;
163
+ w: number;
164
+ h: number;
165
+ z?: number | undefined;
166
+ bleed?: "left" | "right" | "both" | undefined;
167
+ pin?: "top" | "bottom" | undefined;
168
+ };
169
+ t?: {
170
+ x: number;
171
+ y: number;
172
+ w: number;
173
+ h: number;
174
+ z?: number | undefined;
175
+ bleed?: "left" | "right" | "both" | undefined;
176
+ pin?: "top" | "bottom" | undefined;
177
+ } | undefined;
178
+ m?: {
179
+ x: number;
180
+ y: number;
181
+ w: number;
182
+ h: number;
183
+ z?: number | undefined;
184
+ bleed?: "left" | "right" | "both" | undefined;
185
+ pin?: "top" | "bottom" | undefined;
186
+ } | undefined;
187
+ }>;
188
+ width?: "full" | "narrow" | "content" | "wide" | undefined;
189
+ };
190
+ style: {
191
+ colorTheme?: "light" | "light-accent" | "dark" | "dark-accent" | "brand" | undefined;
192
+ padding?: {
193
+ d: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
194
+ t?: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | undefined;
195
+ m?: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | undefined;
196
+ } | undefined;
197
+ minHeight?: {
198
+ d: string;
199
+ t?: string | undefined;
200
+ m?: string | undefined;
201
+ } | undefined;
202
+ background?: {
203
+ type: "none";
204
+ } | {
205
+ type: "color";
206
+ color: string;
207
+ } | {
208
+ type: "image";
209
+ src: string;
210
+ focal?: [number, number] | undefined;
211
+ overlay?: {
212
+ color: string;
213
+ opacity: number;
214
+ } | undefined;
215
+ parallax?: boolean | undefined;
216
+ } | {
217
+ type: "gradient";
218
+ from: string;
219
+ to: string;
220
+ via?: string | undefined;
221
+ angle?: number | undefined;
222
+ } | {
223
+ type: "video";
224
+ src: string;
225
+ poster?: string | undefined;
226
+ focal?: [number, number] | undefined;
227
+ overlay?: {
228
+ color: string;
229
+ opacity: number;
230
+ } | undefined;
231
+ } | undefined;
232
+ animation?: {
233
+ entrance: "none" | "fade" | "rise" | "slide" | "zoom" | "clip";
234
+ stagger?: boolean | undefined;
235
+ } | undefined;
236
+ };
237
+ blocks: {
238
+ id: `blk_${string}`;
239
+ type: string;
240
+ v: number;
241
+ fields: Record<string, unknown>;
242
+ column?: number | undefined;
243
+ label?: string | undefined;
244
+ hidden?: boolean | undefined;
245
+ variant?: string | undefined;
246
+ style?: {
247
+ colorTheme?: "light" | "light-accent" | "dark" | "dark-accent" | "brand" | undefined;
248
+ padding?: {
249
+ d: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl";
250
+ t?: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | undefined;
251
+ m?: "none" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | undefined;
252
+ } | undefined;
253
+ radius?: "none" | "sm" | "md" | "lg" | "xl" | "full" | undefined;
254
+ border?: "none" | "subtle" | "strong" | undefined;
255
+ shadow?: "none" | "sm" | "md" | "lg" | undefined;
256
+ align?: {
257
+ d: "start" | "center" | "end";
258
+ t?: "start" | "center" | "end" | undefined;
259
+ m?: "start" | "center" | "end" | undefined;
260
+ } | undefined;
261
+ imageTreatment?: "none" | "rounded" | "arch" | "circle" | "blob" | "duotone" | "grain" | "frame" | undefined;
262
+ animation?: {
263
+ entrance: "none" | "fade" | "rise" | "slide" | "zoom" | "clip";
264
+ stagger?: boolean | undefined;
265
+ } | undefined;
266
+ typography?: Record<string, "small" | "display" | "h1" | "h2" | "h3" | "h4" | "lead" | "body" | "eyebrow"> | undefined;
267
+ } | undefined;
268
+ }[];
269
+ preset?: string | undefined;
270
+ label?: string | undefined;
271
+ anchor?: string | undefined;
272
+ locked?: boolean | undefined;
273
+ hidden?: boolean | undefined;
274
+ } | undefined>;
275
+
276
+ /** Join class names, skipping falsy values. */
277
+ declare function cx(...parts: (string | false | null | undefined | 0)[]): string;
278
+ /** A CSSProperties-compatible object of custom properties (`{"--x": 1}`). */
279
+ type CssVars = Record<`--${string}`, string | number>;
280
+ declare module "react" {
281
+ interface CSSProperties {
282
+ [key: `--${string}`]: string | number | undefined;
283
+ }
284
+ }
285
+
286
+ /** `acme/map` → `acme-map`. */
287
+ declare const blockTypeClass: (type: string) => string;
288
+ /** `os-align-center os-talign-center os-malign-start` (inheritance applied). */
289
+ declare function alignClasses(align: Responsive<Align>): string;
290
+ /** Wrapper classes + vars for a block's token style overrides. */
291
+ declare function blockStyleProps(style: BlockStyle | undefined, mode: RenderMode): {
292
+ className: string;
293
+ vars: CssVars;
294
+ attrs: Record<string, string>;
295
+ };
296
+ interface BlockViewProps {
297
+ block: Block;
298
+ /** The containing section (absent for standalone renders such as email/OG). */
299
+ section?: Section;
300
+ }
301
+ /**
302
+ * Render one block through the registry: migrate if stale, validate fields, wrap in a
303
+ * `.os-block` container (container-query context, token style overrides, `data-os-id`).
304
+ */
305
+ declare function BlockView({ block, section }: BlockViewProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
306
+
307
+ /** Small stable hash (FNV-1a, base36) used for dismissal / frequency keys. */
308
+ declare function shortHash(text: string): string;
309
+ /** Site-wide announcement bar; dismissal (per message) is remembered by the `announcement` behavior. */
310
+ declare function AnnouncementBar({ announcement, }: {
311
+ announcement: Announcement | null | undefined;
312
+ }): react.JSX.Element | null;
313
+ /** Site footer: the site's footer section inside a `<footer>` landmark, plus optional extras. */
314
+ declare function Footer({ site, children }: {
315
+ site: Site;
316
+ children?: ReactNode;
317
+ }): react.JSX.Element;
318
+ type PopupTrigger = {
319
+ type: "time";
320
+ seconds: number;
321
+ } | {
322
+ type: "scroll";
323
+ percent: number;
324
+ } | {
325
+ type: "exit";
326
+ };
327
+ interface PopupProps {
328
+ /** Stable id; also the frequency-cap storage key. */
329
+ id: string;
330
+ trigger: PopupTrigger;
331
+ /** Show at most once every N days per visitor (default 7; 0 = every visit). */
332
+ frequencyDays?: number;
333
+ /** Accessible title (rendered as the dialog heading). */
334
+ title: string;
335
+ colorTheme?: ColorTheme;
336
+ children?: ReactNode;
337
+ }
338
+ /**
339
+ * Promotional popup as a native `<dialog>` opened by the `popup` behavior on a time, scroll or
340
+ * exit-intent trigger, capped per visitor via localStorage. Esc and the close button dismiss it.
341
+ */
342
+ declare function Popup({ id, trigger, frequencyDays, title, colorTheme, children, }: PopupProps): react.JSX.Element;
343
+ /** Trigger props for the `<Popup>` from the site's pop-up settings. */
344
+ declare function popupTrigger(popup: Popup$1): PopupTrigger;
345
+ /**
346
+ * The site-wide pop-up from `site.popup` (heading, words, image, button, newsletter signup),
347
+ * rendered with `<Popup>`. The newsletter form is the registered `newsletter` block, so it posts
348
+ * to the Hub exactly like one placed on a page. Content changes reset the visitor frequency cap
349
+ * (the dialog id hashes the heading).
350
+ */
351
+ declare function SitePopup({ popup }: {
352
+ popup: Popup$1 | null | undefined;
353
+ }): react.JSX.Element | null;
354
+
355
+ /**
356
+ * Collection data for blocks that list items (summary, events, product, social feed).
357
+ *
358
+ * By default blocks read `doc.collections` from the {@link RenderContext}. Hosts that hold
359
+ * collection data elsewhere provide it through `CollectionsContext`: site-kit passes the parsed
360
+ * content collections, the build time (`now`) and build-time social feeds; Studio passes the
361
+ * items it has loaded. Values given here take precedence over the document.
362
+ */
363
+ interface CollectionsValue {
364
+ /** Items by collection name, then slug. Defaults to `doc.collections`. */
365
+ collections?: Record<string, Record<string, CollectionItem>>;
366
+ /** `_collection.json` per collection. Defaults to `doc.collectionConfigs`. */
367
+ configs?: Record<string, CollectionConfig>;
368
+ /**
369
+ * Reference time for "upcoming" filters and scheduled items. Static builds pass the build
370
+ * time so output is deterministic. Defaults to the current time.
371
+ */
372
+ now?: Date | string;
373
+ /**
374
+ * Include drafts and not-yet-due scheduled items. Defaults to `true` in the Studio editor and
375
+ * previews and `false` in production.
376
+ */
377
+ includeDrafts?: boolean;
378
+ /** Build-time social feed posts, keyed by {@link feedKey} (`instagram:juniperyoga`). */
379
+ feeds?: Record<string, SocialFeedPost[]>;
380
+ }
381
+ /** One post of a build-time social feed (fetched by site-kit with the owner's authorization). */
382
+ interface SocialFeedPost {
383
+ /** Link to the post on the network (https). */
384
+ url: string;
385
+ image: ImageValue;
386
+ caption?: string;
387
+ /** ISO date or date-time. */
388
+ date?: string;
389
+ }
390
+ declare const CollectionsContext: react.Context<CollectionsValue | undefined>;
391
+ /** Key for {@link CollectionsValue.feeds}: `network:handle`, lowercased, without a leading `@`. */
392
+ declare function feedKey(network: string, handle: string): string;
393
+ interface ResolvedCollections {
394
+ collections: Record<string, Record<string, CollectionItem>>;
395
+ configs: Record<string, CollectionConfig>;
396
+ now: Date;
397
+ includeDrafts: boolean;
398
+ feeds: Record<string, SocialFeedPost[]>;
399
+ }
400
+ /** Collection data for the current render (context value merged over the site document). */
401
+ declare function useCollections(): ResolvedCollections;
402
+ /** The item's primary date: post/episode/project date or event start. */
403
+ declare function itemDate(item: CollectionItem): string | undefined;
404
+ /** Published (or scheduled and due) at `now`. */
405
+ declare function isLive(item: CollectionItem, now: Date): boolean;
406
+ /**
407
+ * An event is upcoming until it ends. Date-only events (`2026-11-14`) count as upcoming for
408
+ * that whole day in the event's own time zone (`timezone`, else UTC).
409
+ */
410
+ declare function isUpcoming(item: CollectionItem, now: Date): boolean;
411
+ /** How far ahead (and back, for past events) repeating events are listed. */
412
+ declare const RECURRENCE_HORIZON_DAYS = 366;
413
+ /** A repeating event shown at its next date (or its last one, once the series is over). */
414
+ declare function nextOccurrence(item: CollectionItem, now: Date): CollectionItem;
415
+ interface QueryOptions {
416
+ now?: Date;
417
+ includeDrafts?: boolean;
418
+ }
419
+ /**
420
+ * Items a `reference` field selects: explicit picks (in their order) or the filter, then sort
421
+ * and limit. Drafts and not-yet-due scheduled items are excluded unless `includeDrafts`.
422
+ * `filter.upcoming: true` keeps events that haven't ended; `false` keeps past events.
423
+ */
424
+ declare function queryCollection(ref: ReferenceValue, collections: Record<string, Record<string, CollectionItem>>, opts?: QueryOptions): CollectionItem[];
425
+ /** `queryCollection` bound to the current render's collections, time and draft policy. */
426
+ declare function useCollectionQuery(ref: ReferenceValue): CollectionItem[];
427
+
428
+ /** Header variants whose navigation is always behind the menu button. */
429
+ declare const DRAWER_VARIANTS: readonly HeaderVariant[];
430
+ interface EffectiveHeader extends Header$1 {
431
+ hidden: boolean;
432
+ /** The header overlays the first section (variant `transparent-overlay` or transparent-on-hero). */
433
+ overlay: boolean;
434
+ }
435
+ /** Merge the site header with a page's override. */
436
+ declare function effectiveHeader(site: Site, page: Page | undefined): EffectiveHeader;
437
+ interface ResolvedNavLeaf {
438
+ kind: "leaf";
439
+ label: string;
440
+ href: string;
441
+ current: boolean;
442
+ newTab: boolean;
443
+ }
444
+ interface ResolvedNavFolder {
445
+ kind: "folder";
446
+ label: string;
447
+ current: boolean;
448
+ items: ResolvedNavLeaf[];
449
+ }
450
+ type ResolvedNav = ResolvedNavLeaf | ResolvedNavFolder;
451
+ /** Resolve the nav tree (hidden entries and dangling pages dropped; buttons excluded). */
452
+ declare function resolveNav(nav: readonly NavItem[], doc: SiteDocument | undefined, basePath: string, currentId: string | undefined): ResolvedNav[];
453
+ interface HeaderProps {
454
+ site: Site;
455
+ page?: Page | undefined;
456
+ /** Color theme of the first section; used when the header overlays it. */
457
+ heroTheme?: ColorTheme | undefined;
458
+ /** Show the cart button (sites with commerce). */
459
+ cart?: boolean;
460
+ }
461
+ /**
462
+ * Site header in one of eight layouts, with sticky / transparent-on-hero modes, nav folders
463
+ * (`<details>`, so they work without JavaScript) and a mobile menu (slide, fullscreen or
464
+ * dropdown) driven by the `menu` behavior. Without JavaScript the mobile menu is shown inline.
465
+ */
466
+ declare function Header({ site, page, heroTheme, cart }: HeaderProps): react.JSX.Element | null;
467
+
468
+ /**
469
+ * Page metadata helpers shared by `renderPageHtml` and the Studio (titles, descriptions, URLs,
470
+ * compiled styles). Kept apart from `html.ts` so importing them never pulls `react-dom/server`
471
+ * into a browser bundle.
472
+ */
473
+
474
+ declare const escapeHtml: (s: string) => string;
475
+ /** JSON for a `<script>` element: `<`, U+2028/9 escaped so content can never close the tag. */
476
+ declare function jsonForScript(value: unknown): string;
477
+ /** `compileStyles` memoised per styles object (site-kit renders many pages per build). */
478
+ declare function compiledStylesFor(styles: Styles): CompiledStyles;
479
+ /** The site's absolute URL (with base path), ending in `/`. */
480
+ declare function siteUrlFor(doc: SiteDocument, override?: string): string;
481
+ /** `<title>`: SEO title, else page title, through the site's `%s` template (home: site title). */
482
+ declare function pageTitle(doc: SiteDocument, page: Page): string;
483
+ declare function pageDescription(doc: SiteDocument, page: Page): string | undefined;
484
+
485
+ interface RenderPageHtmlOptions {
486
+ doc: SiteDocument;
487
+ /** Page id, or a page object (collection item pages, unsaved drafts). */
488
+ pageId: string | Page;
489
+ mode?: "production" | "preview";
490
+ /** Site base path (`/maya-yoga` on a GitHub project site); `""` at a domain root. */
491
+ basePath?: string;
492
+ imageMap?: ImageMap;
493
+ /** Hub origin for forms/newsletter/analytics/cart (default: `config.hub`). */
494
+ hubUrl?: string;
495
+ /** Where behavior modules are served (default `<basePath>/_os/behaviors`). */
496
+ behaviorsUrl?: string;
497
+ /**
498
+ * Absolute site URL including the base path, for canonical and Open Graph URLs (default:
499
+ * `https://<customDomain>` or `config.deploy.url`).
500
+ */
501
+ siteUrl?: string;
502
+ /** Structured data emitted as `<script type="application/ld+json">`. */
503
+ jsonLd?: object | object[];
504
+ /**
505
+ * Font stylesheet URLs. Default: the pairing's Google Fonts URL (previews). Published sites
506
+ * pass self-hosted Fontsource CSS; `[]` disables web fonts.
507
+ */
508
+ fontStylesheets?: string[];
509
+ /** Link the stylesheets instead of inlining them (site-kit serves them as cacheable files). */
510
+ stylesheets?: {
511
+ base?: string;
512
+ tokens?: string;
513
+ };
514
+ /** Extra trusted markup appended to `<head>` (site-kit feed links etc.). */
515
+ extraHead?: string;
516
+ }
517
+ /**
518
+ * Render a complete HTML document for a page: head (title, meta, Open Graph, canonical, JSON-LD,
519
+ * fonts, token CSS, base CSS, custom CSS, head injection), the rendered body, and behavior
520
+ * scripts only for behaviors the page actually uses. No React runtime ships.
521
+ */
522
+ declare function renderPageHtml(opts: RenderPageHtmlOptions): string;
523
+ /**
524
+ * Behaviors a page needs: pass rendered HTML, or render options (the page is rendered first).
525
+ * Includes `beacon` for production renders with analytics on.
526
+ */
527
+ declare function behaviorsForPage(input: string | RenderPageHtmlOptions): BehaviorName[];
528
+
529
+ /** Widths offered for remote images that support on-the-fly resizing (Unsplash, imgix). */
530
+ declare const REMOTE_WIDTHS: readonly [480, 640, 768, 960, 1080, 1440, 1920, 2400];
531
+ interface ImgProps {
532
+ /** A media path / URL, or a full image value (src + alt + focal + dimensions). */
533
+ src: string | ImageValue;
534
+ /** Required unless `src` is an image value carrying `alt`. `""` marks a decorative image. */
535
+ alt?: string;
536
+ /** `sizes` attribute; describe the rendered width so the browser picks the right file. */
537
+ sizes?: string;
538
+ width?: number;
539
+ height?: number;
540
+ /** Focal point `[x, y]` (0..1); drives `object-position` when the image is cropped. */
541
+ focal?: FocalPoint;
542
+ /** Crop to an aspect ratio (`4:5`, `16:9`); `original` keeps the intrinsic ratio. */
543
+ aspect?: AspectRatio;
544
+ /**
545
+ * Image treatment. Omitted: the site's default treatment (from Site Styles). `"none"` opts out.
546
+ */
547
+ treatment?: ImageTreatment;
548
+ fit?: "cover" | "contain";
549
+ /**
550
+ * Fill the parent's height, cropping at the focal point (grid-mode cells: the cell's rows set
551
+ * the height, the image never stretches the row).
552
+ */
553
+ fill?: boolean;
554
+ /** Above-the-fold image: eager load + high fetch priority. */
555
+ priority?: boolean;
556
+ className?: string;
557
+ }
558
+ interface Resolved {
559
+ src: string;
560
+ srcset?: string | undefined;
561
+ sources: {
562
+ type: string;
563
+ srcset: string;
564
+ }[];
565
+ width?: number | undefined;
566
+ height?: number | undefined;
567
+ placeholder?: string | undefined;
568
+ }
569
+ /** Build a srcset for Unsplash/imgix-style URLs by rewriting the `w` query parameter. */
570
+ declare function remoteSrcset(src: string, intrinsicWidth?: number): string | undefined;
571
+ /** Resolve an image source through the image map, the media library, then raw fallbacks. */
572
+ declare function resolveImage(src: string, opts?: {
573
+ imageMap?: ImageMap | undefined;
574
+ doc?: SiteDocument | undefined;
575
+ basePath?: string;
576
+ width?: number | undefined;
577
+ height?: number | undefined;
578
+ }): Resolved;
579
+ /**
580
+ * Above-the-fold images: the sources `SectionView` picked for the page's first section (the
581
+ * first two in reading order, background first — the likely LCP element). Those load eagerly
582
+ * with `fetchpriority="high"`; every other image loads lazily unless `priority` is set. A plain
583
+ * value computed from the section, so rendering stays pure.
584
+ */
585
+ type AboveFold = ReadonlySet<string>;
586
+ declare const AboveFoldContext: react.Context<AboveFold | null>;
587
+ /**
588
+ * Responsive image. Reads the `ImageResolverContext` map (AVIF/WebP sources, srcset, intrinsic
589
+ * size, blurred placeholder), always emits width/height when known, crops with
590
+ * `object-position` at the focal point, and applies image-treatment classes.
591
+ */
592
+ declare function Img(props: ImgProps): react.JSX.Element;
593
+
594
+ /** Emit `--{p}{name}` for each breakpoint from a responsive value (inheritance applied). */
595
+ declare function responsiveVars<T>(name: string, value: Responsive<T> | undefined, map: (v: T) => string | number): CssVars;
596
+ /** Section-level vars: padding, min height. */
597
+ declare function sectionStyleVars(style: SectionStyle): CssVars;
598
+ /** Stack-mode container vars: column templates per breakpoint. */
599
+ declare function stackVars(layout: StackLayout): CssVars;
600
+ /**
601
+ * Per-column stacking order at tablet/mobile (`layout.order`, FR-13). Mobile inherits the
602
+ * tablet order. Returns `{ "--to": n, "--mo": n }` for desktop column `index`.
603
+ */
604
+ declare function stackColumnVars(layout: StackLayout, index: number): CssVars;
605
+ /** Group a stack section's blocks by desktop column (out-of-range columns land in the last). */
606
+ declare function blocksByColumn(layout: StackLayout, blocks: readonly Block[]): Block[][];
607
+ /** Grid container vars: row counts per breakpoint. */
608
+ declare function gridVars(layout: GridLayout): CssVars;
609
+
610
+ /**
611
+ * Inline vars for one grid item: `--x/--y/--w/--h/--z`, `--tx…`, `--mx…`, bleed lines
612
+ * (`--cs/--ce`) and pinned row lines (`--rs/--re`).
613
+ */
614
+ declare function gridItemVars(layout: GridLayout, id: string, mobileFallback?: Record<string, GridRect>): CssVars;
615
+ /** Total rows actually needed for mobile when fallback rects extend past the stored count. */
616
+ declare function mobileRows(layout: GridLayout, fallback: Record<string, GridRect>): number;
617
+ /** CSS value for a color value: token → `var(--os-color-…)`, exact colors pass through. */
618
+ declare function colorValue(value: string): string;
619
+
620
+ /** Normalise a base path: `""`, `"/"` → `""`; `"repo/"` → `"/repo"`. */
621
+ declare function normalizeBasePath(basePath: string | undefined): string;
622
+ /** URL path of a page: home is `/`, others end with a slash (`/about/`). */
623
+ declare function pagePath(page: Pick<Page, "slug">, basePath?: string): string;
624
+ /** URL path of a collection item: `/journal/opening-day/`. */
625
+ declare function itemPath(collection: string, slug: string, basePath?: string): string;
626
+ /** Public URL of a media source: repo paths get the base path, remote URLs pass through. */
627
+ declare function mediaUrl(src: string, basePath?: string): string;
628
+ /** Prefix a site-relative path (`/schedule`) with the base path; leave everything else. */
629
+ declare function withBase(href: string, basePath?: string): string;
630
+ interface ResolvedLink {
631
+ href: string;
632
+ /** Open in a new tab (adds `target="_blank" rel="noopener"`). */
633
+ newTab: boolean;
634
+ /** The target no longer exists (deleted page/section). */
635
+ broken: boolean;
636
+ external: boolean;
637
+ }
638
+ interface LinkResolveOptions {
639
+ doc?: SiteDocument | undefined;
640
+ basePath?: string;
641
+ /** The page being rendered: anchors to its own sections become `#fragment`. */
642
+ currentPageId?: string | undefined;
643
+ }
644
+ /** In-page fragment for a section: its `anchor` if set, else its id. */
645
+ declare function sectionFragment(section: {
646
+ id: string;
647
+ anchor?: string | undefined;
648
+ }): string;
649
+ /** Resolve a link target (page, collection item, URL, email, phone, file, anchor) to an href. */
650
+ declare function resolveLink(link: Link$1, opts?: LinkResolveOptions): ResolvedLink;
651
+ interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> {
652
+ to: Link$1;
653
+ children?: ReactNode;
654
+ }
655
+ /** An `<a>` for a structured link, resolved against the current site and base path. */
656
+ declare function Link({ to, children, ...rest }: LinkProps): react.JSX.Element;
657
+
658
+ interface PageViewProps {
659
+ doc: SiteDocument;
660
+ /** A page id in `doc.pages`, or a page object (e.g. an unsaved draft or a collection item page). */
661
+ page: string | Page;
662
+ mode?: RenderMode;
663
+ basePath?: string;
664
+ imageMap?: ImageMap;
665
+ hubUrl?: string;
666
+ /** Studio hooks (canvas selection affordances). */
667
+ editor?: EditorHooks;
668
+ /** Rendered after the footer (popups, cart drawer mount points). */
669
+ extras?: ReactNode;
670
+ }
671
+ /** Resolve the page prop to a page object; throws for unknown ids. */
672
+ declare function resolvePage(doc: SiteDocument, page: string | Page): Page;
673
+ /**
674
+ * A whole page: skip link, announcement bar, header, the page's sections in `<main>`, footer.
675
+ * The same component renders the Studio canvas (`mode: "editor"`), the Hub preview and the
676
+ * published site.
677
+ */
678
+ declare function PageView(props: PageViewProps): react.JSX.Element;
679
+
680
+ type ButtonRole = "primary" | "secondary" | "tertiary";
681
+ interface ButtonProps {
682
+ /** Link target; omit for a `<button>` (e.g. a form submit). */
683
+ to?: Link$1;
684
+ role?: ButtonRole;
685
+ type?: "button" | "submit";
686
+ className?: string;
687
+ children: ReactNode;
688
+ /** Extra attributes (data-os-*, aria-*). */
689
+ attrs?: Record<string, string | undefined>;
690
+ }
691
+ /**
692
+ * A button styled by the Site Styles button system (`--os-button-<role>-*`). Renders an `<a>`
693
+ * for links, a `<button>` otherwise.
694
+ */
695
+ declare function Button({ to, role, type, className, children, attrs, }: ButtonProps): react.JSX.Element;
696
+ declare function networkLabel(network: string): string;
697
+ /** Small outline icon for a social network (inherits `currentColor`). */
698
+ declare function SocialIcon({ network }: {
699
+ network: string;
700
+ }): react.JSX.Element;
701
+ /** A row of social profile links with accessible names. */
702
+ declare function SocialLinks({ profiles, className, }: {
703
+ profiles: readonly SocialProfile[];
704
+ className?: string;
705
+ }): react.JSX.Element | null;
706
+
707
+ interface RecurrenceRule {
708
+ freq: "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY";
709
+ interval: number;
710
+ /** Weekly days, e.g. ["TU", "TH"]. */
711
+ byDay: string[];
712
+ /** Monthly "nth weekday" (1–5 or -1), from BYDAY=2TU. */
713
+ nth?: {
714
+ n: number;
715
+ day: string;
716
+ };
717
+ byMonthDay?: number;
718
+ count?: number;
719
+ /** Last allowed date, YYYY-MM-DD. */
720
+ until?: string;
721
+ }
722
+ declare function parseRecurrence(rule: string): RecurrenceRule | null;
723
+ /**
724
+ * Occurrence dates (YYYY-MM-DD) of a rule starting on `start`, in order.
725
+ * - `from` / `through` bound the dates returned (inclusive);
726
+ * - `max` caps how many are returned (counted from `from`, never from the first date);
727
+ * - COUNT is counted from the first date, as RFC 5545 says (DTSTART is the first occurrence).
728
+ * Without COUNT, the walk jumps straight to `from`, so a series that started years ago costs the
729
+ * same as a new one.
730
+ */
731
+ declare function occurrenceDates(rule: string | RecurrenceRule, start: string, options?: {
732
+ from?: string;
733
+ through?: string;
734
+ max?: number;
735
+ }): string[];
736
+ /** The same event moved to another date (local time of day and length kept). */
737
+ declare function eventOn(item: Extract<CollectionItem, {
738
+ kind: "events";
739
+ }>, date: string): typeof item;
740
+ /**
741
+ * Repeating events as one item per occurrence between `from` and `through` (YYYY-MM-DD),
742
+ * at most `perEvent` each: the earliest ones, or with `newest` the most recent ones (for past
743
+ * lists). Other items pass through unchanged.
744
+ */
745
+ declare function expandOccurrences(items: CollectionItem[], window: {
746
+ from: string;
747
+ through: string;
748
+ perEvent?: number;
749
+ newest?: boolean;
750
+ }): CollectionItem[];
751
+ /** React key for a listed item (repeating events share a slug). */
752
+ declare function itemKey(item: CollectionItem): string;
753
+
754
+ /** Class for a typography preset: `os-type-h2`. */
755
+ declare const typeClass: (preset: TypePreset | "button" | "nav") => string;
756
+ interface RichTextProps {
757
+ doc: RichTextDoc | null | undefined;
758
+ className?: string;
759
+ /** Element wrapping the flow (default `div`). */
760
+ as?: "div" | "section" | "article";
761
+ }
762
+ /**
763
+ * Render a rich-text document (arch §3.5) to semantic HTML. Headings and paragraphs carry
764
+ * typography preset classes; links are base-path aware; no inline styles except token
765
+ * highlight colors.
766
+ */
767
+ declare function RichText({ doc, className, as: Tag }: RichTextProps): react.JSX.Element | null;
768
+
769
+ /** True for the first section of a page (its background image loads eagerly). */
770
+ declare const FirstSectionContext: react.Context<boolean>;
771
+ /** Color/gradient backgrounds become a `--bg` var; image/video become a layer element. */
772
+ declare function backgroundVars(bg: SectionBackground | undefined): CssVars;
773
+ declare function SectionBackgroundLayer({ background, }: {
774
+ background: SectionBackground | undefined;
775
+ }): react.JSX.Element | null;
776
+ interface SectionViewProps {
777
+ section: Section;
778
+ /** Element to render (`div` for the footer's inner section). */
779
+ as?: "section" | "div";
780
+ /** Extra classes on the section element. */
781
+ className?: string;
782
+ children?: ReactNode;
783
+ }
784
+ /**
785
+ * A page section: color-theme wrapper, background layer, then the stack or grid body. Every
786
+ * per-instance value is an inline custom property; classes come from the shared stylesheet.
787
+ */
788
+ declare function SectionView({ section, as: Tag, className }: SectionViewProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | react.ReactPortal | react.ReactElement<unknown, string | react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | react.JSX.Element | null | undefined;
789
+
790
+ /**
791
+ * @omniharmonic/openspace-renderer — the one renderer (ADR-3). React components that turn OpenSpace content
792
+ * into HTML for the Studio canvas, the Hub preview, site-kit builds and OG/email generation.
793
+ * Published pages ship no React runtime: interactivity comes from vanilla `data-os-behavior`
794
+ * modules (see `./behaviors-manifest` and the `@omniharmonic/openspace-renderer/node` entry for files).
795
+ */
796
+ declare const name = "@omniharmonic/openspace-renderer";
797
+
798
+ export { type AboveFold, AboveFoldContext, AnnouncementBar, BehaviorName, BlockFieldsContext, BlockRegistryContext, BlockView, type BlockViewProps, Button, type ButtonProps, type ButtonRole, CollectionsContext, type CollectionsValue, type CssVars, DRAWER_VARIANTS, EditorContext, type EditorHooks, type EffectiveHeader, FirstSectionContext, Footer, Header, type HeaderProps, type ImageMap, ImageResolverContext, Img, type ImgProps, Link, type LinkProps, type LinkResolveOptions, PageView, type PageViewProps, Popup, type PopupProps, type PopupTrigger, type QueryOptions, RECURRENCE_HORIZON_DAYS, REMOTE_WIDTHS, type RecurrenceRule, RenderContext, type RenderContextValue, type RenderMode, type RenderPageHtmlOptions, type RendererBlockDefinition, type RendererBlockRegistry, type ResolvedCollections, type ResolvedImage, type ResolvedImageSource, type ResolvedLink, RichText, type RichTextHookArgs, type RichTextProps, SectionBackgroundLayer, SectionContext, SectionView, type SectionViewProps, SitePopup, type SocialFeedPost, SocialIcon, SocialLinks, alignClasses, backgroundVars, behaviorsForPage, blockStyleProps, blockTypeClass, blocksByColumn, collectImageSrcs, colorValue, compiledStylesFor, cx, effectiveHeader, escapeHtml, eventOn, expandOccurrences, feedKey, fieldPathOf, getBlock, globalRegistry, gridItemVars, gridVars, isLive, isUpcoming, itemDate, itemKey, itemPath, jsonForScript, listBlocks, mediaUrl, mobileRows, name, networkLabel, nextOccurrence, normalizeBasePath, occurrenceDates, pageDescription, pagePath, pageTitle, parseRecurrence, popupTrigger, queryCollection, registerBlocks, remoteSrcset, renderPageHtml, resolveImage, resolveLink, resolveNav, resolvePage, responsiveVars, sectionFragment, sectionStyleVars, shortHash, siteUrlFor, stackColumnVars, stackVars, typeClass, unregisterBlocks, useCollectionQuery, useCollections, useEditor, useImageMap, useRenderContext, withBase };