import { AssetFieldValue, BlockContentBase, BlockContentInputBase, Field, MultilinkFieldValue, PluginFieldValue, RichTextFieldValue, TableFieldValue } from "../overlay/_internal.gen.mjs"; import { Prettify } from "./_utils.mjs"; import { Block, BlockFields } from "./block.mjs"; //#region src/generated/types/field.d.ts /** * @deprecated Use {@link RichTextFieldValue} instead. Will be removed in a future major version. */ type RichtextFieldValue = RichTextFieldValue; /** * Registry of all blocks in the space, used to resolve nested `bloks` fields. * A `Block` union resolves nested content against the registry; `NoBlocks` * (the default) leaves it loose (`BlockContentBase`). */ type NoBlocks = false; /** True when `T` is the un-narrowed base `Block` (i.e. no specific block was supplied). */ type IsBaseBlock = [Block] extends [T] ? true : false; /** * Maps a block's ordered `fields` array to its read content object, splitting * required (`required: true`) from optional fields. Each `F` is a member of the * field union, so it provably satisfies `FieldValue`'s `Field` constraint. */ type ContentFields> = Prettify<{ [F in TFields[number] as F extends { required: true; } ? F["name"] : never]: FieldValue; } & { [F in TFields[number] as F extends { required: true; } ? never : F["name"]]?: FieldValue | null; }>; /** Input (write) variant of {@link ContentFields}, resolving each field via {@link FieldValueInput}. */ type ContentFieldsInput> = Prettify<{ [F in TFields[number] as F extends { required: true; } ? F["name"] : never]: FieldValueInput; } & { [F in TFields[number] as F extends { required: true; } ? never : F["name"]]?: FieldValueInput | null; }>; /** * Content object for a single block instance as returned by the Storyblok * Content Delivery API. Without a `TBlock` argument, this is the loose * runtime shape (any block, `_editable` optional). With a schema-typed * `TBlock`, fields are narrowed per the block's `fields`. */ type BlockContent> = IsBaseBlock extends true ? BlockContentBase : TBlock extends any ? Prettify<{ _uid: string; component: TBlock["name"]; _editable?: string; } & ContentFields> : never; /** Input variant of {@link BlockContent} for write operations (creating/updating stories via the MAPI). `_uid` is optional. */ type BlockContentInput> = IsBaseBlock extends true ? BlockContentInputBase : TBlock extends any ? Prettify<{ _uid?: string; component: TBlock["name"]; _editable?: string; } & ContentFieldsInput> : never; type BlocksFieldValue> = BlockContent[]; interface FieldTypeValueMap { text: string; textarea: string; richtext: RichTextFieldValue; markdown: string; /** * Stored and delivered as a string, not a JSON number. The Management API * rejects numeric values ("must be a string with numbers and allow '-' and * '.'"), the editor writes `String(value)`, and `default_value` is a string * for the same reason. An unset field is `''`. */ number: string; datetime: string; boolean: boolean; option: string; options: string[]; asset: AssetFieldValue; multiasset: AssetFieldValue[]; /** * The legacy `image`/`file` types predate the asset object: they store the URL * as a plain string, protocol-relative (`//a.storyblok.com/…`) unless the field * sets `add_https`, which makes it absolute. */ image: string; file: string; multilink: MultilinkFieldValue; /** * Legacy predecessor of `multilink`, storing a bare URL string rather than a * link object. Read-only in practice: the editor no longer writes it. */ link: string; bloks: BlockContentBase[]; table: TableFieldValue; section: never; tab: never; group: never; /** * Owned by a commerce integration rather than the editor, and exempt from * server-side content type-checking, so the integration is the only thing that * knows the shape. Narrow it where you know which one wrote the field. */ commerce: unknown; custom: PluginFieldValue; } type IsNestable = T extends { is_nestable: false; } ? false : T extends { is_nestable: true; } ? true : true; type AllowEntry = string | { folder: string; } | { tag: string; }; /** * Keeps `TBlock` when its `folder` is `TFolder` or any nested subfolder (mirrors * the editor's `isAnywhereInFolder`). This is a best-effort compile-time check, * compared case-insensitively via `Lowercase` so `folder: 'blog'` and a `Blog` * folder ref narrow the same. TypeScript cannot replicate the CLI's full slug at * the type level, so separator/symbol drift (`'My Layout'` vs `'my-layout'`) is * only reconciled at push/validate time — full folder identity is enforced * there, not here. To rely on narrowing, prefer a `defineFolder` ref over a * string path on both the block's `folder` and the field's `allow`: a shared ref * carries the exact path on both sides, so no drift is possible. */ type MatchesFolder = TBlock extends { folder: infer BF extends string; } ? Lowercase extends Lowercase | `${Lowercase}/${string}` ? TBlock : never : never; /** * Keeps `TBlock` when it declares any tag in `TTag` among its `tags`, the * tag-dimension counterpart to {@link MatchesFolder}. Compared exactly, case * included: a tag name is resolved verbatim against the target space at push * time, so a differing case is a different tag, not the same one spelled * another way. Folder paths are compared case-insensitively because both sides * are slugified; tags have no such reconciliation. * * `TTag` may be a union, which is why the two tag sets are intersected rather * than tested with `extends`: a union on the left of `extends` satisfies no * single block's tag, which would make a multi-tag `deny` remove nothing. * * A block that declares no `tags` never matches. Its remote tags may say * otherwise — tags can be applied in the Storyblok UI to a block whose schema * does not name them — so this narrows by what the schema declares, the same * best-effort reading `folder` gets. */ type MatchesTag = TBlock extends { tags: ReadonlyArray; } ? [Extract] extends [never] ? never : TBlock : never; type ApplyAllow = TField extends { allow: ReadonlyArray; } ? TAllowed extends string ? Extract : TAllowed extends { folder: infer F extends string; } ? TBlocks extends any ? MatchesFolder : never : TAllowed extends { tag: infer T extends string; } ? TBlocks extends any ? MatchesTag : never : never : TBlocks extends any ? IsNestable extends true ? TBlocks : never : never; /** * The folder paths named by the `{ folder: path }` entries of an `allow`/`deny` union. * * Written as an indexed access rather than a conditional with `infer`: when the * union names no folder at all, `Extract` yields `never`, and `never extends * { folder: infer F extends string }` takes the *true* branch with no inference * candidate for `F`, which then falls back to its `string` constraint. That turned * a block-name-only `deny` into "deny every folder". */ type FolderOf = Extract["folder"]; /** * The tag names given by the `{ tag: name }` entries of an `allow`/`deny` union. * Written as an indexed access for the same reason as {@link FolderOf}. */ type TagOf = Extract["tag"]; /** * Removes the registry blocks in `TFolders` (or any nested folder), the * {@link MatchesFolder} counterpart for `deny`. A `never` folder set removes * nothing, so a block-name-only `deny` leaves the union untouched. */ type DenyFolders = [TFolders] extends [never] ? TBlocks : TBlocks extends any ? [MatchesFolder] extends [never] ? TBlocks : never : never; /** * Removes the registry blocks declaring one of `TTags`, the {@link MatchesTag} * counterpart for `deny`. A `never` tag set removes nothing, so a `deny` naming * no tag leaves the union untouched. */ type DenyTags = [TTags] extends [never] ? TBlocks : TBlocks extends any ? [MatchesTag] extends [never] ? TBlocks : never : never; /** * Removes the registry blocks named in `deny`, or living in a folder it names, * the `Exclude` counterpart to {@link ApplyAllow}. A `deny` entry naming no known * block or folder is inert: it removes nothing rather than collapsing the field. * * `TDenied` is split with `Extract` rather than a `TDenied extends string` * conditional, because that conditional would distribute over the deny union and * union the per-entry `Exclude` results back together — re-admitting every denied * block. Names, folders, and tags are therefore removed in one pass each. */ type ApplyDeny = TField extends { deny: ReadonlyArray; } ? DenyTags; }>, FolderOf>, TagOf> : TBlocks; /** * Resolves the block union a `bloks` field accepts: `allow` narrows the registry * first, then `deny` removes from what is left. * * The editor gives a non-empty `allow` precedence and ignores a `deny` beside it, * so composing the two only matters for a field carrying both — which * `defineField` rejects, precisely because the editor would ignore half of it. The * composition is kept for the fields that reach these types without going through * that guard: one written as a plain object, or one read off the wire. On those, * the stricter of the two readings is the safer default. */ type ApplyRestrictions = ApplyDeny>; /** * Resolves a `custom` field to its registered plugin value. When the field's * `field_type` is a key of `TFieldPlugins`, the validator output is merged with * the plugin envelope (`plugin`, optional `_uid`); otherwise it falls back to * the untyped {@link PluginFieldValue}. Same shape for read and write. */ type ResolveCustom = TField extends { field_type: infer F extends string; } ? F extends keyof TFieldPlugins ? Prettify : PluginFieldValue : PluginFieldValue; /** Whether a field declares a block restriction at all. */ type HasRestriction = TField extends { allow: readonly unknown[]; } ? true : TField extends { deny: readonly unknown[]; } ? true : false; /** * Substitutes `TBody` for the embedded-blok bodies of one richtext node, * recursing through the nodes that nest other nodes. * * The generated node union is self-referential, so this walks it structurally * rather than listing the node types: the `blok` arm is replaced, anything with a * `content` array is rebuilt around the same substitution, and every other node * (text, marks, images) passes through untouched. */ type RestrictRichTextNode = TNode extends { type: "blok"; attrs: infer TAttrs; } ? Omit & { attrs: Omit & { body: TBody[] | null; }; } : TNode extends { content?: infer TChildren; } ? TChildren extends readonly (infer TChild)[] ? Omit & { content?: RestrictRichTextNode[]; } : TNode : TNode; /** * The richtext document type a restricted richtext field accepts: every embedded * blok body, at any nesting depth, narrowed to `TBody`. * * Only applied to a field that declares `allow`/`deny`. An unrestricted richtext * field keeps the loose document type, because narrowing it would newly reject * every component that is not in the block registry — a much wider change than * reflecting a restriction the field actually declares. */ type RestrictRichText = TDoc extends { content: readonly (infer TNode)[]; } ? Omit & { content: RestrictRichTextNode[]; } : TDoc; /** `source` values whose options live in the space instead of the schema. */ type RemoteOptionSource = "internal" | "internal_stories" | "internal_languages" | "external"; /** * Resolves an `option`/`options` field to the union of its `options` values. * A remote `source` (datasource, stories, languages, external URL) keeps the * values in the space, so they stay `string` at compile time. Any other * `source` (`''`, `'self'`, or none) reads the field's own `options`. A * `source` typed as plain `string` could be either, so it stays `string` too. * * A single-select `option` field additionally delivers `''` for an unset value * and whenever the editor clears a selection; `exclude_empty_option` only hides * the empty entry in the editor's dropdown. A multi-select `options` field * delivers an empty array instead, so it never carries `''`. */ type ResolveOptionValue = TField extends { source: infer TSource; } ? [Extract] extends [never] ? string extends TSource ? string : ResolveOwnOptionValue : string : ResolveOwnOptionValue; type ResolveOwnOptionValue = TField extends { options: ReadonlyArray<{ value: infer TValue extends string; }>; } ? string extends TValue ? string : TValue : string; /** Resolves a field definition to its runtime content value type (read). */ type FieldValue> = Prettify, TBlocks, TFieldPlugins>[] : BlockContentBase[] : TField extends { type: "richtext"; } ? [TBlocks] extends [never] ? RichTextFieldValue : [TBlocks] extends [Block] ? HasRestriction extends true ? RestrictRichText, TBlocks, TFieldPlugins>> : RichTextFieldValue : RichTextFieldValue : TField extends { type: "custom"; } ? ResolveCustom : TField extends { type: "option"; } ? ResolveOptionValue | "" : TField extends { type: "options"; } ? ResolveOptionValue[] : FieldTypeValueMap[TField["type"]]>; /** Resolves a field definition to its input value type (write). */ type FieldValueInput> = Prettify, TBlocks, TFieldPlugins>[] : BlockContentInputBase[] : TField extends { type: "richtext"; } ? [TBlocks] extends [never] ? RichTextFieldValue : [TBlocks] extends [Block] ? HasRestriction extends true ? RestrictRichText, TBlocks, TFieldPlugins>> : RichTextFieldValue : RichTextFieldValue : TField extends { type: "custom"; } ? ResolveCustom : TField extends { type: "option"; } ? ResolveOptionValue | "" : TField extends { type: "options"; } ? ResolveOptionValue[] : FieldTypeValueMap[TField["type"]]>; //#endregion export { ApplyRestrictions, type AssetFieldValue, BlockContent, BlockContentInput, BlocksFieldValue, type Field, FieldValue, FieldValueInput, HasRestriction, type MultilinkFieldValue, type PluginFieldValue, RestrictRichText, type RichTextFieldValue, RichtextFieldValue, type TableFieldValue }; //# sourceMappingURL=field.d.mts.map