import { ExternalComponentSchemaProperty } from './components.js' import { JSONSchema } from './schema.js' export type ExternalComponentSchemaHandler = ( property: ExternalComponentSchemaProperty ) => ExternalComponentSchemaProperty /** * Defines a custom data type, that can be expanded into multiple properties. Handler is defined as a function that * accepts all the nested properties and returns a new schema. This can be used to define nested structures that * redirect the properties to a nested definition, like Item does. * * `ui` property can be returned as well to customize UI settings for the type (accepts parameters of `ui:options` of UI * Schema (https://rjsf-team.github.io/react-jsonschema-form/docs/api-reference/uiSchema/) ) * * A name of a custom type has to start with a capital letter. * * @param handler * @see https://doc.sitecore.com/xmc/en/developers/xm-cloud/the-simple-field-types.html * https://doc.sitecore.com/xmc/en/developers/xm-cloud/the-link-field-types.html * @example * * registerDataType( * (schema) => ({ * type: 'string', * description: 'My custom data type', * ...schema * }), * { name: 'MyDataType' } * ) * * registerComponent(MyComponent, { * name: 'MyComponent', * properties: { * myField: { * type: 'MyDataType', * title: 'Title will mix with the description' * } * } * }) * */ export function registerDataType( handler: ExternalComponentSchemaHandler, { name }: { name: string } ) { if (name.charAt(0) != name.charAt(0).toUpperCase()) { throw new Error('Custom types must start with a capital letter') } BYOCDataTypes[name] = handler } /** * Using `registerDataType` BYOC defines set of data types that are available in XM cloud. * It includes simple types like Date, String, etc, and then complex types link Link, Item and List. * * Custom types start with a capital letter. * - Top level field values are accessed directly by their names. * - Nested fields inside Item, can be accessed via `item.fields..value` notation, or using * `getFieldValue(item.fields, * 'fieldName', defaultValue)` JSS helper. * - List items are accessed via `list[0].fields..value` notation. * * When describing types of properties in components, you can use types provided by JSS: Field<...> and LinkField, * ItemField, ListField, ImageField, etc. The following is a list of all types, their respective JSS types and data * shapes. * | Type | Typescript | Shape | * | ------------------- | ---------------- | ----- | * | File | FileField | { src: string, title: string, displayName: string } | * | Date | Field | "2021-01-01T00:00:00.000Z" | * | Datetime | Field | "2021-01-01T00:00:00.000Z" | * | RichText | Field | "

Some text

" | * | SingleLineText | Field | "Some text" | * | MultiLineText | Field | "Some\ntext" | * | Image | ImageField | { src: string, ... } | * | Checkbox | Field | true | * | Item | Item | { name: string, displayName: string, id: string, url: string, fields: { [key: string]: Field } } | * | List | Item[] | { name: string, displayName: string, id: string, url: string, fields: { [key: string]: Field } }[] | * | Link | LinkField | { href: string, text: string, linktype: string, title: string, class: string, target: string, anchor: string, querystring: string } | * | DropLink | Item | { name: string, displayName: string, id: string, url: string, fields: { [key: string]: Field } } | * | DropTree | Item | { name: string, displayName: string, id: string, url: string, fields: { [key: string]: Field } } | * | String | Field | "Some text" | * | Number | Field | 123 | * | Integer | Field | 123 | * | Boolean | Field | true | * * @example * * registerComponent(MyComponent, { * name: 'MyComponent', * description: 'Description of my example component', * properties: { * myField: { * type: 'Datetime', * title: 'My Field' * }, * activities: { * type: 'List', * properties: { * name: { * type: 'String', * title: 'Name of a person' * }, * rank: { * type: 'Number', * description: 'Numerical rank, like 1, 2, 3, etc' * } * } * }, * myItem: { * type: 'Item', * properties: { * nestedField: { * type: 'Number' * title: 'Nested Field * } * } * } * } * }) * * * function MyComponebnt(props: {myField: string, myItem: { fields: { nestedField: number } }, activities: { fields: { name: string, rank: number } }[] }) { * return <> *
{props.myField}
*
{props.myItem.fields.nestedField} or {getFieldValue(props.myItem.fields, 'nestedField')}
* * } * */ export const BYOCDataTypes: Record = {} /** Standard definition of a link */ const XMLinkHandler = () => ({ type: 'object', properties: { href: { required: true, type: 'string', title: 'URL', description: 'The URL of the link, except for media items, for which the Url property contains the path to the media item relative to /Sitecore/Media Library.' }, text: { type: 'string', title: 'Text', description: 'The text content of the HTML element.' }, linktype: { type: 'string', enum: ['internal', 'external', 'media', 'anchor', 'mailto', 'javascript'], default: 'internal', title: 'Target', description: 'The target attribute of the HTML element.' }, title: { type: 'string', title: 'Title', description: 'The title attribute of the HTML element.' }, //className: { type: 'string', title: 'Class Name', description: 'The class attribute of the HTML element.' }, class: { type: 'string', title: 'Class Name', description: 'The class attribute of the HTML element.' }, target: { type: 'string', title: 'Target', description: 'The target attribute of the HTML element.', enum: ['', '_blank', '_self', '_parent', '_top'] }, anchor: { type: 'string', title: 'Anchor', description: 'The name attribute of the HTML element, without the leading hash character (“#”).' }, querystring: { type: 'string', title: 'Query String', description: 'Query string parameters to add to the URL.' } } }) const ItemHandler = ({ properties, ...schema }: ExternalComponentSchemaProperty) => ({ ...schema, type: 'object', properties: { name: { type: 'string' }, displayName: { type: 'string' }, id: { type: 'string' }, url: { type: 'string' }, fields: propertiesToFields(properties) } }) function propertiesToFields(properties: Record | undefined = {}) { return { type: 'object', properties: Object.keys(properties).reduce((fields, key) => { const { title, ...rest } = properties[key] return { ...fields, [key]: { type: 'object', properties: { value: { title: title || key, ...properties[key] } } } } }, {}) } } export const XMTypes = { Link: XMLinkHandler, DropLink: ItemHandler, DropTree: ItemHandler, File: () => ({ type: 'object', properties: { src: { type: 'string', title: 'Source', description: 'The URL of the file.' }, title: { type: 'string', title: 'Title', description: 'Title of a file' }, displayName: { type: 'string', title: 'Display name', description: 'Display name of a file' } } }), Date: () => ({ type: 'string', format: 'date' }), Datetime: () => ({ type: 'string', format: 'date-time' }), RichText: () => ({ type: 'string' }), Image: (schema: ExternalComponentSchemaProperty) => ({ type: 'object', properties: { src: { type: 'string' }, ...schema.properties } }), SingleLineText: () => { type: 'string' }, MultiLineText: () => ({ type: 'string' }), Checkbox: () => ({ type: 'boolean' }), String: () => ({ type: 'string' }), Number: () => ({ type: 'number' }), Boolean: () => ({ type: 'boolean' }), Item: ItemHandler, List: ({ properties, ...schema }: ExternalComponentSchemaProperty) => ({ ...schema, type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, displayName: { type: 'string' }, id: { type: 'string' }, url: { type: 'string' }, fields: propertiesToFields(properties) } } }) } as const // Register all default XM data types Object.keys(XMTypes).forEach((property) => { registerDataType(XMTypes[property as keyof typeof XMTypes] as ExternalComponentSchemaHandler, { name: property }) }) export type XMTypes = typeof XMTypes