@bettercms-ai/kit 2.0.0-next.6 → 2.0.0-next.7
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/dist/adapters/lossy.d.ts +3 -3
- package/dist/adapters/lossy.js +3 -3
- package/dist/adapters/lossy.js.map +1 -1
- package/dist/adapters/prod-types.d.ts +48 -4
- package/dist/adapters/prod-types.js +4 -3
- package/dist/adapters/prod-types.js.map +1 -1
- package/dist/adapters/schema.d.ts +39 -0
- package/dist/adapters/schema.js +177 -32
- package/dist/adapters/schema.js.map +1 -1
- package/dist/controls/icon.d.ts +1 -1
- package/dist/controls/icon.js +1 -1
- package/dist/controls/icon.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/ledgers/exclusions.v1.js +103 -0
- package/dist/ledgers/exclusions.v1.js.map +1 -0
- package/dist/library/coverage.d.ts +13 -1
- package/dist/library/coverage.js +12 -1
- package/dist/library/coverage.js.map +1 -1
- package/dist/library/kit-catalog.json +149 -149
- package/dist/testing/coverage-extras.d.ts +8 -0
- package/dist/testing/coverage-extras.js +44 -0
- package/dist/testing/coverage-extras.js.map +1 -0
- package/dist/testing/exclusions.d.ts +30 -0
- package/dist/testing/exclusions.js +13 -0
- package/dist/testing/exclusions.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prod-types.js","names":[],"sources":["../../src/adapters/prod-types.ts"],"sourcesContent":["/**\n * PROD SHAPES — the production schema language and stored values, typed locally. The caps and key rules the backend\n * and the kit must agree on come from @bettercms-ai/types (W1a, a peer of the kit since W1c): one home per constant.\n *\n * Mirrors the backend at F1 + F1.x-props + link objects + tags / rating / code (72ea2b68d), then reference props\n * (73a120c50), validation parity (dd021ceba), option meta (ab15fe893), V2 chrome keys and types (98e48b9e3, 324580b2e),\n * the V2 icon vocabulary (e126bf33d) and the localized flag (279a10490) — origin/dev 81f385ff3:\n * packages/types/src/content-model.ts, component.ts, link-value.ts, button-look.ts, dock-icon-names.ts, video-value.ts\n * src/lib/validation/schemas/content.ts (baseField, fieldUiSchema, chromeUiProblems, fieldsets, MAX_FIELD_DEPTH)\n * src/lib/validation/schemas/components.ts (componentPropDefSchema, subFieldSchema, propUiSchema, propValidationHardening)\n * src/lib/content/validation-parity.ts, src/lib/content/dock-vocabulary.ts (TAB_MAX, TAB_NAME_MAX)\n * When the backend widens, widen these and the adapters together; the round-trip test then shrinks the lossy ledger.\n */\nimport { DEPTH_MAX, HELP_CAPS, TAB_MAX as SHARED_TAB_MAX, TAB_MAX_RENDERABLE_NOW } from \"@bettercms-ai/types\";\n\n/* ── Limits the backend enforces ─────────────────────────────────────── */\n/** Field levels one schema may nest (a top-level field, or a prop, is level 1). */\nexport const MAX_FIELD_DEPTH = DEPTH_MAX;\n/** Tabs (`kind: \"tab\"` fieldsets) a model screen holds TODAY (it rises to types' TAB_MAX at F12), and a tab name's length. */\nexport const TAB_MAX = TAB_MAX_RENDERABLE_NOW;\nexport const TAB_NAME_MAX = 40;\nexport const FIELDSETS_MAX = 30;\nexport const FIELDSET_NAME_MAX = 60;\nexport const FIELDSET_DESCRIPTION_MAX = HELP_CAPS.fieldset;\nexport const FIELD_HELP_MAX = HELP_CAPS.field;\nexport const PROP_HELP_MAX = HELP_CAPS.prop;\nexport const PROP_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const HELP_MORE_MAX = HELP_CAPS.helpMore;\nexport const LOCK_REASON_MAX = 200;\nexport const ITEM_LABEL_MAX = 24;\nexport const PREVIEW_FALLBACK_MAX = 60;\nexport const PROP_GROUP_LABEL_MAX = 32;\nexport const PATTERN_MAX = 200;\n/** A tags value's tag length (TAG_VALUE_MAX_LENGTH), a code value's length (CODE_MAX_LENGTH), a rating's max with none set. */\nexport const TAG_VALUE_MAX_LENGTH = 64;\nexport const CODE_MAX_LENGTH = 100_000;\nexport const RATING_DEFAULT_MAX = 5;\n/** ratingConfigSchema: config.max a whole 1–10; (max − min) / step whole and at most this many stops. */\nexport const RATING_MAX = 10;\nexport const RATING_MAX_STOPS = 100;\n/** tagsConfigSchema: config.min 0–500, config.max 1–500. */\nexport const TAGS_ITEMS_MAX = 500;\n/** A choice option's description (OPTION_DESCRIPTION_MAX): one line. */\nexport const OPTION_DESCRIPTION_MAX = HELP_CAPS.option;\n/** config.patternMessage (PATTERN_MESSAGE_MAX), config.unit on a number (NUMBER_UNIT_MAX). */\nexport const PATTERN_MESSAGE_MAX = 200;\nexport const NUMBER_UNIT_MAX = 24;\n/** fieldUiSchema: ui.rows 1–40, ui.placeholder 1–120, ui.itemTitle 1–60, ui.synced.from 1–64. */\nexport const UI_ROWS_MAX = 40;\nexport const UI_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const ITEM_TITLE_MAX = 60;\nexport const SYNCED_FROM_MAX = 64;\n/** ui.itemName is an identifier: a letter, then letters, digits or _ (≤ 64). */\nexport const ITEM_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\n/** propUiSchema group: description 1–200, order 0–99. */\nexport const PROP_GROUP_DESCRIPTION_MAX = HELP_CAPS.propGroup;\nexport const PROP_GROUP_ORDER_MAX = 99;\n/** propUiSchema tab: at most 8 distinct tabs per component, an id 1–40, a label ≤ TAB_NAME_MAX trimmed, order 0–99. */\nexport const PROP_TAB_MAX = SHARED_TAB_MAX;\nexport const PROP_TAB_ID_MAX = 40;\n/** numberScaleSchema: token value 1–32, label 1–24, title 1–60, css 1–60; 1–50 steps; at most 20 presets. */\nexport const SCALE_TOKEN_VALUE_MAX = 32;\nexport const SCALE_LABEL_MAX = 24;\nexport const SCALE_TITLE_MAX = 60;\nexport const SCALE_CSS_MAX = 60;\nexport const SCALE_STEPS_MAX = 50;\nexport const SCALE_PRESETS_MAX = 20;\n/** The video block's transcript cap (VIDEO_TRANSCRIPT_MAX). */\nexport const VIDEO_TRANSCRIPT_MAX = 100_000;\n\n/* ── Icons: the closed dock vocabulary (packages/types/src/dock-icon-names.ts at e126bf33d: 472 names, Sanity's set\n * plus every distinct V2 corpus icon but `map`; copied verbatim from that generated file, regenerate from it) ── */\nexport const DOCK_ICON_NAMES = [\n \"abc\", \"access-denied\", \"activity\", \"add\", \"add-circle\", \"add-comment\", \"add-document\", \"add-user\", \"address-book\",\n \"adjustments\", \"adjustments-horizontal\", \"alarm\", \"alert-triangle\", \"align-left\", \"api\", \"app-window\", \"apple\",\n \"apps\", \"archive\", \"arrow-back-up\", \"arrow-down\", \"arrow-guide\", \"arrow-left\", \"arrow-right\", \"arrow-top-right\",\n \"arrow-up\", \"arrows-diagonal\", \"arrows-down\", \"arrows-exchange\", \"arrows-horizontal\", \"arrows-left-right\",\n \"arrows-shuffle\", \"arrows-sort\", \"article\", \"aspect-ratio\", \"asterisk\", \"award\", \"axis-x\", \"backpack\", \"badge\",\n \"badge-cc\", \"badges\", \"bar-chart\", \"basket\", \"bell\", \"bill\", \"binary-document\", \"block-content\", \"block-element\",\n \"blockquote\", \"bold\", \"bolt\", \"book\", \"book-2\", \"bookmark\", \"bookmark-filled\", \"books\", \"bottle\", \"box\",\n \"brand-dribbble\", \"brand-facebook\", \"brand-instagram\", \"brand-linkedin\", \"brand-pinterest\", \"brand-tiktok\",\n \"brand-x\", \"brand-youtube\", \"briefcase\", \"broadcast\", \"browser\", \"bug\", \"building\", \"building-skyscraper\",\n \"building-store\", \"bulb-filled\", \"bulb-outline\", \"calculator\", \"calendar\", \"calendar-dollar\", \"calendar-event\",\n \"calendar-exclamation\", \"calendar-off\", \"calendar-plus\", \"calendar-star\", \"calendar-time\", \"camera\", \"campfire\",\n \"carousel-horizontal\", \"case\", \"cash\", \"category\", \"category-2\", \"chart-area\", \"chart-arrows\", \"chart-bar\",\n \"chart-bar-popular\", \"chart-donut\", \"chart-dots\", \"chart-line\", \"chart-upward\", \"check\", \"checkbox\", \"checklist\",\n \"checkmark\", \"checkmark-circle\", \"chef-hat\", \"chevron-down\", \"chevron-left\", \"chevron-right\", \"chevron-up\",\n \"chevrons-right\", \"circle\", \"circle-check\", \"circle-dot\", \"circle-number-1\", \"circle-plus\", \"click\", \"clipboard\",\n \"clipboard-image\", \"clipboard-text\", \"clock\", \"clock-check\", \"clock-hour-4\", \"close\", \"close-circle\", \"code\",\n \"code-block\", \"cog\", \"collapse\", \"color-wheel\", \"columns\", \"columns-2\", \"columns-3\", \"comment\", \"component\",\n \"compose\", \"compose-sparkles\", \"confetti\", \"controls\", \"cookie\", \"copy\", \"credit-card\", \"crop\", \"cube\",\n \"currency-dollar\", \"dashboard\", \"database\", \"database-search\", \"desktop\", \"device-desktop\", \"device-mobile\",\n \"device-tablet\", \"devices\", \"diamond\", \"discount\", \"document\", \"document-pdf\", \"document-remove\", \"document-sheet\",\n \"document-text\", \"document-video\", \"document-word\", \"document-zip\", \"documents\", \"door-exit\", \"dot\",\n \"double-chevron-down\", \"double-chevron-left\", \"double-chevron-right\", \"double-chevron-up\", \"double-quote\",\n \"download\", \"drag-handle\", \"drop\", \"droplet\", \"droplet-half\", \"earth-americas\", \"earth-globe\", \"edit\", \"egg\",\n \"ellipsis-horizontal\", \"ellipsis-vertical\", \"empty\", \"enter\", \"enter-right\", \"envelope\", \"equal\", \"error-filled\",\n \"error-outline\", \"error-screen\", \"expand\", \"eye-closed\", \"eye-off\", \"eye-open\", \"face-happy\", \"face-indifferent\",\n \"face-sad\", \"feather\", \"feedback\", \"file\", \"file-certificate\", \"file-code\", \"file-download\", \"file-text\", \"filter\",\n \"flag\", \"flame\", \"flask\", \"focus-2\", \"folder\", \"folders\", \"forms\", \"frame\", \"generate\", \"gift\", \"git-branch\",\n \"github\", \"grid-dots\", \"groq\", \"hammer\", \"hand-finger\", \"hanger\", \"hash\", \"headphones\", \"headset\", \"heart\",\n \"heart-filled\", \"heart-handshake\", \"help-circle\", \"highlight\", \"history\", \"home\", \"hourglass\", \"ice-cream\",\n \"id-badge\", \"id-badge-2\", \"image\", \"image-remove\", \"images\", \"inbox\", \"infinity\", \"info-circle\", \"info-filled\",\n \"info-outline\", \"info-square-rounded\", \"inline\", \"inline-element\", \"input-search\", \"insert-above\", \"insert-below\",\n \"italic\", \"joystick\", \"json\", \"language\", \"launch\", \"layout\", \"layout-align-center\", \"layout-board\",\n \"layout-board-split\", \"layout-bottombar\", \"layout-bottombar-expand\", \"layout-cards\", \"layout-columns\",\n \"layout-dashboard\", \"layout-grid\", \"layout-list\", \"layout-navbar\", \"layout-rows\", \"layout-sidebar\",\n \"layout-sidebar-right\", \"leaf\", \"leave\", \"lemon\", \"library-photo\", \"lifebuoy\", \"link\", \"link-removed\", \"linkedin\",\n \"list\", \"list-check\", \"list-details\", \"list-numbers\", \"list-tree\", \"lock\", \"logo-js\", \"logo-ts\", \"mail\", \"map-2\",\n \"map-pin\", \"map-pins\", \"marker\", \"marker-removed\", \"master-detail\", \"math-function\", \"meat\", \"menu\", \"message\",\n \"message-2\", \"message-check\", \"message-circle\", \"message-circle-2\", \"message-plus\", \"message-star\", \"microphone\",\n \"microphone-slash\", \"mobile-device\", \"mood-empty\", \"moon\", \"movie\", \"news\", \"notes\", \"number\", \"ok-hand\", \"olist\",\n \"overage\", \"package\", \"packages\", \"palette\", \"panel-left\", \"panel-right\", \"paperclip\", \"pause\", \"pencil\", \"phone\",\n \"photo\", \"photo-scan\", \"photo-search\", \"pig-money\", \"pin\", \"pin-filled\", \"pin-removed\", \"play\", \"player-play\",\n \"player-track-next\", \"playlist-add\", \"plug\", \"plug-connected\", \"plus-minus\", \"point\", \"presentation\", \"printer\",\n \"progress-50\", \"progress-75\", \"projects\", \"publish\", \"qrcode\", \"quote\", \"read-only\", \"receipt\", \"recycle\", \"redo\",\n \"refresh\", \"remove\", \"remove-circle\", \"repeat\", \"reset\", \"restore\", \"retrieve\", \"retry\", \"revert\", \"robot\",\n \"rocket\", \"rosette\", \"route\", \"ruler\", \"ruler-measure\", \"salad\", \"scale\", \"schema\", \"school\", \"search\", \"section\",\n \"select\", \"send\", \"server\", \"settings\", \"share\", \"shield-check\", \"shield-lock\", \"shopping-bag\", \"shopping-cart\",\n \"shopping-cart-plus\", \"signature\", \"sitemap\", \"slideshow\", \"sort\", \"sort-descending\", \"spacing-vertical\", \"sparkle\",\n \"sparkles\", \"speakerphone\", \"spinner\", \"split-horizontal\", \"split-vertical\", \"square\", \"stack\", \"stack-2\",\n \"stack-compact\", \"stairs\", \"stairs-up\", \"star\", \"star-filled\", \"stars\", \"stop\", \"strikethrough\", \"string\",\n \"subtask\", \"sun\", \"superscript\", \"sync\", \"table\", \"table-options\", \"tablet-device\", \"tag\", \"tags\", \"target\", \"task\",\n \"template\", \"tent\", \"terminal\", \"terminal-2\", \"text\", \"th-large\", \"th-list\", \"thumb-up\", \"thumbs-down\", \"thumbs-up\",\n \"ticket\", \"tiers\", \"timeline\", \"toggle-arrow-right\", \"toggle-right\", \"token\", \"tool\", \"tools-kitchen-2\", \"transfer\",\n \"translate\", \"trash\", \"trend-upward\", \"triangle-outline\", \"trolley\", \"truck\", \"truck-delivery\", \"truncate\",\n \"twitter\", \"typography\", \"ulist\", \"unarchive\", \"underline\", \"undo\", \"unknown\", \"unlink\", \"unlock\", \"unpublish\",\n \"upload\", \"user\", \"user-circle\", \"user-plus\", \"user-search\", \"user-star\", \"users\", \"users-group\", \"versions\",\n \"video\", \"vocabulary\", \"vs\", \"warning-filled\", \"warning-outline\", \"weight\", \"world\", \"wrench\", \"zoom-question\",\n] as const;\nexport type DockIconName = (typeof DOCK_ICON_NAMES)[number];\nexport const DOCK_ICON_NAME_SET: ReadonlySet<string> = new Set<string>(DOCK_ICON_NAMES);\n\n/* ── Content model fields ────────────────────────────────────────────── */\nexport type ProdFieldType =\n | \"text\" | \"richtext\" | \"document\" | \"image\" | \"boolean\" | \"number\" | \"select\"\n | \"reference\" | \"multi-reference\" | \"multiReference\" | \"array\" | \"date\" | \"datetime\"\n | \"longtext\" | \"slug\" | \"email\" | \"phone\" | \"link\" | \"color\" | \"json\" | \"file\"\n | \"component-ref\" | \"modular\" | \"sections\" | \"location\" | \"button\" | \"multiselect\" | \"icon\" | \"object\"\n | \"tags\" | \"rating\" | \"code\"\n /** V2 video: a URL string or `{ url, poster?, autoplay?, loop?, transcript? }` (VideoValue). */\n | \"video\"\n /** Legacy input only: normalized to `array` + zones on write, still read. */\n | \"group\" | \"repeater\";\n\nexport type OpenMode = \"inline\" | \"drill\" | \"popover\" | \"dialog\";\n/** backend SHOW_IF_OPS (validation/schemas/content.ts). */\nexport type ShowIfOp = \"eq\" | \"neq\" | \"in\" | \"notIn\" | \"gt\" | \"lt\" | \"isSet\" | \"notEmpty\" | \"empty\";\nexport interface ShowIfRule {\n field: string;\n op: ShowIfOp;\n value?: string | string[];\n}\nexport interface ShowIf {\n match: \"all\" | \"any\";\n rules: ShowIfRule[];\n}\n\nexport type UiLayout = \"list\" | \"grid\" | \"table\" | \"rows\" | \"chips\" | \"gallery\";\n/** ui.control (UI_CONTROLS): the leaf controls, the V2 control layouts among them. */\nexport const UI_CONTROLS = [\"segmented\", \"slider\", \"rating\", \"dropdown\", \"radio\", \"cards\", \"grid\", \"switch\", \"checkbox\"] as const;\nexport type UiControl = (typeof UI_CONTROLS)[number];\n/** The V2 control layouts and the types each fits (CONTROL_TYPES); segmented / slider / rating have their own rules. */\nexport const CONTROL_TYPES: Readonly<Partial<Record<UiControl, readonly string[]>>> = {\n dropdown: [\"select\"],\n radio: [\"select\"],\n cards: [\"select\", \"multiselect\"],\n grid: [\"select\"],\n switch: [\"boolean\"],\n checkbox: [\"boolean\", \"multiselect\"],\n};\n/** A row preview: child keys or dotted paths (V2), a literal fallback, and an icon shown while there is no media. */\nexport interface UiPreview {\n title?: string;\n subtitle?: string;\n media?: string;\n fallback?: string;\n icon?: DockIconName;\n}\nexport interface FieldUi {\n collapsed?: boolean;\n preview?: UiPreview;\n layout?: UiLayout;\n reorderable?: boolean;\n control?: UiControl;\n icon?: DockIconName;\n itemLabel?: string;\n /** text / longtext: the lines a text box opens with (1–40). */\n rows?: number;\n /** a field's placeholder when its config is strict (a prop keeps its own `placeholder`). */\n placeholder?: string;\n /** a list's item type: its identifier and its title (V2 `of[0].name` / `title`). */\n itemName?: string;\n itemTitle?: string;\n /** the value comes from a global: an edit applies everywhere. */\n synced?: { from: string };\n /** link / image / button: a plain string or the object. */\n valueShape?: \"string\" | \"object\";\n /** V2 field access (D58): the editor gates, the backend stores. */\n access?: { view?: \"schema.view\"; edit?: \"schema.manage\" };\n /** true = never shown in the form; false ≡ absent. */\n hidden?: boolean;\n /** modular: per block slug, how a row of that block type reads (V2 `of[i].icon` / `preview`). */\n blocks?: Record<string, { icon?: DockIconName; preview?: UiPreview }>;\n input?: { name: string; writes?: string[] };\n}\n\nexport type ScaleUnit = \"px\" | \"%\" | \"rem\" | \"em\" | \"vw\" | \"vh\";\nexport interface ScaleCustomValue {\n value: number;\n unit: ScaleUnit;\n}\nexport interface ScaleTokenStep {\n value: string;\n label: string;\n title?: string;\n px?: number;\n css?: string;\n}\nexport type ScaleBound = number | Partial<Record<ScaleUnit, number>>;\n/**\n * A number's `config.scale` (CPO D41): a stepped range, numeric steps or token steps, units, presets, and the custom\n * escape hatch (a boolean, or its bounds). A token scale stores the token string; a custom value `{ value, unit }`.\n */\nexport interface NumberScale {\n steps: { min: number; max: number; step?: number } | Array<{ value: number; label?: string }> | ScaleTokenStep[];\n units?: ScaleUnit[];\n defaultUnit?: ScaleUnit;\n allowCustom?: boolean | { min?: ScaleBound; max?: ScaleBound; units?: ScaleUnit[]; step?: number };\n presets?: Array<{ label?: string; value: number | string | ScaleCustomValue }>;\n}\n\nexport interface ArrayZoneConfig {\n nonRepeatable?: ContentModelField[];\n repeatable?: { fields: ContentModelField[]; minItems?: number; maxItems?: number };\n}\n\n/** One choice: a bare value, or `{ label, value }` with optional chrome (a dock icon, one line ≤ 140 under the label). */\nexport interface ChoiceOption {\n label: string;\n value: string;\n icon?: DockIconName;\n description?: string;\n}\nexport type SelectOption = string | ChoiceOption;\n\n/**\n * A list of scalars' item settings (backend arrayItemSettingsSchema, an `array` prop's `config.item`): the V2 item field's\n * description, locale chip, required, and a text / rich text item's length (a text item's pattern).\n */\nexport interface ArrayItemSettings {\n helpText?: string;\n localized?: boolean;\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n}\n\n/** The typed keys of a field's `config` bag; the bag itself stays loose on scalar types. */\nexport interface FieldConfig {\n /** primitive array: scalars, or images, links or rich text (each item judged by its own type). */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n zones?: ArrayZoneConfig;\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference (props): the non-entry targets, and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n min?: number;\n max?: number;\n step?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n /** text / longtext: the message shown when the pattern does not match (≤ 200, needs `pattern`). */\n patternMessage?: string;\n /** number: the error bounds (V2 validation.min / max); they win over min / max. */\n validation?: { min?: number; max?: number };\n /** number: what it counts (\"%\", \"USD\"), 1–24 characters. */\n unit?: string;\n placeholder?: string;\n includeTime?: boolean;\n /** \"site\", an IANA zone name, true (the editor picks a zone) or false (none). */\n timezone?: string | boolean;\n blockSlugs?: string[];\n minItems?: number;\n maxItems?: number;\n allowedSections?: string[];\n componentIds?: string[];\n /** slot (props): the V2 `of.allow` block names it takes, kept as written (backend slotAllowIssues). */\n allow?: string[];\n /** array (props): the item's own settings (backend arrayItemSettingsSchema). */\n item?: ArrayItemSettings;\n scale?: NumberScale;\n /** multiselect: its choices; with `allowCustom` it is free tags. tags: its suggestions. */\n options?: SelectOption[];\n allowCustom?: boolean;\n /** code: the language, and the languages an editor may pick (strict config, with tags' and rating's). */\n language?: string;\n languages?: string[];\n /**\n * text: \"url\" marks a text field the kit edits as a URL (V2 `url`). The backend keeps a text field's config as sent and\n * enforces its minLength / maxLength / pattern, which a link field would not; it reads no other meaning into the key.\n */\n inputType?: \"url\";\n [key: string]: unknown;\n}\n\nexport interface ContentModelField {\n key: string;\n label: string;\n type: ProdFieldType;\n helpText?: string;\n required?: boolean;\n unique?: boolean;\n defaultValue?: unknown;\n /** select: strings or { label, value }. */\n options?: SelectOption[];\n config?: FieldConfig | null;\n /** object (and legacy group / repeater): child fields. */\n fields?: ContentModelField[];\n openMode?: OpenMode;\n helpMore?: string;\n advanced?: boolean;\n recommendedMax?: number;\n fieldsetId?: string;\n placement?: \"cover\" | \"title\" | \"excerpt\" | \"body\" | \"inline\" | \"panel\";\n showIf?: ShowIf;\n readOnly?: true | \"developer\";\n lockReason?: string;\n /** V2 per-field localization: once any field is marked, a translation owns only its localized values. */\n localized?: boolean;\n ui?: FieldUi;\n searchable?: boolean;\n [key: string]: unknown;\n}\n\nexport type FieldsetKind = \"group\" | \"row\" | \"tab\";\nexport interface Fieldset {\n id: string;\n name: string;\n description?: string;\n kind?: FieldsetKind;\n icon?: DockIconName;\n collapsed?: boolean;\n tab?: string;\n collapsible?: boolean;\n columns?: 1 | 2;\n layout?: \"stack\" | \"breakpoints\";\n}\n\n/** A content model as the adapters read and write it (server-owned ids and timestamps left out). */\nexport interface ProdContentModel {\n name: string;\n slug: string;\n description?: string;\n kind?: \"model\" | \"block\";\n fields: ContentModelField[];\n fieldsets?: Fieldset[];\n}\n\n/* ── Component props ─────────────────────────────────────────────────── */\nexport const COMPONENT_PROP_TYPES = [\n \"text\", \"richtext\", \"image\", \"url\", \"boolean\", \"number\", \"select\", \"group\", \"table\", \"slot\", \"form\",\n \"link\", \"button\", \"date\", \"color\", \"file\", \"icon\", \"multiselect\",\n \"object\", \"datetime\", \"slug\", \"email\", \"phone\", \"json\", \"location\", \"tags\", \"rating\", \"code\",\n /** an entry id (or `{ entryId, modelSlug }`) and a list of them, hydrated on delivery; no definition default. */\n \"reference\", \"multi-reference\",\n /** a video value, a list of `config.itemType` values, multi-line text. */\n \"video\", \"array\", \"longtext\",\n] as const;\nexport type ComponentPropType = (typeof COMPONENT_PROP_TYPES)[number];\nexport type ComponentSubFieldType = Exclude<ComponentPropType, \"slot\" | \"form\">;\n\nexport const COMPONENT_CATEGORIES = [\n \"navbar\", \"footer\", \"button\", \"section\", \"slider\", \"tabs\", \"form\", \"custom\", \"hero\", \"content\", \"social-proof\", \"conversion\",\n] as const;\nexport type ComponentCategory = (typeof COMPONENT_CATEGORIES)[number];\n\nexport interface PropGroup {\n id: string;\n label?: string;\n kind?: \"group\" | \"row\";\n icon?: DockIconName;\n collapsed?: boolean;\n /** V2 fieldset keys, on the defining prop: a line under the header (≤ 200), side by side, a static header, a\n * breakpoint switcher, and its place among the groups (0–99; absent = where its first prop sits). */\n description?: string;\n columns?: 1 | 2;\n collapsible?: boolean;\n layout?: \"stack\" | \"breakpoints\";\n order?: number;\n}\n/**\n * A top-level prop's tab. Defined once: the first top-level prop with an id carries its label / icon / order, later\n * props carry `{ id }` only. A nested prop names one as `{ id }` only (tab_nested refuses a definition there; V2 and\n * the dock ignore it); one ui.group's members share one tab.\n */\nexport interface PropTab {\n id: string;\n label?: string;\n icon?: DockIconName;\n /** its place among the tabs (0–99; absent = where its first prop sits). */\n order?: number;\n}\nexport type PropUi = FieldUi & { group?: PropGroup; tab?: PropTab };\n\nexport interface ComponentSubField {\n key: string;\n label: string;\n type: ComponentSubFieldType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n /** strings or { label, value } (subFieldOptionSchema). */\n options?: SelectOption[];\n helpMore?: string;\n advanced?: boolean;\n recommendedMax?: number;\n /** on a table's sub-field: no two rows share it; on a list: no item repeats. */\n unique?: boolean;\n openMode?: OpenMode;\n showIf?: ShowIf;\n /** the prop's ui, inline group included (V2 fieldsets nest). */\n ui?: PropUi;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n fields?: ComponentSubField[];\n config?: { fields?: ComponentSubField[]; options?: SelectOption[] } & Record<string, unknown>;\n}\n\nexport interface ComponentPropDef {\n key: string;\n label: string;\n /**\n * Where the override writes inside the component's blockJson. Binding, not schema: the V2 language has no twin, so\n * fromTypeSchema leaves it unset and the host binds it (componentize lane) before the save.\n */\n target?: { blockId: string; path: string };\n type: ComponentPropType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n showIf?: ShowIf;\n helpMore?: string;\n advanced?: boolean;\n recommendedMax?: number;\n /** a list prop's items (a table's rows) distinct; refused on a top-level scalar (unique_prop_scope). */\n unique?: boolean;\n openMode?: OpenMode;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n config?: {\n componentIds?: string[];\n /** slot: the V2 block names it takes (`textBlock`), resolved to the project's components by the dock. */\n allow?: string[];\n /** array: the item's own settings. */\n item?: ArrayItemSettings;\n multiple?: boolean;\n minItems?: number;\n maxItems?: number;\n options?: SelectOption[];\n fields?: ComponentSubField[];\n min?: number;\n max?: number;\n step?: number;\n /** number: the error bounds (they win over min / max). */\n validation?: { min?: number; max?: number };\n /** text / url / longtext / richtext: length (richtext: its plain text). text / url / longtext: a pattern and its message. */\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n /** reference / multi-reference: the target model, or polymorphic with an allowlist of model slugs. */\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference: page / form / collection targets (backend REF_TARGET_KINDS) and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n /** array: what the list holds. */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n /** table with mode \"types\" (a polymorphic list): its item types, each row's `_type` naming one. */\n itemTypes?: { name: string; label?: string; fields: ComponentSubField[]; icon?: DockIconName; preview?: UiPreview }[];\n } & Record<string, unknown>;\n ui?: PropUi;\n}\n\n/** A component as the adapters read and write it. */\n/**\n * How ONE instance of a component reads as a row in a parent's slot list (backend `components.preview`, migration 0459;\n * types ComponentPreview): V2's document-level `preview`. `title` / `subtitle` / `media` each name a TOP-LEVEL prop key\n * (the API refuses any other: preview_key_unknown), `fallback` is the literal row label (trimmed, 1..120), `icon` a dock\n * icon shown while the row has no media.\n */\nexport type ComponentPreview = UiPreview;\n/** backend componentPreviewSchema: a preview slot names a key of at most this many characters */\nexport const COMPONENT_PREVIEW_KEY_MAX = 64;\n/** backend componentPreviewSchema: the row's literal fallback label, trimmed, at most this many characters */\nexport const COMPONENT_PREVIEW_FALLBACK_MAX = 120;\n\nexport interface ProdComponent {\n name: string;\n slug: string;\n description?: string;\n category?: ComponentCategory;\n props: ComponentPropDef[];\n preview?: ComponentPreview | null;\n /** The component's dock icon (backend `components.icon`, dockIconSchema): V2's block icon. */\n icon?: DockIconName | null;\n /** The V2 tabs no top-level prop sits in (backend `components.tabs`, componentTabsSchema): declared, shown by none. */\n tabs?: PropTab[] | null;\n}\n\n/* ── Stored values ───────────────────────────────────────────────────── */\nexport const LINK_RELS = [\"nofollow\", \"sponsored\", \"ugc\"] as const;\nexport type LinkRel = (typeof LINK_RELS)[number];\nexport const LINK_VALUE_TYPES = [\"url\", \"page\", \"entry\", \"email\", \"phone\", \"anchor\", \"section\", \"file\"] as const;\nexport type LinkValueType = (typeof LINK_VALUE_TYPES)[number];\n/** The F1 link object (link-value.ts). A bare href string stays valid too. */\nexport interface ProdLinkValue {\n type?: LinkValueType;\n href: string;\n title?: string;\n newTab?: boolean;\n target?: \"_self\" | \"_blank\";\n rel?: LinkRel;\n section?: string;\n /** page / entry links: the target's id. */\n ref?: string;\n}\n\nexport const BUTTON_VARIANTS = [\"primary\", \"secondary\", \"tertiary\", \"brand\", \"dark\", \"light\", \"outline\", \"link\"] as const;\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\nexport const BUTTON_SIZES = [\"sm\", \"md\", \"lg\"] as const;\n/** One button as one value (button-look.ts ButtonValue). */\nexport interface ProdButtonValue {\n label: string;\n href: string | ProdLinkValue;\n variant?: ButtonVariant;\n size?: (typeof BUTTON_SIZES)[number];\n arrow?: boolean;\n icon?: DockIconName | null;\n newTab?: boolean;\n rel?: LinkRel;\n /** Stretch across the column (backend button_full_width_invalid refuses a non-boolean a write brings). */\n fullWidth?: boolean;\n}\n\n/** 0–1 fractions of the image cut from each edge. */\nexport interface ProdImageCrop {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n/** The normalized image value (field-value-normalize.ts): other keys are dropped on write. */\nexport interface ProdImageValue {\n id?: string;\n url: string;\n name?: string;\n altText?: string;\n /** 0–1 fractions. */\n focal?: { x: number; y: number };\n crop?: ProdImageCrop;\n}\n\n/** The V2 video value's object form (video-value.ts): exactly the video block's props. A bare URL string is valid too. */\nexport const VIDEO_VALUE_KEYS = [\"url\", \"poster\", \"autoplay\", \"loop\", \"transcript\"] as const;\nexport interface ProdVideoValue {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n transcript?: string;\n}\n\n/** A polymorphic reference value. */\nexport interface ProdPolymorphicRef {\n entryId: string;\n modelSlug: string;\n}\n/**\n * A page, form or collection pick on a prop reference that also takes entries (backend reference-target-config.ts\n * targetRefOf): a bare string there is read as an entry id (acceptsEntries), so a non-entry pick names its kind.\n */\nexport interface ProdTargetRef {\n kind: \"page\" | \"form\" | \"collection\";\n id: string;\n}\n/** What a `form` prop stores. */\nexport interface ProdFormRef {\n formId: string;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"prod-types.js","names":[],"sources":["../../src/adapters/prod-types.ts"],"sourcesContent":["/**\n * PROD SHAPES — the production schema language and stored values, typed locally. The caps and key rules the backend\n * and the kit must agree on come from @bettercms-ai/types (W1a, a peer of the kit since W1c): one home per constant.\n *\n * Mirrors the backend at F1 + F1.x-props + link objects + tags / rating / code (72ea2b68d), then reference props\n * (73a120c50), validation parity (dd021ceba), option meta (ab15fe893), V2 chrome keys and types (98e48b9e3, 324580b2e),\n * the V2 icon vocabulary (e126bf33d) and the localized flag (279a10490) — origin/dev 81f385ff3:\n * packages/types/src/content-model.ts, component.ts, link-value.ts, button-look.ts, dock-icon-names.ts, video-value.ts\n * src/lib/validation/schemas/content.ts (baseField, fieldUiSchema, chromeUiProblems, fieldsets, MAX_FIELD_DEPTH)\n * src/lib/validation/schemas/components.ts (componentPropDefSchema, subFieldSchema, propUiSchema, propValidationHardening)\n * src/lib/content/validation-parity.ts, src/lib/content/dock-vocabulary.ts (TAB_MAX, TAB_NAME_MAX)\n * When the backend widens, widen these and the adapters together; the round-trip test then shrinks the lossy ledger.\n */\nimport { DEPTH_MAX, HELP_CAPS, TAB_MAX as SHARED_TAB_MAX, type KitCategory, type V2Placement, type V2Shape } from \"@bettercms-ai/types\";\n\n/* ── Limits the backend enforces ─────────────────────────────────────── */\n/** Field levels one schema may nest (a top-level field, or a prop, is level 1). */\nexport const MAX_FIELD_DEPTH = DEPTH_MAX;\n/** Tabs (`kind: \"tab\"` fieldsets) a model screen holds (types' TAB_MAX: no render cap, the dock folds the rest into More), and a tab name's length. */\nexport const TAB_MAX = SHARED_TAB_MAX;\nexport const TAB_NAME_MAX = 40;\nexport const FIELDSETS_MAX = 30;\nexport const FIELDSET_NAME_MAX = 60;\nexport const FIELDSET_DESCRIPTION_MAX = HELP_CAPS.fieldset;\nexport const FIELD_HELP_MAX = HELP_CAPS.field;\nexport const PROP_HELP_MAX = HELP_CAPS.prop;\nexport const PROP_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const HELP_MORE_MAX = HELP_CAPS.helpMore;\nexport const LOCK_REASON_MAX = 200;\nexport const ITEM_LABEL_MAX = 24;\nexport const PREVIEW_FALLBACK_MAX = 60;\nexport const PROP_GROUP_LABEL_MAX = 32;\nexport const PATTERN_MAX = 200;\n/** A tags value's tag length (TAG_VALUE_MAX_LENGTH), a code value's length (CODE_MAX_LENGTH), a rating's max with none set. */\nexport const TAG_VALUE_MAX_LENGTH = 64;\nexport const CODE_MAX_LENGTH = 100_000;\nexport const RATING_DEFAULT_MAX = 5;\n/** ratingConfigSchema: config.max a whole 1–10; (max − min) / step whole and at most this many stops. */\nexport const RATING_MAX = 10;\nexport const RATING_MAX_STOPS = 100;\n/** tagsConfigSchema: config.min 0–500, config.max 1–500. */\nexport const TAGS_ITEMS_MAX = 500;\n/** A choice option's description (OPTION_DESCRIPTION_MAX): one line. */\nexport const OPTION_DESCRIPTION_MAX = HELP_CAPS.option;\n/** config.patternMessage (PATTERN_MESSAGE_MAX), config.unit on a number (NUMBER_UNIT_MAX). */\nexport const PATTERN_MESSAGE_MAX = 200;\nexport const NUMBER_UNIT_MAX = 24;\n/** fieldUiSchema: ui.rows 1–40, ui.placeholder 1–120, ui.itemTitle 1–60, ui.synced.from 1–64. */\nexport const UI_ROWS_MAX = 40;\nexport const UI_PLACEHOLDER_MAX = HELP_CAPS.placeholder;\nexport const ITEM_TITLE_MAX = 60;\nexport const SYNCED_FROM_MAX = 64;\n/** ui.itemName is an identifier: a letter, then letters, digits or _ (≤ 64). */\nexport const ITEM_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;\n/** propUiSchema group: description 1–200, order 0–99. */\nexport const PROP_GROUP_DESCRIPTION_MAX = HELP_CAPS.propGroup;\nexport const PROP_GROUP_ORDER_MAX = 99;\n/** propUiSchema tab: at most 8 distinct tabs per component, an id 1–40, a label ≤ TAB_NAME_MAX trimmed, order 0–99. */\nexport const PROP_TAB_MAX = SHARED_TAB_MAX;\nexport const PROP_TAB_ID_MAX = 40;\n/** numberScaleSchema: token value 1–32, label 1–24, title 1–60, css 1–60; 1–50 steps; at most 20 presets. */\nexport const SCALE_TOKEN_VALUE_MAX = 32;\nexport const SCALE_LABEL_MAX = 24;\nexport const SCALE_TITLE_MAX = 60;\nexport const SCALE_CSS_MAX = 60;\nexport const SCALE_STEPS_MAX = 50;\nexport const SCALE_PRESETS_MAX = 20;\n/** The video block's transcript cap (VIDEO_TRANSCRIPT_MAX). */\nexport const VIDEO_TRANSCRIPT_MAX = 100_000;\n\n/* ── Icons: the closed dock vocabulary (packages/types/src/dock-icon-names.ts at 282655543: 473 names, Sanity's set\n * plus every distinct V2 corpus icon but `map`; copied verbatim from that generated file, regenerate from it) ── */\nexport const DOCK_ICON_NAMES = [\n \"abc\", \"access-denied\", \"activity\", \"add\", \"add-circle\", \"add-comment\", \"add-document\", \"add-user\", \"address-book\",\n \"adjustments\", \"adjustments-horizontal\", \"alarm\", \"alert-triangle\", \"align-left\", \"api\", \"app-window\", \"apple\",\n \"apps\", \"archive\", \"arrow-back-up\", \"arrow-down\", \"arrow-guide\", \"arrow-left\", \"arrow-right\", \"arrow-top-right\",\n \"arrow-up\", \"arrows-diagonal\", \"arrows-down\", \"arrows-exchange\", \"arrows-horizontal\", \"arrows-left-right\",\n \"arrows-shuffle\", \"arrows-sort\", \"article\", \"aspect-ratio\", \"asterisk\", \"award\", \"axis-x\", \"backpack\", \"badge\",\n \"badge-cc\", \"badges\", \"bar-chart\", \"basket\", \"bell\", \"bill\", \"binary-document\", \"block-content\", \"block-element\",\n \"blockquote\", \"bold\", \"bolt\", \"book\", \"book-2\", \"bookmark\", \"bookmark-filled\", \"books\", \"bottle\", \"box\",\n \"brand-dribbble\", \"brand-facebook\", \"brand-instagram\", \"brand-linkedin\", \"brand-pinterest\", \"brand-tiktok\",\n \"brand-x\", \"brand-youtube\", \"briefcase\", \"broadcast\", \"browser\", \"bug\", \"building\", \"building-skyscraper\",\n \"building-store\", \"bulb-filled\", \"bulb-outline\", \"calculator\", \"calendar\", \"calendar-dollar\", \"calendar-event\",\n \"calendar-exclamation\", \"calendar-off\", \"calendar-plus\", \"calendar-star\", \"calendar-time\", \"camera\", \"campfire\",\n \"carousel-horizontal\", \"case\", \"cash\", \"category\", \"category-2\", \"chart-area\", \"chart-arrows\", \"chart-bar\",\n \"chart-bar-popular\", \"chart-donut\", \"chart-dots\", \"chart-line\", \"chart-upward\", \"check\", \"checkbox\", \"checklist\",\n \"checkmark\", \"checkmark-circle\", \"chef-hat\", \"chevron-down\", \"chevron-left\", \"chevron-right\", \"chevron-up\",\n \"chevrons-right\", \"circle\", \"circle-check\", \"circle-dot\", \"circle-number-1\", \"circle-plus\", \"click\", \"clipboard\",\n \"clipboard-image\", \"clipboard-text\", \"clock\", \"clock-check\", \"clock-hour-4\", \"close\", \"close-circle\", \"code\",\n \"code-block\", \"cog\", \"collapse\", \"color-wheel\", \"columns\", \"columns-2\", \"columns-3\", \"comment\", \"component\",\n \"compose\", \"compose-sparkles\", \"confetti\", \"controls\", \"cookie\", \"copy\", \"credit-card\", \"crop\", \"cube\",\n \"currency-dollar\", \"dashboard\", \"database\", \"database-search\", \"desktop\", \"device-desktop\", \"device-mobile\",\n \"device-tablet\", \"devices\", \"diamond\", \"discount\", \"document\", \"document-pdf\", \"document-remove\", \"document-sheet\",\n \"document-text\", \"document-video\", \"document-word\", \"document-zip\", \"documents\", \"door-exit\", \"dot\",\n \"double-chevron-down\", \"double-chevron-left\", \"double-chevron-right\", \"double-chevron-up\", \"double-quote\",\n \"download\", \"drag-handle\", \"drop\", \"droplet\", \"droplet-half\", \"earth-americas\", \"earth-globe\", \"edit\", \"egg\",\n \"ellipsis-horizontal\", \"ellipsis-vertical\", \"empty\", \"enter\", \"enter-right\", \"envelope\", \"equal\", \"error-filled\",\n \"error-outline\", \"error-screen\", \"expand\", \"eye-closed\", \"eye-off\", \"eye-open\", \"face-happy\", \"face-indifferent\",\n \"face-sad\", \"feather\", \"feedback\", \"file\", \"file-certificate\", \"file-code\", \"file-download\", \"file-settings\", \"file-text\", \"filter\",\n \"flag\", \"flame\", \"flask\", \"focus-2\", \"folder\", \"folders\", \"forms\", \"frame\", \"generate\", \"gift\", \"git-branch\",\n \"github\", \"grid-dots\", \"groq\", \"hammer\", \"hand-finger\", \"hanger\", \"hash\", \"headphones\", \"headset\", \"heart\",\n \"heart-filled\", \"heart-handshake\", \"help-circle\", \"highlight\", \"history\", \"home\", \"hourglass\", \"ice-cream\",\n \"id-badge\", \"id-badge-2\", \"image\", \"image-remove\", \"images\", \"inbox\", \"infinity\", \"info-circle\", \"info-filled\",\n \"info-outline\", \"info-square-rounded\", \"inline\", \"inline-element\", \"input-search\", \"insert-above\", \"insert-below\",\n \"italic\", \"joystick\", \"json\", \"language\", \"launch\", \"layout\", \"layout-align-center\", \"layout-board\",\n \"layout-board-split\", \"layout-bottombar\", \"layout-bottombar-expand\", \"layout-cards\", \"layout-columns\",\n \"layout-dashboard\", \"layout-grid\", \"layout-list\", \"layout-navbar\", \"layout-rows\", \"layout-sidebar\",\n \"layout-sidebar-right\", \"leaf\", \"leave\", \"lemon\", \"library-photo\", \"lifebuoy\", \"link\", \"link-removed\", \"linkedin\",\n \"list\", \"list-check\", \"list-details\", \"list-numbers\", \"list-tree\", \"lock\", \"logo-js\", \"logo-ts\", \"mail\", \"map-2\",\n \"map-pin\", \"map-pins\", \"marker\", \"marker-removed\", \"master-detail\", \"math-function\", \"meat\", \"menu\", \"message\",\n \"message-2\", \"message-check\", \"message-circle\", \"message-circle-2\", \"message-plus\", \"message-star\", \"microphone\",\n \"microphone-slash\", \"mobile-device\", \"mood-empty\", \"moon\", \"movie\", \"news\", \"notes\", \"number\", \"ok-hand\", \"olist\",\n \"overage\", \"package\", \"packages\", \"palette\", \"panel-left\", \"panel-right\", \"paperclip\", \"pause\", \"pencil\", \"phone\",\n \"photo\", \"photo-scan\", \"photo-search\", \"pig-money\", \"pin\", \"pin-filled\", \"pin-removed\", \"play\", \"player-play\",\n \"player-track-next\", \"playlist-add\", \"plug\", \"plug-connected\", \"plus-minus\", \"point\", \"presentation\", \"printer\",\n \"progress-50\", \"progress-75\", \"projects\", \"publish\", \"qrcode\", \"quote\", \"read-only\", \"receipt\", \"recycle\", \"redo\",\n \"refresh\", \"remove\", \"remove-circle\", \"repeat\", \"reset\", \"restore\", \"retrieve\", \"retry\", \"revert\", \"robot\",\n \"rocket\", \"rosette\", \"route\", \"ruler\", \"ruler-measure\", \"salad\", \"scale\", \"schema\", \"school\", \"search\", \"section\",\n \"select\", \"send\", \"server\", \"settings\", \"share\", \"shield-check\", \"shield-lock\", \"shopping-bag\", \"shopping-cart\",\n \"shopping-cart-plus\", \"signature\", \"sitemap\", \"slideshow\", \"sort\", \"sort-descending\", \"spacing-vertical\", \"sparkle\",\n \"sparkles\", \"speakerphone\", \"spinner\", \"split-horizontal\", \"split-vertical\", \"square\", \"stack\", \"stack-2\",\n \"stack-compact\", \"stairs\", \"stairs-up\", \"star\", \"star-filled\", \"stars\", \"stop\", \"strikethrough\", \"string\",\n \"subtask\", \"sun\", \"superscript\", \"sync\", \"table\", \"table-options\", \"tablet-device\", \"tag\", \"tags\", \"target\", \"task\",\n \"template\", \"tent\", \"terminal\", \"terminal-2\", \"text\", \"th-large\", \"th-list\", \"thumb-up\", \"thumbs-down\", \"thumbs-up\",\n \"ticket\", \"tiers\", \"timeline\", \"toggle-arrow-right\", \"toggle-right\", \"token\", \"tool\", \"tools-kitchen-2\", \"transfer\",\n \"translate\", \"trash\", \"trend-upward\", \"triangle-outline\", \"trolley\", \"truck\", \"truck-delivery\", \"truncate\",\n \"twitter\", \"typography\", \"ulist\", \"unarchive\", \"underline\", \"undo\", \"unknown\", \"unlink\", \"unlock\", \"unpublish\",\n \"upload\", \"user\", \"user-circle\", \"user-plus\", \"user-search\", \"user-star\", \"users\", \"users-group\", \"versions\",\n \"video\", \"vocabulary\", \"vs\", \"warning-filled\", \"warning-outline\", \"weight\", \"world\", \"wrench\", \"zoom-question\",\n] as const;\nexport type DockIconName = (typeof DOCK_ICON_NAMES)[number];\nexport const DOCK_ICON_NAME_SET: ReadonlySet<string> = new Set<string>(DOCK_ICON_NAMES);\n\n/* ── Content model fields ────────────────────────────────────────────── */\nexport type ProdFieldType =\n | \"text\" | \"richtext\" | \"document\" | \"image\" | \"boolean\" | \"number\" | \"select\"\n | \"reference\" | \"multi-reference\" | \"multiReference\" | \"array\" | \"date\" | \"datetime\"\n | \"longtext\" | \"slug\" | \"email\" | \"phone\" | \"link\" | \"color\" | \"json\" | \"file\"\n | \"component-ref\" | \"modular\" | \"sections\" | \"location\" | \"button\" | \"multiselect\" | \"icon\" | \"object\"\n | \"tags\" | \"rating\" | \"code\"\n /** V2 video: a URL string or `{ url, poster?, autoplay?, loop?, transcript? }` (VideoValue). */\n | \"video\"\n /** Legacy input only: normalized to `array` + zones on write, still read. */\n | \"group\" | \"repeater\";\n\nexport type OpenMode = \"inline\" | \"drill\" | \"popover\" | \"dialog\";\n/** backend SHOW_IF_OPS (validation/schemas/content.ts). */\nexport type ShowIfOp = \"eq\" | \"neq\" | \"in\" | \"notIn\" | \"gt\" | \"lt\" | \"isSet\" | \"notEmpty\" | \"empty\";\nexport interface ShowIfRule {\n field: string;\n op: ShowIfOp;\n value?: string | string[];\n /** past the siblings (A3, showIfScopeSchema): the parent's level, or the document's top level */\n scope?: \"parent\" | \"root\";\n}\nexport interface ShowIf {\n match: \"all\" | \"any\";\n rules: ShowIfRule[];\n}\n\nexport type UiLayout = \"list\" | \"grid\" | \"table\" | \"rows\" | \"chips\" | \"gallery\";\n/** ui.control (UI_CONTROLS): the leaf controls, the V2 control layouts among them. */\nexport const UI_CONTROLS = [\"segmented\", \"slider\", \"rating\", \"dropdown\", \"radio\", \"cards\", \"grid\", \"switch\", \"checkbox\"] as const;\nexport type UiControl = (typeof UI_CONTROLS)[number];\n/** The V2 control layouts and the types each fits (CONTROL_TYPES); segmented / slider / rating have their own rules. */\nexport const CONTROL_TYPES: Readonly<Partial<Record<UiControl, readonly string[]>>> = {\n dropdown: [\"select\"],\n radio: [\"select\"],\n cards: [\"select\", \"multiselect\"],\n grid: [\"select\"],\n switch: [\"boolean\"],\n checkbox: [\"boolean\", \"multiselect\"],\n};\n/** A row preview: child keys or dotted paths (V2), a literal fallback, and an icon shown while there is no media. */\nexport interface UiPreview {\n title?: string;\n subtitle?: string;\n media?: string;\n fallback?: string;\n icon?: DockIconName;\n}\nexport interface FieldUi {\n collapsed?: boolean;\n preview?: UiPreview;\n layout?: UiLayout;\n reorderable?: boolean;\n control?: UiControl;\n icon?: DockIconName;\n itemLabel?: string;\n /** text / longtext: the lines a text box opens with (1–40). */\n rows?: number;\n /** a field's placeholder when its config is strict (a prop keeps its own `placeholder`). */\n placeholder?: string;\n /** a list's item type: its identifier and its title (V2 `of[0].name` / `title`). */\n itemName?: string;\n itemTitle?: string;\n /** the value comes from a global: an edit applies everywhere. */\n synced?: { from: string };\n /** link / image / button: a plain string or the object. */\n valueShape?: \"string\" | \"object\";\n /** V2 field access (D58): the editor gates, the backend stores. */\n access?: { view?: \"schema.view\"; edit?: \"schema.manage\" };\n /** true = never shown in the form; false ≡ absent. */\n hidden?: boolean;\n /** modular: per block slug, how a row of that block type reads (V2 `of[i].icon` / `preview`). */\n blocks?: Record<string, { icon?: DockIconName; preview?: UiPreview }>;\n input?: { name: string; writes?: string[] };\n}\n\nexport type ScaleUnit = \"px\" | \"%\" | \"rem\" | \"em\" | \"vw\" | \"vh\";\nexport interface ScaleCustomValue {\n value: number;\n unit: ScaleUnit;\n}\nexport interface ScaleTokenStep {\n value: string;\n label: string;\n title?: string;\n px?: number;\n css?: string;\n}\nexport type ScaleBound = number | Partial<Record<ScaleUnit, number>>;\n/**\n * A number's `config.scale` (CPO D41): a stepped range, numeric steps or token steps, units, presets, and the custom\n * escape hatch (a boolean, or its bounds). A token scale stores the token string; a custom value `{ value, unit }`.\n */\nexport interface NumberScale {\n steps: { min: number; max: number; step?: number } | Array<{ value: number; label?: string }> | ScaleTokenStep[];\n units?: ScaleUnit[];\n defaultUnit?: ScaleUnit;\n allowCustom?: boolean | { min?: ScaleBound; max?: ScaleBound; units?: ScaleUnit[]; step?: number };\n presets?: Array<{ label?: string; value: number | string | ScaleCustomValue }>;\n}\n\nexport interface ArrayZoneConfig {\n nonRepeatable?: ContentModelField[];\n repeatable?: { fields: ContentModelField[]; minItems?: number; maxItems?: number };\n}\n\n/** One choice: a bare value, or `{ label, value }` with optional chrome (a dock icon, one line ≤ 140 under the label). */\nexport interface ChoiceOption {\n label: string;\n value: string;\n icon?: DockIconName;\n description?: string;\n}\nexport type SelectOption = string | ChoiceOption;\n\n/**\n * A list of scalars' item settings (backend arrayItemSettingsSchema, an `array` prop's `config.item`): the V2 item field's\n * description, locale chip, required, and a text / rich text item's length (a text item's pattern).\n */\nexport interface ArrayItemSettings {\n helpText?: string;\n localized?: boolean;\n required?: boolean;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n}\n\n/** The typed keys of a field's `config` bag; the bag itself stays loose on scalar types. */\nexport interface FieldConfig {\n /** primitive array: scalars, or images, links or rich text (each item judged by its own type). */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n zones?: ArrayZoneConfig;\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference (props): the non-entry targets, and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n min?: number;\n max?: number;\n step?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n /** text / longtext: the message shown when the pattern does not match (≤ 200, needs `pattern`). */\n patternMessage?: string;\n /** number: the error bounds (V2 validation.min / max); they win over min / max. */\n validation?: { min?: number; max?: number };\n /** number: what it counts (\"%\", \"USD\"), 1–24 characters. */\n unit?: string;\n placeholder?: string;\n includeTime?: boolean;\n /** \"site\", an IANA zone name, true (the editor picks a zone) or false (none). */\n timezone?: string | boolean;\n blockSlugs?: string[];\n minItems?: number;\n maxItems?: number;\n allowedSections?: string[];\n componentIds?: string[];\n /** slot (props): the V2 `of.allow` block names it takes, kept as written (backend slotAllowIssues). */\n allow?: string[];\n /** array (props): the item's own settings (backend arrayItemSettingsSchema). */\n item?: ArrayItemSettings;\n scale?: NumberScale;\n /** multiselect: its choices; with `allowCustom` it is free tags. tags: its suggestions. */\n options?: SelectOption[];\n allowCustom?: boolean;\n /** code: the language, and the languages an editor may pick (strict config, with tags' and rating's). */\n language?: string;\n languages?: string[];\n /**\n * text: \"url\" marks a text field the kit edits as a URL (V2 `url`). The backend keeps a text field's config as sent and\n * enforces its minLength / maxLength / pattern, which a link field would not; it reads no other meaning into the key.\n */\n inputType?: \"url\";\n [key: string]: unknown;\n}\n\nexport interface ContentModelField {\n key: string;\n label: string;\n type: ProdFieldType;\n helpText?: string;\n required?: boolean;\n unique?: boolean;\n defaultValue?: unknown;\n /** select: strings or { label, value }. */\n options?: SelectOption[];\n config?: FieldConfig | null;\n /** object (and legacy group / repeater): child fields. */\n fields?: ContentModelField[];\n openMode?: OpenMode;\n helpMore?: string;\n advanced?: boolean;\n /** the AI-assist sparkle (A3: baseField and a prop's `ai`) */\n ai?: boolean;\n recommendedMax?: number;\n fieldsetId?: string;\n placement?: \"cover\" | \"title\" | \"excerpt\" | \"body\" | \"inline\" | \"panel\";\n showIf?: ShowIf;\n readOnly?: true | \"developer\";\n lockReason?: string;\n /** V2 per-field localization: once any field is marked, a translation owns only its localized values. */\n localized?: boolean;\n ui?: FieldUi;\n searchable?: boolean;\n [key: string]: unknown;\n}\n\nexport type FieldsetKind = \"group\" | \"row\" | \"tab\";\nexport interface Fieldset {\n id: string;\n name: string;\n description?: string;\n kind?: FieldsetKind;\n icon?: DockIconName;\n collapsed?: boolean;\n tab?: string;\n collapsible?: boolean;\n columns?: 1 | 2;\n layout?: \"stack\" | \"breakpoints\";\n}\n\n/** A content model as the adapters read and write it (server-owned ids and timestamps left out). */\nexport interface ProdContentModel {\n name: string;\n slug: string;\n description?: string;\n kind?: \"model\" | \"block\";\n /** the collection's dock icon (content_models.icon, migration 0458; modelIconSchema) */\n icon?: DockIconName;\n fields: ContentModelField[];\n fieldsets?: Fieldset[];\n /** how an entry row reads in the list (content_models.list_preview, widened to V2 PreviewConfig by A3) */\n listPreview?: ComponentPreview | null;\n /** content_models.v2 (A3, contentModelV2Schema): what V2 says that has no native column */\n v2?: { shape?: V2Shape; placement?: V2Placement; category?: KitCategory; variants?: Record<string, { icon?: DockIconName; description?: string }> };\n}\n\n/* ── Component props ─────────────────────────────────────────────────── */\nexport const COMPONENT_PROP_TYPES = [\n \"text\", \"richtext\", \"image\", \"url\", \"boolean\", \"number\", \"select\", \"group\", \"table\", \"slot\", \"form\",\n \"link\", \"button\", \"date\", \"color\", \"file\", \"icon\", \"multiselect\",\n \"object\", \"datetime\", \"slug\", \"email\", \"phone\", \"json\", \"location\", \"tags\", \"rating\", \"code\",\n /** an entry id (or `{ entryId, modelSlug }`) and a list of them, hydrated on delivery; no definition default. */\n \"reference\", \"multi-reference\",\n /** a video value, a list of `config.itemType` values, multi-line text. */\n \"video\", \"array\", \"longtext\",\n] as const;\nexport type ComponentPropType = (typeof COMPONENT_PROP_TYPES)[number];\nexport type ComponentSubFieldType = Exclude<ComponentPropType, \"slot\" | \"form\">;\n\nexport const COMPONENT_CATEGORIES = [\n \"navbar\", \"footer\", \"button\", \"section\", \"slider\", \"tabs\", \"form\", \"custom\", \"hero\", \"content\", \"social-proof\", \"conversion\",\n] as const;\nexport type ComponentCategory = (typeof COMPONENT_CATEGORIES)[number];\n\nexport interface PropGroup {\n id: string;\n label?: string;\n kind?: \"group\" | \"row\";\n icon?: DockIconName;\n collapsed?: boolean;\n /** V2 fieldset keys, on the defining prop: a line under the header (≤ 200), side by side, a static header, a\n * breakpoint switcher, and its place among the groups (0–99; absent = where its first prop sits). */\n description?: string;\n columns?: 1 | 2;\n collapsible?: boolean;\n layout?: \"stack\" | \"breakpoints\";\n order?: number;\n}\n/**\n * A top-level prop's tab. Defined once: the first top-level prop with an id carries its label / icon / order, later\n * props carry `{ id }` only. A nested prop names one as `{ id }` only (tab_nested refuses a definition there; V2 and\n * the dock ignore it); one ui.group's members share one tab.\n */\nexport interface PropTab {\n id: string;\n label?: string;\n icon?: DockIconName;\n /** its place among the tabs (0–99; absent = where its first prop sits). */\n order?: number;\n}\nexport type PropUi = FieldUi & { group?: PropGroup; tab?: PropTab };\n\nexport interface ComponentSubField {\n key: string;\n label: string;\n type: ComponentSubFieldType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n /** strings or { label, value } (subFieldOptionSchema). */\n options?: SelectOption[];\n helpMore?: string;\n advanced?: boolean;\n /** the AI-assist sparkle (A3: baseField and a prop's `ai`) */\n ai?: boolean;\n recommendedMax?: number;\n /** on a table's sub-field: no two rows share it; on a list: no item repeats. */\n unique?: boolean;\n openMode?: OpenMode;\n showIf?: ShowIf;\n /** the prop's ui, inline group included (V2 fieldsets nest). */\n ui?: PropUi;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n fields?: ComponentSubField[];\n config?: { fields?: ComponentSubField[]; options?: SelectOption[] } & Record<string, unknown>;\n}\n\nexport interface ComponentPropDef {\n key: string;\n label: string;\n /**\n * Where the override writes inside the component's blockJson. Binding, not schema: the V2 language has no twin, so\n * fromTypeSchema leaves it unset and the host binds it (componentize lane) before the save.\n */\n target?: { blockId: string; path: string };\n type: ComponentPropType;\n defaultValue?: unknown;\n required?: boolean;\n /** null = no help on purpose (V2 has no description); absent = the platform's auto-help. */\n helpText?: string | null;\n placeholder?: string;\n showIf?: ShowIf;\n helpMore?: string;\n advanced?: boolean;\n /** the AI-assist sparkle (A3: baseField and a prop's `ai`) */\n ai?: boolean;\n recommendedMax?: number;\n /** a list prop's items (a table's rows) distinct; refused on a top-level scalar (unique_prop_scope). */\n unique?: boolean;\n openMode?: OpenMode;\n readOnly?: true | \"developer\";\n lockReason?: string;\n localized?: boolean;\n config?: {\n componentIds?: string[];\n /** slot: the V2 block names it takes (`textBlock`), resolved to the project's components by the dock. */\n allow?: string[];\n /** array: the item's own settings. */\n item?: ArrayItemSettings;\n multiple?: boolean;\n minItems?: number;\n maxItems?: number;\n options?: SelectOption[];\n fields?: ComponentSubField[];\n min?: number;\n max?: number;\n step?: number;\n /** number: the error bounds (they win over min / max). */\n validation?: { min?: number; max?: number };\n /** text / url / longtext / richtext: length (richtext: its plain text). text / url / longtext: a pattern and its message. */\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n patternMessage?: string;\n /** reference / multi-reference: the target model, or polymorphic with an allowlist of model slugs. */\n contentModelId?: string;\n polymorphic?: boolean;\n allowedModelSlugs?: string[];\n /** reference / multi-reference: page / form / collection targets (backend REF_TARGET_KINDS) and a filter over the target's fields. */\n targets?: (\"page\" | \"form\" | \"collection\")[];\n filter?: string;\n /** array: what the list holds. */\n itemType?: \"text\" | \"number\" | \"date\" | \"image\" | \"link\" | \"richtext\";\n /** table with mode \"types\" (a polymorphic list): its item types, each row's `_type` naming one. */\n itemTypes?: { name: string; label?: string; fields: ComponentSubField[]; icon?: DockIconName; preview?: UiPreview }[];\n } & Record<string, unknown>;\n ui?: PropUi;\n}\n\n/** A component as the adapters read and write it. */\n/**\n * How ONE instance of a component reads as a row in a parent's slot list (backend `components.preview`, migration 0459;\n * types ComponentPreview): V2's document-level `preview`. `title` / `subtitle` / `media` each name a TOP-LEVEL prop key\n * (the API refuses any other: preview_key_unknown), `fallback` is the literal row label (trimmed, 1..120), `icon` a dock\n * icon shown while the row has no media.\n */\nexport type ComponentPreview = UiPreview;\n/** backend componentPreviewSchema: a preview slot names a key of at most this many characters */\nexport const COMPONENT_PREVIEW_KEY_MAX = 64;\n/** backend componentPreviewSchema: the row's literal fallback label, trimmed, at most this many characters */\nexport const COMPONENT_PREVIEW_FALLBACK_MAX = 120;\n\nexport interface ProdComponent {\n name: string;\n slug: string;\n description?: string;\n /** a library id, or since A3 the kit catalogue's own category verbatim (CATEGORY_ORDER) */\n category?: ComponentCategory | KitCategory;\n props: ComponentPropDef[];\n preview?: ComponentPreview | null;\n /** The component's dock icon (backend `components.icon`, dockIconSchema): V2's block icon. */\n icon?: DockIconName | null;\n /** The V2 tabs no top-level prop sits in (backend `components.tabs`, componentTabsSchema): declared, shown by none. */\n tabs?: PropTab[] | null;\n /** components.v2 (A3, componentV2Schema): category and icon are native columns */\n v2?: { shape?: V2Shape; placement?: V2Placement };\n /** the section family it is a variant of (its label): every member of a V2 block's variant family names the block */\n sectionType?: string;\n}\n\n/* ── Stored values ───────────────────────────────────────────────────── */\nexport const LINK_RELS = [\"nofollow\", \"sponsored\", \"ugc\"] as const;\nexport type LinkRel = (typeof LINK_RELS)[number];\nexport const LINK_VALUE_TYPES = [\"url\", \"page\", \"entry\", \"email\", \"phone\", \"anchor\", \"section\", \"file\"] as const;\nexport type LinkValueType = (typeof LINK_VALUE_TYPES)[number];\n/** The F1 link object (link-value.ts). A bare href string stays valid too. */\nexport interface ProdLinkValue {\n type?: LinkValueType;\n href: string;\n title?: string;\n newTab?: boolean;\n target?: \"_self\" | \"_blank\";\n rel?: LinkRel;\n section?: string;\n /** page / entry links: the target's id. */\n ref?: string;\n}\n\nexport const BUTTON_VARIANTS = [\"primary\", \"secondary\", \"tertiary\", \"brand\", \"dark\", \"light\", \"outline\", \"link\"] as const;\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\nexport const BUTTON_SIZES = [\"sm\", \"md\", \"lg\"] as const;\n/** One button as one value (button-look.ts ButtonValue). */\nexport interface ProdButtonValue {\n label: string;\n href: string | ProdLinkValue;\n variant?: ButtonVariant;\n size?: (typeof BUTTON_SIZES)[number];\n arrow?: boolean;\n icon?: DockIconName | null;\n newTab?: boolean;\n rel?: LinkRel;\n /** Stretch across the column (backend button_full_width_invalid refuses a non-boolean a write brings). */\n fullWidth?: boolean;\n}\n\n/** 0–1 fractions of the image cut from each edge. */\nexport interface ProdImageCrop {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n/** The normalized image value (field-value-normalize.ts): other keys are dropped on write. */\nexport interface ProdImageValue {\n id?: string;\n url: string;\n name?: string;\n altText?: string;\n /** 0–1 fractions. */\n focal?: { x: number; y: number };\n crop?: ProdImageCrop;\n}\n\n/** The V2 video value's object form (video-value.ts): exactly the video block's props. A bare URL string is valid too. */\nexport const VIDEO_VALUE_KEYS = [\"url\", \"poster\", \"autoplay\", \"loop\", \"transcript\"] as const;\nexport interface ProdVideoValue {\n url: string;\n poster?: string;\n autoplay?: boolean;\n loop?: boolean;\n transcript?: string;\n}\n\n/** A polymorphic reference value. */\nexport interface ProdPolymorphicRef {\n entryId: string;\n modelSlug: string;\n}\n/**\n * A page, form or collection pick on a prop reference that also takes entries (backend reference-target-config.ts\n * targetRefOf): a bare string there is read as an entry id (acceptsEntries), so a non-entry pick names its kind.\n */\nexport interface ProdTargetRef {\n kind: \"page\" | \"form\" | \"collection\";\n id: string;\n}\n/** What a `form` prop stores. */\nexport interface ProdFormRef {\n formId: string;\n}\n"],"mappings":"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"}
|
|
@@ -152,3 +152,42 @@ export declare function fromTypeSchema(v2: TypeSchema, target: "props"): FromTyp
|
|
|
152
152
|
export declare function toTypeSchema(model: ProdContentModel, opts?: ToTypeSchemaOptions): TypeSchema;
|
|
153
153
|
/** A prod component's props → TypeSchema (kind "block"); prop groups read back as fieldsets. */
|
|
154
154
|
export declare function propsToTypeSchema(component: ProdComponent, opts?: ToTypeSchemaOptions): TypeSchema;
|
|
155
|
+
/**
|
|
156
|
+
* V2's block variants as prod stores them for components (a kit block made a section): one component per variant, all
|
|
157
|
+
* one family (`sectionType` = the block's title, name = the variant's), and a variant group (component_variant_groups)
|
|
158
|
+
* whose `v2.variants` lists the members in order with their description. Pure: the host creates the members, then the
|
|
159
|
+
* group with the ids it got back (`variantGroupV2`). A block without variants, or with an id no slug can carry, is the
|
|
160
|
+
* one component `fromTypeSchema(v2, "props")` makes, its variants named as catalog.
|
|
161
|
+
*/
|
|
162
|
+
export interface VariantFamilyPlan {
|
|
163
|
+
members: ProdComponent[];
|
|
164
|
+
/** null: no family (one component) */
|
|
165
|
+
group: {
|
|
166
|
+
name: string;
|
|
167
|
+
slug: string;
|
|
168
|
+
variants: {
|
|
169
|
+
member: string;
|
|
170
|
+
description?: string;
|
|
171
|
+
}[];
|
|
172
|
+
} | null;
|
|
173
|
+
lossy: LossyItem[];
|
|
174
|
+
catalog: CatalogItem[];
|
|
175
|
+
}
|
|
176
|
+
export declare function variantFamilyFromTypeSchema(v2: TypeSchema): VariantFamilyPlan;
|
|
177
|
+
/** The group's `v2` (component_variant_groups.v2, variantGroupV2Schema) once the members exist: `idOf` maps a member slug to its id. */
|
|
178
|
+
export declare function variantGroupV2(group: NonNullable<VariantFamilyPlan["group"]>, idOf: (memberSlug: string) => string): {
|
|
179
|
+
variants: {
|
|
180
|
+
description?: string | undefined;
|
|
181
|
+
componentId: string;
|
|
182
|
+
}[];
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* A family read back as one V2 block: the first member's props, the family's name and title, and its variants from the
|
|
186
|
+
* group's members in `v2.variants` order (the id after the group slug, the member's name, the meta's description).
|
|
187
|
+
*/
|
|
188
|
+
export declare function variantFamilyToTypeSchema(members: ProdComponent[], group: {
|
|
189
|
+
slug: string;
|
|
190
|
+
variants: {
|
|
191
|
+
description?: string;
|
|
192
|
+
}[];
|
|
193
|
+
}, opts?: ToTypeSchemaOptions): TypeSchema;
|
package/dist/adapters/schema.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dockIconOf, tablerIconOf } from "./icons.js";
|
|
|
4
4
|
import { slotChildrenFromStored, slotChildrenToStored } from "../controls/slot-services.js";
|
|
5
5
|
import { isDataTableProp, tableConfigFromOptions, tableOptionsFromConfig } from "../controls/table/prod.js";
|
|
6
6
|
import { buttonFromProd, buttonToProd, datetimeFromProd, datetimeToProd, imageFromProd, imageToProd, isCropRect, linkFromProd, linkToProd, listFromProd, listToProd, ratingFromProd, ratingToProd, referenceFromProd, referenceToProd, scaleFromProd, scaleToProd, typedRowsFromProd, typedRowsToProd, videoFromProd, videoToProd, zoneListFromProd, zoneListToProd } from "./values.js";
|
|
7
|
-
import { BLOCK_FIELD_TYPES_RENDERABLE_NOW, KEY_FORMAT, PRIMITIVE_ARRAY_RULE, RICHTEXT_FEATURES, isReservedKey, normaliseRichTextFeatures } from "@bettercms-ai/types";
|
|
7
|
+
import { BLOCK_FIELD_TYPES_RENDERABLE_NOW, KEY_FORMAT, KIT_CATEGORIES, PRIMITIVE_ARRAY_RULE, RICHTEXT_FEATURES, SEGMENTED_MAX, SEGMENTED_MIN, isReservedKey, normaliseRichTextFeatures } from "@bettercms-ai/types";
|
|
8
8
|
//#region src/adapters/schema.ts
|
|
9
9
|
var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
10
10
|
function deepEqual(a, b) {
|
|
@@ -185,6 +185,44 @@ var CATEGORY_TO_PROD = {
|
|
|
185
185
|
};
|
|
186
186
|
/** The field types a kind:'block' model holds today: the ones a block primitive renders (types, staged to G7). */
|
|
187
187
|
var BLOCK_FIELD_TYPES = new Set(BLOCK_FIELD_TYPES_RENDERABLE_NOW);
|
|
188
|
+
/** What a block row's cell may hold, and a primitive list's items (backend block-model-mapping.ts CELL_FIELD_TYPES, LIST_ITEM_TYPES). */
|
|
189
|
+
/** The block-variant select's key on a model (c2ee279dd: a top-level select, only beside v2.variants). */
|
|
190
|
+
var BLOCK_VARIANT_KEY = "_variant";
|
|
191
|
+
var BLOCK_CELL_TYPES = /* @__PURE__ */ new Set([
|
|
192
|
+
"text",
|
|
193
|
+
"longtext",
|
|
194
|
+
"richtext",
|
|
195
|
+
"image",
|
|
196
|
+
"link",
|
|
197
|
+
"slug",
|
|
198
|
+
"email",
|
|
199
|
+
"phone",
|
|
200
|
+
"color",
|
|
201
|
+
"select",
|
|
202
|
+
"number",
|
|
203
|
+
"rating",
|
|
204
|
+
"date",
|
|
205
|
+
"datetime",
|
|
206
|
+
"boolean"
|
|
207
|
+
]);
|
|
208
|
+
var BLOCK_LIST_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
209
|
+
"text",
|
|
210
|
+
"number",
|
|
211
|
+
"date"
|
|
212
|
+
]);
|
|
213
|
+
/**
|
|
214
|
+
* Whether a block model may hold this stored field (backend findUnmappableFields / containerProblem): its type has a
|
|
215
|
+
* primitive, and an object or array has rows of cells (an array no non-repeating fields), or lists text, numbers or dates.
|
|
216
|
+
*/
|
|
217
|
+
function blockRenders(f) {
|
|
218
|
+
if (!BLOCK_FIELD_TYPES.has(f.type)) return false;
|
|
219
|
+
if (f.type !== "object" && f.type !== "array") return true;
|
|
220
|
+
const zones = f.config?.zones;
|
|
221
|
+
if (f.type === "array" && zones?.nonRepeatable?.length) return false;
|
|
222
|
+
const row = f.type === "object" ? f.fields ?? [] : zones ? zones.repeatable?.fields ?? [] : null;
|
|
223
|
+
if (row === null) return BLOCK_LIST_ITEM_TYPES.has(String(f.config?.itemType));
|
|
224
|
+
return row.length > 0 && row.every((c) => BLOCK_CELL_TYPES.has(c.type));
|
|
225
|
+
}
|
|
188
226
|
/** A slot prop's config.minItems / maxItems cap (SLOT_ITEMS_CAP, componentPropDefSchema). */
|
|
189
227
|
var SLOT_ITEMS_CAP = 50;
|
|
190
228
|
/** A table prop's rows and an array prop's items: their config.minItems / maxItems cap (LIST_ITEMS_CAP). */
|
|
@@ -613,7 +651,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
|
|
|
613
651
|
case "hidden":
|
|
614
652
|
if (typeof f.hidden === "boolean") node.ui.hidden = f.hidden;
|
|
615
653
|
else {
|
|
616
|
-
const showIf = conditionToShowIf(f.hidden, p, siblings, ctx,
|
|
654
|
+
const showIf = conditionToShowIf(f.hidden, p, siblings, ctx, top && (props || !!ctx.variants));
|
|
617
655
|
if (showIf) node.showIf = showIf;
|
|
618
656
|
}
|
|
619
657
|
break;
|
|
@@ -646,7 +684,7 @@ function convertField(f, at, lv, ctx, siblings, top, inTable = false, inItem = f
|
|
|
646
684
|
node.advanced = f.advanced;
|
|
647
685
|
break;
|
|
648
686
|
case "ai":
|
|
649
|
-
|
|
687
|
+
node.ai = f.ai;
|
|
650
688
|
break;
|
|
651
689
|
case "reference":
|
|
652
690
|
if (shape.kind === "leaf" && shape.type === "reference") referenceToConfig(f.reference, node, p, ctx);
|
|
@@ -1277,8 +1315,8 @@ function layoutToProd(f, shape, node, at, ctx) {
|
|
|
1277
1315
|
const t = shape.type;
|
|
1278
1316
|
const n = f.options?.length ?? 0;
|
|
1279
1317
|
if (l === "segmented" && t === "select") {
|
|
1280
|
-
if (n >=
|
|
1281
|
-
else if (n
|
|
1318
|
+
if (n >= SEGMENTED_MIN && n <= SEGMENTED_MAX) node.ui.control = "segmented";
|
|
1319
|
+
else if (n > SEGMENTED_MAX) cat(ctx, at, "segmented fallback");
|
|
1282
1320
|
else lose(ctx, at, "layout");
|
|
1283
1321
|
} else if (l === "slider" && t === "number" && f.type === "number") node.ui.control = "slider";
|
|
1284
1322
|
else if (UI_CONTROLS.includes(l) && l !== "segmented" && l !== "slider" && CONTROL_TYPES[l]?.includes(t)) node.ui.control = l;
|
|
@@ -1784,13 +1822,14 @@ function conditionToShowIf(h, at, siblings, ctx, variantOk) {
|
|
|
1784
1822
|
/** One V2 rule as a prod rule, or null when prod cannot hold it (a scope, a non-sibling, a value showIf cannot test). */
|
|
1785
1823
|
function conditionRule(r, head, siblings, variantOk) {
|
|
1786
1824
|
const variant = variantOk && r.field === "_variant";
|
|
1787
|
-
if (!r.field || r.scope
|
|
1825
|
+
if (!r.field || r.scope === void 0 && !variant && !siblings.some((s) => s.name === r.field)) return null;
|
|
1788
1826
|
const allowed = head ? [
|
|
1789
1827
|
"field",
|
|
1790
1828
|
"equals",
|
|
1791
1829
|
"notEmpty",
|
|
1792
1830
|
"op",
|
|
1793
1831
|
"value",
|
|
1832
|
+
"scope",
|
|
1794
1833
|
"also",
|
|
1795
1834
|
"match"
|
|
1796
1835
|
] : [
|
|
@@ -1798,7 +1837,8 @@ function conditionRule(r, head, siblings, variantOk) {
|
|
|
1798
1837
|
"equals",
|
|
1799
1838
|
"notEmpty",
|
|
1800
1839
|
"op",
|
|
1801
|
-
"value"
|
|
1840
|
+
"value",
|
|
1841
|
+
"scope"
|
|
1802
1842
|
];
|
|
1803
1843
|
if (Object.keys(r).some((k) => !allowed.includes(k) && r[k] !== void 0)) return null;
|
|
1804
1844
|
if ([
|
|
@@ -1806,22 +1846,26 @@ function conditionRule(r, head, siblings, variantOk) {
|
|
|
1806
1846
|
r.equals !== void 0,
|
|
1807
1847
|
r.notEmpty !== void 0
|
|
1808
1848
|
].filter(Boolean).length !== 1) return null;
|
|
1849
|
+
const scope = r.scope ? { scope: r.scope } : {};
|
|
1809
1850
|
if (r.notEmpty !== void 0) return r.notEmpty === true ? {
|
|
1810
1851
|
field: r.field,
|
|
1811
|
-
op: "notEmpty"
|
|
1852
|
+
op: "notEmpty",
|
|
1853
|
+
...scope
|
|
1812
1854
|
} : null;
|
|
1813
1855
|
const target = r.op !== void 0 ? r.value : r.equals;
|
|
1814
1856
|
const op = r.op !== void 0 ? SHOW_IF_OP[r.op] : Array.isArray(target) ? "in" : "eq";
|
|
1815
1857
|
if (!op) return null;
|
|
1816
1858
|
if (VALUELESS.has(op)) return r.value === void 0 ? {
|
|
1817
1859
|
field: r.field,
|
|
1818
|
-
op
|
|
1860
|
+
op,
|
|
1861
|
+
...scope
|
|
1819
1862
|
} : null;
|
|
1820
|
-
const sibType = variant ? void 0 : siblings.find((s) => s.name === r.field)?.type;
|
|
1863
|
+
const sibType = variant || r.scope ? void 0 : siblings.find((s) => s.name === r.field)?.type;
|
|
1821
1864
|
if (op === "gt" || op === "lt") return typeof target === "number" && Number.isFinite(target) ? {
|
|
1822
1865
|
field: r.field,
|
|
1823
1866
|
op,
|
|
1824
|
-
value: String(target)
|
|
1867
|
+
value: String(target),
|
|
1868
|
+
...scope
|
|
1825
1869
|
} : null;
|
|
1826
1870
|
const list = Array.isArray(target) ? target : [target];
|
|
1827
1871
|
const strs = [];
|
|
@@ -1838,16 +1882,19 @@ function conditionRule(r, head, siblings, variantOk) {
|
|
|
1838
1882
|
if (op === "in" || op === "notIn") return {
|
|
1839
1883
|
field: r.field,
|
|
1840
1884
|
op,
|
|
1841
|
-
value: strs
|
|
1885
|
+
value: strs,
|
|
1886
|
+
...scope
|
|
1842
1887
|
};
|
|
1843
1888
|
return Array.isArray(target) ? strs.length ? {
|
|
1844
1889
|
field: r.field,
|
|
1845
1890
|
op: op === "eq" ? "in" : "notIn",
|
|
1846
|
-
value: strs
|
|
1891
|
+
value: strs,
|
|
1892
|
+
...scope
|
|
1847
1893
|
} : null : {
|
|
1848
1894
|
field: r.field,
|
|
1849
1895
|
op,
|
|
1850
|
-
value: strs[0]
|
|
1896
|
+
value: strs[0],
|
|
1897
|
+
...scope
|
|
1851
1898
|
};
|
|
1852
1899
|
}
|
|
1853
1900
|
var nonEmpty = (o) => Object.keys(o).length ? o : void 0;
|
|
@@ -1864,6 +1911,7 @@ function common(n) {
|
|
|
1864
1911
|
"showIf",
|
|
1865
1912
|
"helpMore",
|
|
1866
1913
|
"advanced",
|
|
1914
|
+
"ai",
|
|
1867
1915
|
"recommendedMax",
|
|
1868
1916
|
"openMode",
|
|
1869
1917
|
"localized",
|
|
@@ -1957,9 +2005,12 @@ function fromTypeSchema(v2, target) {
|
|
|
1957
2005
|
blockModels: /* @__PURE__ */ new Map()
|
|
1958
2006
|
};
|
|
1959
2007
|
const props = target === "props";
|
|
2008
|
+
if (!props && v2.variants?.length) ctx.variants = true;
|
|
1960
2009
|
const slug = kebab(v2.name);
|
|
1961
|
-
const
|
|
2010
|
+
const kitCategory = KIT_CATEGORIES.includes(v2.category ?? "") ? v2.category : void 0;
|
|
2011
|
+
const category = kitCategory ?? (v2.category !== void 0 ? CATEGORY_TO_PROD[v2.category] : void 0);
|
|
1962
2012
|
const fieldsets = [];
|
|
2013
|
+
const block = {};
|
|
1963
2014
|
for (const k of Object.keys(v2)) {
|
|
1964
2015
|
if (v2[k] === void 0) continue;
|
|
1965
2016
|
const p = `/${k}`;
|
|
@@ -1973,23 +2024,22 @@ function fromTypeSchema(v2, target) {
|
|
|
1973
2024
|
case "fieldsets":
|
|
1974
2025
|
case "groups": break;
|
|
1975
2026
|
case "kind":
|
|
1976
|
-
if (props
|
|
1977
|
-
else if (!props && v2.kind !== "entry") cat(ctx, p, v2.kind === "global" ? "kind:global" : v2.kind === "page" ? "kind:page" : v2.kind === "block" ? "kind:block (fields)" : "kind:object");
|
|
2027
|
+
if (props ? v2.kind !== "block" : v2.kind !== "entry") block.shape = v2.kind;
|
|
1978
2028
|
break;
|
|
1979
2029
|
case "icon":
|
|
1980
|
-
if (!
|
|
1981
|
-
break;
|
|
1982
|
-
case "preview":
|
|
1983
|
-
if (!props) cat(ctx, p, "document preview");
|
|
2030
|
+
if (!dockIconOf(v2.icon)) cat(ctx, p, "block icon");
|
|
1984
2031
|
break;
|
|
2032
|
+
case "preview": break;
|
|
1985
2033
|
case "variants":
|
|
1986
|
-
cat(ctx, p, "variants");
|
|
2034
|
+
if (!ctx.variants) cat(ctx, p, "variants");
|
|
1987
2035
|
break;
|
|
1988
2036
|
case "category":
|
|
1989
|
-
if (
|
|
2037
|
+
if (kitCategory) {
|
|
2038
|
+
if (!props) block.category = kitCategory;
|
|
2039
|
+
} else if (!props || !category || capitalize(category) !== v2.category) cat(ctx, p, "category");
|
|
1990
2040
|
break;
|
|
1991
2041
|
case "placement":
|
|
1992
|
-
|
|
2042
|
+
block.placement = v2.placement;
|
|
1993
2043
|
break;
|
|
1994
2044
|
case "initialValue":
|
|
1995
2045
|
cat(ctx, p, "seeded instance");
|
|
@@ -2010,6 +2060,7 @@ function fromTypeSchema(v2, target) {
|
|
|
2010
2060
|
};
|
|
2011
2061
|
if (v2.description !== void 0) component.description = v2.description;
|
|
2012
2062
|
if (category) component.category = category;
|
|
2063
|
+
if (nonEmpty(block)) component.v2 = { ...block };
|
|
2013
2064
|
const icon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
|
|
2014
2065
|
if (icon) component.icon = icon;
|
|
2015
2066
|
if (v2.preview !== void 0) {
|
|
@@ -2030,7 +2081,30 @@ function fromTypeSchema(v2, target) {
|
|
|
2030
2081
|
fields: nodes.map(emitField)
|
|
2031
2082
|
};
|
|
2032
2083
|
if (v2.description !== void 0) model.description = v2.description;
|
|
2033
|
-
|
|
2084
|
+
const modelIcon = v2.icon === void 0 ? void 0 : dockIconOf(v2.icon);
|
|
2085
|
+
if (modelIcon) model.icon = modelIcon;
|
|
2086
|
+
model.kind = block.shape === "block" && model.fields.every(blockRenders) ? "block" : "model";
|
|
2087
|
+
if (ctx.variants) {
|
|
2088
|
+
const variants = v2.variants;
|
|
2089
|
+
model.fields.unshift({
|
|
2090
|
+
key: BLOCK_VARIANT_KEY,
|
|
2091
|
+
label: "Variant",
|
|
2092
|
+
type: "select",
|
|
2093
|
+
required: false,
|
|
2094
|
+
options: variants.map((v) => ({
|
|
2095
|
+
label: v.name,
|
|
2096
|
+
value: v.id
|
|
2097
|
+
}))
|
|
2098
|
+
});
|
|
2099
|
+
model.v2 = {
|
|
2100
|
+
...block,
|
|
2101
|
+
variants: Object.fromEntries(variants.map((v) => [v.id, v.description !== void 0 ? { description: v.description } : {}]))
|
|
2102
|
+
};
|
|
2103
|
+
} else if (nonEmpty(block)) model.v2 = { ...block };
|
|
2104
|
+
if (v2.preview !== void 0) {
|
|
2105
|
+
const preview = componentPreviewToProd(v2.preview, new Set(model.fields.map((x) => x.key)), v2.fields, "/preview", ctx);
|
|
2106
|
+
if (Object.keys(preview).length) model.listPreview = preview;
|
|
2107
|
+
}
|
|
2034
2108
|
if (fieldsets.length) model.fieldsets = fieldsets;
|
|
2035
2109
|
return {
|
|
2036
2110
|
schema: model,
|
|
@@ -2414,9 +2488,12 @@ function previewFromProd(pv, children) {
|
|
|
2414
2488
|
function showIfToCondition(s, siblings, at, ctx) {
|
|
2415
2489
|
const conds = [];
|
|
2416
2490
|
for (const r of s.rules) {
|
|
2417
|
-
const c = showIfRuleToCondition(r, siblings);
|
|
2491
|
+
const c = showIfRuleToCondition(r, r.scope ? [] : siblings);
|
|
2418
2492
|
if (!c) return void ctx.onLoss(at, "condition", `V2 conditions have no "${r.op}" with this value; the field always shows.`);
|
|
2419
|
-
conds.push(
|
|
2493
|
+
conds.push(r.scope ? {
|
|
2494
|
+
...c,
|
|
2495
|
+
scope: r.scope
|
|
2496
|
+
} : c);
|
|
2420
2497
|
}
|
|
2421
2498
|
if (!conds.length) return void 0;
|
|
2422
2499
|
const [head, ...also] = conds;
|
|
@@ -2530,6 +2607,7 @@ function readToV2(n, at, siblings, ctx, top, lane) {
|
|
|
2530
2607
|
if (n.unique !== void 0) val.unique = n.unique;
|
|
2531
2608
|
if (n.recommendedMax !== void 0) val.recommendedMax = n.recommendedMax;
|
|
2532
2609
|
if (n.advanced !== void 0) f.advanced = n.advanced;
|
|
2610
|
+
if (n.ai !== void 0) f.ai = n.ai;
|
|
2533
2611
|
if (n.openMode !== void 0) f.openMode = n.openMode;
|
|
2534
2612
|
if (n.readOnly === true) f.readOnly = true;
|
|
2535
2613
|
if (n.archived === true) f.archived = true;
|
|
@@ -2972,14 +3050,27 @@ function toTypeSchema(model, opts = {}) {
|
|
|
2972
3050
|
if (s.icon !== void 0) ctx.onLoss(`/fieldsets/${i}/icon`, "fieldset icon", "V2 sections have no icon.");
|
|
2973
3051
|
fieldsets.push(fs);
|
|
2974
3052
|
});
|
|
2975
|
-
const
|
|
3053
|
+
const variantField = model.v2?.variants ? model.fields.find((f) => f.key === BLOCK_VARIANT_KEY && f.type === "select") : void 0;
|
|
3054
|
+
const nodes = model.fields.filter((f) => f !== variantField).map(readField);
|
|
2976
3055
|
const out = {
|
|
2977
3056
|
name: camel(model.slug),
|
|
2978
3057
|
title: model.name,
|
|
2979
|
-
kind: model.kind === "block" ? "block" : "entry",
|
|
3058
|
+
kind: model.kind === "block" ? "block" : model.v2?.shape ?? "entry",
|
|
2980
3059
|
fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "fields"))
|
|
2981
3060
|
};
|
|
3061
|
+
if (model.v2?.placement) out.placement = model.v2.placement;
|
|
3062
|
+
if (variantField) {
|
|
3063
|
+
const meta = model.v2.variants;
|
|
3064
|
+
out.variants = (variantField.options ?? []).map((o) => ({
|
|
3065
|
+
id: o.value,
|
|
3066
|
+
name: o.label,
|
|
3067
|
+
...meta[o.value]?.description !== void 0 ? { description: meta[o.value].description } : {}
|
|
3068
|
+
}));
|
|
3069
|
+
}
|
|
3070
|
+
if (model.listPreview) out.preview = previewFromProd(model.listPreview, nodes);
|
|
3071
|
+
if (model.v2?.category) out.category = model.v2.category;
|
|
2982
3072
|
if (model.description !== void 0) out.description = model.description;
|
|
3073
|
+
if (model.icon) out.icon = tablerIconOf(model.icon);
|
|
2983
3074
|
if (fieldsets.length) out.fieldsets = fieldsets;
|
|
2984
3075
|
if (groups.length) out.groups = groups;
|
|
2985
3076
|
return out;
|
|
@@ -3048,18 +3139,72 @@ function propsToTypeSchema(component, opts = {}) {
|
|
|
3048
3139
|
const out = {
|
|
3049
3140
|
name: camel(component.slug),
|
|
3050
3141
|
title: component.name,
|
|
3051
|
-
kind: "block",
|
|
3142
|
+
kind: component.v2?.shape ?? "block",
|
|
3052
3143
|
fields: nodes.map((n, i) => readToV2(n, `/fields/${i}`, nodes, ctx, true, "props"))
|
|
3053
3144
|
};
|
|
3145
|
+
if (component.v2?.placement) out.placement = component.v2.placement;
|
|
3054
3146
|
if (component.description !== void 0 && component.description !== null) out.description = component.description;
|
|
3055
|
-
if (component.category !== void 0 && component.category !== "custom") out.category = capitalize(component.category);
|
|
3147
|
+
if (component.category !== void 0 && component.category !== "custom") out.category = KIT_CATEGORIES.includes(component.category) ? component.category : capitalize(component.category);
|
|
3056
3148
|
if (component.icon) out.icon = tablerIconOf(component.icon);
|
|
3057
3149
|
if (component.preview) out.preview = previewFromProd(component.preview, nodes);
|
|
3058
3150
|
if (fieldsets.length) out.fieldsets = fieldsets;
|
|
3059
3151
|
if (groups.length) out.groups = groups;
|
|
3060
3152
|
return out;
|
|
3061
3153
|
}
|
|
3154
|
+
/** A V2 variant id a member slug can carry, so the read takes it back verbatim (the component slug rule). */
|
|
3155
|
+
var VARIANT_ID_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
3156
|
+
function variantFamilyFromTypeSchema(v2) {
|
|
3157
|
+
const base = fromTypeSchema(v2, "props");
|
|
3158
|
+
const variants = v2.variants ?? [];
|
|
3159
|
+
if (!variants.length || !variants.every((v) => VARIANT_ID_RE.test(v.id))) return {
|
|
3160
|
+
members: [base.schema],
|
|
3161
|
+
group: null,
|
|
3162
|
+
lossy: base.lossy,
|
|
3163
|
+
catalog: base.catalog
|
|
3164
|
+
};
|
|
3165
|
+
const slug = base.schema.slug;
|
|
3166
|
+
return {
|
|
3167
|
+
members: variants.map((v) => ({
|
|
3168
|
+
...base.schema,
|
|
3169
|
+
name: v.name,
|
|
3170
|
+
slug: `${slug}-${v.id}`,
|
|
3171
|
+
sectionType: v2.title
|
|
3172
|
+
})),
|
|
3173
|
+
group: {
|
|
3174
|
+
name: v2.title,
|
|
3175
|
+
slug,
|
|
3176
|
+
variants: variants.map((v) => ({
|
|
3177
|
+
member: `${slug}-${v.id}`,
|
|
3178
|
+
...v.description !== void 0 ? { description: v.description } : {}
|
|
3179
|
+
}))
|
|
3180
|
+
},
|
|
3181
|
+
lossy: base.lossy,
|
|
3182
|
+
catalog: base.catalog.filter((c) => c.feature !== "variants")
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
/** The group's `v2` (component_variant_groups.v2, variantGroupV2Schema) once the members exist: `idOf` maps a member slug to its id. */
|
|
3186
|
+
function variantGroupV2(group, idOf) {
|
|
3187
|
+
return { variants: group.variants.map((v) => ({
|
|
3188
|
+
componentId: idOf(v.member),
|
|
3189
|
+
...v.description !== void 0 ? { description: v.description } : {}
|
|
3190
|
+
})) };
|
|
3191
|
+
}
|
|
3192
|
+
/**
|
|
3193
|
+
* A family read back as one V2 block: the first member's props, the family's name and title, and its variants from the
|
|
3194
|
+
* group's members in `v2.variants` order (the id after the group slug, the member's name, the meta's description).
|
|
3195
|
+
*/
|
|
3196
|
+
function variantFamilyToTypeSchema(members, group, opts = {}) {
|
|
3197
|
+
const out = propsToTypeSchema(members[0], opts);
|
|
3198
|
+
out.name = camel(group.slug);
|
|
3199
|
+
if (members[0].sectionType) out.title = members[0].sectionType;
|
|
3200
|
+
out.variants = members.map((m, i) => ({
|
|
3201
|
+
id: m.slug.slice(group.slug.length + 1),
|
|
3202
|
+
name: m.name,
|
|
3203
|
+
...group.variants[i]?.description !== void 0 ? { description: group.variants[i].description } : {}
|
|
3204
|
+
}));
|
|
3205
|
+
return out;
|
|
3206
|
+
}
|
|
3062
3207
|
//#endregion
|
|
3063
|
-
export { CATEGORY_TO_PROD, SHOPIFY_REF_LOCK, fromTypeSchema, leafValuePlan, planFromProd, planToProd, propsToTypeSchema, schemaSlugOf, toTypeSchema };
|
|
3208
|
+
export { CATEGORY_TO_PROD, SHOPIFY_REF_LOCK, fromTypeSchema, leafValuePlan, planFromProd, planToProd, propsToTypeSchema, schemaSlugOf, toTypeSchema, variantFamilyFromTypeSchema, variantFamilyToTypeSchema, variantGroupV2 };
|
|
3064
3209
|
|
|
3065
3210
|
//# sourceMappingURL=schema.js.map
|