@shapio/editor-sdk 0.5.3 → 0.5.4
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/index.d.ts +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/types.ts","../src/defineEditor.ts"],"sourcesContent":["import type { DataType, DataTypeSettings, JsonValue, MEDIA_KINDS } from '@shapio/schema';\nimport type { ComponentType } from 'react';\n\n/**\n * The contract between the Shapio admin and a field editor (brief §6, ADR 0009). Built-in editors and\n * custom editors loaded at runtime from a project's `extensions/editors/*.js` receive exactly these props.\n *\n * An editor gets the field's value and metadata plus a small, explicit context. It never gets a network\n * client, a session, tokens or secrets: everything it can do goes through `onChange` and the context\n * functions. The server validates the value the same way whatever editor produced it.\n *\n * Bump EDITOR_CONTRACT_VERSION on any breaking change. The admin refuses editors built for another\n * version and falls back to the field's built-in editor.\n */\nexport const EDITOR_CONTRACT_VERSION = 1;\n\n/** The rich-text envelope stored for `richtext` fields (ADR 0003). `doc` is ProseMirror JSON. */\nexport type RichTextValue = {\n format: 'shapio-richtext';\n version: number;\n doc: { type: 'doc'; content?: JsonValue[] };\n};\n\n/** A component value: its fields keyed by API key. */\nexport type ComponentValue = { [apiKey: string]: unknown };\n\n/** A dynamic-zone item: a component value plus the component's API key in `__component`. */\nexport type DynamicZoneItem = ComponentValue & { __component: string };\n\n/**\n * The value type of each data type, as the admin API reads and writes it. Dates and times are canonical\n * UTC ISO-8601 text; decimals and big integers are strings so no precision is lost; relations and media\n * are IDs (lists of IDs when the field holds several).\n */\nexport type EditorValueMap = {\n string: string;\n text: string;\n richtext: RichTextValue;\n number: number;\n integer: number;\n decimal: string;\n biginteger: string;\n boolean: boolean;\n date: string;\n datetime: string;\n time: string;\n enum: string | string[];\n json: JsonValue;\n slug: string;\n email: string;\n url: string;\n uid: string;\n media: string | string[];\n relation: string | string[];\n component: ComponentValue | ComponentValue[];\n dynamiczone: DynamicZoneItem[];\n};\n\nexport type EditorValue<T extends DataType = DataType> = EditorValueMap[T];\n\n/** The field being edited, from the active model definition. */\nexport type EditorField<T extends DataType = DataType> = {\n /** Stable field ID (never changes when the field is renamed). */\n id: string;\n apiKey: string;\n label: string;\n description: string | undefined;\n type: T;\n required: boolean;\n /** True when the value is per locale; false when it is shared by every locale of the entry. */\n localized: boolean;\n /** Type settings: validation rules (min, max, pattern...), enum values, relation target... */\n settings: DataTypeSettings[T];\n /** This editor's options, as configured on the field in the model builder. */\n options: Readonly<Record<string, JsonValue>>;\n};\n\n/** The model (or component) the field belongs to. */\nexport type EditorModel = {\n id: string;\n apiKey: string;\n label: string;\n kind: 'collection' | 'singleton' | 'component';\n localized: boolean;\n};\n\nexport type MediaKind = (typeof MEDIA_KINDS)[number];\n\nexport type MediaPickOptions = {\n multiple?: boolean;\n /** Restrict the picker to these kinds of files. */\n allowedKinds?: readonly MediaKind[];\n};\n\n/** What the media picker hands back: enough to preview and reference an asset, never storage details. */\nexport type PickedMedia = {\n id: string;\n filename: string;\n mimeType: string;\n /** For previews in the admin only. Private assets have expiring URLs; store the `id`, never the URL. */\n url: string;\n alt: string;\n width: number | null;\n height: number | null;\n};\n\n/** The limited capabilities an editor may use. */\nexport type EditorContext = {\n /** The content locale being edited, or null when the model is not localized. */\n locale: string | null;\n /** The entry being edited, or null for an entry that is not created yet. */\n entryId: string | null;\n /** The admin's interface language (e.g. `en`), for editors that ship their own strings. */\n uiLanguage: string;\n /** Opens the media library; resolves with the chosen assets, or null when the person cancels. */\n pickMedia: (options?: MediaPickOptions) => Promise<PickedMedia[] | null>;\n /** Translates one of the admin's own keys (e.g. `common.remove`); unknown keys return `fallback`. */\n translate: (key: string, fallback: string, values?: Readonly<Record<string, string | number>>) => string;\n};\n\nexport type FieldValidationState = {\n /** Server and client validation messages for this value, already translated. */\n errors: readonly string[];\n invalid: boolean;\n};\n\nexport type FieldEditorProps<T extends DataType = DataType> = {\n /** The DOM id the field's label points at: put it on the editor's main focusable element. */\n inputId: string;\n /** The DOM id of the field's visible label: use `aria-labelledby` on group controls (radio groups...). */\n labelId: string;\n /** Ids of the description and error elements: pass to `aria-describedby`. */\n describedBy: string | undefined;\n /** The current value; null when the field is empty. */\n value: EditorValue<T> | null;\n /** Report a new value. Pass null to clear the field. */\n onChange: (value: EditorValue<T> | null) => void;\n /** Call when the editor loses focus (client-side checks run then). */\n onBlur: () => void;\n validation: FieldValidationState;\n /** Shown but not editable (e.g. a field the person may not change). Keep the value selectable. */\n readOnly: boolean;\n /** Temporarily unavailable (e.g. while the entry is being saved or reloaded). */\n disabled: boolean;\n field: EditorField<T>;\n model: EditorModel;\n context: EditorContext;\n};\n\n/** A field editor: what a module in `extensions/editors/` exports (see `defineEditor`). */\nexport type EditorDefinition<T extends DataType = DataType> = {\n /** Namespaced ID referenced from field definitions, e.g. `acme.starRating` (letters, digits, `_`, `-`). */\n id: string;\n /** The contract version the editor was built against (set by `defineEditor`). */\n contractVersion: number;\n /** Data types the editor can edit. */\n dataTypes: readonly T[];\n component: ComponentType<FieldEditorProps<T>>;\n};\n","import type { DataType } from '@shapio/schema';\nimport { EDITOR_CONTRACT_VERSION, type EditorDefinition } from './types.js';\n\n/** `vendor.name`, the same rule as `CUSTOM_EDITOR_ID_PATTERN` in @shapio/schema. */\nconst EDITOR_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*(\\.[A-Za-z][A-Za-z0-9_-]*)+$/;\n\n/**\n * Declares a field editor. Export the result from your editor module as `editor`:\n *\n * ```tsx\n * export const editor = defineEditor({\n * id: 'acme.starRating',\n * dataTypes: ['integer'],\n * component: StarRating,\n * });\n * ```\n *\n * It stamps the contract version the editor was built against, so the admin can refuse an editor built\n * for an incompatible release instead of failing at runtime.\n */\nexport const defineEditor = <T extends DataType>(\n definition: Omit<EditorDefinition<T>, 'contractVersion'>,\n): EditorDefinition<T> => {\n if (!EDITOR_ID_PATTERN.test(definition.id)) {\n throw new Error(\n `Editor ID \"${definition.id}\" must be namespaced like \"acme.starRating\" (letters, digits, _ and -)`,\n );\n }\n if (definition.dataTypes.length === 0) {\n throw new Error(`Editor \"${definition.id}\" must list at least one data type`);\n }\n return { ...definition, contractVersion: EDITOR_CONTRACT_VERSION };\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** Runtime check of a module export (the admin's loader uses it on untrusted modules). */\nexport const isEditorDefinition = (value: unknown): value is EditorDefinition =>\n isRecord(value) &&\n typeof value.id === 'string' &&\n EDITOR_ID_PATTERN.test(value.id) &&\n typeof value.contractVersion === 'number' &&\n Array.isArray(value.dataTypes) &&\n value.dataTypes.every((type) => typeof type === 'string') &&\n (typeof value.component === 'function' || isRecord(value.component));\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,0BAA0B;;;;ACVvC,MAAM,oBAAoB;;;;;;;;;;;;;;;AAgB1B,MAAa,gBACX,eACwB;CACxB,IAAI,CAAC,kBAAkB,KAAK,WAAW,EAAE,GACvC,MAAM,IAAI,MACR,cAAc,WAAW,GAAG,uEAC9B;CAEF,IAAI,WAAW,UAAU,WAAW,GAClC,MAAM,IAAI,MAAM,WAAW,WAAW,GAAG,mCAAmC;CAE9E,OAAO;EAAE,GAAG;EAAY,iBAAA;CAAyC;AACnE;AAEA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;;AAGrE,MAAa,sBAAsB,UACjC,SAAS,KAAK,KACd,OAAO,MAAM,OAAO,YACpB,kBAAkB,KAAK,MAAM,EAAE,KAC/B,OAAO,MAAM,oBAAoB,YACjC,MAAM,QAAQ,MAAM,SAAS,KAC7B,MAAM,UAAU,OAAO,SAAS,OAAO,SAAS,QAAQ,MACvD,OAAO,MAAM,cAAc,cAAc,SAAS,MAAM,SAAS"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/types.ts","../src/defineEditor.ts"],"sourcesContent":["import type { DataType, DataTypeSettings, JsonValue, MEDIA_KINDS } from '@shapio/schema';\nimport type { ComponentType } from 'react';\n\n/**\n * The contract between the Shapio admin and a field editor (brief §6, ADR 0009). Built-in editors and\n * custom editors loaded at runtime from a project's `extensions/editors/*.js` receive exactly these props.\n *\n * An editor gets the field's value and metadata plus a small, explicit context. It never gets a network\n * client, a session, tokens or secrets: everything it can do goes through `onChange` and the context\n * functions. The server validates the value the same way whatever editor produced it.\n *\n * Bump EDITOR_CONTRACT_VERSION on any breaking change. The admin refuses editors built for another\n * version and falls back to the field's built-in editor.\n */\nexport const EDITOR_CONTRACT_VERSION = 1;\n\n/** The rich-text envelope stored for `richtext` fields (ADR 0003). `doc` is ProseMirror JSON. */\nexport type RichTextValue = {\n format: 'shapio-richtext';\n version: number;\n doc: { type: 'doc'; content?: JsonValue[] };\n};\n\n/** A component value: its fields keyed by API key. */\nexport type ComponentValue = { [apiKey: string]: unknown };\n\n/** A dynamic-zone item: a component value plus the component's API key in `__component`. */\nexport type DynamicZoneItem = ComponentValue & { __component: string };\n\n/**\n * The value type of each data type, as the admin API reads and writes it. Dates and times are canonical\n * UTC ISO-8601 text; decimals and big integers are strings so no precision is lost; relations and media\n * are IDs (lists of IDs when the field holds several).\n */\nexport type EditorValueMap = {\n string: string;\n text: string;\n code: string;\n richtext: RichTextValue;\n number: number;\n integer: number;\n decimal: string;\n biginteger: string;\n boolean: boolean;\n date: string;\n datetime: string;\n time: string;\n enum: string | string[];\n json: JsonValue;\n slug: string;\n email: string;\n url: string;\n uid: string;\n media: string | string[];\n relation: string | string[];\n component: ComponentValue | ComponentValue[];\n dynamiczone: DynamicZoneItem[];\n};\n\nexport type EditorValue<T extends DataType = DataType> = EditorValueMap[T];\n\n/** The field being edited, from the active model definition. */\nexport type EditorField<T extends DataType = DataType> = {\n /** Stable field ID (never changes when the field is renamed). */\n id: string;\n apiKey: string;\n label: string;\n description: string | undefined;\n type: T;\n required: boolean;\n /** True when the value is per locale; false when it is shared by every locale of the entry. */\n localized: boolean;\n /** Type settings: validation rules (min, max, pattern...), enum values, relation target... */\n settings: DataTypeSettings[T];\n /** This editor's options, as configured on the field in the model builder. */\n options: Readonly<Record<string, JsonValue>>;\n};\n\n/** The model (or component) the field belongs to. */\nexport type EditorModel = {\n id: string;\n apiKey: string;\n label: string;\n kind: 'collection' | 'singleton' | 'component';\n localized: boolean;\n};\n\nexport type MediaKind = (typeof MEDIA_KINDS)[number];\n\nexport type MediaPickOptions = {\n multiple?: boolean;\n /** Restrict the picker to these kinds of files. */\n allowedKinds?: readonly MediaKind[];\n};\n\n/** What the media picker hands back: enough to preview and reference an asset, never storage details. */\nexport type PickedMedia = {\n id: string;\n filename: string;\n mimeType: string;\n /** For previews in the admin only. Private assets have expiring URLs; store the `id`, never the URL. */\n url: string;\n alt: string;\n width: number | null;\n height: number | null;\n};\n\n/** The limited capabilities an editor may use. */\nexport type EditorContext = {\n /** The content locale being edited, or null when the model is not localized. */\n locale: string | null;\n /** The entry being edited, or null for an entry that is not created yet. */\n entryId: string | null;\n /** The admin's interface language (e.g. `en`), for editors that ship their own strings. */\n uiLanguage: string;\n /** Opens the media library; resolves with the chosen assets, or null when the person cancels. */\n pickMedia: (options?: MediaPickOptions) => Promise<PickedMedia[] | null>;\n /** Translates one of the admin's own keys (e.g. `common.remove`); unknown keys return `fallback`. */\n translate: (key: string, fallback: string, values?: Readonly<Record<string, string | number>>) => string;\n};\n\nexport type FieldValidationState = {\n /** Server and client validation messages for this value, already translated. */\n errors: readonly string[];\n invalid: boolean;\n};\n\nexport type FieldEditorProps<T extends DataType = DataType> = {\n /** The DOM id the field's label points at: put it on the editor's main focusable element. */\n inputId: string;\n /** The DOM id of the field's visible label: use `aria-labelledby` on group controls (radio groups...). */\n labelId: string;\n /** Ids of the description and error elements: pass to `aria-describedby`. */\n describedBy: string | undefined;\n /** The current value; null when the field is empty. */\n value: EditorValue<T> | null;\n /** Report a new value. Pass null to clear the field. */\n onChange: (value: EditorValue<T> | null) => void;\n /** Call when the editor loses focus (client-side checks run then). */\n onBlur: () => void;\n validation: FieldValidationState;\n /** Shown but not editable (e.g. a field the person may not change). Keep the value selectable. */\n readOnly: boolean;\n /** Temporarily unavailable (e.g. while the entry is being saved or reloaded). */\n disabled: boolean;\n field: EditorField<T>;\n model: EditorModel;\n context: EditorContext;\n};\n\n/** A field editor: what a module in `extensions/editors/` exports (see `defineEditor`). */\nexport type EditorDefinition<T extends DataType = DataType> = {\n /** Namespaced ID referenced from field definitions, e.g. `acme.starRating` (letters, digits, `_`, `-`). */\n id: string;\n /** The contract version the editor was built against (set by `defineEditor`). */\n contractVersion: number;\n /** Data types the editor can edit. */\n dataTypes: readonly T[];\n component: ComponentType<FieldEditorProps<T>>;\n};\n","import type { DataType } from '@shapio/schema';\nimport { EDITOR_CONTRACT_VERSION, type EditorDefinition } from './types.js';\n\n/** `vendor.name`, the same rule as `CUSTOM_EDITOR_ID_PATTERN` in @shapio/schema. */\nconst EDITOR_ID_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*(\\.[A-Za-z][A-Za-z0-9_-]*)+$/;\n\n/**\n * Declares a field editor. Export the result from your editor module as `editor`:\n *\n * ```tsx\n * export const editor = defineEditor({\n * id: 'acme.starRating',\n * dataTypes: ['integer'],\n * component: StarRating,\n * });\n * ```\n *\n * It stamps the contract version the editor was built against, so the admin can refuse an editor built\n * for an incompatible release instead of failing at runtime.\n */\nexport const defineEditor = <T extends DataType>(\n definition: Omit<EditorDefinition<T>, 'contractVersion'>,\n): EditorDefinition<T> => {\n if (!EDITOR_ID_PATTERN.test(definition.id)) {\n throw new Error(\n `Editor ID \"${definition.id}\" must be namespaced like \"acme.starRating\" (letters, digits, _ and -)`,\n );\n }\n if (definition.dataTypes.length === 0) {\n throw new Error(`Editor \"${definition.id}\" must list at least one data type`);\n }\n return { ...definition, contractVersion: EDITOR_CONTRACT_VERSION };\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** Runtime check of a module export (the admin's loader uses it on untrusted modules). */\nexport const isEditorDefinition = (value: unknown): value is EditorDefinition =>\n isRecord(value) &&\n typeof value.id === 'string' &&\n EDITOR_ID_PATTERN.test(value.id) &&\n typeof value.contractVersion === 'number' &&\n Array.isArray(value.dataTypes) &&\n value.dataTypes.every((type) => typeof type === 'string') &&\n (typeof value.component === 'function' || isRecord(value.component));\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,0BAA0B;;;;ACVvC,MAAM,oBAAoB;;;;;;;;;;;;;;;AAgB1B,MAAa,gBACX,eACwB;CACxB,IAAI,CAAC,kBAAkB,KAAK,WAAW,EAAE,GACvC,MAAM,IAAI,MACR,cAAc,WAAW,GAAG,uEAC9B;CAEF,IAAI,WAAW,UAAU,WAAW,GAClC,MAAM,IAAI,MAAM,WAAW,WAAW,GAAG,mCAAmC;CAE9E,OAAO;EAAE,GAAG;EAAY,iBAAA;CAAyC;AACnE;AAEA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;;AAGrE,MAAa,sBAAsB,UACjC,SAAS,KAAK,KACd,OAAO,MAAM,OAAO,YACpB,kBAAkB,KAAK,MAAM,EAAE,KAC/B,OAAO,MAAM,oBAAoB,YACjC,MAAM,QAAQ,MAAM,SAAS,KAC7B,MAAM,UAAU,OAAO,SAAS,OAAO,SAAS,QAAQ,MACvD,OAAO,MAAM,cAAc,cAAc,SAAS,MAAM,SAAS"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shapio/editor-sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Contract and helpers for custom Shapio field editors loaded at runtime.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://shapio.dev",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@shapio/schema": "0.5.
|
|
30
|
+
"@shapio/schema": "0.5.4"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": "^19.0.0"
|