@getvitops/emdash 0.3.4 → 0.3.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # @getvitops/emdash
2
2
 
3
+ ## 0.3.6
4
+
5
+ **The `vitops.carousel` block now renders `@getvitops/astro`'s `<Carousel />`** instead of emitting
6
+ `<wc-carousel>` markup of its own — one implementation, so the block cannot drift from the
7
+ component. Two new fields: **Loop** (a toggle; cloning the slides is opt-in now, and `autoplay`
8
+ implies it) and **Slide aspect ratio** (default `16 / 9`, which is what keeps slide heights equal
9
+ whatever each image's proportions are). Existing carousel blocks keep working; they simply stop
10
+ looping unless you tick Loop.
11
+
12
+ **The `@getvitops/astro` peer range is now `>=6.0.0`**, because `Carousel.astro` does not exist
13
+ before it. Upgrade the whole toolchain together — it ships as one version.
14
+
15
+ ### Patch Changes
16
+
17
+ - @getvitops/utils@6.0.0
18
+
19
+ ## 0.3.5
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @getvitops/utils@5.0.0
25
+
3
26
  ## 0.3.4
4
27
 
5
28
  ### Patch Changes
package/README.md CHANGED
@@ -38,6 +38,56 @@ export default defineConfig({
38
38
 
39
39
  Tested against `emdash@0.31.x`.
40
40
 
41
+ ## Rendering mode
42
+
43
+ An EmDash site **must** run `output: 'server'` with an adapter — the admin at `/_emdash/admin`,
44
+ the media and API routes, preview tokens and scheduled publishing all need a server, and
45
+ `vitopsHosting()` supplies the adapter. That makes prerendering **per-route and opt-in**, the
46
+ opposite of a plain Astro site (which should stay `output: 'static'` and opt _out_ with
47
+ `export const prerender = false` — see [`@getvitops/astro`](../astro/README.md#rendering-mode)).
48
+
49
+ So on an EmDash site:
50
+
51
+ - **Put `export const prerender = true` on every page that doesn't need per-request data** — the
52
+ home page, legal pages, anything whose content is known at build time. A route with no
53
+ `prerender` export is rendered again for every visitor on every request.
54
+ - **Give dynamic routes a `getStaticPaths()`** so database-backed pages build to static HTML too.
55
+ Query the collection at build time and return one entry per path:
56
+
57
+ ```astro
58
+ ---
59
+ // src/pages/[...slug].astro
60
+ import { getEmDashCollection, getEmDashEntry } from 'emdash';
61
+ import { PortableText } from 'emdash/ui';
62
+ import Layout from '../layouts/Layout.astro';
63
+
64
+ export const prerender = true;
65
+
66
+ export async function getStaticPaths() {
67
+ const { entries } = await getEmDashCollection('pages', { status: 'published' });
68
+ return entries.map((entry) => ({ params: { slug: entry.slug } }));
69
+ }
70
+
71
+ const { slug } = Astro.params;
72
+ const { entry: page } = await getEmDashEntry('pages', slug);
73
+ ---
74
+
75
+ <Layout title={page.data.title}>
76
+ <PortableText value={page.data.content} />
77
+ </Layout>
78
+ ```
79
+
80
+ The trade-off is deliberate and worth stating: **a prerendered page reflects the database as of the
81
+ last build**, so publishing from the admin only appears after a redeploy. Leave a route
82
+ server-rendered when that is unacceptable — the preview route (draft content is served at request
83
+ time via a signed token), a page that must go live within seconds, or anything personalised. That
84
+ is the narrow case, not the default.
85
+
86
+ One exception to the exception: if you are building a **distributable EmDash theme** rather than a
87
+ site, EmDash's own guidance is that content pages must stay server-rendered, since a theme's users
88
+ edit content through the admin and expect it live. The rule above is for a site you build and
89
+ deploy yourself.
90
+
41
91
  ## Hosting seam: `vitopsHosting()`
42
92
 
43
93
  One call resolves the Astro adapter + EmDash database/storage for a hosting
@@ -83,7 +133,7 @@ export default defineConfig({
83
133
  | Copy snippet | `vitops.copyButton` | `<wc-copy>` with code/inline fallback |
84
134
  | Banner | `vitops.banner` | `<wc-dismissable>` banner with tone colours |
85
135
  | Disclosure | `vitops.details` | native `<details>`/`<summary>` |
86
- | Carousel | `vitops.carousel` | `<wc-carousel>` scroll-snap carousel |
136
+ | Carousel | `vitops.carousel` | `@getvitops/astro`'s `<Carousel />` |
87
137
 
88
138
  All blocks render accessible markup that works without JavaScript; the
89
139
  web components progressively enhance it.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;;;KAgBY,gBAAA,GAAmB,OAAA,CAC7B,WAAA,CAAY,uBAAA;EACV,IAAA;AAAA;AAAA,KAGQ,oBAAA,GAAuB,IAAA,CAAK,uBAAA;EACtC,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,MAAA,EAAQ,oBAAoB;;;KCO7B,aAAA;AAAA,UAIK,oBAAA;EDwJhB;ECtJC,MAAA,GAAS,aAAa;EACtB,UAAA;IDduC,yDCgBrC,SAAA;IAEA,YAAA,WAXQ;IAaR,OAAA;EAAA;EAEF,IAAA;IAfuB,2EAiBrB,WAAA,WAbiC;IAejC,QAAA,YAboB;IAepB,UAAA,WAfO;IAiBP,OAAA;EAAA;AAAA;AAAA,UAIa,aAAA;EACf,MAAA,EAAQ,aAAA;EACR,OAAA,EAAS,gBAAgB;EAVvB;EAYF,QAAA;EARE;EAUF,OAAA;AAAA;AAAA,iBAoBoB,aAAA,CAAc,OAAA,GAAS,oBAAA,GAA4B,OAAA,CAAQ,aAAA;;;UCxChE,mBAAA;;;;;;;;;;;EAWf,OAAA;EFtCM;EEwCN,MAAM;AAAA;;;;;iBAOQ,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,gBAAgB;;iBAajE,YAAA,CAAa,OAAA,GAAS,mBAAwB,oBAAA,cAAA,kBAAA,mBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;;;KAgBY,gBAAA,GAAmB,OAAA,CAC7B,WAAA,CAAY,uBAAA;EACV,IAAA;AAAA;AAAA,KAGQ,oBAAA,GAAuB,IAAA,CAAK,uBAAA;EACtC,MAAA,GAAS,gBAAA;AAAA;AAAA,cAGE,MAAA,EAAQ,oBAAoB;;;KCO7B,aAAA;AAAA,UAIK,oBAAA;EDoKhB;EClKC,MAAA,GAAS,aAAa;EACtB,UAAA;IDduC,yDCgBrC,SAAA;IAEA,YAAA,WAXQ;IAaR,OAAA;EAAA;EAEF,IAAA;IAfuB,2EAiBrB,WAAA,WAbiC;IAejC,QAAA,YAboB;IAepB,UAAA,WAfO;IAiBP,OAAA;EAAA;AAAA;AAAA,UAIa,aAAA;EACf,MAAA,EAAQ,aAAA;EACR,OAAA,EAAS,gBAAgB;EAVvB;EAYF,QAAA;EARE;EAUF,OAAA;AAAA;AAAA,iBAoBoB,aAAA,CAAc,OAAA,GAAS,oBAAA,GAA4B,OAAA,CAAQ,aAAA;;;UCxChE,mBAAA;;;;;;;;;;;EAWf,OAAA;EFtCM;EEwCN,MAAM;AAAA;;;;;iBAOQ,YAAA,CAAa,OAAA,GAAS,mBAAA,GAA2B,gBAAgB;;iBAajE,YAAA,CAAa,OAAA,GAAS,mBAAwB,oBAAA,cAAA,kBAAA,mBAAA"}
package/dist/index.mjs CHANGED
@@ -291,6 +291,18 @@ const BLOCKS = [
291
291
  action_id: "autoplay",
292
292
  label: "Autoplay interval (ms, 0 = off)",
293
293
  initial_value: 0
294
+ },
295
+ {
296
+ type: "toggle",
297
+ action_id: "loop",
298
+ label: "Loop (clones the slides — autoplay implies it)",
299
+ initial_value: false
300
+ },
301
+ {
302
+ type: "text_input",
303
+ action_id: "aspect",
304
+ label: "Slide aspect ratio",
305
+ initial_value: "16 / 9"
294
306
  }
295
307
  ]
296
308
  }
@@ -358,7 +370,7 @@ async function vitopsHosting(options = {}) {
358
370
 
359
371
  //#endregion
360
372
  //#region package.json
361
- var version = "0.3.4";
373
+ var version = "0.3.6";
362
374
 
363
375
  //#endregion
364
376
  //#region src/index.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../src/icon-options.ts","../src/blocks.ts","../src/hosting.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * Semantic icon names, as Block Kit combobox options.\n *\n * Derived from `@getvitops/utils`' `iconMap` at import time. This used to be a\n * generated copy, so that the package carried no runtime `@getvitops/*`\n * dependency and could version independently of the fixed toolchain group —\n * but that independence was already fiction: the blocks render from the\n * toolchain's SVG sprite (`<use href=\"…/icons.svg#icon-menu\">`), so an emdash\n * built against one icon vocabulary and a sprite built against another produce\n * empty boxes, silently and with no install-time error. Depending on the real\n * map makes the vocabulary one thing rather than two that can drift.\n *\n * `fa7` is the reference collection: every semantic name is defined there, and\n * the other sets are alternate resolutions of the same names.\n */\nimport { iconMap } from '@getvitops/utils';\n\nexport const SEMANTIC_ICON_OPTIONS: { label: string; value: string }[] = Object.keys(iconMap.fa7)\n .sort()\n // Label and value are the same string on purpose: the editor picks a semantic\n // name, and showing the set-specific glyph id would leak the icon set into\n // authored content — the thing the semantic layer exists to prevent.\n .map((name) => ({ label: name, value: name }));\n","/**\n * Portable Text block-type declarations for the EmDash editor.\n *\n * v1 exposes only Vitops patterns whose data model fits EmDash's flat Block Kit\n * fields (text_input / number_input / select / toggle). Repeating structured\n * patterns (Cards, wc-entries, FAQ, forms) go through EmDash Sections or\n * Field Kit `list` widgets on json collection fields instead — see the README.\n *\n * Every `action_id` here must have a matching prop consumed by the block's\n * .astro component in `src/astro/blocks/` (enforced by blocks.test.ts).\n */\n\nimport type { PortableTextBlockConfig } from 'emdash';\nimport { SEMANTIC_ICON_OPTIONS } from './icon-options.ts';\n\n/** The v1 subset of EmDash Block Kit field elements used by these blocks. */\nexport type BlockKitFieldDef = Extract<\n NonNullable<PortableTextBlockConfig['fields']>[number],\n { type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox' }\n>;\n\nexport type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {\n fields?: BlockKitFieldDef[];\n};\n\nexport const BLOCKS: PortableTextBlockDef[] = [\n {\n /**\n * A link or button, optionally with an icon at either end.\n *\n * Flattened on purpose: Block Kit has no object-group element, so a shape\n * like `{ label, url }` becomes sibling fields. Same constraint the\n * marketing-blocks plugin documents.\n *\n * The icons are `combobox` — a searchable typeahead over a STATIC option\n * list, which is exactly what the semantic name list is. It is the closest\n * fit Block Kit offers: its element union is closed (no custom element, no\n * raw HTML), so `<wc-icon-picker>` cannot be mounted inside a block modal\n * however much nicer it would be. Revisit if EmDash opens that union.\n */\n type: 'vitops.actionLink',\n label: 'Link or button',\n icon: 'link',\n placeholder: 'A link or button, with optional icons',\n fields: [\n { type: 'text_input', action_id: 'label', label: 'Label' },\n { type: 'text_input', action_id: 'href', label: 'URL' },\n {\n type: 'select',\n action_id: 'variant',\n label: 'Style',\n initial_value: 'link',\n options: [\n { label: 'Link', value: 'link' },\n { label: 'Button', value: 'btn' },\n { label: 'Call to action', value: 'cta' },\n ],\n },\n {\n type: 'combobox',\n action_id: 'startIcon',\n label: 'Icon before the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'combobox',\n action_id: 'endIcon',\n label: 'Icon after the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'select',\n action_id: 'role',\n label: 'Colour role',\n initial_value: '',\n options: [\n { label: 'Default', value: '' },\n { label: 'Success', value: 'success' },\n { label: 'Danger', value: 'danger' },\n { label: 'Warning', value: 'warning' },\n { label: 'Info', value: 'info' },\n ],\n },\n { type: 'toggle', action_id: 'newTab', label: 'Open in a new tab', initial_value: false },\n ],\n },\n {\n type: 'vitops.imageCompare',\n label: 'Image compare',\n placeholder: 'Before/after image slider',\n fields: [\n { type: 'text_input', action_id: 'before', label: 'Before image URL' },\n { type: 'text_input', action_id: 'beforeAlt', label: 'Before alt text' },\n {\n type: 'text_input',\n action_id: 'beforeLabel',\n label: 'Before label',\n initial_value: 'Before',\n },\n { type: 'text_input', action_id: 'after', label: 'After image URL' },\n { type: 'text_input', action_id: 'afterAlt', label: 'After alt text' },\n { type: 'text_input', action_id: 'afterLabel', label: 'After label', initial_value: 'After' },\n ],\n },\n {\n type: 'vitops.copyButton',\n label: 'Copy snippet',\n icon: 'code',\n placeholder: 'Text with a copy-to-clipboard button',\n fields: [\n { type: 'text_input', action_id: 'text', label: 'Text to copy', multiline: true },\n { type: 'text_input', action_id: 'label', label: 'Button label', initial_value: 'Copy' },\n {\n type: 'select',\n action_id: 'display',\n label: 'Display',\n initial_value: 'code-block',\n options: [\n { label: 'Code block', value: 'code-block' },\n { label: 'Inline', value: 'inline' },\n ],\n },\n ],\n },\n {\n type: 'vitops.banner',\n label: 'Banner',\n placeholder: 'Announcement or notice',\n fields: [\n { type: 'text_input', action_id: 'message', label: 'Message', multiline: true },\n {\n type: 'select',\n action_id: 'tone',\n label: 'Tone',\n initial_value: 'info',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Success', value: 'success' },\n { label: 'Warning', value: 'warning' },\n { label: 'Danger', value: 'danger' },\n ],\n },\n { type: 'toggle', action_id: 'dismissible', label: 'Dismissible', initial_value: true },\n ],\n },\n {\n type: 'vitops.details',\n label: 'Disclosure',\n placeholder: 'Collapsible details/summary',\n fields: [\n { type: 'text_input', action_id: 'summary', label: 'Summary (always visible)' },\n { type: 'text_input', action_id: 'body', label: 'Body (revealed)', multiline: true },\n { type: 'toggle', action_id: 'open', label: 'Open by default', initial_value: false },\n ],\n },\n {\n type: 'vitops.carousel',\n label: 'Carousel',\n placeholder: 'Image carousel',\n fields: [\n {\n type: 'text_input',\n action_id: 'images',\n label: 'Image URLs (one per line)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'alts',\n label: 'Alt texts (one per line, same order)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'label',\n label: 'Accessible name',\n initial_value: 'Carousel',\n },\n {\n type: 'number_input',\n action_id: 'autoplay',\n label: 'Autoplay interval (ms, 0 = off)',\n initial_value: 0,\n },\n ],\n },\n];\n","/**\n * Hosting seam — one call in astro.config.mjs resolves the adapter + EmDash\n * database/storage for the chosen hosting target, so a site can start on\n * Cloudflare and move to a Node host (VPS / docker-compose / k8s) — or back —\n * by flipping a single value instead of rewiring its config:\n *\n * ```js\n * import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';\n * const { adapter, database, storage } = await vitopsHosting();\n * export default defineConfig({\n * output: 'server',\n * adapter,\n * integrations: [react(), emdash({ database, storage, plugins: [vitopsEmdash()] })],\n * });\n * ```\n *\n * Targets:\n * - `'cloudflare'` (default) — `@astrojs/cloudflare` + D1/R2 bindings.\n * Needs `@astrojs/cloudflare` and `@emdash-cms/cloudflare` installed.\n * - `'node'` — `@astrojs/node` (standalone) + SQLite file + local uploads.\n * Needs `@astrojs/node` and `better-sqlite3` installed. Scheduled publishing\n * runs in-process on Node (no worker/cron trigger needed). For production,\n * move storage to S3-compatible (`s3()` from `emdash/astro`) and/or the\n * database to Postgres (`postgres()` from `emdash/db`) via `options.node`.\n *\n * The adapter packages are optional peer dependencies, resolved lazily — a\n * site only installs the stack for the target it uses. Content lives in the\n * database and media in the storage backend, so switching hosts is a data\n * migration (D1 export / EmDash seed round-trip + media copy), not a rewrite.\n */\nimport type { AstroIntegration } from 'astro';\n\nexport type HostingTarget = 'cloudflare' | 'node';\n\nconst TARGETS: HostingTarget[] = ['cloudflare', 'node'];\n\nexport interface VitopsHostingOptions {\n /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */\n target?: HostingTarget;\n cloudflare?: {\n /** D1 binding name in wrangler config (default 'DB'). */\n dbBinding?: string;\n /** R2 binding name in wrangler config (default 'MEDIA'). */\n mediaBinding?: string;\n /** D1 Sessions API read-replication mode (default 'auto'). */\n session?: string;\n };\n node?: {\n /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */\n databaseUrl?: string;\n /** Full database descriptor override (e.g. postgres() from 'emdash/db'). */\n database?: unknown;\n /** Local uploads directory (default './data/uploads'). */\n uploadsDir?: string;\n /** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */\n storage?: unknown;\n };\n}\n\nexport interface VitopsHosting {\n target: HostingTarget;\n adapter: AstroIntegration;\n /** EmDash database descriptor — pass to emdash({ database }). */\n database: unknown;\n /** EmDash storage descriptor — pass to emdash({ storage }). */\n storage: unknown;\n}\n\n/**\n * Import an optional hosting dependency. The specifier is passed as a\n * variable so bundlers/type-checkers leave it as a plain runtime import —\n * these packages are optional peers a consumer installs per target.\n */\nasync function importFor(target: HostingTarget, specifier: string, install: string) {\n try {\n return await import(/* @vite-ignore */ specifier);\n } catch (cause) {\n throw new Error(\n `[vitops] hosting target '${target}' needs ${specifier}, which isn't installed.\\n` +\n `Install the ${target} stack first: ${install}`,\n { cause },\n );\n }\n}\n\nexport async function vitopsHosting(options: VitopsHostingOptions = {}): Promise<VitopsHosting> {\n const target = (process.env.HOSTING as HostingTarget) || options.target || 'cloudflare';\n\n if (target === 'cloudflare') {\n const install = 'pnpm add @astrojs/cloudflare @emdash-cms/cloudflare';\n const cf = options.cloudflare ?? {};\n const { default: cloudflare } = await importFor(target, '@astrojs/cloudflare', install);\n const { d1, r2 } = await importFor(target, '@emdash-cms/cloudflare', install);\n return {\n target,\n adapter: cloudflare(),\n database: d1({ binding: cf.dbBinding ?? 'DB', session: cf.session ?? 'auto' }),\n storage: r2({ binding: cf.mediaBinding ?? 'MEDIA' }),\n };\n }\n\n if (target === 'node') {\n const install = 'pnpm add @astrojs/node better-sqlite3';\n const opts = options.node ?? {};\n const { default: node } = await importFor(target, '@astrojs/node', install);\n // Required peer of this package, so always present — but still routed\n // through importFor: a literal import('emdash/db') would be statically\n // analyzable, and bundling the plugin runtime would drag EmDash's whole\n // integration graph into the site's server/client bundles.\n const { sqlite } = await importFor(target, 'emdash/db', install);\n const { local } = await importFor(target, 'emdash/astro', install);\n const { mkdirSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n\n const databaseUrl = opts.databaseUrl ?? process.env.DATABASE_PATH ?? 'file:./data/emdash.db';\n const uploadsDir = opts.uploadsDir ?? './data/uploads';\n // better-sqlite3 refuses to create parent directories, so make sure the\n // defaults exist (runs at astro config load — dev and build). A deployed\n // host still needs the data directory mounted/persisted as a volume.\n if (!opts.database && databaseUrl.startsWith('file:')) {\n mkdirSync(dirname(databaseUrl.slice('file:'.length)), { recursive: true });\n }\n if (!opts.storage) mkdirSync(uploadsDir, { recursive: true });\n\n return {\n target,\n adapter: node({ mode: 'standalone' }),\n database: opts.database ?? sqlite({ url: databaseUrl }),\n storage: opts.storage ?? local({ directory: uploadsDir, baseUrl: '/_emdash/api/media/file' }),\n };\n }\n\n throw new Error(\n `[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? 'HOSTING env var' : 'options.target'}). ` +\n `Valid targets: ${TARGETS.join(', ')}.`,\n );\n}\n","","/**\n * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.\n *\n * Declares Vitops Portable Text block types (editable from the EmDash admin's\n * slash menu) and ships the Astro components that render them via the\n * `componentsEntry` (`@getvitops/emdash/astro`).\n *\n * Composes with — does not replace — the `vitops()` Astro integration from\n * `@getvitops/astro`: the integration owns design-system CSS generation and\n * copies the web-component bundles into `public/<wcBase>/`; this plugin only\n * adds the editor-facing layer. Register both in the consumer site:\n *\n * ```js\n * // astro.config.mjs\n * integrations: [\n * react(),\n * vitops({ css: {...} }),\n * emdash({ database, storage, plugins: [vitopsEmdash()] }),\n * ]\n * ```\n */\nimport { definePlugin } from 'emdash';\nimport type { PluginDescriptor } from 'emdash';\nimport { BLOCKS } from './blocks.ts';\n\nexport { BLOCKS } from './blocks.ts';\nexport type { BlockKitFieldDef, PortableTextBlockDef } from './blocks.ts';\nexport { vitopsHosting } from './hosting.ts';\nexport type { HostingTarget, VitopsHosting, VitopsHostingOptions } from './hosting.ts';\n\n/**\n * Read from package.json rather than hand-maintained.\n *\n * This was a literal with a \"keep in sync\" comment, which drifted on every\n * release: `changeset version` bumps package.json and leaves the literal alone.\n * `blocks.test.ts` asserts they match, but the release chain didn't run tests,\n * so `@getvitops/emdash@0.2.1` shipped a descriptor reporting `0.2.0`. Deriving\n * it removes the failure mode instead of relying on remembering.\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\nconst PLUGIN_ID = 'vitops';\nconst FRAGMENTS_CAPABILITY = 'hooks.page-fragments:register';\n\nexport interface VitopsEmdashOptions {\n /**\n * How the web-component runtime scripts reach the page:\n * - `'integration'` (default) — the site's layout renders `<Head />` from\n * `@getvitops/astro`, which emits the script tags; this plugin adds none.\n * - `'fragments'` — for layouts using EmDash's `<EmDashHead/>`/`<EmDashBodyEnd/>`\n * instead: the plugin injects the script tags via the `page:fragments` hook.\n * Either way the bundles themselves must exist under `public/<wcBase>/`,\n * which the `vitops()` integration provides (webComponents: true).\n * Do not enable both delivery paths — the scripts would load twice.\n */\n scripts?: 'integration' | 'fragments';\n /** Public base path of the web-component bundles (default '/vitops'). */\n wcBase?: string;\n}\n\n/**\n * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()\n * integration's `plugins` array.\n */\nexport function vitopsEmdash(options: VitopsEmdashOptions = {}): PluginDescriptor {\n return {\n id: PLUGIN_ID,\n version: VERSION,\n format: 'native',\n entrypoint: '@getvitops/emdash',\n componentsEntry: '@getvitops/emdash/astro',\n options,\n capabilities: options.scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n } as PluginDescriptor;\n}\n\n/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */\nexport function createPlugin(options: VitopsEmdashOptions = {}) {\n const scripts = options.scripts ?? 'integration';\n const wcBase = options.wcBase ?? '/vitops';\n\n return definePlugin({\n id: PLUGIN_ID,\n version: VERSION,\n capabilities: scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n\n hooks:\n scripts === 'fragments'\n ? {\n // Mirrors @getvitops/astro Head.astro: module polyfills + elements\n // in head, deferred enhancements at end of body.\n 'page:fragments': async () => [\n {\n kind: 'html',\n placement: 'head',\n html:\n `<link rel=\"modulepreload\" href=\"${wcBase}/polyfills.js\">` +\n `<script type=\"module\" src=\"${wcBase}/polyfills.js\"></script>` +\n `<script type=\"module\" src=\"${wcBase}/elements.js\"></script>`,\n key: 'vitops-wc',\n },\n {\n kind: 'external-script',\n placement: 'body:end',\n src: `${wcBase}/deferred.js`,\n defer: true,\n key: 'vitops-deferred',\n },\n ],\n }\n : {},\n\n admin: {\n portableTextBlocks: BLOCKS,\n },\n });\n}\n\nexport default createPlugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["pkg.version"],"sources":["../src/icon-options.ts","../src/blocks.ts","../src/hosting.ts","../package.json","../src/index.ts"],"sourcesContent":["/**\n * Semantic icon names, as Block Kit combobox options.\n *\n * Derived from `@getvitops/utils`' `iconMap` at import time. This used to be a\n * generated copy, so that the package carried no runtime `@getvitops/*`\n * dependency and could version independently of the fixed toolchain group —\n * but that independence was already fiction: the blocks render from the\n * toolchain's SVG sprite (`<use href=\"…/icons.svg#icon-menu\">`), so an emdash\n * built against one icon vocabulary and a sprite built against another produce\n * empty boxes, silently and with no install-time error. Depending on the real\n * map makes the vocabulary one thing rather than two that can drift.\n *\n * `fa7` is the reference collection: every semantic name is defined there, and\n * the other sets are alternate resolutions of the same names.\n */\nimport { iconMap } from '@getvitops/utils';\n\nexport const SEMANTIC_ICON_OPTIONS: { label: string; value: string }[] = Object.keys(iconMap.fa7)\n .sort()\n // Label and value are the same string on purpose: the editor picks a semantic\n // name, and showing the set-specific glyph id would leak the icon set into\n // authored content — the thing the semantic layer exists to prevent.\n .map((name) => ({ label: name, value: name }));\n","/**\n * Portable Text block-type declarations for the EmDash editor.\n *\n * v1 exposes only Vitops patterns whose data model fits EmDash's flat Block Kit\n * fields (text_input / number_input / select / toggle). Repeating structured\n * patterns (Cards, wc-entries, FAQ, forms) go through EmDash Sections or\n * Field Kit `list` widgets on json collection fields instead — see the README.\n *\n * Every `action_id` here must have a matching prop consumed by the block's\n * .astro component in `src/astro/blocks/` (enforced by blocks.test.ts).\n */\n\nimport type { PortableTextBlockConfig } from 'emdash';\nimport { SEMANTIC_ICON_OPTIONS } from './icon-options.ts';\n\n/** The v1 subset of EmDash Block Kit field elements used by these blocks. */\nexport type BlockKitFieldDef = Extract<\n NonNullable<PortableTextBlockConfig['fields']>[number],\n { type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox' }\n>;\n\nexport type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {\n fields?: BlockKitFieldDef[];\n};\n\nexport const BLOCKS: PortableTextBlockDef[] = [\n {\n /**\n * A link or button, optionally with an icon at either end.\n *\n * Flattened on purpose: Block Kit has no object-group element, so a shape\n * like `{ label, url }` becomes sibling fields. Same constraint the\n * marketing-blocks plugin documents.\n *\n * The icons are `combobox` — a searchable typeahead over a STATIC option\n * list, which is exactly what the semantic name list is. It is the closest\n * fit Block Kit offers: its element union is closed (no custom element, no\n * raw HTML), so `<wc-icon-picker>` cannot be mounted inside a block modal\n * however much nicer it would be. Revisit if EmDash opens that union.\n */\n type: 'vitops.actionLink',\n label: 'Link or button',\n icon: 'link',\n placeholder: 'A link or button, with optional icons',\n fields: [\n { type: 'text_input', action_id: 'label', label: 'Label' },\n { type: 'text_input', action_id: 'href', label: 'URL' },\n {\n type: 'select',\n action_id: 'variant',\n label: 'Style',\n initial_value: 'link',\n options: [\n { label: 'Link', value: 'link' },\n { label: 'Button', value: 'btn' },\n { label: 'Call to action', value: 'cta' },\n ],\n },\n {\n type: 'combobox',\n action_id: 'startIcon',\n label: 'Icon before the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'combobox',\n action_id: 'endIcon',\n label: 'Icon after the label',\n placeholder: 'None',\n options: SEMANTIC_ICON_OPTIONS,\n },\n {\n type: 'select',\n action_id: 'role',\n label: 'Colour role',\n initial_value: '',\n options: [\n { label: 'Default', value: '' },\n { label: 'Success', value: 'success' },\n { label: 'Danger', value: 'danger' },\n { label: 'Warning', value: 'warning' },\n { label: 'Info', value: 'info' },\n ],\n },\n { type: 'toggle', action_id: 'newTab', label: 'Open in a new tab', initial_value: false },\n ],\n },\n {\n type: 'vitops.imageCompare',\n label: 'Image compare',\n placeholder: 'Before/after image slider',\n fields: [\n { type: 'text_input', action_id: 'before', label: 'Before image URL' },\n { type: 'text_input', action_id: 'beforeAlt', label: 'Before alt text' },\n {\n type: 'text_input',\n action_id: 'beforeLabel',\n label: 'Before label',\n initial_value: 'Before',\n },\n { type: 'text_input', action_id: 'after', label: 'After image URL' },\n { type: 'text_input', action_id: 'afterAlt', label: 'After alt text' },\n { type: 'text_input', action_id: 'afterLabel', label: 'After label', initial_value: 'After' },\n ],\n },\n {\n type: 'vitops.copyButton',\n label: 'Copy snippet',\n icon: 'code',\n placeholder: 'Text with a copy-to-clipboard button',\n fields: [\n { type: 'text_input', action_id: 'text', label: 'Text to copy', multiline: true },\n { type: 'text_input', action_id: 'label', label: 'Button label', initial_value: 'Copy' },\n {\n type: 'select',\n action_id: 'display',\n label: 'Display',\n initial_value: 'code-block',\n options: [\n { label: 'Code block', value: 'code-block' },\n { label: 'Inline', value: 'inline' },\n ],\n },\n ],\n },\n {\n type: 'vitops.banner',\n label: 'Banner',\n placeholder: 'Announcement or notice',\n fields: [\n { type: 'text_input', action_id: 'message', label: 'Message', multiline: true },\n {\n type: 'select',\n action_id: 'tone',\n label: 'Tone',\n initial_value: 'info',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Success', value: 'success' },\n { label: 'Warning', value: 'warning' },\n { label: 'Danger', value: 'danger' },\n ],\n },\n { type: 'toggle', action_id: 'dismissible', label: 'Dismissible', initial_value: true },\n ],\n },\n {\n type: 'vitops.details',\n label: 'Disclosure',\n placeholder: 'Collapsible details/summary',\n fields: [\n { type: 'text_input', action_id: 'summary', label: 'Summary (always visible)' },\n { type: 'text_input', action_id: 'body', label: 'Body (revealed)', multiline: true },\n { type: 'toggle', action_id: 'open', label: 'Open by default', initial_value: false },\n ],\n },\n {\n type: 'vitops.carousel',\n label: 'Carousel',\n placeholder: 'Image carousel',\n fields: [\n {\n type: 'text_input',\n action_id: 'images',\n label: 'Image URLs (one per line)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'alts',\n label: 'Alt texts (one per line, same order)',\n multiline: true,\n },\n {\n type: 'text_input',\n action_id: 'label',\n label: 'Accessible name',\n initial_value: 'Carousel',\n },\n {\n type: 'number_input',\n action_id: 'autoplay',\n label: 'Autoplay interval (ms, 0 = off)',\n initial_value: 0,\n },\n {\n type: 'toggle',\n action_id: 'loop',\n label: 'Loop (clones the slides — autoplay implies it)',\n initial_value: false,\n },\n {\n type: 'text_input',\n action_id: 'aspect',\n label: 'Slide aspect ratio',\n initial_value: '16 / 9',\n },\n ],\n },\n];\n","/**\n * Hosting seam — one call in astro.config.mjs resolves the adapter + EmDash\n * database/storage for the chosen hosting target, so a site can start on\n * Cloudflare and move to a Node host (VPS / docker-compose / k8s) — or back —\n * by flipping a single value instead of rewiring its config:\n *\n * ```js\n * import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';\n * const { adapter, database, storage } = await vitopsHosting();\n * export default defineConfig({\n * output: 'server',\n * adapter,\n * integrations: [react(), emdash({ database, storage, plugins: [vitopsEmdash()] })],\n * });\n * ```\n *\n * Targets:\n * - `'cloudflare'` (default) — `@astrojs/cloudflare` + D1/R2 bindings.\n * Needs `@astrojs/cloudflare` and `@emdash-cms/cloudflare` installed.\n * - `'node'` — `@astrojs/node` (standalone) + SQLite file + local uploads.\n * Needs `@astrojs/node` and `better-sqlite3` installed. Scheduled publishing\n * runs in-process on Node (no worker/cron trigger needed). For production,\n * move storage to S3-compatible (`s3()` from `emdash/astro`) and/or the\n * database to Postgres (`postgres()` from `emdash/db`) via `options.node`.\n *\n * The adapter packages are optional peer dependencies, resolved lazily — a\n * site only installs the stack for the target it uses. Content lives in the\n * database and media in the storage backend, so switching hosts is a data\n * migration (D1 export / EmDash seed round-trip + media copy), not a rewrite.\n */\nimport type { AstroIntegration } from 'astro';\n\nexport type HostingTarget = 'cloudflare' | 'node';\n\nconst TARGETS: HostingTarget[] = ['cloudflare', 'node'];\n\nexport interface VitopsHostingOptions {\n /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */\n target?: HostingTarget;\n cloudflare?: {\n /** D1 binding name in wrangler config (default 'DB'). */\n dbBinding?: string;\n /** R2 binding name in wrangler config (default 'MEDIA'). */\n mediaBinding?: string;\n /** D1 Sessions API read-replication mode (default 'auto'). */\n session?: string;\n };\n node?: {\n /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */\n databaseUrl?: string;\n /** Full database descriptor override (e.g. postgres() from 'emdash/db'). */\n database?: unknown;\n /** Local uploads directory (default './data/uploads'). */\n uploadsDir?: string;\n /** Full storage descriptor override (e.g. s3() from 'emdash/astro'). */\n storage?: unknown;\n };\n}\n\nexport interface VitopsHosting {\n target: HostingTarget;\n adapter: AstroIntegration;\n /** EmDash database descriptor — pass to emdash({ database }). */\n database: unknown;\n /** EmDash storage descriptor — pass to emdash({ storage }). */\n storage: unknown;\n}\n\n/**\n * Import an optional hosting dependency. The specifier is passed as a\n * variable so bundlers/type-checkers leave it as a plain runtime import —\n * these packages are optional peers a consumer installs per target.\n */\nasync function importFor(target: HostingTarget, specifier: string, install: string) {\n try {\n return await import(/* @vite-ignore */ specifier);\n } catch (cause) {\n throw new Error(\n `[vitops] hosting target '${target}' needs ${specifier}, which isn't installed.\\n` +\n `Install the ${target} stack first: ${install}`,\n { cause },\n );\n }\n}\n\nexport async function vitopsHosting(options: VitopsHostingOptions = {}): Promise<VitopsHosting> {\n const target = (process.env.HOSTING as HostingTarget) || options.target || 'cloudflare';\n\n if (target === 'cloudflare') {\n const install = 'pnpm add @astrojs/cloudflare @emdash-cms/cloudflare';\n const cf = options.cloudflare ?? {};\n const { default: cloudflare } = await importFor(target, '@astrojs/cloudflare', install);\n const { d1, r2 } = await importFor(target, '@emdash-cms/cloudflare', install);\n return {\n target,\n adapter: cloudflare(),\n database: d1({ binding: cf.dbBinding ?? 'DB', session: cf.session ?? 'auto' }),\n storage: r2({ binding: cf.mediaBinding ?? 'MEDIA' }),\n };\n }\n\n if (target === 'node') {\n const install = 'pnpm add @astrojs/node better-sqlite3';\n const opts = options.node ?? {};\n const { default: node } = await importFor(target, '@astrojs/node', install);\n // Required peer of this package, so always present — but still routed\n // through importFor: a literal import('emdash/db') would be statically\n // analyzable, and bundling the plugin runtime would drag EmDash's whole\n // integration graph into the site's server/client bundles.\n const { sqlite } = await importFor(target, 'emdash/db', install);\n const { local } = await importFor(target, 'emdash/astro', install);\n const { mkdirSync } = await import('node:fs');\n const { dirname } = await import('node:path');\n\n const databaseUrl = opts.databaseUrl ?? process.env.DATABASE_PATH ?? 'file:./data/emdash.db';\n const uploadsDir = opts.uploadsDir ?? './data/uploads';\n // better-sqlite3 refuses to create parent directories, so make sure the\n // defaults exist (runs at astro config load — dev and build). A deployed\n // host still needs the data directory mounted/persisted as a volume.\n if (!opts.database && databaseUrl.startsWith('file:')) {\n mkdirSync(dirname(databaseUrl.slice('file:'.length)), { recursive: true });\n }\n if (!opts.storage) mkdirSync(uploadsDir, { recursive: true });\n\n return {\n target,\n adapter: node({ mode: 'standalone' }),\n database: opts.database ?? sqlite({ url: databaseUrl }),\n storage: opts.storage ?? local({ directory: uploadsDir, baseUrl: '/_emdash/api/media/file' }),\n };\n }\n\n throw new Error(\n `[vitops] unknown hosting target '${target}' (from ${process.env.HOSTING ? 'HOSTING env var' : 'options.target'}). ` +\n `Valid targets: ${TARGETS.join(', ')}.`,\n );\n}\n","","/**\n * @getvitops/emdash — EmDash CMS native plugin for the Vitops design system.\n *\n * Declares Vitops Portable Text block types (editable from the EmDash admin's\n * slash menu) and ships the Astro components that render them via the\n * `componentsEntry` (`@getvitops/emdash/astro`).\n *\n * Composes with — does not replace — the `vitops()` Astro integration from\n * `@getvitops/astro`: the integration owns design-system CSS generation and\n * copies the web-component bundles into `public/<wcBase>/`; this plugin only\n * adds the editor-facing layer. Register both in the consumer site:\n *\n * ```js\n * // astro.config.mjs\n * integrations: [\n * react(),\n * vitops({ css: {...} }),\n * emdash({ database, storage, plugins: [vitopsEmdash()] }),\n * ]\n * ```\n */\nimport { definePlugin } from 'emdash';\nimport type { PluginDescriptor } from 'emdash';\nimport { BLOCKS } from './blocks.ts';\n\nexport { BLOCKS } from './blocks.ts';\nexport type { BlockKitFieldDef, PortableTextBlockDef } from './blocks.ts';\nexport { vitopsHosting } from './hosting.ts';\nexport type { HostingTarget, VitopsHosting, VitopsHostingOptions } from './hosting.ts';\n\n/**\n * Read from package.json rather than hand-maintained.\n *\n * This was a literal with a \"keep in sync\" comment, which drifted on every\n * release: `changeset version` bumps package.json and leaves the literal alone.\n * `blocks.test.ts` asserts they match, but the release chain didn't run tests,\n * so `@getvitops/emdash@0.2.1` shipped a descriptor reporting `0.2.0`. Deriving\n * it removes the failure mode instead of relying on remembering.\n */\nimport pkg from '../package.json' with { type: 'json' };\n\nconst VERSION = pkg.version;\nconst PLUGIN_ID = 'vitops';\nconst FRAGMENTS_CAPABILITY = 'hooks.page-fragments:register';\n\nexport interface VitopsEmdashOptions {\n /**\n * How the web-component runtime scripts reach the page:\n * - `'integration'` (default) — the site's layout renders `<Head />` from\n * `@getvitops/astro`, which emits the script tags; this plugin adds none.\n * - `'fragments'` — for layouts using EmDash's `<EmDashHead/>`/`<EmDashBodyEnd/>`\n * instead: the plugin injects the script tags via the `page:fragments` hook.\n * Either way the bundles themselves must exist under `public/<wcBase>/`,\n * which the `vitops()` integration provides (webComponents: true).\n * Do not enable both delivery paths — the scripts would load twice.\n */\n scripts?: 'integration' | 'fragments';\n /** Public base path of the web-component bundles (default '/vitops'). */\n wcBase?: string;\n}\n\n/**\n * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()\n * integration's `plugins` array.\n */\nexport function vitopsEmdash(options: VitopsEmdashOptions = {}): PluginDescriptor {\n return {\n id: PLUGIN_ID,\n version: VERSION,\n format: 'native',\n entrypoint: '@getvitops/emdash',\n componentsEntry: '@getvitops/emdash/astro',\n options,\n capabilities: options.scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n } as PluginDescriptor;\n}\n\n/** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */\nexport function createPlugin(options: VitopsEmdashOptions = {}) {\n const scripts = options.scripts ?? 'integration';\n const wcBase = options.wcBase ?? '/vitops';\n\n return definePlugin({\n id: PLUGIN_ID,\n version: VERSION,\n capabilities: scripts === 'fragments' ? [FRAGMENTS_CAPABILITY] : [],\n\n hooks:\n scripts === 'fragments'\n ? {\n // Mirrors @getvitops/astro Head.astro: module polyfills + elements\n // in head, deferred enhancements at end of body.\n 'page:fragments': async () => [\n {\n kind: 'html',\n placement: 'head',\n html:\n `<link rel=\"modulepreload\" href=\"${wcBase}/polyfills.js\">` +\n `<script type=\"module\" src=\"${wcBase}/polyfills.js\"></script>` +\n `<script type=\"module\" src=\"${wcBase}/elements.js\"></script>`,\n key: 'vitops-wc',\n },\n {\n kind: 'external-script',\n placement: 'body:end',\n src: `${wcBase}/deferred.js`,\n defer: true,\n key: 'vitops-deferred',\n },\n ],\n }\n : {},\n\n admin: {\n portableTextBlocks: BLOCKS,\n },\n });\n}\n\nexport default createPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiBA,MAAa,wBAA4D,OAAO,KAAK,QAAQ,GAAG,CAAC,CAC9F,KAAK,CAAC,CAIN,KAAK,UAAU;CAAE,OAAO;CAAM,OAAO;AAAK,EAAE;;;;ACG/C,MAAa,SAAiC;CAC5C;;;;;;;;;;;;;;EAcE,MAAM;EACN,OAAO;EACP,MAAM;EACN,aAAa;EACb,QAAQ;GACN;IAAE,MAAM;IAAc,WAAW;IAAS,OAAO;GAAQ;GACzD;IAAE,MAAM;IAAc,WAAW;IAAQ,OAAO;GAAM;GACtD;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;IACf,SAAS;KACP;MAAE,OAAO;MAAQ,OAAO;KAAO;KAC/B;MAAE,OAAO;MAAU,OAAO;KAAM;KAChC;MAAE,OAAO;MAAkB,OAAO;KAAM;IAC1C;GACF;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,aAAa;IACb,SAAS;GACX;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,aAAa;IACb,SAAS;GACX;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;IACf,SAAS;KACP;MAAE,OAAO;MAAW,OAAO;KAAG;KAC9B;MAAE,OAAO;MAAW,OAAO;KAAU;KACrC;MAAE,OAAO;MAAU,OAAO;KAAS;KACnC;MAAE,OAAO;MAAW,OAAO;KAAU;KACrC;MAAE,OAAO;MAAQ,OAAO;KAAO;IACjC;GACF;GACA;IAAE,MAAM;IAAU,WAAW;IAAU,OAAO;IAAqB,eAAe;GAAM;EAC1F;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACb,QAAQ;GACN;IAAE,MAAM;IAAc,WAAW;IAAU,OAAO;GAAmB;GACrE;IAAE,MAAM;IAAc,WAAW;IAAa,OAAO;GAAkB;GACvE;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;GACjB;GACA;IAAE,MAAM;IAAc,WAAW;IAAS,OAAO;GAAkB;GACnE;IAAE,MAAM;IAAc,WAAW;IAAY,OAAO;GAAiB;GACrE;IAAE,MAAM;IAAc,WAAW;IAAc,OAAO;IAAe,eAAe;GAAQ;EAC9F;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,aAAa;EACb,QAAQ;GACN;IAAE,MAAM;IAAc,WAAW;IAAQ,OAAO;IAAgB,WAAW;GAAK;GAChF;IAAE,MAAM;IAAc,WAAW;IAAS,OAAO;IAAgB,eAAe;GAAO;GACvF;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;IACf,SAAS,CACP;KAAE,OAAO;KAAc,OAAO;IAAa,GAC3C;KAAE,OAAO;KAAU,OAAO;IAAS,CACrC;GACF;EACF;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACb,QAAQ;GACN;IAAE,MAAM;IAAc,WAAW;IAAW,OAAO;IAAW,WAAW;GAAK;GAC9E;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;IACf,SAAS;KACP;MAAE,OAAO;MAAQ,OAAO;KAAO;KAC/B;MAAE,OAAO;MAAW,OAAO;KAAU;KACrC;MAAE,OAAO;MAAW,OAAO;KAAU;KACrC;MAAE,OAAO;MAAU,OAAO;KAAS;IACrC;GACF;GACA;IAAE,MAAM;IAAU,WAAW;IAAe,OAAO;IAAe,eAAe;GAAK;EACxF;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACb,QAAQ;GACN;IAAE,MAAM;IAAc,WAAW;IAAW,OAAO;GAA2B;GAC9E;IAAE,MAAM;IAAc,WAAW;IAAQ,OAAO;IAAmB,WAAW;GAAK;GACnF;IAAE,MAAM;IAAU,WAAW;IAAQ,OAAO;IAAmB,eAAe;GAAM;EACtF;CACF;CACA;EACE,MAAM;EACN,OAAO;EACP,aAAa;EACb,QAAQ;GACN;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,WAAW;GACb;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,WAAW;GACb;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;GACjB;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;GACjB;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;GACjB;GACA;IACE,MAAM;IACN,WAAW;IACX,OAAO;IACP,eAAe;GACjB;EACF;CACF;AACF;;;;ACtKA,MAAM,UAA2B,CAAC,cAAc,MAAM;;;;;;AAuCtD,eAAe,UAAU,QAAuB,WAAmB,SAAiB;CAClF,IAAI;EACF,OAAO,MAAM;;GAA0B;;CACzC,SAAS,OAAO;EACd,MAAM,IAAI,MACR,4BAA4B,OAAO,UAAU,UAAU,wCACtC,OAAO,iBAAiB,WACzC,EAAE,MAAM,CACV;CACF;AACF;AAEA,eAAsB,cAAc,UAAgC,CAAC,GAA2B;CAC9F,MAAM,SAAU,QAAQ,IAAI,WAA6B,QAAQ,UAAU;CAE3E,IAAI,WAAW,cAAc;EAC3B,MAAM,UAAU;EAChB,MAAM,KAAK,QAAQ,cAAc,CAAC;EAClC,MAAM,EAAE,SAAS,eAAe,MAAM,UAAU,QAAQ,uBAAuB,OAAO;EACtF,MAAM,EAAE,IAAI,OAAO,MAAM,UAAU,QAAQ,0BAA0B,OAAO;EAC5E,OAAO;GACL;GACA,SAAS,WAAW;GACpB,UAAU,GAAG;IAAE,SAAS,GAAG,aAAa;IAAM,SAAS,GAAG,WAAW;GAAO,CAAC;GAC7E,SAAS,GAAG,EAAE,SAAS,GAAG,gBAAgB,QAAQ,CAAC;EACrD;CACF;CAEA,IAAI,WAAW,QAAQ;EACrB,MAAM,UAAU;EAChB,MAAM,OAAO,QAAQ,QAAQ,CAAC;EAC9B,MAAM,EAAE,SAAS,SAAS,MAAM,UAAU,QAAQ,iBAAiB,OAAO;EAK1E,MAAM,EAAE,WAAW,MAAM,UAAU,QAAQ,aAAa,OAAO;EAC/D,MAAM,EAAE,UAAU,MAAM,UAAU,QAAQ,gBAAgB,OAAO;EACjE,MAAM,EAAE,cAAc,MAAM,OAAO;EACnC,MAAM,EAAE,YAAY,MAAM,OAAO;EAEjC,MAAM,cAAc,KAAK,eAAe,QAAQ,IAAI,iBAAiB;EACrE,MAAM,aAAa,KAAK,cAAc;EAItC,IAAI,CAAC,KAAK,YAAY,YAAY,WAAW,OAAO,GAClD,UAAU,QAAQ,YAAY,MAAM,CAAc,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;EAE3E,IAAI,CAAC,KAAK,SAAS,UAAU,YAAY,EAAE,WAAW,KAAK,CAAC;EAE5D,OAAO;GACL;GACA,SAAS,KAAK,EAAE,MAAM,aAAa,CAAC;GACpC,UAAU,KAAK,YAAY,OAAO,EAAE,KAAK,YAAY,CAAC;GACtD,SAAS,KAAK,WAAW,MAAM;IAAE,WAAW;IAAY,SAAS;GAA0B,CAAC;EAC9F;CACF;CAEA,MAAM,IAAI,MACR,oCAAoC,OAAO,UAAU,QAAQ,IAAI,UAAU,oBAAoB,iBAAiB,oBAC5F,QAAQ,KAAK,IAAI,EAAE,EACzC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AE/FA,MAAM,UAAUA;AAChB,MAAM,YAAY;AAClB,MAAM,uBAAuB;;;;;AAsB7B,SAAgB,aAAa,UAA+B,CAAC,GAAqB;CAChF,OAAO;EACL,IAAI;EACJ,SAAS;EACT,QAAQ;EACR,YAAY;EACZ,iBAAiB;EACjB;EACA,cAAc,QAAQ,YAAY,cAAc,CAAC,oBAAoB,IAAI,CAAC;CAC5E;AACF;;AAGA,SAAgB,aAAa,UAA+B,CAAC,GAAG;CAC9D,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,SAAS,QAAQ,UAAU;CAEjC,OAAO,aAAa;EAClB,IAAI;EACJ,SAAS;EACT,cAAc,YAAY,cAAc,CAAC,oBAAoB,IAAI,CAAC;EAElE,OACE,YAAY,cACR,EAGE,kBAAkB,YAAY,CAC5B;GACE,MAAM;GACN,WAAW;GACX,MACE,mCAAmC,OAAO,4CACZ,OAAO,sDACP,OAAO;GACvC,KAAK;EACP,GACA;GACE,MAAM;GACN,WAAW;GACX,KAAK,GAAG,OAAO;GACf,OAAO;GACP,KAAK;EACP,CACF,EACF,IACA,CAAC;EAEP,OAAO,EACL,oBAAoB,OACtB;CACF,CAAC;AACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/emdash",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "EmDash CMS native plugin for the Vitops design system — Portable Text block types editors can insert from the admin, rendered with the design-system web components and CSS framework.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -22,7 +22,7 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@getvitops/utils": "4.1.0"
25
+ "@getvitops/utils": "6.0.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@astrojs/cloudflare": "^14.0.0",
@@ -30,12 +30,12 @@
30
30
  "astro": "^7.1.2",
31
31
  "emdash": "^0.31.0",
32
32
  "vitest": "^4.1.9",
33
- "@getvitops/astro": "4.1.0"
33
+ "@getvitops/astro": "6.0.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@astrojs/cloudflare": ">=14",
37
37
  "@emdash-cms/cloudflare": ">=0.31.0",
38
- "@getvitops/astro": ">=2.0.0",
38
+ "@getvitops/astro": ">=6.0.0",
39
39
  "astro": ">=7",
40
40
  "emdash": ">=0.31.0"
41
41
  },
@@ -1,4 +1,13 @@
1
1
  ---
2
+ /**
3
+ * Thin adapter over `@getvitops/astro`'s `<Carousel />`.
4
+ *
5
+ * The markup, the image priority matrix and the accessibility wiring all live
6
+ * there — this only maps the block's newline-separated fields onto its `slides`
7
+ * prop. Emitting the markup here too would be a second copy to keep in step, and
8
+ * the one that drifts is always the one nobody is looking at.
9
+ */
10
+ import Carousel from '@getvitops/astro/components/Carousel.astro';
2
11
  import { resolveBlockValue, splitLines } from './value.ts';
3
12
 
4
13
  interface Value {
@@ -6,25 +15,24 @@ interface Value {
6
15
  alts?: string;
7
16
  label?: string;
8
17
  autoplay?: number;
18
+ loop?: boolean;
19
+ aspect?: string;
9
20
  }
10
21
 
11
22
  const value = resolveBlockValue<Value>(Astro.props);
12
23
  const images = splitLines(value.images);
13
24
  const alts = splitLines(value.alts);
14
- const label = value.label || 'Carousel';
15
25
  const autoplay = Number(value.autoplay) || 0;
16
26
  ---
17
27
 
18
- {images.length > 0 && (
19
- <wc-carousel
20
- class="carousel carousel--scroll-buttons carousel--scroll-markers"
21
- role="region"
22
- aria-roledescription="carousel"
23
- aria-label={label}
24
- autoplay={autoplay > 0 ? autoplay : undefined}
25
- >
26
- {images.map((src, i) => (
27
- <div><img src={src} alt={alts[i] ?? ''} loading="lazy" /></div>
28
- ))}
29
- </wc-carousel>
30
- )}
28
+ {
29
+ images.length > 0 && (
30
+ <Carousel
31
+ label={value.label || 'Carousel'}
32
+ autoplay={autoplay > 0 ? autoplay : undefined}
33
+ loop={Boolean(value.loop)}
34
+ aspect={value.aspect || '16 / 9'}
35
+ slides={images.map((src, i) => ({ src, alt: alts[i] ?? '' }))}
36
+ />
37
+ )
38
+ }