@gradial/aci 0.1.0 → 0.1.2

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.
Files changed (68) hide show
  1. package/README.md +47 -2
  2. package/bin/aci +0 -0
  3. package/bin/aci.js +157 -0
  4. package/dist/assets/index.d.ts +3 -0
  5. package/dist/assets/index.js +3 -0
  6. package/dist/astro/index.d.ts +24 -2
  7. package/dist/astro/index.js +42 -4
  8. package/dist/block-ref.d.ts +34 -0
  9. package/dist/block-ref.js +34 -0
  10. package/dist/content/index.d.ts +0 -3
  11. package/dist/content/index.js +0 -3
  12. package/dist/content/provider.d.ts +32 -8
  13. package/dist/content/provider.js +26 -16
  14. package/dist/content/routes.d.ts +6 -12
  15. package/dist/content/routes.js +9 -55
  16. package/dist/content/validation.js +1 -1
  17. package/dist/define-component.d.ts +1 -0
  18. package/dist/define-component.js +1 -0
  19. package/dist/define-layout.d.ts +1 -0
  20. package/dist/define-layout.js +6 -1
  21. package/dist/dev/browser.d.ts +1 -1
  22. package/dist/dev/browser.js +1 -1
  23. package/dist/dev/index.d.ts +9 -3
  24. package/dist/dev/index.js +74 -8
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +4 -0
  27. package/dist/next/asset-route.d.ts +9 -0
  28. package/dist/next/asset-route.js +15 -0
  29. package/dist/next/config.d.ts +6 -0
  30. package/dist/next/config.js +25 -0
  31. package/dist/next/dev-refresh.js +4 -4
  32. package/dist/next/edge-config.d.ts +1 -0
  33. package/dist/next/edge-config.js +92 -0
  34. package/dist/next/index.d.ts +2 -0
  35. package/dist/next/index.js +2 -0
  36. package/dist/next/middleware.js +4 -6
  37. package/dist/next/server.d.ts +9 -24
  38. package/dist/next/server.js +100 -152
  39. package/dist/providers/file.d.ts +11 -17
  40. package/dist/providers/file.js +44 -78
  41. package/dist/providers/s3.d.ts +26 -0
  42. package/dist/providers/s3.js +174 -0
  43. package/dist/react/GradialMedia.d.ts +24 -0
  44. package/dist/react/GradialMedia.js +31 -0
  45. package/dist/react/GradialPicture.d.ts +14 -0
  46. package/dist/react/GradialPicture.js +30 -0
  47. package/dist/react/GradialVideoPlayer.d.ts +13 -0
  48. package/dist/react/GradialVideoPlayer.js +28 -0
  49. package/dist/react/index.d.ts +3 -0
  50. package/dist/react/index.js +3 -0
  51. package/dist/sveltekit/index.d.ts +18 -2
  52. package/dist/sveltekit/index.js +40 -4
  53. package/dist/testing/index.d.ts +14 -12
  54. package/dist/testing/index.js +41 -28
  55. package/dist/types/component.d.ts +24 -2
  56. package/dist/types/config.d.ts +4 -0
  57. package/dist/types/image.d.ts +51 -0
  58. package/dist/types/image.js +58 -0
  59. package/dist/types/index.d.ts +3 -0
  60. package/dist/types/index.js +3 -0
  61. package/dist/types/layout.d.ts +12 -0
  62. package/dist/types/media.d.ts +69 -0
  63. package/dist/types/media.js +86 -0
  64. package/dist/types/video.d.ts +70 -0
  65. package/dist/types/video.js +22 -0
  66. package/package.json +30 -2
  67. package/src/cli/compile-registry.mjs +303 -0
  68. package/src/cli/validate-content.mjs +489 -0
@@ -0,0 +1,24 @@
1
+ import { type CSSProperties, type ImgHTMLAttributes, type VideoHTMLAttributes } from 'react';
2
+ import type { GradialAsset } from '../types/media.js';
3
+ export interface GradialMediaProps {
4
+ /** The compiler-resolved asset to render. */
5
+ asset: GradialAsset;
6
+ /** CSS class applied to the rendered element. */
7
+ className?: string;
8
+ /** Inline styles applied to the rendered element. */
9
+ style?: CSSProperties;
10
+ /** Loading strategy for images. Defaults to 'lazy'. */
11
+ loading?: 'eager' | 'lazy';
12
+ /** Additional image attributes (only applied when asset is an image). */
13
+ imgProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt'>;
14
+ /** Additional video attributes (only applied when asset is a video). */
15
+ videoProps?: Omit<VideoHTMLAttributes<HTMLVideoElement>, 'src' | 'poster' | 'width' | 'height'>;
16
+ }
17
+ /**
18
+ * Polymorphic media component — renders a GradialAsset as the appropriate
19
+ * HTML element based on $type.
20
+ *
21
+ * - gradial.image → <picture> or <img> via GradialPicture
22
+ * - gradial.video → <video> via GradialVideoPlayer
23
+ */
24
+ export declare function GradialMedia({ asset, className, style, loading, imgProps, videoProps, }: GradialMediaProps): import("react").FunctionComponentElement<import("./GradialPicture.js").GradialPictureProps> | import("react").FunctionComponentElement<import("./GradialVideoPlayer.js").GradialVideoPlayerProps> | null;
@@ -0,0 +1,31 @@
1
+ import { createElement } from 'react';
2
+ import { GradialPicture } from './GradialPicture.js';
3
+ import { GradialVideoPlayer } from './GradialVideoPlayer.js';
4
+ /**
5
+ * Polymorphic media component — renders a GradialAsset as the appropriate
6
+ * HTML element based on $type.
7
+ *
8
+ * - gradial.image → <picture> or <img> via GradialPicture
9
+ * - gradial.video → <video> via GradialVideoPlayer
10
+ */
11
+ export function GradialMedia({ asset, className, style, loading = 'lazy', imgProps, videoProps, }) {
12
+ switch (asset.$type) {
13
+ case 'gradial.image':
14
+ return createElement(GradialPicture, {
15
+ image: asset,
16
+ className,
17
+ style,
18
+ loading,
19
+ ...imgProps,
20
+ });
21
+ case 'gradial.video':
22
+ return createElement(GradialVideoPlayer, {
23
+ video: asset,
24
+ className,
25
+ style,
26
+ ...videoProps,
27
+ });
28
+ default:
29
+ return null;
30
+ }
31
+ }
@@ -0,0 +1,14 @@
1
+ import { type ImgHTMLAttributes } from 'react';
2
+ import type { GradialImage } from '../types/image.js';
3
+ export interface GradialPictureProps extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src' | 'alt'> {
4
+ image: GradialImage;
5
+ }
6
+ /**
7
+ * Renders a GradialImage as a <picture> element with responsive <source> entries,
8
+ * or a plain <img> when no art-directed sources exist.
9
+ *
10
+ * The compiler output determines the rendering:
11
+ * - sources[] populated → <picture> with <source> elements (art direction / format variants)
12
+ * - sources[] empty → <img> with fallback src
13
+ */
14
+ export declare function GradialPicture({ image, ...attrs }: GradialPictureProps): import("react").DetailedReactHTMLElement<import("react").HTMLAttributes<HTMLElement>, HTMLElement>;
@@ -0,0 +1,30 @@
1
+ import { createElement } from 'react';
2
+ /**
3
+ * Renders a GradialImage as a <picture> element with responsive <source> entries,
4
+ * or a plain <img> when no art-directed sources exist.
5
+ *
6
+ * The compiler output determines the rendering:
7
+ * - sources[] populated → <picture> with <source> elements (art direction / format variants)
8
+ * - sources[] empty → <img> with fallback src
9
+ */
10
+ export function GradialPicture({ image, ...attrs }) {
11
+ const img = createElement('img', {
12
+ src: image.fallback.src,
13
+ alt: image.alt,
14
+ width: image.fallback.width > 0 ? image.fallback.width : undefined,
15
+ height: image.fallback.height > 0 ? image.fallback.height : undefined,
16
+ loading: 'lazy',
17
+ decoding: 'async',
18
+ ...attrs,
19
+ });
20
+ if (!image.sources?.length) {
21
+ return img;
22
+ }
23
+ return createElement('picture', null, ...image.sources.map((source, index) => createElement('source', {
24
+ key: index,
25
+ media: source.media,
26
+ type: source.type,
27
+ srcSet: source.srcset,
28
+ sizes: source.sizes,
29
+ })), img);
30
+ }
@@ -0,0 +1,13 @@
1
+ import { type VideoHTMLAttributes } from 'react';
2
+ import type { GradialVideo } from '../types/video.js';
3
+ export interface GradialVideoPlayerProps extends Omit<VideoHTMLAttributes<HTMLVideoElement>, 'src' | 'poster' | 'width' | 'height'> {
4
+ video: GradialVideo;
5
+ }
6
+ /**
7
+ * Renders a GradialVideo as a <video> element.
8
+ *
9
+ * - Uses poster.fallback.src for the poster attribute (single URL — browser limitation).
10
+ * - Includes <source> elements for format variants when available.
11
+ * - Falls back to src attribute on <video> when no source variants exist.
12
+ */
13
+ export declare function GradialVideoPlayer({ video, ...attrs }: GradialVideoPlayerProps): import("react").DetailedReactHTMLElement<Record<string, unknown>, HTMLElement>;
@@ -0,0 +1,28 @@
1
+ import { createElement } from 'react';
2
+ /**
3
+ * Renders a GradialVideo as a <video> element.
4
+ *
5
+ * - Uses poster.fallback.src for the poster attribute (single URL — browser limitation).
6
+ * - Includes <source> elements for format variants when available.
7
+ * - Falls back to src attribute on <video> when no source variants exist.
8
+ */
9
+ export function GradialVideoPlayer({ video, ...attrs }) {
10
+ const posterSrc = video.poster?.fallback.src;
11
+ const hasSources = video.sources && video.sources.length > 0;
12
+ const videoAttrs = {
13
+ poster: posterSrc,
14
+ width: video.width > 0 ? video.width : undefined,
15
+ height: video.height > 0 ? video.height : undefined,
16
+ ...attrs,
17
+ };
18
+ // If no format variants, put src directly on <video>
19
+ if (!hasSources) {
20
+ videoAttrs.src = video.src;
21
+ }
22
+ const children = (video.sources ?? []).map((source, index) => createElement('source', {
23
+ key: index,
24
+ src: source.src,
25
+ type: source.type,
26
+ }));
27
+ return createElement('video', videoAttrs, ...children);
28
+ }
@@ -0,0 +1,3 @@
1
+ export { GradialPicture } from './GradialPicture.js';
2
+ export { GradialVideoPlayer } from './GradialVideoPlayer.js';
3
+ export { GradialMedia } from './GradialMedia.js';
@@ -0,0 +1,3 @@
1
+ export { GradialPicture } from './GradialPicture.js';
2
+ export { GradialVideoPlayer } from './GradialVideoPlayer.js';
3
+ export { GradialMedia } from './GradialMedia.js';
@@ -1,2 +1,18 @@
1
- import { type BareMetalContentWatchOptions, type VitePluginLike } from '../dev/index.js';
2
- export declare function baremetalSvelteKit(options?: BareMetalContentWatchOptions): VitePluginLike;
1
+ import { type GradialContentWatchOptions, type VitePluginLike } from '../dev/index.js';
2
+ /**
3
+ * Generates prerender entries from the compiled content manifest for SvelteKit.
4
+ *
5
+ * Usage in svelte.config.js:
6
+ * ```js
7
+ * import { gradialEntries } from '@gradial/aci/sveltekit';
8
+ * export default { kit: { prerender: { entries: await gradialEntries() } } };
9
+ * ```
10
+ *
11
+ * Or in a catch-all `+page.server.ts`:
12
+ * ```ts
13
+ * import { gradialEntries } from '@gradial/aci/sveltekit';
14
+ * export const entries = gradialEntries;
15
+ * ```
16
+ */
17
+ export declare function gradialEntries(): Promise<string[]>;
18
+ export declare function gradialSvelteKit(options?: GradialContentWatchOptions): VitePluginLike;
@@ -1,9 +1,45 @@
1
- import { baremetalContentWatchPlugin, devRefreshPort, devRefreshScriptTag, } from '../dev/index.js';
2
- export function baremetalSvelteKit(options = {}) {
3
- const watchPlugin = baremetalContentWatchPlugin(options);
1
+ import { gradialContentWatchPlugin, gradialDamAssetPlugin, devRefreshPort, devRefreshScriptTag, } from '../dev/index.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Entries helper — for SvelteKit SSG prerendering
4
+ // ---------------------------------------------------------------------------
5
+ /**
6
+ * Generates prerender entries from the compiled content manifest for SvelteKit.
7
+ *
8
+ * Usage in svelte.config.js:
9
+ * ```js
10
+ * import { gradialEntries } from '@gradial/aci/sveltekit';
11
+ * export default { kit: { prerender: { entries: await gradialEntries() } } };
12
+ * ```
13
+ *
14
+ * Or in a catch-all `+page.server.ts`:
15
+ * ```ts
16
+ * import { gradialEntries } from '@gradial/aci/sveltekit';
17
+ * export const entries = gradialEntries;
18
+ * ```
19
+ */
20
+ export async function gradialEntries() {
21
+ const { FileContentProvider } = await import('../providers/file.js');
22
+ const { normalizeRoute } = await import('../content/routes.js');
23
+ const provider = new FileContentProvider();
24
+ const routes = await provider.listRoutes();
25
+ return routes.map((entry) => {
26
+ const normalized = normalizeRoute(entry.path);
27
+ return normalized === '/' ? '/' : `/${normalized.replace(/^\/|\/$/g, '')}/`;
28
+ });
29
+ }
30
+ // ---------------------------------------------------------------------------
31
+ // SvelteKit Vite plugin
32
+ // ---------------------------------------------------------------------------
33
+ export function gradialSvelteKit(options = {}) {
34
+ const watchPlugin = gradialContentWatchPlugin(options);
35
+ const damAssetPlugin = gradialDamAssetPlugin(options);
4
36
  return {
5
37
  ...watchPlugin,
6
- name: 'baremetal-sveltekit',
38
+ name: 'gradial-sveltekit',
39
+ configureServer(server) {
40
+ damAssetPlugin.configureServer?.(server);
41
+ watchPlugin.configureServer?.(server);
42
+ },
7
43
  transformIndexHtml(html) {
8
44
  if (process.env.NODE_ENV === 'production') {
9
45
  return html;
@@ -1,20 +1,22 @@
1
- import { type ContentProvider, type RenderInputOptions } from '../content/provider.js';
1
+ import { type ContentProvider, type RouteEntry, type RenderInputOptions } from '../content/provider.js';
2
2
  import type { KernelPage, KernelRouteMetadata, KernelSiteConfig, RenderInput } from '../content/types.js';
3
- export interface FixtureContentProviderOptions<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> {
4
- siteConfig?: TSiteConfig;
5
- pages?: Record<string, TPage | null>;
3
+ export interface FixtureContentProviderOptions {
4
+ siteConfig?: KernelSiteConfig;
5
+ pages?: Record<string, KernelPage>;
6
+ fragments?: Record<string, unknown>;
6
7
  }
7
- export declare class FixtureContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig> implements ContentProvider<TPage, TSiteConfig> {
8
+ export declare class FixtureContentProvider implements ContentProvider {
8
9
  private siteConfig;
9
10
  private pages;
10
- constructor(options?: FixtureContentProviderOptions<TPage, TSiteConfig>);
11
- loadSiteConfig(): Promise<TSiteConfig>;
12
- loadPage(route?: string): Promise<TPage | null>;
13
- listRoutes(): Promise<string[]>;
14
- listPublishedRoutes(): Promise<string[]>;
11
+ private fragments;
12
+ constructor(options?: FixtureContentProviderOptions);
13
+ getSiteConfig<T = unknown>(): Promise<T>;
14
+ getPage<T = unknown>(route: string): Promise<T>;
15
+ getFragment<T = unknown>(id: string): Promise<T>;
16
+ listRoutes(): Promise<RouteEntry[]>;
17
+ loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput>;
15
18
  resolveRouteMetadata(route?: string): Promise<KernelRouteMetadata>;
16
- loadRenderInput(route?: string, options?: RenderInputOptions): Promise<RenderInput<TPage, TSiteConfig>>;
17
19
  }
18
- export declare function createFixtureContentProvider<TPage extends KernelPage = KernelPage, TSiteConfig extends KernelSiteConfig = KernelSiteConfig>(options?: FixtureContentProviderOptions<TPage, TSiteConfig>): FixtureContentProvider<TPage, TSiteConfig>;
20
+ export declare function createFixtureContentProvider(options?: FixtureContentProviderOptions): FixtureContentProvider;
19
21
  export declare function defaultFixtureSiteConfig(): KernelSiteConfig;
20
22
  export declare function defaultFixturePage(): KernelPage;
@@ -1,51 +1,64 @@
1
- import { loadRenderInput, routeMetadataForContent, } from '../content/provider.js';
1
+ import { PageNotFoundError, FragmentNotFoundError, loadRenderInput, routeMetadataForContent, } from '../content/provider.js';
2
2
  import { normalizeRoute } from '../content/routes.js';
3
3
  export class FixtureContentProvider {
4
4
  siteConfig;
5
5
  pages;
6
+ fragments;
6
7
  constructor(options = {}) {
7
8
  this.siteConfig = cloneFixture(options.siteConfig ?? defaultFixtureSiteConfig());
8
9
  this.pages = new Map();
9
- const inputPages = options.pages ?? {
10
- '/': defaultFixturePage(),
11
- };
10
+ this.fragments = new Map();
11
+ const inputPages = options.pages ?? { '/': defaultFixturePage() };
12
12
  for (const [route, page] of Object.entries(inputPages)) {
13
- if (page) {
14
- this.pages.set(normalizeRoute(route), cloneFixture(page));
13
+ this.pages.set(normalizeRoute(route), cloneFixture(page));
14
+ }
15
+ if (options.fragments) {
16
+ for (const [id, fragment] of Object.entries(options.fragments)) {
17
+ this.fragments.set(id, cloneFixture(fragment));
15
18
  }
16
19
  }
17
20
  }
18
- async loadSiteConfig() {
21
+ async getSiteConfig() {
19
22
  return cloneFixture(this.siteConfig);
20
23
  }
21
- async loadPage(route = '/') {
24
+ async getPage(route) {
22
25
  const page = this.pages.get(normalizeRoute(route));
23
- return page ? cloneFixture(page) : null;
24
- }
25
- async listRoutes() {
26
- return [...this.pages.keys()].sort((left, right) => left.localeCompare(right));
26
+ if (!page) {
27
+ throw new PageNotFoundError(route);
28
+ }
29
+ return cloneFixture(page);
27
30
  }
28
- async listPublishedRoutes() {
29
- const routes = await this.listRoutes();
30
- const published = [];
31
- for (const route of routes) {
32
- const page = await this.loadPage(route);
33
- if (page?.status === 'published') {
34
- published.push(route);
35
- }
31
+ async getFragment(id) {
32
+ const fragment = this.fragments.get(id);
33
+ if (fragment === undefined) {
34
+ throw new FragmentNotFoundError(id);
36
35
  }
37
- return published;
36
+ return cloneFixture(fragment);
38
37
  }
39
- async resolveRouteMetadata(route = '/') {
40
- const [siteConfig, page] = await Promise.all([
41
- this.loadSiteConfig(),
42
- this.loadPage(route),
43
- ]);
44
- return routeMetadataForContent(siteConfig, page);
38
+ async listRoutes() {
39
+ return [...this.pages.entries()]
40
+ .sort(([a], [b]) => a.localeCompare(b))
41
+ .map(([routePath, page]) => ({
42
+ slug: routePath === '/' ? 'home' : routePath.replace(/^\//, ''),
43
+ path: routePath,
44
+ payloadRef: `routes/${routePath === '/' ? 'home' : routePath.replace(/^\//, '')}.json`,
45
+ digest: '',
46
+ }));
45
47
  }
46
48
  async loadRenderInput(route = '/', options = {}) {
47
49
  return loadRenderInput(this, route, options);
48
50
  }
51
+ async resolveRouteMetadata(route = '/') {
52
+ const siteConfig = await this.getSiteConfig();
53
+ let page;
54
+ try {
55
+ page = await this.getPage(route);
56
+ }
57
+ catch {
58
+ page = null;
59
+ }
60
+ return routeMetadataForContent(siteConfig, page);
61
+ }
49
62
  }
50
63
  export function createFixtureContentProvider(options = {}) {
51
64
  return new FixtureContentProvider(options);
@@ -56,7 +69,7 @@ export function defaultFixtureSiteConfig() {
56
69
  $type: 'site',
57
70
  status: 'published',
58
71
  title: 'Fixture Site',
59
- domain: 'www.baremetal.local',
72
+ domain: 'www.aci.local',
60
73
  defaultLocale: 'en-us',
61
74
  seo: {
62
75
  title: 'Fixture Site',
@@ -1,26 +1,48 @@
1
1
  import type { IslandMode, VaryDimension } from './render-mode.js';
2
+ import type { ImageSlotContract } from './image.js';
3
+ import type { z } from 'zod';
2
4
  export interface ComponentRenderModes {
3
5
  canStatic: boolean;
4
6
  canSSR: boolean;
5
7
  canClientIsland: boolean;
6
8
  }
9
+ /**
10
+ * A CMS-registered component — sync or async (server components).
11
+ * Accepts content props derived from the Zod schema.
12
+ */
13
+ type CmsComponentFn<TProps = any> = ((props: TProps) => import('react').ReactElement | null) | ((props: TProps) => Promise<import('react').ReactElement | null>);
14
+ /**
15
+ * Infers the props type from a schema.
16
+ * If TSchema is a Zod type, returns z.infer<TSchema>.
17
+ * Otherwise returns Record<string, unknown>.
18
+ */
19
+ type InferContentProps<TSchema> = TSchema extends z.ZodType<infer T> ? T : Record<string, unknown>;
7
20
  export interface ComponentDefinition<TSchema = unknown> {
8
21
  name: string;
9
- component?: unknown;
22
+ /**
23
+ * The React component that renders this CMS block.
24
+ * Accepts at least the schema-derived props, but may accept additional
25
+ * composition params (className, etc.) passed by parent sections.
26
+ */
27
+ component?: CmsComponentFn<any>;
10
28
  schema: TSchema;
11
29
  renderModes?: ComponentRenderModes;
12
30
  render?: ComponentRenderModes;
13
31
  defaultIslandMode?: IslandMode;
14
32
  varyDimensions?: VaryDimension[];
15
33
  vary?: VaryDimension[];
34
+ imageSlots?: Record<string, ImageSlotContract>;
16
35
  }
17
36
  export interface ComponentContract<TSchema = unknown> {
18
37
  name: string;
19
- component?: unknown;
38
+ component?: CmsComponentFn<any>;
20
39
  schema: TSchema;
21
40
  renderModes: ComponentRenderModes;
22
41
  render: ComponentRenderModes;
23
42
  defaultIslandMode?: IslandMode;
24
43
  varyDimensions?: VaryDimension[];
25
44
  vary?: VaryDimension[];
45
+ imageSlots?: Record<string, ImageSlotContract>;
26
46
  }
47
+ export type InferComponentProps<TContract> = TContract extends ComponentContract<infer TSchema> ? InferContentProps<TSchema> : Record<string, unknown>;
48
+ export type { CmsComponentFn, InferContentProps };
@@ -12,6 +12,7 @@ export interface BareMetalConfig {
12
12
  rendererEntry: string;
13
13
  capabilities: RenderCapabilities;
14
14
  routes: RouteConfig;
15
+ dam?: DAMConfig;
15
16
  externalDependencies?: ExternalDependency[];
16
17
  rendererProtocol: 'http' | 'stdio-json';
17
18
  }
@@ -26,3 +27,6 @@ export interface ExternalDependency {
26
27
  description?: string;
27
28
  cacheTTL?: number;
28
29
  }
30
+ export interface DAMConfig {
31
+ autoApproveUploads?: boolean;
32
+ }
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ export interface ImageSource {
3
+ src: string;
4
+ width: number;
5
+ height: number;
6
+ type: string;
7
+ }
8
+ export interface PictureSource {
9
+ media?: string;
10
+ type: string;
11
+ srcset: string;
12
+ sizes?: string;
13
+ }
14
+ export interface GradialImage {
15
+ $type: 'gradial.image';
16
+ assetId: string;
17
+ versionId: string;
18
+ alt: string;
19
+ fallback: ImageSource;
20
+ sources: PictureSource[];
21
+ }
22
+ export interface ImageSlotContract {
23
+ outputs: SlotOutput[];
24
+ formats: string[];
25
+ sizes: string;
26
+ }
27
+ export interface SlotOutput {
28
+ aspectRatio: string;
29
+ widths: number[];
30
+ media?: string;
31
+ }
32
+ export declare const GradialImageSchema: z.ZodObject<{
33
+ $type: z.ZodLiteral<"gradial.image">;
34
+ assetId: z.ZodString;
35
+ versionId: z.ZodString;
36
+ alt: z.ZodString;
37
+ fallback: z.ZodObject<{
38
+ src: z.ZodString;
39
+ width: z.ZodNumber;
40
+ height: z.ZodNumber;
41
+ type: z.ZodString;
42
+ }, z.core.$strip>;
43
+ sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
44
+ media: z.ZodOptional<z.ZodString>;
45
+ type: z.ZodString;
46
+ srcset: z.ZodString;
47
+ sizes: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strip>>>>;
49
+ }, z.core.$strip>;
50
+ export type ImageHTMLAttributes = Record<string, string | number | boolean | undefined | null>;
51
+ export declare function renderImageHTML(image: GradialImage, attrs?: ImageHTMLAttributes): string;
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod';
2
+ export const GradialImageSchema = z.object({
3
+ $type: z.literal('gradial.image'),
4
+ assetId: z.string().min(1),
5
+ versionId: z.string().min(1),
6
+ alt: z.string(),
7
+ fallback: z.object({
8
+ src: z.string().min(1),
9
+ width: z.number().int().nonnegative(),
10
+ height: z.number().int().nonnegative(),
11
+ type: z.string().min(1),
12
+ }),
13
+ sources: z.array(z.object({
14
+ media: z.string().optional(),
15
+ type: z.string().min(1),
16
+ srcset: z.string().min(1),
17
+ sizes: z.string().optional(),
18
+ })).nullable().default([]),
19
+ });
20
+ export function renderImageHTML(image, attrs = {}) {
21
+ const imgAttrs = renderAttrs({
22
+ ...attrs,
23
+ src: image.fallback.src,
24
+ alt: image.alt,
25
+ width: image.fallback.width > 0 ? image.fallback.width : undefined,
26
+ height: image.fallback.height > 0 ? image.fallback.height : undefined,
27
+ });
28
+ const img = `<img${imgAttrs}>`;
29
+ if (!image.sources.length) {
30
+ return img;
31
+ }
32
+ const sources = image.sources.map((source) => `<source${renderAttrs({
33
+ media: source.media,
34
+ type: source.type,
35
+ srcset: source.srcset,
36
+ sizes: source.sizes,
37
+ })}>`).join('');
38
+ return `<picture>${sources}${img}</picture>`;
39
+ }
40
+ function renderAttrs(attrs) {
41
+ const rendered = Object.entries(attrs)
42
+ .filter(([, value]) => value !== undefined && value !== null && value !== false)
43
+ .map(([name, value]) => value === true ? escapeName(name) : `${escapeName(name)}="${escapeAttr(String(value))}"`);
44
+ if (!rendered.length) {
45
+ return '';
46
+ }
47
+ return ' ' + rendered.join(' ');
48
+ }
49
+ function escapeName(name) {
50
+ return name.replace(/[^A-Za-z0-9_:-]/g, '');
51
+ }
52
+ function escapeAttr(value) {
53
+ return value
54
+ .replace(/&/g, '&amp;')
55
+ .replace(/"/g, '&quot;')
56
+ .replace(/</g, '&lt;')
57
+ .replace(/>/g, '&gt;');
58
+ }
@@ -4,3 +4,6 @@ export * from './layout.js';
4
4
  export * from './page.js';
5
5
  export * from './renderer.js';
6
6
  export * from './render-mode.js';
7
+ export * from './image.js';
8
+ export * from './video.js';
9
+ export * from './media.js';
@@ -4,3 +4,6 @@ export * from './layout.js';
4
4
  export * from './page.js';
5
5
  export * from './renderer.js';
6
6
  export * from './render-mode.js';
7
+ export * from './image.js';
8
+ export * from './video.js';
9
+ export * from './media.js';
@@ -1,12 +1,24 @@
1
+ import type { FragmentRefNode } from './page.js';
1
2
  export interface LayoutSlot {
2
3
  name: string;
3
4
  required: boolean;
4
5
  }
6
+ /** Default content for non-required layout slots, keyed by slot name. */
7
+ export type LayoutDefaults = Record<string, FragmentRefNode[]>;
5
8
  export interface LayoutDefinition {
6
9
  name: string;
7
10
  slots: LayoutSlot[];
11
+ /** Default fragment references for slots not provided by the page. */
12
+ defaults?: LayoutDefaults;
8
13
  }
9
14
  export interface LayoutContract {
10
15
  name: string;
11
16
  slots: LayoutSlot[];
17
+ defaults?: LayoutDefaults;
18
+ }
19
+ /** A compiled layout file as stored on disk. */
20
+ export interface CompiledLayout {
21
+ name: string;
22
+ slots: LayoutSlot[];
23
+ defaults: LayoutDefaults;
12
24
  }
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ import { type GradialImage } from './image.js';
3
+ import { type GradialVideo } from './video.js';
4
+ /** Discriminated union of all compiler-resolved asset types. */
5
+ export type GradialAsset = GradialImage | GradialVideo;
6
+ export declare const GradialAssetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7
+ $type: z.ZodLiteral<"gradial.image">;
8
+ assetId: z.ZodString;
9
+ versionId: z.ZodString;
10
+ alt: z.ZodString;
11
+ fallback: z.ZodObject<{
12
+ src: z.ZodString;
13
+ width: z.ZodNumber;
14
+ height: z.ZodNumber;
15
+ type: z.ZodString;
16
+ }, z.core.$strip>;
17
+ sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
18
+ media: z.ZodOptional<z.ZodString>;
19
+ type: z.ZodString;
20
+ srcset: z.ZodString;
21
+ sizes: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>>>>;
23
+ }, z.core.$strip>, z.ZodObject<{
24
+ $type: z.ZodLiteral<"gradial.video">;
25
+ assetId: z.ZodString;
26
+ versionId: z.ZodString;
27
+ alt: z.ZodString;
28
+ src: z.ZodString;
29
+ type: z.ZodString;
30
+ width: z.ZodNumber;
31
+ height: z.ZodNumber;
32
+ poster: z.ZodOptional<z.ZodObject<{
33
+ $type: z.ZodLiteral<"gradial.image">;
34
+ assetId: z.ZodString;
35
+ versionId: z.ZodString;
36
+ alt: z.ZodString;
37
+ fallback: z.ZodObject<{
38
+ src: z.ZodString;
39
+ width: z.ZodNumber;
40
+ height: z.ZodNumber;
41
+ type: z.ZodString;
42
+ }, z.core.$strip>;
43
+ sources: z.ZodDefault<z.ZodNullable<z.ZodArray<z.ZodObject<{
44
+ media: z.ZodOptional<z.ZodString>;
45
+ type: z.ZodString;
46
+ srcset: z.ZodString;
47
+ sizes: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strip>>>>;
49
+ }, z.core.$strip>>;
50
+ sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
51
+ src: z.ZodString;
52
+ type: z.ZodString;
53
+ }, z.core.$strip>>>;
54
+ duration: z.ZodOptional<z.ZodNumber>;
55
+ }, z.core.$strip>], "$type">;
56
+ export declare function isGradialImage(value: unknown): value is GradialImage;
57
+ export declare function isGradialVideo(value: unknown): value is GradialVideo;
58
+ export declare function isGradialAsset(value: unknown): value is GradialAsset;
59
+ /**
60
+ * Renders a GradialVideo to an HTML string.
61
+ * Uses poster.fallback.src for the poster attribute.
62
+ * Includes <source> elements for format variants.
63
+ */
64
+ export declare function renderVideoHTML(video: GradialVideo, attrs?: Record<string, string | number | boolean | undefined | null>): string;
65
+ /**
66
+ * Renders any GradialAsset to an HTML string.
67
+ * Delegates to renderImageHTML or renderVideoHTML based on $type.
68
+ */
69
+ export declare function renderAssetHTML(asset: GradialAsset, attrs?: Record<string, string | number | boolean | undefined | null>): string;