@riverbankcms/sdk 0.7.0 → 0.7.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.
- package/README.md +175 -0
- package/dist/cli/index.js +42 -95
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init-docs/content/agents-section.md +50 -0
- package/dist/cli/init-docs/content/cli-reference.md +574 -0
- package/dist/cli/init-docs/content/content-management.md +384 -0
- package/dist/cli/init-docs/content/context-brand.md +125 -0
- package/dist/cli/init-docs/content/context-brief.md +77 -0
- package/dist/cli/init-docs/content/context-knowledge.md +111 -0
- package/dist/cli/init-docs/content/getting-started.md +130 -0
- package/dist/cli/init-docs/content/site-workflows-readme.md +96 -0
- package/dist/cli/init-docs/content/workflow-add-block.md +228 -0
- package/dist/cli/init-docs/content/workflow-create-page.md +193 -0
- package/dist/cli/init-docs/content/workflow-publish.md +280 -0
- package/dist/client/client.d.mts +2 -2
- package/dist/client/client.d.ts +2 -2
- package/dist/client/client.js +536 -71
- package/dist/client/client.js.map +1 -1
- package/dist/client/client.mjs +536 -78
- package/dist/client/client.mjs.map +1 -1
- package/dist/client/hooks.d.mts +2 -2
- package/dist/client/hooks.d.ts +2 -2
- package/dist/client/usePage-C9tJpuKa.d.mts +6813 -0
- package/dist/client/usePage-Db9kzA41.d.ts +6813 -0
- package/dist/server/{Layout-wBtJLTVX.d.ts → Layout-Ce7PU9I5.d.ts} +1 -1
- package/dist/server/{Layout-B7cvis7r.d.mts → Layout-WllR8Zug.d.mts} +1 -1
- package/dist/server/{chunk-7FIJSGHU.mjs → chunk-5JT452F2.mjs} +537 -76
- package/dist/server/chunk-5JT452F2.mjs.map +1 -0
- package/dist/server/chunk-AET56TQX.mjs +45 -0
- package/dist/server/chunk-AET56TQX.mjs.map +1 -0
- package/dist/server/{chunk-P7UVAMK6.js → chunk-HMENX4Y7.js} +543 -82
- package/dist/server/chunk-HMENX4Y7.js.map +1 -0
- package/dist/server/chunk-LQUKXIW7.mjs +13 -0
- package/dist/server/chunk-LQUKXIW7.mjs.map +1 -0
- package/dist/server/chunk-VODFQMUW.js +45 -0
- package/dist/server/chunk-VODFQMUW.js.map +1 -0
- package/dist/server/chunk-WYNEYDXO.js +13 -0
- package/dist/server/chunk-WYNEYDXO.js.map +1 -0
- package/dist/server/{components-CICSJyp_.d.ts → components--LT61IKp.d.ts} +2 -2
- package/dist/server/{components-CMMwDXTW.d.mts → components-RPzRQve6.d.mts} +2 -2
- package/dist/server/components.d.mts +4 -4
- package/dist/server/components.d.ts +4 -4
- package/dist/server/components.js +0 -1
- package/dist/server/components.js.map +1 -1
- package/dist/server/components.mjs +0 -1
- package/dist/server/config-validation.js +0 -1
- package/dist/server/config-validation.js.map +1 -1
- package/dist/server/config-validation.mjs +0 -1
- package/dist/server/config.js +0 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/config.mjs +0 -1
- package/dist/server/config.mjs.map +1 -1
- package/dist/server/data.d.mts +1 -1
- package/dist/server/data.d.ts +1 -1
- package/dist/server/data.js +0 -1
- package/dist/server/data.js.map +1 -1
- package/dist/server/data.mjs +0 -1
- package/dist/server/env.d.mts +23 -0
- package/dist/server/env.d.ts +23 -0
- package/dist/server/env.js +7 -0
- package/dist/server/env.js.map +1 -0
- package/dist/server/env.mjs +7 -0
- package/dist/server/{index-DI_qlYx3.d.mts → index-BL66CU6d.d.mts} +2 -2
- package/dist/server/{index-Bucs6UqG.d.mts → index-Bkva0WAj.d.mts} +1 -1
- package/dist/server/{index-BTwWvSBu.d.ts → index-CJk9iQQW.d.ts} +2 -2
- package/dist/server/{index-Cp7tJuRt.d.ts → index-CSBWKA3r.d.ts} +1 -1
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +2 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1 -2
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/{loadContent-DmgpFcFC.d.ts → loadContent-CXUWMuzY.d.ts} +2 -2
- package/dist/server/{loadContent-C-YYUKQa.d.mts → loadContent-F_tAS0Nl.d.mts} +2 -2
- package/dist/server/{loadPage-IDGVDFBB.js → loadPage-6I7F6GRF.js} +1 -2
- package/dist/server/loadPage-6I7F6GRF.js.map +1 -0
- package/dist/server/{loadPage-B8mQUUSo.d.mts → loadPage-CxlYLe5K.d.mts} +1 -1
- package/dist/server/{loadPage-DNQTTRHL.mjs → loadPage-JI2SML4M.mjs} +1 -2
- package/dist/server/{loadPage-DP3nrHBi.d.ts → loadPage-i2r-X5b9.d.ts} +1 -1
- package/dist/server/metadata.d.mts +3 -3
- package/dist/server/metadata.d.ts +3 -3
- package/dist/server/metadata.js +0 -1
- package/dist/server/metadata.js.map +1 -1
- package/dist/server/metadata.mjs +0 -1
- package/dist/server/navigation.js +0 -1
- package/dist/server/navigation.js.map +1 -1
- package/dist/server/navigation.mjs +0 -1
- package/dist/server/next/revalidate.d.mts +66 -0
- package/dist/server/next/revalidate.d.ts +66 -0
- package/dist/server/next/revalidate.js +59 -0
- package/dist/server/next/revalidate.js.map +1 -0
- package/dist/server/next/revalidate.mjs +59 -0
- package/dist/server/next/revalidate.mjs.map +1 -0
- package/dist/server/next/tags.d.mts +78 -0
- package/dist/server/next/tags.d.ts +78 -0
- package/dist/server/next/tags.js +34 -0
- package/dist/server/next/tags.js.map +1 -0
- package/dist/server/next/tags.mjs +34 -0
- package/dist/server/next/tags.mjs.map +1 -0
- package/dist/server/next.d.mts +163 -5
- package/dist/server/next.d.ts +163 -5
- package/dist/server/next.js +78 -11
- package/dist/server/next.js.map +1 -1
- package/dist/server/next.mjs +75 -8
- package/dist/server/next.mjs.map +1 -1
- package/dist/server/rendering/server.d.mts +3 -3
- package/dist/server/rendering/server.d.ts +3 -3
- package/dist/server/rendering/server.js +0 -1
- package/dist/server/rendering/server.js.map +1 -1
- package/dist/server/rendering/server.mjs +0 -1
- package/dist/server/rendering.d.mts +6 -6
- package/dist/server/rendering.d.ts +6 -6
- package/dist/server/rendering.js +2 -3
- package/dist/server/rendering.js.map +1 -1
- package/dist/server/rendering.mjs +3 -4
- package/dist/server/routing.d.mts +2 -2
- package/dist/server/routing.d.ts +2 -2
- package/dist/server/routing.js +2 -4
- package/dist/server/routing.js.map +1 -1
- package/dist/server/routing.mjs +1 -3
- package/dist/server/routing.mjs.map +1 -1
- package/dist/server/server.d.mts +4 -4
- package/dist/server/server.d.ts +4 -4
- package/dist/server/server.js +4 -5
- package/dist/server/server.js.map +1 -1
- package/dist/server/server.mjs +4 -5
- package/dist/server/theme-bridge.js +0 -1
- package/dist/server/theme-bridge.js.map +1 -1
- package/dist/server/theme-bridge.mjs +0 -1
- package/dist/server/theme-bridge.mjs.map +1 -1
- package/dist/server/theme.js +1 -3
- package/dist/server/theme.js.map +1 -1
- package/dist/server/theme.mjs +0 -2
- package/dist/server/theme.mjs.map +1 -1
- package/dist/server/{types-BvcJU7zk.d.ts → types-DnkRh0UL.d.ts} +118 -9
- package/dist/server/{types-1cLz0vnq.d.mts → types-MF2AWoKv.d.mts} +118 -9
- package/dist/server/webhooks.d.mts +75 -0
- package/dist/server/webhooks.d.ts +75 -0
- package/dist/server/webhooks.js +11 -0
- package/dist/server/webhooks.js.map +1 -0
- package/dist/server/webhooks.mjs +11 -0
- package/dist/server/webhooks.mjs.map +1 -0
- package/package.json +23 -3
- package/dist/server/chunk-7FIJSGHU.mjs.map +0 -1
- package/dist/server/chunk-BJTO5JO5.mjs +0 -11
- package/dist/server/chunk-DGUM43GV.js +0 -11
- package/dist/server/chunk-DGUM43GV.js.map +0 -1
- package/dist/server/chunk-P7UVAMK6.js.map +0 -1
- package/dist/server/loadPage-IDGVDFBB.js.map +0 -1
- /package/dist/server/{chunk-BJTO5JO5.mjs.map → env.mjs.map} +0 -0
- /package/dist/server/{loadPage-DNQTTRHL.mjs.map → loadPage-JI2SML4M.mjs.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/routing/checkRedirect.ts","../../src/routing/resolveRoute.ts","../../src/routing/contentRoutes.ts","../../src/routing/resolveOccurrence.ts"],"sourcesContent":["/**\n * Redirect checking for 404 handling\n *\n * Check if a path has a configured redirect rule before returning 404.\n */\n\nimport type { RiverbankClient } from '../client/types';\n\n/**\n * Redirect rule data\n */\nexport type RedirectRule = {\n /**\n * Destination URL to redirect to\n */\n destination: string;\n\n /**\n * Whether this is a permanent redirect (301/308) or temporary (302/307)\n */\n permanent: boolean;\n};\n\n/**\n * Result of redirect checking\n */\nexport type RedirectResult =\n | {\n hasRedirect: true;\n redirect: RedirectRule;\n }\n | {\n hasRedirect: false;\n redirect: null;\n };\n\n/**\n * Parameters for checking redirects\n */\nexport type CheckRedirectParams = {\n /**\n * SDK client instance\n */\n client: RiverbankClient;\n\n /**\n * Site ID\n */\n siteId: string;\n\n /**\n * URL path to check (e.g., '/old-page')\n */\n path: string;\n};\n\n/**\n * Check if a path has a configured redirect rule\n *\n * Use this when a page is not found to check if there's a redirect\n * configured for the path before returning a 404.\n *\n * @example\n * ```tsx\n * import { checkRedirectForPath } from '@riverbankcms/sdk/routing';\n * import { notFound, redirect } from 'next/navigation';\n *\n * export default async function Page({ params }) {\n * const page = await getPage(params.path);\n *\n * if (!page) {\n * // Check for redirect before 404\n * const result = await checkRedirectForPath({\n * client,\n * siteId: 'your-site-id',\n * path: params.path,\n * });\n *\n * if (result.hasRedirect) {\n * redirect(result.redirect.destination);\n * }\n *\n * notFound();\n * }\n *\n * return <PageContent page={page} />;\n * }\n * ```\n */\nexport async function checkRedirectForPath(\n params: CheckRedirectParams\n): Promise<RedirectResult> {\n const { client, siteId, path } = params;\n\n try {\n const response = await client.checkRedirect({\n siteId,\n path,\n });\n\n // API returns { redirectTo: string | null, status?: number }\n if (response.redirectTo) {\n // Status 301 and 308 are permanent, others are temporary\n const isPermanent = response.status === 301 || response.status === 308;\n\n return {\n hasRedirect: true,\n redirect: {\n destination: response.redirectTo,\n permanent: isPermanent,\n },\n };\n }\n\n return { hasRedirect: false, redirect: null };\n } catch (error) {\n console.warn('[checkRedirectForPath] Failed to check redirect', {\n siteId,\n path,\n error: error instanceof Error ? error.message : String(error),\n });\n\n return { hasRedirect: false, redirect: null };\n }\n}\n","/**\n * Route resolution helper for dynamic page routing\n *\n * Resolves URL paths to pages, entries, redirects, or 404s.\n */\n\nimport type { RiverbankClient } from '../client/types';\nimport type { LoadPageResult } from '../rendering/helpers/loadPage';\nimport { checkRedirectForPath } from './checkRedirect';\n\nexport type RouteResolution =\n | {\n type: 'page';\n pageData: LoadPageResult;\n }\n | {\n type: 'redirect';\n destination: string;\n permanent: boolean;\n }\n | {\n type: 'not-found';\n };\n\nexport type ResolveRouteParams = {\n /**\n * Builder client instance\n */\n client: RiverbankClient;\n\n /**\n * Site ID\n */\n siteId: string;\n\n /**\n * URL path to resolve (e.g., '/about', '/blog/post-1')\n */\n path: string;\n\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * @default false\n */\n preview?: boolean;\n};\n\n/**\n * Resolve a URL path to page data, redirect, or 404\n *\n * This helper attempts to fetch the page at the given path. If the page\n * is not found, it checks for configured redirects before returning 404.\n *\n * Returns a discriminated union indicating:\n * - `page`: Page was found, includes full page data\n * - `redirect`: Path has a redirect rule configured\n * - `not-found`: No page or redirect exists for this path\n *\n * @example\n * ```tsx\n * import { resolveRoute } from '@riverbankcms/sdk/routing';\n * import { notFound, redirect } from 'next/navigation';\n *\n * export default async function DynamicPage({ params }) {\n * const path = `/${params.slug?.join('/') || ''}`;\n * const resolution = await resolveRoute({\n * client,\n * siteId: 'your-site-id',\n * path,\n * });\n *\n * if (resolution.type === 'redirect') {\n * redirect(resolution.destination);\n * }\n *\n * if (resolution.type === 'not-found') {\n * notFound();\n * }\n *\n * // resolution.type === 'page'\n * return <Page {...resolution.pageData} />;\n * }\n * ```\n */\nexport async function resolveRoute(\n params: ResolveRouteParams\n): Promise<RouteResolution> {\n const { client, siteId, path, preview = false } = params;\n\n try {\n // Attempt to fetch page data\n const pageResponse = await client.getPage({\n siteId,\n path,\n preview,\n });\n\n if (pageResponse) {\n // Successfully found page - load full page data\n const { loadPage } = await import('../rendering/helpers/loadPage');\n const pageData = await loadPage({\n client,\n siteId,\n path,\n preview,\n });\n\n return {\n type: 'page',\n pageData,\n };\n }\n } catch (error) {\n // Distinguish between expected 404s and unexpected errors\n const is404 = error instanceof Error &&\n (error.message.includes('404') ||\n error.message.includes('Not Found') ||\n error.message.includes('not found'));\n\n if (is404) {\n console.debug('[resolveRoute] Page not found', { path });\n } else {\n // Unexpected error - log as warning for visibility\n console.warn('[resolveRoute] Failed to fetch page', {\n path,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // No page found - check for redirect before returning 404\n try {\n const redirectResult = await checkRedirectForPath({\n client,\n siteId,\n path,\n });\n\n if (redirectResult.hasRedirect) {\n return {\n type: 'redirect',\n destination: redirectResult.redirect.destination,\n permanent: redirectResult.redirect.permanent,\n };\n }\n } catch (redirectError) {\n console.debug('[resolveRoute] Redirect check failed', {\n path,\n error: redirectError instanceof Error ? redirectError.message : String(redirectError),\n });\n }\n\n return {\n type: 'not-found',\n };\n}\n\n/**\n * Batch resolve multiple routes in parallel\n *\n * Useful for pre-fetching multiple routes or validating a sitemap.\n *\n * @example\n * ```tsx\n * const resolutions = await resolveRoutes({\n * client,\n * siteId: 'your-site-id',\n * paths: ['/', '/about', '/blog', '/contact'],\n * });\n *\n * resolutions.forEach(({ path, resolution }) => {\n * if (resolution.type === 'page') {\n * console.log(`${path} → Page: ${resolution.pageData.page.name}`);\n * } else if (resolution.type === 'redirect') {\n * console.log(`${path} → Redirect to ${resolution.destination}`);\n * } else {\n * console.log(`${path} → Not found`);\n * }\n * });\n * ```\n */\nexport async function resolveRoutes(params: {\n client: RiverbankClient;\n siteId: string;\n paths: string[];\n preview?: boolean;\n}): Promise<Array<{ path: string; resolution: RouteResolution }>> {\n const { client, siteId, paths, preview } = params;\n\n const resolutions = await Promise.all(\n paths.map(async (path) => {\n const resolution = await resolveRoute({\n client,\n siteId,\n path,\n preview,\n });\n\n return { path, resolution };\n })\n );\n\n return resolutions;\n}\n","/**\n * Content route matching utilities.\n *\n * Derive route matching from SDK config to determine whether a URL path\n * is a CMS page or a content entry, without duplicating route patterns.\n */\n\nimport type { RiverbankSiteConfig, ContentTypeConfig } from '../config';\n\n/**\n * Extract the first path segment from each routable content type's routePattern.\n *\n * Useful for simple prefix-based routing decisions. For more precise matching\n * that handles nested patterns, use `isContentEntryPath` instead.\n *\n * @param config - The SDK config object from defineConfig()\n * @returns Array of first path segments from content type routePatterns\n *\n * @example\n * ```typescript\n * import { getContentEntryPrefixes } from '@riverbankcms/sdk/routing';\n * import config from '../riverbank.config';\n *\n * // Config with routePatterns: '/blog/{slug}', '/work/projects/{slug}'\n * const prefixes = getContentEntryPrefixes(config);\n * // Returns ['blog', 'work']\n * ```\n */\nexport function getContentEntryPrefixes(config: RiverbankSiteConfig): string[] {\n const contentTypes = config.content?.contentTypes ?? [];\n\n return contentTypes\n .filter((ct): ct is ContentTypeConfig & { routePattern: string } =>\n ct.hasPages && typeof ct.routePattern === 'string'\n )\n .map(ct => {\n // '/blog/{slug}' → 'blog'\n // '/work/projects/{slug}' → 'work'\n // '/{slug}' → undefined (no static prefix)\n const match = ct.routePattern.match(/^\\/([^/]+)/);\n const segment = match?.[1];\n // Skip dynamic segments (those containing {})\n if (segment?.includes('{')) return undefined;\n return segment;\n })\n .filter((prefix): prefix is string => typeof prefix === 'string');\n}\n\n/**\n * Result of checking if a path matches a content entry route.\n */\nexport type ContentEntryMatch = {\n /** Whether the path matches a content entry route pattern */\n isEntry: boolean;\n /** The content type key if matched (e.g., 'blog-post') */\n contentType?: string;\n /** The slug extracted from the path if matched */\n slug?: string;\n};\n\n/**\n * Check if a URL path matches any content entry route pattern.\n *\n * Supports nested patterns like '/work/projects/{slug}'. Returns the matched\n * content type key and extracted slug, useful for routing decisions.\n *\n * **Note:** Content types are checked in array order. If multiple patterns\n * could match a path, the first matching content type wins. Order more specific\n * patterns before generic ones in your config.\n *\n * @param config - The SDK config object from defineConfig()\n * @param path - URL path as string ('/blog/my-post') or segments ['blog', 'my-post']\n * @returns Match result with content type and slug if matched\n *\n * @example\n * ```typescript\n * import { isContentEntryPath } from '@riverbankcms/sdk/routing';\n * import config from '../riverbank.config';\n *\n * // Simple pattern: '/blog/{slug}'\n * isContentEntryPath(config, '/blog/my-post');\n * // Returns { isEntry: true, contentType: 'blog-post', slug: 'my-post' }\n *\n * // Nested pattern: '/work/projects/{slug}'\n * isContentEntryPath(config, '/work/projects/website-redesign');\n * // Returns { isEntry: true, contentType: 'project', slug: 'website-redesign' }\n *\n * // Non-matching path\n * isContentEntryPath(config, '/about');\n * // Returns { isEntry: false }\n * ```\n */\nexport function isContentEntryPath(\n config: RiverbankSiteConfig,\n path: string | string[]\n): ContentEntryMatch {\n const segments = typeof path === 'string'\n ? path.split('/').filter(Boolean)\n : path;\n\n const contentTypes = config.content?.contentTypes ?? [];\n\n for (const ct of contentTypes) {\n if (!ct.hasPages || !ct.routePattern) continue;\n\n // Parse pattern: '/blog/{slug}' → ['blog'], '/work/projects/{slug}' → ['work', 'projects']\n const patternSegments = parseRoutePattern(ct.routePattern);\n\n // Check if path segments match the pattern\n const match = matchPattern(segments, patternSegments);\n if (match.matches) {\n return {\n isEntry: true,\n contentType: ct.key,\n slug: match.slug,\n };\n }\n }\n\n return { isEntry: false };\n}\n\n/**\n * Parse a route pattern into static segments before {slug}.\n *\n * @example\n * parseRoutePattern('/blog/{slug}') // ['blog']\n * parseRoutePattern('/work/projects/{slug}') // ['work', 'projects']\n * parseRoutePattern('/{slug}') // []\n */\nfunction parseRoutePattern(pattern: string): string[] {\n const segments = pattern.split('/').filter(Boolean);\n const staticSegments: string[] = [];\n\n for (const segment of segments) {\n if (segment.includes('{')) break; // Stop at dynamic segment\n staticSegments.push(segment);\n }\n\n return staticSegments;\n}\n\n/**\n * Check if path segments match pattern segments.\n * Path must have at least one more segment than pattern (the slug).\n *\n * @returns Match result with extracted slug if matched\n */\nfunction matchPattern(\n pathSegments: string[],\n patternSegments: string[]\n): { matches: boolean; slug?: string } {\n // Path needs: pattern segments + at least 1 slug segment\n if (pathSegments.length < patternSegments.length + 1) {\n return { matches: false };\n }\n\n // All pattern segments must match exactly\n for (let i = 0; i < patternSegments.length; i++) {\n if (pathSegments[i] !== patternSegments[i]) {\n return { matches: false };\n }\n }\n\n // Extract slug (everything after the pattern segments)\n // For '/blog/{slug}' with path '/blog/my-post' → slug = 'my-post'\n // For nested slugs, join remaining segments (rare but supported)\n const slugSegments = pathSegments.slice(patternSegments.length);\n const slug = slugSegments.join('/');\n\n return { matches: true, slug };\n}\n","/**\n * Event occurrence resolution for dynamic URLs\n *\n * Resolves URL segments like \"2025-01-15\" or \"abc123-uuid\" to event occurrence data.\n */\n\nimport type { RiverbankClient, ResolveEventOccurrenceResponse } from '../client/types';\n\n/**\n * Data for a resolved event occurrence\n */\nexport type OccurrenceData = NonNullable<ResolveEventOccurrenceResponse['occurrence']>;\n\n/**\n * Result of occurrence resolution\n */\nexport type OccurrenceResolution =\n | {\n found: true;\n occurrence: OccurrenceData;\n }\n | {\n found: false;\n occurrence: null;\n };\n\n/**\n * Parameters for resolving an event occurrence\n */\nexport type ResolveOccurrenceParams = {\n /**\n * SDK client instance\n */\n client: RiverbankClient;\n\n /**\n * Site ID\n */\n siteId: string;\n\n /**\n * Parent content entry ID (the event entry)\n */\n entryId: string;\n\n /**\n * URL segment to resolve - either a date (YYYY-MM-DD) or UUID\n */\n segment: string;\n};\n\n/**\n * Check if a string is a valid ISO date (YYYY-MM-DD)\n */\nfunction isISODateString(str: string): boolean {\n return /^\\d{4}-\\d{2}-\\d{2}$/.test(str);\n}\n\n/**\n * Check if a string is a valid UUID\n */\nfunction isUUID(str: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);\n}\n\n/**\n * Validate URL segment format before making API call\n */\nfunction isValidSegment(segment: string): boolean {\n return isISODateString(segment) || isUUID(segment);\n}\n\n/**\n * Resolve an event occurrence from a URL segment\n *\n * Supports two segment formats:\n * - Date format: \"2025-01-15\" → finds occurrence starting on that date\n * - UUID format: \"abc123...\" → finds occurrence by ID\n *\n * @example\n * ```tsx\n * import { resolveOccurrence } from '@riverbankcms/sdk/routing';\n *\n * // In a Next.js dynamic route: /events/[slug]/[occurrence]\n * export default async function EventOccurrencePage({ params }) {\n * const result = await resolveOccurrence({\n * client,\n * siteId: 'your-site-id',\n * entryId: eventEntry.id,\n * segment: params.occurrence, // e.g., \"2025-01-15\"\n * });\n *\n * if (!result.found) {\n * notFound();\n * }\n *\n * return <EventOccurrence occurrence={result.occurrence} />;\n * }\n * ```\n */\nexport async function resolveOccurrence(\n params: ResolveOccurrenceParams\n): Promise<OccurrenceResolution> {\n const { client, siteId, entryId, segment } = params;\n\n // Validate segment format client-side to avoid unnecessary API calls\n if (!isValidSegment(segment)) {\n console.debug('[resolveOccurrence] Invalid segment format', { segment });\n return { found: false, occurrence: null };\n }\n\n try {\n const response = await client.resolveEventOccurrence({\n siteId,\n entryId,\n segment,\n });\n\n if (response.occurrence) {\n return {\n found: true,\n occurrence: response.occurrence,\n };\n }\n\n return { found: false, occurrence: null };\n } catch (error) {\n console.warn('[resolveOccurrence] Failed to resolve occurrence', {\n siteId,\n entryId,\n segment,\n error: error instanceof Error ? error.message : String(error),\n });\n\n return { found: false, occurrence: null };\n }\n}\n\nexport { isISODateString, isUUID, isValidSegment };\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/routing/checkRedirect.ts","../../src/routing/resolveRoute.ts","../../src/routing/contentRoutes.ts","../../src/routing/resolveOccurrence.ts"],"sourcesContent":["/**\n * Redirect checking for 404 handling\n *\n * Check if a path has a configured redirect rule before returning 404.\n */\n\nimport type { RiverbankClient } from '../client/types';\n\n/**\n * Redirect rule data\n */\nexport type RedirectRule = {\n /**\n * Destination URL to redirect to\n */\n destination: string;\n\n /**\n * Whether this is a permanent redirect (301/308) or temporary (302/307)\n */\n permanent: boolean;\n};\n\n/**\n * Result of redirect checking\n */\nexport type RedirectResult =\n | {\n hasRedirect: true;\n redirect: RedirectRule;\n }\n | {\n hasRedirect: false;\n redirect: null;\n };\n\n/**\n * Parameters for checking redirects\n */\nexport type CheckRedirectParams = {\n /**\n * SDK client instance\n */\n client: RiverbankClient;\n\n /**\n * Site ID\n */\n siteId: string;\n\n /**\n * URL path to check (e.g., '/old-page')\n */\n path: string;\n};\n\n/**\n * Check if a path has a configured redirect rule\n *\n * Use this when a page is not found to check if there's a redirect\n * configured for the path before returning a 404.\n *\n * @example\n * ```tsx\n * import { checkRedirectForPath } from '@riverbankcms/sdk/routing';\n * import { notFound, redirect } from 'next/navigation';\n *\n * export default async function Page({ params }) {\n * const page = await getPage(params.path);\n *\n * if (!page) {\n * // Check for redirect before 404\n * const result = await checkRedirectForPath({\n * client,\n * siteId: 'your-site-id',\n * path: params.path,\n * });\n *\n * if (result.hasRedirect) {\n * redirect(result.redirect.destination);\n * }\n *\n * notFound();\n * }\n *\n * return <PageContent page={page} />;\n * }\n * ```\n */\nexport async function checkRedirectForPath(\n params: CheckRedirectParams\n): Promise<RedirectResult> {\n const { client, siteId, path } = params;\n\n try {\n const response = await client.checkRedirect({\n siteId,\n path,\n });\n\n // API returns { redirectTo: string | null, status?: number }\n if (response.redirectTo) {\n // Status 301 and 308 are permanent, others are temporary\n const isPermanent = response.status === 301 || response.status === 308;\n\n return {\n hasRedirect: true,\n redirect: {\n destination: response.redirectTo,\n permanent: isPermanent,\n },\n };\n }\n\n return { hasRedirect: false, redirect: null };\n } catch (error) {\n console.warn('[checkRedirectForPath] Failed to check redirect', {\n siteId,\n path,\n error: error instanceof Error ? error.message : String(error),\n });\n\n return { hasRedirect: false, redirect: null };\n }\n}\n","/**\n * Route resolution helper for dynamic page routing\n *\n * Resolves URL paths to pages, entries, redirects, or 404s.\n */\n\nimport type { RiverbankClient } from '../client/types';\nimport type { LoadPageResult } from '../rendering/helpers/loadPage';\nimport { checkRedirectForPath } from './checkRedirect';\n\nexport type RouteResolution =\n | {\n type: 'page';\n pageData: LoadPageResult;\n }\n | {\n type: 'redirect';\n destination: string;\n permanent: boolean;\n }\n | {\n type: 'not-found';\n };\n\nexport type ResolveRouteParams = {\n /**\n * Builder client instance\n */\n client: RiverbankClient;\n\n /**\n * Site ID\n */\n siteId: string;\n\n /**\n * URL path to resolve (e.g., '/about', '/blog/post-1')\n */\n path: string;\n\n /**\n * If true, fetches draft/unpublished content instead of published content.\n * @default false\n */\n preview?: boolean;\n};\n\n/**\n * Resolve a URL path to page data, redirect, or 404\n *\n * This helper attempts to fetch the page at the given path. If the page\n * is not found, it checks for configured redirects before returning 404.\n *\n * Returns a discriminated union indicating:\n * - `page`: Page was found, includes full page data\n * - `redirect`: Path has a redirect rule configured\n * - `not-found`: No page or redirect exists for this path\n *\n * @example\n * ```tsx\n * import { resolveRoute } from '@riverbankcms/sdk/routing';\n * import { notFound, redirect } from 'next/navigation';\n *\n * export default async function DynamicPage({ params }) {\n * const path = `/${params.slug?.join('/') || ''}`;\n * const resolution = await resolveRoute({\n * client,\n * siteId: 'your-site-id',\n * path,\n * });\n *\n * if (resolution.type === 'redirect') {\n * redirect(resolution.destination);\n * }\n *\n * if (resolution.type === 'not-found') {\n * notFound();\n * }\n *\n * // resolution.type === 'page'\n * return <Page {...resolution.pageData} />;\n * }\n * ```\n */\nexport async function resolveRoute(\n params: ResolveRouteParams\n): Promise<RouteResolution> {\n const { client, siteId, path, preview = false } = params;\n\n try {\n // Attempt to fetch page data\n const pageResponse = await client.getPage({\n siteId,\n path,\n preview,\n });\n\n if (pageResponse) {\n // Successfully found page - load full page data\n const { loadPage } = await import('../rendering/helpers/loadPage');\n const pageData = await loadPage({\n client,\n siteId,\n path,\n preview,\n });\n\n return {\n type: 'page',\n pageData,\n };\n }\n } catch (error) {\n // Distinguish between expected 404s and unexpected errors\n const is404 = error instanceof Error &&\n (error.message.includes('404') ||\n error.message.includes('Not Found') ||\n error.message.includes('not found'));\n\n if (is404) {\n console.debug('[resolveRoute] Page not found', { path });\n } else {\n // Unexpected error - log as warning for visibility\n console.warn('[resolveRoute] Failed to fetch page', {\n path,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n // No page found - check for redirect before returning 404\n try {\n const redirectResult = await checkRedirectForPath({\n client,\n siteId,\n path,\n });\n\n if (redirectResult.hasRedirect) {\n return {\n type: 'redirect',\n destination: redirectResult.redirect.destination,\n permanent: redirectResult.redirect.permanent,\n };\n }\n } catch (redirectError) {\n console.debug('[resolveRoute] Redirect check failed', {\n path,\n error: redirectError instanceof Error ? redirectError.message : String(redirectError),\n });\n }\n\n return {\n type: 'not-found',\n };\n}\n\n/**\n * Batch resolve multiple routes in parallel\n *\n * Useful for pre-fetching multiple routes or validating a sitemap.\n *\n * @example\n * ```tsx\n * const resolutions = await resolveRoutes({\n * client,\n * siteId: 'your-site-id',\n * paths: ['/', '/about', '/blog', '/contact'],\n * });\n *\n * resolutions.forEach(({ path, resolution }) => {\n * if (resolution.type === 'page') {\n * console.log(`${path} → Page: ${resolution.pageData.page.name}`);\n * } else if (resolution.type === 'redirect') {\n * console.log(`${path} → Redirect to ${resolution.destination}`);\n * } else {\n * console.log(`${path} → Not found`);\n * }\n * });\n * ```\n */\nexport async function resolveRoutes(params: {\n client: RiverbankClient;\n siteId: string;\n paths: string[];\n preview?: boolean;\n}): Promise<Array<{ path: string; resolution: RouteResolution }>> {\n const { client, siteId, paths, preview } = params;\n\n const resolutions = await Promise.all(\n paths.map(async (path) => {\n const resolution = await resolveRoute({\n client,\n siteId,\n path,\n preview,\n });\n\n return { path, resolution };\n })\n );\n\n return resolutions;\n}\n","/**\n * Content route matching utilities.\n *\n * Derive route matching from SDK config to determine whether a URL path\n * is a CMS page or a content entry, without duplicating route patterns.\n */\n\nimport type { RiverbankSiteConfig, ContentTypeConfig } from '../config';\n\n/**\n * Extract the first path segment from each routable content type's routePattern.\n *\n * Useful for simple prefix-based routing decisions. For more precise matching\n * that handles nested patterns, use `isContentEntryPath` instead.\n *\n * @param config - The SDK config object from defineConfig()\n * @returns Array of first path segments from content type routePatterns\n *\n * @example\n * ```typescript\n * import { getContentEntryPrefixes } from '@riverbankcms/sdk/routing';\n * import config from '../riverbank.config';\n *\n * // Config with routePatterns: '/blog/{slug}', '/work/projects/{slug}'\n * const prefixes = getContentEntryPrefixes(config);\n * // Returns ['blog', 'work']\n * ```\n */\nexport function getContentEntryPrefixes(config: RiverbankSiteConfig): string[] {\n const contentTypes = config.content?.contentTypes ?? [];\n\n return contentTypes\n .filter((ct): ct is ContentTypeConfig & { routePattern: string } =>\n ct.hasPages && typeof ct.routePattern === 'string'\n )\n .map(ct => {\n // '/blog/{slug}' → 'blog'\n // '/work/projects/{slug}' → 'work'\n // '/{slug}' → undefined (no static prefix)\n const match = ct.routePattern.match(/^\\/([^/]+)/);\n const segment = match?.[1];\n // Skip dynamic segments (those containing {})\n if (segment?.includes('{')) return undefined;\n return segment;\n })\n .filter((prefix): prefix is string => typeof prefix === 'string');\n}\n\n/**\n * Result of checking if a path matches a content entry route.\n */\nexport type ContentEntryMatch = {\n /** Whether the path matches a content entry route pattern */\n isEntry: boolean;\n /** The content type key if matched (e.g., 'blog-post') */\n contentType?: string;\n /** The slug extracted from the path if matched */\n slug?: string;\n};\n\n/**\n * Check if a URL path matches any content entry route pattern.\n *\n * Supports nested patterns like '/work/projects/{slug}'. Returns the matched\n * content type key and extracted slug, useful for routing decisions.\n *\n * **Note:** Content types are checked in array order. If multiple patterns\n * could match a path, the first matching content type wins. Order more specific\n * patterns before generic ones in your config.\n *\n * @param config - The SDK config object from defineConfig()\n * @param path - URL path as string ('/blog/my-post') or segments ['blog', 'my-post']\n * @returns Match result with content type and slug if matched\n *\n * @example\n * ```typescript\n * import { isContentEntryPath } from '@riverbankcms/sdk/routing';\n * import config from '../riverbank.config';\n *\n * // Simple pattern: '/blog/{slug}'\n * isContentEntryPath(config, '/blog/my-post');\n * // Returns { isEntry: true, contentType: 'blog-post', slug: 'my-post' }\n *\n * // Nested pattern: '/work/projects/{slug}'\n * isContentEntryPath(config, '/work/projects/website-redesign');\n * // Returns { isEntry: true, contentType: 'project', slug: 'website-redesign' }\n *\n * // Non-matching path\n * isContentEntryPath(config, '/about');\n * // Returns { isEntry: false }\n * ```\n */\nexport function isContentEntryPath(\n config: RiverbankSiteConfig,\n path: string | string[]\n): ContentEntryMatch {\n const segments = typeof path === 'string'\n ? path.split('/').filter(Boolean)\n : path;\n\n const contentTypes = config.content?.contentTypes ?? [];\n\n for (const ct of contentTypes) {\n if (!ct.hasPages || !ct.routePattern) continue;\n\n // Parse pattern: '/blog/{slug}' → ['blog'], '/work/projects/{slug}' → ['work', 'projects']\n const patternSegments = parseRoutePattern(ct.routePattern);\n\n // Check if path segments match the pattern\n const match = matchPattern(segments, patternSegments);\n if (match.matches) {\n return {\n isEntry: true,\n contentType: ct.key,\n slug: match.slug,\n };\n }\n }\n\n return { isEntry: false };\n}\n\n/**\n * Parse a route pattern into static segments before {slug}.\n *\n * @example\n * parseRoutePattern('/blog/{slug}') // ['blog']\n * parseRoutePattern('/work/projects/{slug}') // ['work', 'projects']\n * parseRoutePattern('/{slug}') // []\n */\nfunction parseRoutePattern(pattern: string): string[] {\n const segments = pattern.split('/').filter(Boolean);\n const staticSegments: string[] = [];\n\n for (const segment of segments) {\n if (segment.includes('{')) break; // Stop at dynamic segment\n staticSegments.push(segment);\n }\n\n return staticSegments;\n}\n\n/**\n * Check if path segments match pattern segments.\n * Path must have at least one more segment than pattern (the slug).\n *\n * @returns Match result with extracted slug if matched\n */\nfunction matchPattern(\n pathSegments: string[],\n patternSegments: string[]\n): { matches: boolean; slug?: string } {\n // Path needs: pattern segments + at least 1 slug segment\n if (pathSegments.length < patternSegments.length + 1) {\n return { matches: false };\n }\n\n // All pattern segments must match exactly\n for (let i = 0; i < patternSegments.length; i++) {\n if (pathSegments[i] !== patternSegments[i]) {\n return { matches: false };\n }\n }\n\n // Extract slug (everything after the pattern segments)\n // For '/blog/{slug}' with path '/blog/my-post' → slug = 'my-post'\n // For nested slugs, join remaining segments (rare but supported)\n const slugSegments = pathSegments.slice(patternSegments.length);\n const slug = slugSegments.join('/');\n\n return { matches: true, slug };\n}\n","/**\n * Event occurrence resolution for dynamic URLs\n *\n * Resolves URL segments like \"2025-01-15\" or \"abc123-uuid\" to event occurrence data.\n */\n\nimport type { RiverbankClient, ResolveEventOccurrenceResponse } from '../client/types';\n\n/**\n * Data for a resolved event occurrence\n */\nexport type OccurrenceData = NonNullable<ResolveEventOccurrenceResponse['occurrence']>;\n\n/**\n * Result of occurrence resolution\n */\nexport type OccurrenceResolution =\n | {\n found: true;\n occurrence: OccurrenceData;\n }\n | {\n found: false;\n occurrence: null;\n };\n\n/**\n * Parameters for resolving an event occurrence\n */\nexport type ResolveOccurrenceParams = {\n /**\n * SDK client instance\n */\n client: RiverbankClient;\n\n /**\n * Site ID\n */\n siteId: string;\n\n /**\n * Parent content entry ID (the event entry)\n */\n entryId: string;\n\n /**\n * URL segment to resolve - either a date (YYYY-MM-DD) or UUID\n */\n segment: string;\n};\n\n/**\n * Check if a string is a valid ISO date (YYYY-MM-DD)\n */\nfunction isISODateString(str: string): boolean {\n return /^\\d{4}-\\d{2}-\\d{2}$/.test(str);\n}\n\n/**\n * Check if a string is a valid UUID\n */\nfunction isUUID(str: string): boolean {\n return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);\n}\n\n/**\n * Validate URL segment format before making API call\n */\nfunction isValidSegment(segment: string): boolean {\n return isISODateString(segment) || isUUID(segment);\n}\n\n/**\n * Resolve an event occurrence from a URL segment\n *\n * Supports two segment formats:\n * - Date format: \"2025-01-15\" → finds occurrence starting on that date\n * - UUID format: \"abc123...\" → finds occurrence by ID\n *\n * @example\n * ```tsx\n * import { resolveOccurrence } from '@riverbankcms/sdk/routing';\n *\n * // In a Next.js dynamic route: /events/[slug]/[occurrence]\n * export default async function EventOccurrencePage({ params }) {\n * const result = await resolveOccurrence({\n * client,\n * siteId: 'your-site-id',\n * entryId: eventEntry.id,\n * segment: params.occurrence, // e.g., \"2025-01-15\"\n * });\n *\n * if (!result.found) {\n * notFound();\n * }\n *\n * return <EventOccurrence occurrence={result.occurrence} />;\n * }\n * ```\n */\nexport async function resolveOccurrence(\n params: ResolveOccurrenceParams\n): Promise<OccurrenceResolution> {\n const { client, siteId, entryId, segment } = params;\n\n // Validate segment format client-side to avoid unnecessary API calls\n if (!isValidSegment(segment)) {\n console.debug('[resolveOccurrence] Invalid segment format', { segment });\n return { found: false, occurrence: null };\n }\n\n try {\n const response = await client.resolveEventOccurrence({\n siteId,\n entryId,\n segment,\n });\n\n if (response.occurrence) {\n return {\n found: true,\n occurrence: response.occurrence,\n };\n }\n\n return { found: false, occurrence: null };\n } catch (error) {\n console.warn('[resolveOccurrence] Failed to resolve occurrence', {\n siteId,\n entryId,\n segment,\n error: error instanceof Error ? error.message : String(error),\n });\n\n return { found: false, occurrence: null };\n }\n}\n\nexport { isISODateString, isUUID, isValidSegment };\n"],"mappings":"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","names":[]}
|
package/dist/server/server.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { c as createRiverbankClient } from './index-
|
|
2
|
-
export { E as EntriesResponse, b as EntryResponse, P as PageResponse, R as RiverbankClient, a as RiverbankClientConfig, S as SiteResponse } from './types-
|
|
3
|
-
export { L as LoadPageParams, a as LoadPageResult, R as RuntimeSdkConfig, l as loadPage } from './loadPage-
|
|
4
|
-
export { C as ContentEntryData, E as EntryContentResult, L as LoadContentParams, b as LoadContentResult, P as PageContentResult, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-
|
|
1
|
+
export { c as createRiverbankClient } from './index-Bkva0WAj.mjs';
|
|
2
|
+
export { E as EntriesResponse, b as EntryResponse, P as PageResponse, R as RiverbankClient, a as RiverbankClientConfig, S as SiteResponse } from './types-MF2AWoKv.mjs';
|
|
3
|
+
export { L as LoadPageParams, a as LoadPageResult, R as RuntimeSdkConfig, l as loadPage } from './loadPage-CxlYLe5K.mjs';
|
|
4
|
+
export { C as ContentEntryData, E as EntryContentResult, L as LoadContentParams, b as LoadContentResult, P as PageContentResult, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-F_tAS0Nl.mjs';
|
|
5
5
|
import './schema-Z6-afHJG.mjs';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import './types-Dsu9wsUh.mjs';
|
package/dist/server/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { c as createRiverbankClient } from './index-
|
|
2
|
-
export { E as EntriesResponse, b as EntryResponse, P as PageResponse, R as RiverbankClient, a as RiverbankClientConfig, S as SiteResponse } from './types-
|
|
3
|
-
export { L as LoadPageParams, a as LoadPageResult, R as RuntimeSdkConfig, l as loadPage } from './loadPage-
|
|
4
|
-
export { C as ContentEntryData, E as EntryContentResult, L as LoadContentParams, b as LoadContentResult, P as PageContentResult, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-
|
|
1
|
+
export { c as createRiverbankClient } from './index-CSBWKA3r.js';
|
|
2
|
+
export { E as EntriesResponse, b as EntryResponse, P as PageResponse, R as RiverbankClient, a as RiverbankClientConfig, S as SiteResponse } from './types-DnkRh0UL.js';
|
|
3
|
+
export { L as LoadPageParams, a as LoadPageResult, R as RuntimeSdkConfig, l as loadPage } from './loadPage-i2r-X5b9.js';
|
|
4
|
+
export { C as ContentEntryData, E as EntryContentResult, L as LoadContentParams, b as LoadContentResult, P as PageContentResult, a as isEntryContent, i as isPageContent, l as loadContent } from './loadContent-CXUWMuzY.js';
|
|
5
5
|
import './schema-Z6-afHJG.js';
|
|
6
6
|
import 'zod';
|
|
7
7
|
import './types-CVykEqXN.js';
|
package/dist/server/server.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkHMENX4Y7js = require('./chunk-HMENX4Y7.js');
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
var _chunkP3NNN73Gjs = require('./chunk-P3NNN73G.js');
|
|
6
7
|
|
|
7
8
|
|
|
8
|
-
var _chunkJWRNMNWIjs = require('./chunk-JWRNMNWI.js');
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _chunkJWRNMNWIjs = require('./chunk-JWRNMNWI.js');
|
|
12
12
|
require('./chunk-Y7347JMZ.js');
|
|
13
13
|
require('./chunk-RVDS7VSP.js');
|
|
14
14
|
require('./chunk-YYO3RIFO.js');
|
|
15
|
-
require('./chunk-DGUM43GV.js');
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
exports.createRiverbankClient =
|
|
21
|
+
exports.createRiverbankClient = _chunkHMENX4Y7js.createRiverbankClient; exports.isEntryContent = _chunkJWRNMNWIjs.isEntryContent; exports.isPageContent = _chunkJWRNMNWIjs.isPageContent; exports.loadContent = _chunkJWRNMNWIjs.loadContent; exports.loadPage = _chunkP3NNN73Gjs.loadPage;
|
|
23
22
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/server.js"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;
|
|
1
|
+
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/server.js"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B;AACE;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACF,2RAAC","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/server.js"}
|
package/dist/server/server.mjs
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRiverbankClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5JT452F2.mjs";
|
|
4
|
+
import {
|
|
5
|
+
loadPage
|
|
6
|
+
} from "./chunk-C6FIJC7T.mjs";
|
|
4
7
|
import {
|
|
5
8
|
isEntryContent,
|
|
6
9
|
isPageContent,
|
|
7
10
|
loadContent
|
|
8
11
|
} from "./chunk-74XUVNOO.mjs";
|
|
9
|
-
import {
|
|
10
|
-
loadPage
|
|
11
|
-
} from "./chunk-C6FIJC7T.mjs";
|
|
12
12
|
import "./chunk-A2FZMRDW.mjs";
|
|
13
13
|
import "./chunk-AEFWG657.mjs";
|
|
14
14
|
import "./chunk-BYBJA6SP.mjs";
|
|
15
|
-
import "./chunk-BJTO5JO5.mjs";
|
|
16
15
|
export {
|
|
17
16
|
createRiverbankClient,
|
|
18
17
|
isEntryContent,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme-bridge.js","../../src/theme-bridge/ThemeBridgeProvider.tsx","../../src/theme-bridge/generateCssVars.ts"],"names":[],"mappings":"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","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme-bridge.js","sourcesContent":[null,"/**\n * ThemeBridgeProvider\n *\n * A lightweight theme provider for SDK sites that want to style Builder blocks\n * without using the full CMS theme system. Generates CSS variables from a\n * simplified configuration.\n *\n * @example Simple tokens only\n * ```tsx\n * import { ThemeBridgeProvider } from '@riverbankcms/sdk/theme-bridge';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * background: '#ffffff',\n * text: '#1e293b',\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * );\n * }\n * ```\n *\n * @example With component CSS\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * white: '#ffffff',\n * surface: '#f8fafc',\n * text: '#1e293b',\n * border: '#e2e8f0',\n * },\n * corners: 'rounded',\n * shadows: 'medium',\n * components: {\n * buttons: true,\n * cards: true,\n * inputs: true,\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n *\n * @example Pass-through to existing design system\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: 'var(--brand-purple)',\n * secondary: 'var(--brand-navy)',\n * background: 'var(--ds-bg)',\n * },\n * components: { buttons: true },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n */\n\nimport * as React from 'react';\nimport { generateThemeBridgeCss } from './generateCssVars';\nimport type { ThemeBridgeConfig } from './types';\n\nexport interface ThemeBridgeProviderProps {\n /** Theme configuration */\n config: ThemeBridgeConfig;\n /** Child elements to wrap */\n children: React.ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** HTML element to use for wrapper (defaults to 'div') */\n as?: keyof React.JSX.IntrinsicElements;\n}\n\n/**\n * Wraps content and injects CSS variables for Builder block styling.\n *\n * This component generates CSS variables from a simplified theme config\n * and injects them into a wrapper element. All Builder blocks rendered\n * inside will pick up these variables for colors, typography, spacing, etc.\n *\n * Optionally generates component CSS for buttons, cards, and inputs when\n * `config.components` is specified.\n */\nexport function ThemeBridgeProvider({\n config,\n children,\n className = '',\n as: Tag = 'div',\n}: ThemeBridgeProviderProps) {\n // Generate CSS variables and stylesheet\n const { css, cssVars } = React.useMemo(\n () => generateThemeBridgeCss(config),\n [config]\n );\n\n // Convert cssVars to inline style format\n const inlineStyle = React.useMemo(() => {\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(cssVars)) {\n style[key] = value;\n }\n return style;\n }, [cssVars]);\n\n return (\n <>\n {/* Inject CSS for theme scope */}\n <style dangerouslySetInnerHTML={{ __html: css }} />\n\n {/* Wrapper with theme scope attribute and inline CSS variables */}\n {React.createElement(\n Tag,\n {\n 'data-theme-scope': 'sdk',\n className: `theme-scope ${className}`.trim(),\n style: inlineStyle as React.CSSProperties,\n },\n children\n )}\n </>\n );\n}\n\n/**\n * Hook to access generated CSS variables from theme config.\n * Useful for custom components that need theme values.\n */\nexport function useThemeBridgeCss(config: ThemeBridgeConfig) {\n return React.useMemo(() => generateThemeBridgeCss(config), [config]);\n}\n","/**\n * CSS Variable Generator for Theme Bridge\n *\n * Generates the CSS variables that Builder blocks expect,\n * from a simplified ThemeBridgeConfig.\n *\n * Key differences from the full CMS theme system:\n * - No shade generation (SDK sites define their own tokens)\n * - Supports arbitrary token names\n * - Supports pass-through of CSS variable references\n * - Opt-in component CSS generation using core generators from @riverbankcms/blocks\n */\n\nimport {\n generateButtonCoreCss,\n getDefaultButtonVariants,\n generateCardCoreCss,\n getDefaultCardVariants,\n generateInputCoreCss,\n getDefaultInputSettings,\n type ButtonCoreSettings,\n type CardCoreSettings,\n} from '@riverbankcms/blocks';\n\nimport type {\n ThemeBridgeConfig,\n ThemeBridgeOutput,\n ThemeBridgeSpacing,\n ThemeBridgeCorners,\n ThemeBridgeShadows,\n} from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst SCOPE_ID = 'sdk';\n\nconst SYSTEM_FONT_STACK = 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"';\n\nconst SPACING_CONFIG: Record<ThemeBridgeSpacing, { mult: number; rhythm: string }> = {\n comfortable: { mult: 1.15, rhythm: '1.2rem' },\n standard: { mult: 1.0, rhythm: '1rem' },\n dense: { mult: 0.88, rhythm: '0.75rem' },\n};\n\nconst CORNERS_CONFIG: Record<ThemeBridgeCorners, { control: string; card: string }> = {\n square: { control: '0px', card: '0px' },\n soft: { control: '4px', card: '8px' },\n rounded: { control: '8px', card: '12px' },\n pill: { control: '9999px', card: '16px' },\n};\n\nconst SHADOWS_CONFIG: Record<ThemeBridgeShadows, {\n sm: string;\n md: string;\n lg: string;\n elev: string;\n}> = {\n none: {\n sm: 'none',\n md: 'none',\n lg: 'none',\n elev: 'none',\n },\n low: {\n sm: '0 1px 2px rgba(0,0,0,0.04)',\n md: '0 2px 4px rgba(0,0,0,0.06)',\n lg: '0 4px 8px rgba(0,0,0,0.08)',\n elev: '0 1px 3px rgba(0,0,0,0.06)',\n },\n medium: {\n sm: '0 1px 2px rgba(0,0,0,0.06)',\n md: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',\n lg: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',\n elev: '0 2px 6px rgba(0,0,0,0.08)',\n },\n high: {\n sm: '0 1px 3px rgba(0,0,0,0.08)',\n md: '0 6px 12px -2px rgba(0,0,0,0.15), 0 3px 6px -3px rgba(0,0,0,0.1)',\n lg: '0 20px 25px -5px rgba(0,0,0,0.15), 0 8px 10px -6px rgba(0,0,0,0.1)',\n elev: '0 4px 12px rgba(0,0,0,0.12)',\n },\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert hex color to RGB channel format (e.g., \"109 40 217\")\n */\nfunction hexToRgb(hex: string): string {\n const h = hex.replace(/^#/, '');\n const m = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n if (m.length !== 6) {\n console.warn(`[ThemeBridge] Invalid hex color: ${hex}`);\n return '0 0 0';\n }\n const int = parseInt(m, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `${r} ${g} ${b}`;\n}\n\n/**\n * Normalize token value to CSS-ready format.\n *\n * - Hex colors are converted to RGB channels\n * - CSS variable references are passed through\n * - Already-formatted RGB values are passed through\n */\nfunction normalizeTokenValue(value: string): string {\n // Pass through CSS variable references\n if (value.startsWith('var(')) {\n return value;\n }\n\n // Convert hex to RGB\n if (value.startsWith('#')) {\n return hexToRgb(value);\n }\n\n // Already RGB or other format - use directly\n return value;\n}\n\n// ============================================================================\n// Main Generator\n// ============================================================================\n\n/**\n * Generate CSS variables from a ThemeBridgeConfig.\n */\nexport function generateThemeBridgeCss(config: ThemeBridgeConfig): ThemeBridgeOutput {\n // Validate required config\n if (!config || typeof config !== 'object') {\n throw new Error(\n '[ThemeBridge] Invalid config: expected an object with a \"tokens\" property.'\n );\n }\n\n if (!config.tokens || typeof config.tokens !== 'object' || Array.isArray(config.tokens)) {\n throw new Error(\n '[ThemeBridge] Missing \"tokens\" property in config. ' +\n 'ThemeBridgeConfig requires a \"tokens\" object with color definitions. ' +\n 'Example: { tokens: { primary: \"#6d28d9\", background: \"#ffffff\" } }'\n );\n }\n\n const cssVars: Record<string, string> = {};\n const cssChunks: string[] = [];\n\n // -------------------------------------------------------------------------\n // Color Tokens\n // -------------------------------------------------------------------------\n\n for (const [name, value] of Object.entries(config.tokens)) {\n cssVars[`--tb-${name}`] = normalizeTokenValue(value);\n }\n\n // -------------------------------------------------------------------------\n // Typography\n // -------------------------------------------------------------------------\n\n const typography = config.typography ?? {};\n cssVars['--tb-font-heading'] = typography.headingFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-body'] = typography.bodyFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-weight-heading'] = String(typography.headingWeight ?? 600);\n cssVars['--tb-font-weight-body'] = String(typography.bodyWeight ?? 400);\n\n // Font sizes (standard scale)\n cssVars['--tb-fs-h1'] = '2.5rem';\n cssVars['--tb-fs-h2'] = '2rem';\n cssVars['--tb-fs-h3'] = '1.5rem';\n cssVars['--tb-fs-body'] = '1rem';\n\n // Letter spacing and line height\n cssVars['--tb-ls-heading'] = '-0.02em';\n cssVars['--tb-ls-body'] = '0';\n cssVars['--tb-lh-heading'] = '1.2';\n cssVars['--tb-lh-body'] = '1.6';\n\n // Text transforms\n cssVars['--tb-tt-heading'] = 'none';\n cssVars['--tb-fv-heading'] = 'normal';\n\n // -------------------------------------------------------------------------\n // Spacing\n // -------------------------------------------------------------------------\n\n const spacingPreset = SPACING_CONFIG[config.spacing ?? 'standard'];\n cssVars['--tb-space-mult'] = String(spacingPreset.mult);\n cssVars['--tb-rt-space-y'] = spacingPreset.rhythm;\n\n // -------------------------------------------------------------------------\n // Corners\n // -------------------------------------------------------------------------\n\n const corners = config.corners ?? 'rounded';\n const cornersPreset = CORNERS_CONFIG[corners];\n cssVars['--tb-radius-control'] = cornersPreset.control;\n cssVars['--tb-radius-card'] = cornersPreset.card;\n\n // -------------------------------------------------------------------------\n // Shadows\n // -------------------------------------------------------------------------\n\n const shadows = config.shadows ?? 'medium';\n const shadowsPreset = SHADOWS_CONFIG[shadows];\n cssVars['--tb-shadow-sm'] = shadowsPreset.sm;\n cssVars['--tb-shadow-md'] = shadowsPreset.md;\n cssVars['--tb-shadow-lg'] = shadowsPreset.lg;\n cssVars['--tb-shadow-elev'] = shadowsPreset.elev;\n\n // -------------------------------------------------------------------------\n // Motion\n // -------------------------------------------------------------------------\n\n cssVars['--tb-motion-duration'] = '180ms';\n cssVars['--tb-motion-ease'] = 'cubic-bezier(0.2, 0.8, 0.2, 1)';\n\n // -------------------------------------------------------------------------\n // Generate CSS Variables Block\n // -------------------------------------------------------------------------\n\n const cssLines = Object.entries(cssVars)\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n cssChunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) {\\n${cssLines}\\n}`);\n\n // -------------------------------------------------------------------------\n // Component CSS (opt-in)\n // -------------------------------------------------------------------------\n\n if (config.components?.buttons) {\n const buttonCss = generateButtonsCss(config, corners, shadows);\n cssChunks.push(buttonCss);\n }\n\n if (config.components?.cards) {\n const cardCss = generateCardsCss(config, corners, shadows);\n cssChunks.push(cardCss);\n }\n\n if (config.components?.inputs) {\n const inputCss = generateInputsCss(config, corners, shadows);\n cssChunks.push(inputCss);\n }\n\n // -------------------------------------------------------------------------\n // Custom Overrides\n // -------------------------------------------------------------------------\n\n if (config.overrides) {\n const overrideCss = generateOverrideCss(config.overrides);\n cssChunks.push(overrideCss);\n }\n\n return {\n cssVars,\n css: cssChunks.join('\\n\\n'),\n };\n}\n\n// ============================================================================\n// Component CSS Generators\n// ============================================================================\n\nfunction generateButtonsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const buttonConfig = config.components?.buttons;\n\n // Get variants to generate\n let variants = getDefaultButtonVariants();\n\n if (typeof buttonConfig === 'object' && buttonConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(buttonConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: ButtonCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n fontWeight: 500,\n textTransform: 'none',\n };\n\n return generateButtonCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateCardsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const cardConfig = config.components?.cards;\n\n // Get variants to generate\n let variants = getDefaultCardVariants();\n\n if (typeof cardConfig === 'object' && cardConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(cardConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: CardCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n };\n\n return generateCardCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateInputsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const settings = getDefaultInputSettings();\n\n // Override with theme settings\n settings.corners = corners;\n settings.shadow = shadows === 'none' ? 'none' : 'low';\n\n return generateInputCoreCss(SCOPE_ID, settings);\n}\n\nfunction generateOverrideCss(overrides: Record<string, string>): string {\n const chunks: string[] = [];\n\n for (const [selector, rules] of Object.entries(overrides)) {\n chunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) ${selector} {\\n ${rules}\\n}`);\n }\n\n return chunks.join('\\n\\n');\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme-bridge.js","../../src/theme-bridge/ThemeBridgeProvider.tsx","../../src/theme-bridge/generateCssVars.ts"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B;AACA;AC6DA,2EAAuB;AD3DvB;AACA;AEuBA,IAAM,SAAA,EAAW,KAAA;AAEjB,IAAM,kBAAA,EAAoB,6EAAA;AAE1B,IAAM,eAAA,EAA+E;AAAA,EACnF,WAAA,EAAa,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,SAAS,CAAA;AAAA,EAC5C,QAAA,EAAU,EAAE,IAAA,EAAM,CAAA,EAAK,MAAA,EAAQ,OAAO,CAAA;AAAA,EACtC,KAAA,EAAO,EAAE,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,UAAU;AACzC,CAAA;AAEA,IAAM,eAAA,EAAgF;AAAA,EACpF,MAAA,EAAQ,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,MAAM,CAAA;AAAA,EACtC,IAAA,EAAM,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,MAAM,CAAA;AAAA,EACpC,OAAA,EAAS,EAAE,OAAA,EAAS,KAAA,EAAO,IAAA,EAAM,OAAO,CAAA;AAAA,EACxC,IAAA,EAAM,EAAE,OAAA,EAAS,QAAA,EAAU,IAAA,EAAM,OAAO;AAC1C,CAAA;AAEA,IAAM,eAAA,EAKD;AAAA,EACH,IAAA,EAAM;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,IAAA,EAAM;AAAA,EACR,CAAA;AAAA,EACA,GAAA,EAAK;AAAA,IACH,EAAA,EAAI,4BAAA;AAAA,IACJ,EAAA,EAAI,4BAAA;AAAA,IACJ,EAAA,EAAI,4BAAA;AAAA,IACJ,IAAA,EAAM;AAAA,EACR,CAAA;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,EAAA,EAAI,4BAAA;AAAA,IACJ,EAAA,EAAI,gEAAA;AAAA,IACJ,EAAA,EAAI,kEAAA;AAAA,IACJ,IAAA,EAAM;AAAA,EACR,CAAA;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,EAAA,EAAI,4BAAA;AAAA,IACJ,EAAA,EAAI,kEAAA;AAAA,IACJ,EAAA,EAAI,oEAAA;AAAA,IACJ,IAAA,EAAM;AAAA,EACR;AACF,CAAA;AASA,SAAS,QAAA,CAAS,GAAA,EAAqB;AACrC,EAAA,MAAM,EAAA,EAAI,GAAA,CAAI,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAC9B,EAAA,MAAM,EAAA,EAAI,CAAA,CAAE,OAAA,IAAW,EAAA,EAAI,CAAA,CAAE,KAAA,CAAM,EAAE,CAAA,CAAE,GAAA,CAAI,CAAA,CAAA,EAAA,GAAK,EAAA,EAAI,CAAC,CAAA,CAAE,IAAA,CAAK,EAAE,EAAA,EAAI,CAAA;AAClE,EAAA,GAAA,CAAI,CAAA,CAAE,OAAA,IAAW,CAAA,EAAG;AAClB,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,iCAAA,EAAoC,GAAG,CAAA,CAAA;AAC7C,IAAA;AACT,EAAA;AAC0B,EAAA;AACF,EAAA;AACD,EAAA;AACP,EAAA;AACK,EAAA;AACvB;AASoD;AAEpB,EAAA;AACrB,IAAA;AACT,EAAA;AAG2B,EAAA;AACJ,IAAA;AACvB,EAAA;AAGO,EAAA;AACT;AASqF;AAExC,EAAA;AAC/B,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAE+C,EAAA;AACnC,IAAA;AACR,MAAA;AAGF,IAAA;AACF,EAAA;AAEyC,EAAA;AACZ,EAAA;AAMqB,EAAA;AACG,IAAA;AACrD,EAAA;AAMyC,EAAA;AACC,EAAA;AACW,EAAA;AACR,EAAA;AACQ,EAAA;AAG7B,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AAGG,EAAA;AACH,EAAA;AACG,EAAA;AACH,EAAA;AAGG,EAAA;AACA,EAAA;AAM0B,EAAA;AACD,EAAA;AACX,EAAA;AAMT,EAAA;AACU,EAAA;AACG,EAAA;AACH,EAAA;AAMV,EAAA;AACU,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AAMV,EAAA;AACJ,EAAA;AAOhB,EAAA;AAGsC,EAAA;AAAkB;AAAK,CAAA;AAM3C,EAAA;AACe,IAAA;AACrB,IAAA;AAC1B,EAAA;AAE8B,EAAA;AACsB,IAAA;AAC5B,IAAA;AACxB,EAAA;AAE+B,EAAA;AACuB,IAAA;AAC7B,IAAA;AACzB,EAAA;AAMsB,EAAA;AAC2B,IAAA;AACrB,IAAA;AAC5B,EAAA;AAEO,EAAA;AACL,IAAA;AAC0B,IAAA;AAC5B,EAAA;AACF;AAUU;AACgC,EAAA;AAGA,EAAA;AAEa,EAAA;AAED,IAAA;AACC,IAAA;AACrD,EAAA;AAEqC,EAAA;AACnC,IAAA;AACQ,IAAA;AACK,IAAA;AACD,IAAA;AACG,IAAA;AACjB,EAAA;AAEiD,EAAA;AACnD;AAMU;AAC8B,EAAA;AAGA,EAAA;AAEW,EAAA;AAEC,IAAA;AACG,IAAA;AACrD,EAAA;AAEmC,EAAA;AACjC,IAAA;AACQ,IAAA;AACK,IAAA;AACf,EAAA;AAEuD,EAAA;AACzD;AAMU;AACiC,EAAA;AAGtB,EAAA;AAC6B,EAAA;AAEF,EAAA;AAChD;AAEwE;AAC5C,EAAA;AAEqB,EAAA;AACI,IAAA;AAA6B,EAAA;AAAK,CAAA;AACrF,EAAA;AAEyB,EAAA;AAC3B;AF1JyD;AACA;AC1ErD;AAtBgC;AAClC,EAAA;AACA,EAAA;AACY,EAAA;AACF,EAAA;AACiB;AAEI,EAAA;AACM,IAAA;AAC5B,IAAA;AACT,EAAA;AAGwC,EAAA;AACC,IAAA;AACa,IAAA;AACrC,MAAA;AACf,IAAA;AACO,IAAA;AACG,EAAA;AAKR,EAAA;AAAO,oBAAA;AAGA,IAAA;AACL,MAAA;AACA,MAAA;AACsB,QAAA;AACuB,QAAA;AACpC,QAAA;AACT,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEJ;AAM6D;AACT,EAAA;AACpD;ADqFyD;AACA;AACA;AACA;AACA","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme-bridge.js","sourcesContent":[null,"/**\n * ThemeBridgeProvider\n *\n * A lightweight theme provider for SDK sites that want to style Builder blocks\n * without using the full CMS theme system. Generates CSS variables from a\n * simplified configuration.\n *\n * @example Simple tokens only\n * ```tsx\n * import { ThemeBridgeProvider } from '@riverbankcms/sdk/theme-bridge';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * background: '#ffffff',\n * text: '#1e293b',\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * );\n * }\n * ```\n *\n * @example With component CSS\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * white: '#ffffff',\n * surface: '#f8fafc',\n * text: '#1e293b',\n * border: '#e2e8f0',\n * },\n * corners: 'rounded',\n * shadows: 'medium',\n * components: {\n * buttons: true,\n * cards: true,\n * inputs: true,\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n *\n * @example Pass-through to existing design system\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: 'var(--brand-purple)',\n * secondary: 'var(--brand-navy)',\n * background: 'var(--ds-bg)',\n * },\n * components: { buttons: true },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n */\n\nimport * as React from 'react';\nimport { generateThemeBridgeCss } from './generateCssVars';\nimport type { ThemeBridgeConfig } from './types';\n\nexport interface ThemeBridgeProviderProps {\n /** Theme configuration */\n config: ThemeBridgeConfig;\n /** Child elements to wrap */\n children: React.ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** HTML element to use for wrapper (defaults to 'div') */\n as?: keyof React.JSX.IntrinsicElements;\n}\n\n/**\n * Wraps content and injects CSS variables for Builder block styling.\n *\n * This component generates CSS variables from a simplified theme config\n * and injects them into a wrapper element. All Builder blocks rendered\n * inside will pick up these variables for colors, typography, spacing, etc.\n *\n * Optionally generates component CSS for buttons, cards, and inputs when\n * `config.components` is specified.\n */\nexport function ThemeBridgeProvider({\n config,\n children,\n className = '',\n as: Tag = 'div',\n}: ThemeBridgeProviderProps) {\n // Generate CSS variables and stylesheet\n const { css, cssVars } = React.useMemo(\n () => generateThemeBridgeCss(config),\n [config]\n );\n\n // Convert cssVars to inline style format\n const inlineStyle = React.useMemo(() => {\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(cssVars)) {\n style[key] = value;\n }\n return style;\n }, [cssVars]);\n\n return (\n <>\n {/* Inject CSS for theme scope */}\n <style dangerouslySetInnerHTML={{ __html: css }} />\n\n {/* Wrapper with theme scope attribute and inline CSS variables */}\n {React.createElement(\n Tag,\n {\n 'data-theme-scope': 'sdk',\n className: `theme-scope ${className}`.trim(),\n style: inlineStyle as React.CSSProperties,\n },\n children\n )}\n </>\n );\n}\n\n/**\n * Hook to access generated CSS variables from theme config.\n * Useful for custom components that need theme values.\n */\nexport function useThemeBridgeCss(config: ThemeBridgeConfig) {\n return React.useMemo(() => generateThemeBridgeCss(config), [config]);\n}\n","/**\n * CSS Variable Generator for Theme Bridge\n *\n * Generates the CSS variables that Builder blocks expect,\n * from a simplified ThemeBridgeConfig.\n *\n * Key differences from the full CMS theme system:\n * - No shade generation (SDK sites define their own tokens)\n * - Supports arbitrary token names\n * - Supports pass-through of CSS variable references\n * - Opt-in component CSS generation using core generators from @riverbankcms/blocks\n */\n\nimport {\n generateButtonCoreCss,\n getDefaultButtonVariants,\n generateCardCoreCss,\n getDefaultCardVariants,\n generateInputCoreCss,\n getDefaultInputSettings,\n type ButtonCoreSettings,\n type CardCoreSettings,\n} from '@riverbankcms/blocks';\n\nimport type {\n ThemeBridgeConfig,\n ThemeBridgeOutput,\n ThemeBridgeSpacing,\n ThemeBridgeCorners,\n ThemeBridgeShadows,\n} from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst SCOPE_ID = 'sdk';\n\nconst SYSTEM_FONT_STACK = 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"';\n\nconst SPACING_CONFIG: Record<ThemeBridgeSpacing, { mult: number; rhythm: string }> = {\n comfortable: { mult: 1.15, rhythm: '1.2rem' },\n standard: { mult: 1.0, rhythm: '1rem' },\n dense: { mult: 0.88, rhythm: '0.75rem' },\n};\n\nconst CORNERS_CONFIG: Record<ThemeBridgeCorners, { control: string; card: string }> = {\n square: { control: '0px', card: '0px' },\n soft: { control: '4px', card: '8px' },\n rounded: { control: '8px', card: '12px' },\n pill: { control: '9999px', card: '16px' },\n};\n\nconst SHADOWS_CONFIG: Record<ThemeBridgeShadows, {\n sm: string;\n md: string;\n lg: string;\n elev: string;\n}> = {\n none: {\n sm: 'none',\n md: 'none',\n lg: 'none',\n elev: 'none',\n },\n low: {\n sm: '0 1px 2px rgba(0,0,0,0.04)',\n md: '0 2px 4px rgba(0,0,0,0.06)',\n lg: '0 4px 8px rgba(0,0,0,0.08)',\n elev: '0 1px 3px rgba(0,0,0,0.06)',\n },\n medium: {\n sm: '0 1px 2px rgba(0,0,0,0.06)',\n md: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',\n lg: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',\n elev: '0 2px 6px rgba(0,0,0,0.08)',\n },\n high: {\n sm: '0 1px 3px rgba(0,0,0,0.08)',\n md: '0 6px 12px -2px rgba(0,0,0,0.15), 0 3px 6px -3px rgba(0,0,0,0.1)',\n lg: '0 20px 25px -5px rgba(0,0,0,0.15), 0 8px 10px -6px rgba(0,0,0,0.1)',\n elev: '0 4px 12px rgba(0,0,0,0.12)',\n },\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert hex color to RGB channel format (e.g., \"109 40 217\")\n */\nfunction hexToRgb(hex: string): string {\n const h = hex.replace(/^#/, '');\n const m = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n if (m.length !== 6) {\n console.warn(`[ThemeBridge] Invalid hex color: ${hex}`);\n return '0 0 0';\n }\n const int = parseInt(m, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `${r} ${g} ${b}`;\n}\n\n/**\n * Normalize token value to CSS-ready format.\n *\n * - Hex colors are converted to RGB channels\n * - CSS variable references are passed through\n * - Already-formatted RGB values are passed through\n */\nfunction normalizeTokenValue(value: string): string {\n // Pass through CSS variable references\n if (value.startsWith('var(')) {\n return value;\n }\n\n // Convert hex to RGB\n if (value.startsWith('#')) {\n return hexToRgb(value);\n }\n\n // Already RGB or other format - use directly\n return value;\n}\n\n// ============================================================================\n// Main Generator\n// ============================================================================\n\n/**\n * Generate CSS variables from a ThemeBridgeConfig.\n */\nexport function generateThemeBridgeCss(config: ThemeBridgeConfig): ThemeBridgeOutput {\n // Validate required config\n if (!config || typeof config !== 'object') {\n throw new Error(\n '[ThemeBridge] Invalid config: expected an object with a \"tokens\" property.'\n );\n }\n\n if (!config.tokens || typeof config.tokens !== 'object' || Array.isArray(config.tokens)) {\n throw new Error(\n '[ThemeBridge] Missing \"tokens\" property in config. ' +\n 'ThemeBridgeConfig requires a \"tokens\" object with color definitions. ' +\n 'Example: { tokens: { primary: \"#6d28d9\", background: \"#ffffff\" } }'\n );\n }\n\n const cssVars: Record<string, string> = {};\n const cssChunks: string[] = [];\n\n // -------------------------------------------------------------------------\n // Color Tokens\n // -------------------------------------------------------------------------\n\n for (const [name, value] of Object.entries(config.tokens)) {\n cssVars[`--tb-${name}`] = normalizeTokenValue(value);\n }\n\n // -------------------------------------------------------------------------\n // Typography\n // -------------------------------------------------------------------------\n\n const typography = config.typography ?? {};\n cssVars['--tb-font-heading'] = typography.headingFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-body'] = typography.bodyFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-weight-heading'] = String(typography.headingWeight ?? 600);\n cssVars['--tb-font-weight-body'] = String(typography.bodyWeight ?? 400);\n\n // Font sizes (standard scale)\n cssVars['--tb-fs-h1'] = '2.5rem';\n cssVars['--tb-fs-h2'] = '2rem';\n cssVars['--tb-fs-h3'] = '1.5rem';\n cssVars['--tb-fs-body'] = '1rem';\n\n // Letter spacing and line height\n cssVars['--tb-ls-heading'] = '-0.02em';\n cssVars['--tb-ls-body'] = '0';\n cssVars['--tb-lh-heading'] = '1.2';\n cssVars['--tb-lh-body'] = '1.6';\n\n // Text transforms\n cssVars['--tb-tt-heading'] = 'none';\n cssVars['--tb-fv-heading'] = 'normal';\n\n // -------------------------------------------------------------------------\n // Spacing\n // -------------------------------------------------------------------------\n\n const spacingPreset = SPACING_CONFIG[config.spacing ?? 'standard'];\n cssVars['--tb-space-mult'] = String(spacingPreset.mult);\n cssVars['--tb-rt-space-y'] = spacingPreset.rhythm;\n\n // -------------------------------------------------------------------------\n // Corners\n // -------------------------------------------------------------------------\n\n const corners = config.corners ?? 'rounded';\n const cornersPreset = CORNERS_CONFIG[corners];\n cssVars['--tb-radius-control'] = cornersPreset.control;\n cssVars['--tb-radius-card'] = cornersPreset.card;\n\n // -------------------------------------------------------------------------\n // Shadows\n // -------------------------------------------------------------------------\n\n const shadows = config.shadows ?? 'medium';\n const shadowsPreset = SHADOWS_CONFIG[shadows];\n cssVars['--tb-shadow-sm'] = shadowsPreset.sm;\n cssVars['--tb-shadow-md'] = shadowsPreset.md;\n cssVars['--tb-shadow-lg'] = shadowsPreset.lg;\n cssVars['--tb-shadow-elev'] = shadowsPreset.elev;\n\n // -------------------------------------------------------------------------\n // Motion\n // -------------------------------------------------------------------------\n\n cssVars['--tb-motion-duration'] = '180ms';\n cssVars['--tb-motion-ease'] = 'cubic-bezier(0.2, 0.8, 0.2, 1)';\n\n // -------------------------------------------------------------------------\n // Generate CSS Variables Block\n // -------------------------------------------------------------------------\n\n const cssLines = Object.entries(cssVars)\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n cssChunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) {\\n${cssLines}\\n}`);\n\n // -------------------------------------------------------------------------\n // Component CSS (opt-in)\n // -------------------------------------------------------------------------\n\n if (config.components?.buttons) {\n const buttonCss = generateButtonsCss(config, corners, shadows);\n cssChunks.push(buttonCss);\n }\n\n if (config.components?.cards) {\n const cardCss = generateCardsCss(config, corners, shadows);\n cssChunks.push(cardCss);\n }\n\n if (config.components?.inputs) {\n const inputCss = generateInputsCss(config, corners, shadows);\n cssChunks.push(inputCss);\n }\n\n // -------------------------------------------------------------------------\n // Custom Overrides\n // -------------------------------------------------------------------------\n\n if (config.overrides) {\n const overrideCss = generateOverrideCss(config.overrides);\n cssChunks.push(overrideCss);\n }\n\n return {\n cssVars,\n css: cssChunks.join('\\n\\n'),\n };\n}\n\n// ============================================================================\n// Component CSS Generators\n// ============================================================================\n\nfunction generateButtonsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const buttonConfig = config.components?.buttons;\n\n // Get variants to generate\n let variants = getDefaultButtonVariants();\n\n if (typeof buttonConfig === 'object' && buttonConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(buttonConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: ButtonCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n fontWeight: 500,\n textTransform: 'none',\n };\n\n return generateButtonCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateCardsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const cardConfig = config.components?.cards;\n\n // Get variants to generate\n let variants = getDefaultCardVariants();\n\n if (typeof cardConfig === 'object' && cardConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(cardConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: CardCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n };\n\n return generateCardCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateInputsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const settings = getDefaultInputSettings();\n\n // Override with theme settings\n settings.corners = corners;\n settings.shadow = shadows === 'none' ? 'none' : 'low';\n\n return generateInputCoreCss(SCOPE_ID, settings);\n}\n\nfunction generateOverrideCss(overrides: Record<string, string>): string {\n const chunks: string[] = [];\n\n for (const [selector, rules] of Object.entries(overrides)) {\n chunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) ${selector} {\\n ${rules}\\n}`);\n }\n\n return chunks.join('\\n\\n');\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme-bridge/ThemeBridgeProvider.tsx","../../src/theme-bridge/generateCssVars.ts"],"sourcesContent":["/**\n * ThemeBridgeProvider\n *\n * A lightweight theme provider for SDK sites that want to style Builder blocks\n * without using the full CMS theme system. Generates CSS variables from a\n * simplified configuration.\n *\n * @example Simple tokens only\n * ```tsx\n * import { ThemeBridgeProvider } from '@riverbankcms/sdk/theme-bridge';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * background: '#ffffff',\n * text: '#1e293b',\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * );\n * }\n * ```\n *\n * @example With component CSS\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * white: '#ffffff',\n * surface: '#f8fafc',\n * text: '#1e293b',\n * border: '#e2e8f0',\n * },\n * corners: 'rounded',\n * shadows: 'medium',\n * components: {\n * buttons: true,\n * cards: true,\n * inputs: true,\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n *\n * @example Pass-through to existing design system\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: 'var(--brand-purple)',\n * secondary: 'var(--brand-navy)',\n * background: 'var(--ds-bg)',\n * },\n * components: { buttons: true },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n */\n\nimport * as React from 'react';\nimport { generateThemeBridgeCss } from './generateCssVars';\nimport type { ThemeBridgeConfig } from './types';\n\nexport interface ThemeBridgeProviderProps {\n /** Theme configuration */\n config: ThemeBridgeConfig;\n /** Child elements to wrap */\n children: React.ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** HTML element to use for wrapper (defaults to 'div') */\n as?: keyof React.JSX.IntrinsicElements;\n}\n\n/**\n * Wraps content and injects CSS variables for Builder block styling.\n *\n * This component generates CSS variables from a simplified theme config\n * and injects them into a wrapper element. All Builder blocks rendered\n * inside will pick up these variables for colors, typography, spacing, etc.\n *\n * Optionally generates component CSS for buttons, cards, and inputs when\n * `config.components` is specified.\n */\nexport function ThemeBridgeProvider({\n config,\n children,\n className = '',\n as: Tag = 'div',\n}: ThemeBridgeProviderProps) {\n // Generate CSS variables and stylesheet\n const { css, cssVars } = React.useMemo(\n () => generateThemeBridgeCss(config),\n [config]\n );\n\n // Convert cssVars to inline style format\n const inlineStyle = React.useMemo(() => {\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(cssVars)) {\n style[key] = value;\n }\n return style;\n }, [cssVars]);\n\n return (\n <>\n {/* Inject CSS for theme scope */}\n <style dangerouslySetInnerHTML={{ __html: css }} />\n\n {/* Wrapper with theme scope attribute and inline CSS variables */}\n {React.createElement(\n Tag,\n {\n 'data-theme-scope': 'sdk',\n className: `theme-scope ${className}`.trim(),\n style: inlineStyle as React.CSSProperties,\n },\n children\n )}\n </>\n );\n}\n\n/**\n * Hook to access generated CSS variables from theme config.\n * Useful for custom components that need theme values.\n */\nexport function useThemeBridgeCss(config: ThemeBridgeConfig) {\n return React.useMemo(() => generateThemeBridgeCss(config), [config]);\n}\n","/**\n * CSS Variable Generator for Theme Bridge\n *\n * Generates the CSS variables that Builder blocks expect,\n * from a simplified ThemeBridgeConfig.\n *\n * Key differences from the full CMS theme system:\n * - No shade generation (SDK sites define their own tokens)\n * - Supports arbitrary token names\n * - Supports pass-through of CSS variable references\n * - Opt-in component CSS generation using core generators from @riverbankcms/blocks\n */\n\nimport {\n generateButtonCoreCss,\n getDefaultButtonVariants,\n generateCardCoreCss,\n getDefaultCardVariants,\n generateInputCoreCss,\n getDefaultInputSettings,\n type ButtonCoreSettings,\n type CardCoreSettings,\n} from '@riverbankcms/blocks';\n\nimport type {\n ThemeBridgeConfig,\n ThemeBridgeOutput,\n ThemeBridgeSpacing,\n ThemeBridgeCorners,\n ThemeBridgeShadows,\n} from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst SCOPE_ID = 'sdk';\n\nconst SYSTEM_FONT_STACK = 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"';\n\nconst SPACING_CONFIG: Record<ThemeBridgeSpacing, { mult: number; rhythm: string }> = {\n comfortable: { mult: 1.15, rhythm: '1.2rem' },\n standard: { mult: 1.0, rhythm: '1rem' },\n dense: { mult: 0.88, rhythm: '0.75rem' },\n};\n\nconst CORNERS_CONFIG: Record<ThemeBridgeCorners, { control: string; card: string }> = {\n square: { control: '0px', card: '0px' },\n soft: { control: '4px', card: '8px' },\n rounded: { control: '8px', card: '12px' },\n pill: { control: '9999px', card: '16px' },\n};\n\nconst SHADOWS_CONFIG: Record<ThemeBridgeShadows, {\n sm: string;\n md: string;\n lg: string;\n elev: string;\n}> = {\n none: {\n sm: 'none',\n md: 'none',\n lg: 'none',\n elev: 'none',\n },\n low: {\n sm: '0 1px 2px rgba(0,0,0,0.04)',\n md: '0 2px 4px rgba(0,0,0,0.06)',\n lg: '0 4px 8px rgba(0,0,0,0.08)',\n elev: '0 1px 3px rgba(0,0,0,0.06)',\n },\n medium: {\n sm: '0 1px 2px rgba(0,0,0,0.06)',\n md: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',\n lg: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',\n elev: '0 2px 6px rgba(0,0,0,0.08)',\n },\n high: {\n sm: '0 1px 3px rgba(0,0,0,0.08)',\n md: '0 6px 12px -2px rgba(0,0,0,0.15), 0 3px 6px -3px rgba(0,0,0,0.1)',\n lg: '0 20px 25px -5px rgba(0,0,0,0.15), 0 8px 10px -6px rgba(0,0,0,0.1)',\n elev: '0 4px 12px rgba(0,0,0,0.12)',\n },\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert hex color to RGB channel format (e.g., \"109 40 217\")\n */\nfunction hexToRgb(hex: string): string {\n const h = hex.replace(/^#/, '');\n const m = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n if (m.length !== 6) {\n console.warn(`[ThemeBridge] Invalid hex color: ${hex}`);\n return '0 0 0';\n }\n const int = parseInt(m, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `${r} ${g} ${b}`;\n}\n\n/**\n * Normalize token value to CSS-ready format.\n *\n * - Hex colors are converted to RGB channels\n * - CSS variable references are passed through\n * - Already-formatted RGB values are passed through\n */\nfunction normalizeTokenValue(value: string): string {\n // Pass through CSS variable references\n if (value.startsWith('var(')) {\n return value;\n }\n\n // Convert hex to RGB\n if (value.startsWith('#')) {\n return hexToRgb(value);\n }\n\n // Already RGB or other format - use directly\n return value;\n}\n\n// ============================================================================\n// Main Generator\n// ============================================================================\n\n/**\n * Generate CSS variables from a ThemeBridgeConfig.\n */\nexport function generateThemeBridgeCss(config: ThemeBridgeConfig): ThemeBridgeOutput {\n // Validate required config\n if (!config || typeof config !== 'object') {\n throw new Error(\n '[ThemeBridge] Invalid config: expected an object with a \"tokens\" property.'\n );\n }\n\n if (!config.tokens || typeof config.tokens !== 'object' || Array.isArray(config.tokens)) {\n throw new Error(\n '[ThemeBridge] Missing \"tokens\" property in config. ' +\n 'ThemeBridgeConfig requires a \"tokens\" object with color definitions. ' +\n 'Example: { tokens: { primary: \"#6d28d9\", background: \"#ffffff\" } }'\n );\n }\n\n const cssVars: Record<string, string> = {};\n const cssChunks: string[] = [];\n\n // -------------------------------------------------------------------------\n // Color Tokens\n // -------------------------------------------------------------------------\n\n for (const [name, value] of Object.entries(config.tokens)) {\n cssVars[`--tb-${name}`] = normalizeTokenValue(value);\n }\n\n // -------------------------------------------------------------------------\n // Typography\n // -------------------------------------------------------------------------\n\n const typography = config.typography ?? {};\n cssVars['--tb-font-heading'] = typography.headingFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-body'] = typography.bodyFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-weight-heading'] = String(typography.headingWeight ?? 600);\n cssVars['--tb-font-weight-body'] = String(typography.bodyWeight ?? 400);\n\n // Font sizes (standard scale)\n cssVars['--tb-fs-h1'] = '2.5rem';\n cssVars['--tb-fs-h2'] = '2rem';\n cssVars['--tb-fs-h3'] = '1.5rem';\n cssVars['--tb-fs-body'] = '1rem';\n\n // Letter spacing and line height\n cssVars['--tb-ls-heading'] = '-0.02em';\n cssVars['--tb-ls-body'] = '0';\n cssVars['--tb-lh-heading'] = '1.2';\n cssVars['--tb-lh-body'] = '1.6';\n\n // Text transforms\n cssVars['--tb-tt-heading'] = 'none';\n cssVars['--tb-fv-heading'] = 'normal';\n\n // -------------------------------------------------------------------------\n // Spacing\n // -------------------------------------------------------------------------\n\n const spacingPreset = SPACING_CONFIG[config.spacing ?? 'standard'];\n cssVars['--tb-space-mult'] = String(spacingPreset.mult);\n cssVars['--tb-rt-space-y'] = spacingPreset.rhythm;\n\n // -------------------------------------------------------------------------\n // Corners\n // -------------------------------------------------------------------------\n\n const corners = config.corners ?? 'rounded';\n const cornersPreset = CORNERS_CONFIG[corners];\n cssVars['--tb-radius-control'] = cornersPreset.control;\n cssVars['--tb-radius-card'] = cornersPreset.card;\n\n // -------------------------------------------------------------------------\n // Shadows\n // -------------------------------------------------------------------------\n\n const shadows = config.shadows ?? 'medium';\n const shadowsPreset = SHADOWS_CONFIG[shadows];\n cssVars['--tb-shadow-sm'] = shadowsPreset.sm;\n cssVars['--tb-shadow-md'] = shadowsPreset.md;\n cssVars['--tb-shadow-lg'] = shadowsPreset.lg;\n cssVars['--tb-shadow-elev'] = shadowsPreset.elev;\n\n // -------------------------------------------------------------------------\n // Motion\n // -------------------------------------------------------------------------\n\n cssVars['--tb-motion-duration'] = '180ms';\n cssVars['--tb-motion-ease'] = 'cubic-bezier(0.2, 0.8, 0.2, 1)';\n\n // -------------------------------------------------------------------------\n // Generate CSS Variables Block\n // -------------------------------------------------------------------------\n\n const cssLines = Object.entries(cssVars)\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n cssChunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) {\\n${cssLines}\\n}`);\n\n // -------------------------------------------------------------------------\n // Component CSS (opt-in)\n // -------------------------------------------------------------------------\n\n if (config.components?.buttons) {\n const buttonCss = generateButtonsCss(config, corners, shadows);\n cssChunks.push(buttonCss);\n }\n\n if (config.components?.cards) {\n const cardCss = generateCardsCss(config, corners, shadows);\n cssChunks.push(cardCss);\n }\n\n if (config.components?.inputs) {\n const inputCss = generateInputsCss(config, corners, shadows);\n cssChunks.push(inputCss);\n }\n\n // -------------------------------------------------------------------------\n // Custom Overrides\n // -------------------------------------------------------------------------\n\n if (config.overrides) {\n const overrideCss = generateOverrideCss(config.overrides);\n cssChunks.push(overrideCss);\n }\n\n return {\n cssVars,\n css: cssChunks.join('\\n\\n'),\n };\n}\n\n// ============================================================================\n// Component CSS Generators\n// ============================================================================\n\nfunction generateButtonsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const buttonConfig = config.components?.buttons;\n\n // Get variants to generate\n let variants = getDefaultButtonVariants();\n\n if (typeof buttonConfig === 'object' && buttonConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(buttonConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: ButtonCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n fontWeight: 500,\n textTransform: 'none',\n };\n\n return generateButtonCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateCardsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const cardConfig = config.components?.cards;\n\n // Get variants to generate\n let variants = getDefaultCardVariants();\n\n if (typeof cardConfig === 'object' && cardConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(cardConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: CardCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n };\n\n return generateCardCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateInputsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const settings = getDefaultInputSettings();\n\n // Override with theme settings\n settings.corners = corners;\n settings.shadow = shadows === 'none' ? 'none' : 'low';\n\n return generateInputCoreCss(SCOPE_ID, settings);\n}\n\nfunction generateOverrideCss(overrides: Record<string, string>): string {\n const chunks: string[] = [];\n\n for (const [selector, rules] of Object.entries(overrides)) {\n chunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) ${selector} {\\n ${rules}\\n}`);\n }\n\n return chunks.join('\\n\\n');\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/theme-bridge/ThemeBridgeProvider.tsx","../../src/theme-bridge/generateCssVars.ts"],"sourcesContent":["/**\n * ThemeBridgeProvider\n *\n * A lightweight theme provider for SDK sites that want to style Builder blocks\n * without using the full CMS theme system. Generates CSS variables from a\n * simplified configuration.\n *\n * @example Simple tokens only\n * ```tsx\n * import { ThemeBridgeProvider } from '@riverbankcms/sdk/theme-bridge';\n *\n * export default function RootLayout({ children }) {\n * return (\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * background: '#ffffff',\n * text: '#1e293b',\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * );\n * }\n * ```\n *\n * @example With component CSS\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: '#6d28d9',\n * secondary: '#4c1d95',\n * white: '#ffffff',\n * surface: '#f8fafc',\n * text: '#1e293b',\n * border: '#e2e8f0',\n * },\n * corners: 'rounded',\n * shadows: 'medium',\n * components: {\n * buttons: true,\n * cards: true,\n * inputs: true,\n * },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n *\n * @example Pass-through to existing design system\n * ```tsx\n * <ThemeBridgeProvider\n * config={{\n * tokens: {\n * primary: 'var(--brand-purple)',\n * secondary: 'var(--brand-navy)',\n * background: 'var(--ds-bg)',\n * },\n * components: { buttons: true },\n * }}\n * >\n * {children}\n * </ThemeBridgeProvider>\n * ```\n */\n\nimport * as React from 'react';\nimport { generateThemeBridgeCss } from './generateCssVars';\nimport type { ThemeBridgeConfig } from './types';\n\nexport interface ThemeBridgeProviderProps {\n /** Theme configuration */\n config: ThemeBridgeConfig;\n /** Child elements to wrap */\n children: React.ReactNode;\n /** Additional class name for the wrapper */\n className?: string;\n /** HTML element to use for wrapper (defaults to 'div') */\n as?: keyof React.JSX.IntrinsicElements;\n}\n\n/**\n * Wraps content and injects CSS variables for Builder block styling.\n *\n * This component generates CSS variables from a simplified theme config\n * and injects them into a wrapper element. All Builder blocks rendered\n * inside will pick up these variables for colors, typography, spacing, etc.\n *\n * Optionally generates component CSS for buttons, cards, and inputs when\n * `config.components` is specified.\n */\nexport function ThemeBridgeProvider({\n config,\n children,\n className = '',\n as: Tag = 'div',\n}: ThemeBridgeProviderProps) {\n // Generate CSS variables and stylesheet\n const { css, cssVars } = React.useMemo(\n () => generateThemeBridgeCss(config),\n [config]\n );\n\n // Convert cssVars to inline style format\n const inlineStyle = React.useMemo(() => {\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(cssVars)) {\n style[key] = value;\n }\n return style;\n }, [cssVars]);\n\n return (\n <>\n {/* Inject CSS for theme scope */}\n <style dangerouslySetInnerHTML={{ __html: css }} />\n\n {/* Wrapper with theme scope attribute and inline CSS variables */}\n {React.createElement(\n Tag,\n {\n 'data-theme-scope': 'sdk',\n className: `theme-scope ${className}`.trim(),\n style: inlineStyle as React.CSSProperties,\n },\n children\n )}\n </>\n );\n}\n\n/**\n * Hook to access generated CSS variables from theme config.\n * Useful for custom components that need theme values.\n */\nexport function useThemeBridgeCss(config: ThemeBridgeConfig) {\n return React.useMemo(() => generateThemeBridgeCss(config), [config]);\n}\n","/**\n * CSS Variable Generator for Theme Bridge\n *\n * Generates the CSS variables that Builder blocks expect,\n * from a simplified ThemeBridgeConfig.\n *\n * Key differences from the full CMS theme system:\n * - No shade generation (SDK sites define their own tokens)\n * - Supports arbitrary token names\n * - Supports pass-through of CSS variable references\n * - Opt-in component CSS generation using core generators from @riverbankcms/blocks\n */\n\nimport {\n generateButtonCoreCss,\n getDefaultButtonVariants,\n generateCardCoreCss,\n getDefaultCardVariants,\n generateInputCoreCss,\n getDefaultInputSettings,\n type ButtonCoreSettings,\n type CardCoreSettings,\n} from '@riverbankcms/blocks';\n\nimport type {\n ThemeBridgeConfig,\n ThemeBridgeOutput,\n ThemeBridgeSpacing,\n ThemeBridgeCorners,\n ThemeBridgeShadows,\n} from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst SCOPE_ID = 'sdk';\n\nconst SYSTEM_FONT_STACK = 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\"';\n\nconst SPACING_CONFIG: Record<ThemeBridgeSpacing, { mult: number; rhythm: string }> = {\n comfortable: { mult: 1.15, rhythm: '1.2rem' },\n standard: { mult: 1.0, rhythm: '1rem' },\n dense: { mult: 0.88, rhythm: '0.75rem' },\n};\n\nconst CORNERS_CONFIG: Record<ThemeBridgeCorners, { control: string; card: string }> = {\n square: { control: '0px', card: '0px' },\n soft: { control: '4px', card: '8px' },\n rounded: { control: '8px', card: '12px' },\n pill: { control: '9999px', card: '16px' },\n};\n\nconst SHADOWS_CONFIG: Record<ThemeBridgeShadows, {\n sm: string;\n md: string;\n lg: string;\n elev: string;\n}> = {\n none: {\n sm: 'none',\n md: 'none',\n lg: 'none',\n elev: 'none',\n },\n low: {\n sm: '0 1px 2px rgba(0,0,0,0.04)',\n md: '0 2px 4px rgba(0,0,0,0.06)',\n lg: '0 4px 8px rgba(0,0,0,0.08)',\n elev: '0 1px 3px rgba(0,0,0,0.06)',\n },\n medium: {\n sm: '0 1px 2px rgba(0,0,0,0.06)',\n md: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',\n lg: '0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)',\n elev: '0 2px 6px rgba(0,0,0,0.08)',\n },\n high: {\n sm: '0 1px 3px rgba(0,0,0,0.08)',\n md: '0 6px 12px -2px rgba(0,0,0,0.15), 0 3px 6px -3px rgba(0,0,0,0.1)',\n lg: '0 20px 25px -5px rgba(0,0,0,0.15), 0 8px 10px -6px rgba(0,0,0,0.1)',\n elev: '0 4px 12px rgba(0,0,0,0.12)',\n },\n};\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert hex color to RGB channel format (e.g., \"109 40 217\")\n */\nfunction hexToRgb(hex: string): string {\n const h = hex.replace(/^#/, '');\n const m = h.length === 3 ? h.split('').map(c => c + c).join('') : h;\n if (m.length !== 6) {\n console.warn(`[ThemeBridge] Invalid hex color: ${hex}`);\n return '0 0 0';\n }\n const int = parseInt(m, 16);\n const r = (int >> 16) & 255;\n const g = (int >> 8) & 255;\n const b = int & 255;\n return `${r} ${g} ${b}`;\n}\n\n/**\n * Normalize token value to CSS-ready format.\n *\n * - Hex colors are converted to RGB channels\n * - CSS variable references are passed through\n * - Already-formatted RGB values are passed through\n */\nfunction normalizeTokenValue(value: string): string {\n // Pass through CSS variable references\n if (value.startsWith('var(')) {\n return value;\n }\n\n // Convert hex to RGB\n if (value.startsWith('#')) {\n return hexToRgb(value);\n }\n\n // Already RGB or other format - use directly\n return value;\n}\n\n// ============================================================================\n// Main Generator\n// ============================================================================\n\n/**\n * Generate CSS variables from a ThemeBridgeConfig.\n */\nexport function generateThemeBridgeCss(config: ThemeBridgeConfig): ThemeBridgeOutput {\n // Validate required config\n if (!config || typeof config !== 'object') {\n throw new Error(\n '[ThemeBridge] Invalid config: expected an object with a \"tokens\" property.'\n );\n }\n\n if (!config.tokens || typeof config.tokens !== 'object' || Array.isArray(config.tokens)) {\n throw new Error(\n '[ThemeBridge] Missing \"tokens\" property in config. ' +\n 'ThemeBridgeConfig requires a \"tokens\" object with color definitions. ' +\n 'Example: { tokens: { primary: \"#6d28d9\", background: \"#ffffff\" } }'\n );\n }\n\n const cssVars: Record<string, string> = {};\n const cssChunks: string[] = [];\n\n // -------------------------------------------------------------------------\n // Color Tokens\n // -------------------------------------------------------------------------\n\n for (const [name, value] of Object.entries(config.tokens)) {\n cssVars[`--tb-${name}`] = normalizeTokenValue(value);\n }\n\n // -------------------------------------------------------------------------\n // Typography\n // -------------------------------------------------------------------------\n\n const typography = config.typography ?? {};\n cssVars['--tb-font-heading'] = typography.headingFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-body'] = typography.bodyFamily ?? SYSTEM_FONT_STACK;\n cssVars['--tb-font-weight-heading'] = String(typography.headingWeight ?? 600);\n cssVars['--tb-font-weight-body'] = String(typography.bodyWeight ?? 400);\n\n // Font sizes (standard scale)\n cssVars['--tb-fs-h1'] = '2.5rem';\n cssVars['--tb-fs-h2'] = '2rem';\n cssVars['--tb-fs-h3'] = '1.5rem';\n cssVars['--tb-fs-body'] = '1rem';\n\n // Letter spacing and line height\n cssVars['--tb-ls-heading'] = '-0.02em';\n cssVars['--tb-ls-body'] = '0';\n cssVars['--tb-lh-heading'] = '1.2';\n cssVars['--tb-lh-body'] = '1.6';\n\n // Text transforms\n cssVars['--tb-tt-heading'] = 'none';\n cssVars['--tb-fv-heading'] = 'normal';\n\n // -------------------------------------------------------------------------\n // Spacing\n // -------------------------------------------------------------------------\n\n const spacingPreset = SPACING_CONFIG[config.spacing ?? 'standard'];\n cssVars['--tb-space-mult'] = String(spacingPreset.mult);\n cssVars['--tb-rt-space-y'] = spacingPreset.rhythm;\n\n // -------------------------------------------------------------------------\n // Corners\n // -------------------------------------------------------------------------\n\n const corners = config.corners ?? 'rounded';\n const cornersPreset = CORNERS_CONFIG[corners];\n cssVars['--tb-radius-control'] = cornersPreset.control;\n cssVars['--tb-radius-card'] = cornersPreset.card;\n\n // -------------------------------------------------------------------------\n // Shadows\n // -------------------------------------------------------------------------\n\n const shadows = config.shadows ?? 'medium';\n const shadowsPreset = SHADOWS_CONFIG[shadows];\n cssVars['--tb-shadow-sm'] = shadowsPreset.sm;\n cssVars['--tb-shadow-md'] = shadowsPreset.md;\n cssVars['--tb-shadow-lg'] = shadowsPreset.lg;\n cssVars['--tb-shadow-elev'] = shadowsPreset.elev;\n\n // -------------------------------------------------------------------------\n // Motion\n // -------------------------------------------------------------------------\n\n cssVars['--tb-motion-duration'] = '180ms';\n cssVars['--tb-motion-ease'] = 'cubic-bezier(0.2, 0.8, 0.2, 1)';\n\n // -------------------------------------------------------------------------\n // Generate CSS Variables Block\n // -------------------------------------------------------------------------\n\n const cssLines = Object.entries(cssVars)\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n cssChunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) {\\n${cssLines}\\n}`);\n\n // -------------------------------------------------------------------------\n // Component CSS (opt-in)\n // -------------------------------------------------------------------------\n\n if (config.components?.buttons) {\n const buttonCss = generateButtonsCss(config, corners, shadows);\n cssChunks.push(buttonCss);\n }\n\n if (config.components?.cards) {\n const cardCss = generateCardsCss(config, corners, shadows);\n cssChunks.push(cardCss);\n }\n\n if (config.components?.inputs) {\n const inputCss = generateInputsCss(config, corners, shadows);\n cssChunks.push(inputCss);\n }\n\n // -------------------------------------------------------------------------\n // Custom Overrides\n // -------------------------------------------------------------------------\n\n if (config.overrides) {\n const overrideCss = generateOverrideCss(config.overrides);\n cssChunks.push(overrideCss);\n }\n\n return {\n cssVars,\n css: cssChunks.join('\\n\\n'),\n };\n}\n\n// ============================================================================\n// Component CSS Generators\n// ============================================================================\n\nfunction generateButtonsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const buttonConfig = config.components?.buttons;\n\n // Get variants to generate\n let variants = getDefaultButtonVariants();\n\n if (typeof buttonConfig === 'object' && buttonConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(buttonConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: ButtonCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n fontWeight: 500,\n textTransform: 'none',\n };\n\n return generateButtonCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateCardsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const cardConfig = config.components?.cards;\n\n // Get variants to generate\n let variants = getDefaultCardVariants();\n\n if (typeof cardConfig === 'object' && cardConfig.variants) {\n // Filter to only requested variants\n const requestedIds = new Set(cardConfig.variants);\n variants = variants.filter(v => requestedIds.has(v.id as any));\n }\n\n const settings: CardCoreSettings = {\n corners,\n shadow: shadows,\n borderWidth: 'thin',\n };\n\n return generateCardCoreCss(SCOPE_ID, variants, settings);\n}\n\nfunction generateInputsCss(\n config: ThemeBridgeConfig,\n corners: ThemeBridgeCorners,\n shadows: ThemeBridgeShadows\n): string {\n const settings = getDefaultInputSettings();\n\n // Override with theme settings\n settings.corners = corners;\n settings.shadow = shadows === 'none' ? 'none' : 'low';\n\n return generateInputCoreCss(SCOPE_ID, settings);\n}\n\nfunction generateOverrideCss(overrides: Record<string, string>): string {\n const chunks: string[] = [];\n\n for (const [selector, rules] of Object.entries(overrides)) {\n chunks.push(`:where([data-theme-scope=\"${SCOPE_ID}\"]) ${selector} {\\n ${rules}\\n}`);\n }\n\n return chunks.join('\\n\\n');\n}\n"],"mappings":"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","names":[]}
|
package/dist/server/theme.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
2
|
-
|
|
3
|
-
// src/theme/index.ts
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/theme/index.ts
|
|
4
2
|
function applyTheme(theme, element) {
|
|
5
3
|
const targetElement = _nullishCoalesce(element, () => ( (typeof document !== "undefined" ? document.documentElement : null)));
|
|
6
4
|
if (!targetElement) {
|
package/dist/server/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme.js","../../src/theme/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"sources":["/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme.js","../../src/theme/index.ts"],"names":[],"mappings":"AAAA;ACkBO,SAAS,UAAA,CAAW,KAAA,EAAgC,OAAA,EAA6B;AACtF,EAAA,MAAM,cAAA,mBAAgB,OAAA,UAAA,CAAY,OAAO,SAAA,IAAa,YAAA,EAAc,QAAA,CAAS,gBAAA,EAAkB,IAAA,GAAA;AAE/F,EAAA,GAAA,CAAI,CAAC,aAAA,EAAe;AAClB,IAAA,OAAA,CAAQ,IAAA,CAAK,yDAAyD,CAAA;AACtE,IAAA,MAAA;AAAA,EACF;AAIA,EAAA,aAAA,CAAc,YAAA,CAAa,oBAAA,EAAsB,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAIxE;AAiBO,SAAS,WAAA,CAAY,QAAA,EAAmC,CAAC,CAAA,EAA4B;AAI1F,EAAA,OAAO,EAAE,GAAG,QAAQ,CAAA;AACtB;AD1CA;AACE;AACA;AACF,mEAAC","file":"/Users/will/Projects/Business/cms/builder/packages/sdk/dist/server/theme.js","sourcesContent":[null,"/**\n * Theme utilities for Riverbank CMS\n *\n * These utilities help you work with Builder themes in your application.\n */\n\n/**\n * Apply a Builder theme to the DOM by injecting CSS variables\n *\n * @param theme - The theme object from Riverbank CMS\n * @param element - The element to apply the theme to (default: document.documentElement)\n *\n * @example\n * ```ts\n * const site = await client.getSite({ slug: 'my-site' });\n * applyTheme(site.theme);\n * ```\n */\nexport function applyTheme(theme: Record<string, unknown>, element?: HTMLElement): void {\n const targetElement = element ?? (typeof document !== 'undefined' ? document.documentElement : null);\n\n if (!targetElement) {\n console.warn('[Builder SDK] Cannot apply theme: no document available');\n return;\n }\n\n // Theme application will use the theme runtime from @riverbankcms/blocks\n // For now, we'll set a data attribute that the theme runtime can pick up\n targetElement.setAttribute('data-builder-theme', JSON.stringify(theme));\n\n // Note: Full CSS variable injection will be implemented once we\n // finalize the theme transformation API from @riverbankcms/blocks\n}\n\n/**\n * Create a theme object with type-safe defaults\n *\n * @param partial - Partial theme configuration\n * @returns Complete theme object\n *\n * @example\n * ```ts\n * const customTheme = createTheme({\n * colors: {\n * primary: '#007bff',\n * },\n * });\n * ```\n */\nexport function createTheme(partial: Record<string, unknown> = {}): Record<string, unknown> {\n // For now, return the partial as-is\n // Full theme creation with defaults will be implemented once we\n // finalize the theme structure from @riverbankcms/blocks\n return { ...partial };\n}\n\n/**\n * Re-export theme types from @riverbankcms/blocks when available\n */\nexport type Theme = Record<string, unknown>;\n"]}
|
package/dist/server/theme.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme/index.ts"],"sourcesContent":["/**\n * Theme utilities for Riverbank CMS\n *\n * These utilities help you work with Builder themes in your application.\n */\n\n/**\n * Apply a Builder theme to the DOM by injecting CSS variables\n *\n * @param theme - The theme object from Riverbank CMS\n * @param element - The element to apply the theme to (default: document.documentElement)\n *\n * @example\n * ```ts\n * const site = await client.getSite({ slug: 'my-site' });\n * applyTheme(site.theme);\n * ```\n */\nexport function applyTheme(theme: Record<string, unknown>, element?: HTMLElement): void {\n const targetElement = element ?? (typeof document !== 'undefined' ? document.documentElement : null);\n\n if (!targetElement) {\n console.warn('[Builder SDK] Cannot apply theme: no document available');\n return;\n }\n\n // Theme application will use the theme runtime from @riverbankcms/blocks\n // For now, we'll set a data attribute that the theme runtime can pick up\n targetElement.setAttribute('data-builder-theme', JSON.stringify(theme));\n\n // Note: Full CSS variable injection will be implemented once we\n // finalize the theme transformation API from @riverbankcms/blocks\n}\n\n/**\n * Create a theme object with type-safe defaults\n *\n * @param partial - Partial theme configuration\n * @returns Complete theme object\n *\n * @example\n * ```ts\n * const customTheme = createTheme({\n * colors: {\n * primary: '#007bff',\n * },\n * });\n * ```\n */\nexport function createTheme(partial: Record<string, unknown> = {}): Record<string, unknown> {\n // For now, return the partial as-is\n // Full theme creation with defaults will be implemented once we\n // finalize the theme structure from @riverbankcms/blocks\n return { ...partial };\n}\n\n/**\n * Re-export theme types from @riverbankcms/blocks when available\n */\nexport type Theme = Record<string, unknown>;\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/theme/index.ts"],"sourcesContent":["/**\n * Theme utilities for Riverbank CMS\n *\n * These utilities help you work with Builder themes in your application.\n */\n\n/**\n * Apply a Builder theme to the DOM by injecting CSS variables\n *\n * @param theme - The theme object from Riverbank CMS\n * @param element - The element to apply the theme to (default: document.documentElement)\n *\n * @example\n * ```ts\n * const site = await client.getSite({ slug: 'my-site' });\n * applyTheme(site.theme);\n * ```\n */\nexport function applyTheme(theme: Record<string, unknown>, element?: HTMLElement): void {\n const targetElement = element ?? (typeof document !== 'undefined' ? document.documentElement : null);\n\n if (!targetElement) {\n console.warn('[Builder SDK] Cannot apply theme: no document available');\n return;\n }\n\n // Theme application will use the theme runtime from @riverbankcms/blocks\n // For now, we'll set a data attribute that the theme runtime can pick up\n targetElement.setAttribute('data-builder-theme', JSON.stringify(theme));\n\n // Note: Full CSS variable injection will be implemented once we\n // finalize the theme transformation API from @riverbankcms/blocks\n}\n\n/**\n * Create a theme object with type-safe defaults\n *\n * @param partial - Partial theme configuration\n * @returns Complete theme object\n *\n * @example\n * ```ts\n * const customTheme = createTheme({\n * colors: {\n * primary: '#007bff',\n * },\n * });\n * ```\n */\nexport function createTheme(partial: Record<string, unknown> = {}): Record<string, unknown> {\n // For now, return the partial as-is\n // Full theme creation with defaults will be implemented once we\n // finalize the theme structure from @riverbankcms/blocks\n return { ...partial };\n}\n\n/**\n * Re-export theme types from @riverbankcms/blocks when available\n */\nexport type Theme = Record<string, unknown>;\n"],"mappings":";AAkBO,SAAS,WAAW,OAAgC,SAA6B;AACtF,QAAM,gBAAgB,YAAY,OAAO,aAAa,cAAc,SAAS,kBAAkB;AAE/F,MAAI,CAAC,eAAe;AAClB,YAAQ,KAAK,yDAAyD;AACtE;AAAA,EACF;AAIA,gBAAc,aAAa,sBAAsB,KAAK,UAAU,KAAK,CAAC;AAIxE;AAiBO,SAAS,YAAY,UAAmC,CAAC,GAA4B;AAI1F,SAAO,EAAE,GAAG,QAAQ;AACtB;","names":[]}
|