diff --git a/backport-changelog/7.1/12854.md b/backport-changelog/7.1/12854.md new file mode 100644 index 00000000000000..c04335bde88684 --- /dev/null +++ b/backport-changelog/7.1/12854.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/12854 + +* https://github.com/WordPress/gutenberg/pull/81168 diff --git a/docs/reference-guides/view-config-reference.md b/docs/reference-guides/view-config-reference.md index 6bed75d2067272..8c441f60c912a1 100644 --- a/docs/reference-guides/view-config-reference.md +++ b/docs/reference-guides/view-config-reference.md @@ -19,39 +19,166 @@ This section lists the properties of the view configuration. It has four top-lev - [`default_view`](#default_view): the view applied when the user has not made any changes. - [`default_layouts`](#default_layouts): the layout types available to the user, and the view overrides each one applies. - [`view_list`](#view_list): the preconfigured views displayed in the screen's sidebar. -- [`form`](#form): the fields of the Quick Edit form. +- [`form`](#form): the layout and fields of the form (QuickEdit). + +> The property tables below are generated from the [view-config JSON schema](https://github.com/WordPress/gutenberg/blob/trunk/schemas/json/view-config.json), which mirrors the schema exposed by the REST API endpoint. To update them, edit the JSON schema and run `npm run docs:view-config-ref`. ## default_view -> The view object is the same structure the [DataViews component](/packages/dataviews/README.md#view-object) consumes; see its documentation for the complete list of view properties and filter operators. There are two properties from the DataViews API that cannot be configured via the server filter: `search` and `page`. These are URL-managed that are set by the editor when the user searches or paginates. +> The view object is the same structure the [DataViews component](/packages/dataviews/README.md#view-object) consumes. There are two properties from the DataViews API that cannot be configured via the server filter: `search` and `page`, which are URL-managed properties set by the editor when the user searches or paginates. See the interactive [DataViews Storybook](https://wordpress.github.io/gutenberg/?path=/docs/dataviews-dataviews--docs) for examples. The default DataViews configuration for the screen: layout type, visible fields, sorting, filtering, and pagination. It is the view users see before they customize anything. -| Property | Description | Type | -| -------- | ----------- | ---- | -| type | The layout type, one of `table`, `grid`, `list`, or `activity`. | `string` | -| fields | Ids of the fields that are visible, in display order. | `[ string ]` | -| titleField | Id of the field used as the record title. | `string` | -| mediaField | Id of the field used as the record media (e.g. featured image or preview). | `string` | -| descriptionField | Id of the field used as the record description. | `string` | -| showTitle | Whether the title is shown. Defaults to `true`. | `boolean` | -| showMedia | Whether the media is shown. Defaults to `true`. | `boolean` | -| showDescription | Whether the description is shown. Defaults to `true`. | `boolean` | -| showLevels | Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`. | `boolean` | -| sort | The default sort: the field id and the direction (`asc` or `desc`). | `{ field, direction }` | -| filters | Filters applied to the dataset. A filter with `isLocked` set cannot be removed by the user. | `[ { field, operator, value, isLocked } ]` | -| perPage | Number of records per page. | `integer` | -| layout | Configuration specific to the selected layout type. See [`default_layouts`](#default_layouts). | `object` | +```php +$default_view = array( + 'type' => 'table', + 'perPage' => 20, + 'titleField' => 'title', + 'fields' => array( 'author', 'status', 'date' ), + 'sort' => array( + 'field' => 'date', + 'direction' => 'desc', + ), +); +``` -## default_layouts + +### type + +The layout type. + +- Type: `string` +- Enum: `"table" | "grid" | "list" | "activity" | "pickerGrid" | "pickerTable" | "pickerActivity"` + +--- + +### layout + +Configuration specific to the selected layout type. Accepts the `layout` options of any layout type; see [`default_layouts`](#default_layouts). + +- Type: `object` + +--- + +### filters + +Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user. + +- Type: `[ object ]` +- Item properties: + - `field` (`string`): The field to filter by. + - `operator` (`"is" | "isNot" | "isAny" | "isNone" | "isAll" | "isNotAll" | "lessThan" | "greaterThan" | "lessThanOrEqual" | "greaterThanOrEqual" | "before" | "after"`): The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`. + - `value` (`any`): The value to filter by. + - `isLocked` (`boolean`): Whether the filter is locked. A locked filter cannot be changed or removed by the user. + +--- + +### sort + +The default sort: the field id and the direction (`asc` or `desc`). + +- Type: `object` +- Properties: + - `field` (`string`): The field to sort by. + - `direction` (`"asc" | "desc"`): The direction to sort by, `asc` or `desc`. + +--- + +### perPage + +Number of records per page. Also used as the batch size when infinite scroll is enabled. + +- Type: `integer` + +--- + +### fields + +Ids of the fields that are visible, in display order. + +- Type: `[ string ]` + +--- + +### titleField + +Id of the field used as the record title. + +- Type: `string` + +--- + +### mediaField -The layout types the user can switch between. Each key is a layout type (`table`, `grid`, `list`, `activity`); a type that is not present is not offered in the UI. Each value is a partial view configuration applied when the user switches to that layout type — an empty array means the type is available with no overrides. +Id of the field used as the record media (e.g. featured image or preview). -| Property | Description | Type | -| -------- | ----------- | ---- | -| table | View overrides applied when the table layout is selected. | `object` | -| grid | View overrides applied when the grid layout is selected. | `object` | -| list | View overrides applied when the list layout is selected. | `object` | +- Type: `string` + +--- + +### descriptionField + +Id of the field used as the record description. + +- Type: `string` + +--- + +### showTitle + +Whether the title is shown. Defaults to `true`. + +- Type: `boolean` + +--- + +### showMedia + +Whether the media is shown. Defaults to `true`. + +- Type: `boolean` + +--- + +### showDescription + +Whether the description is shown. Defaults to `true`. + +- Type: `boolean` + +--- + +### showLevels + +Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`. + +- Type: `boolean` + +--- + +### groupBy + +The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`). + +- Type: `object` +- Properties: + - `field` (`string`): The field to group by. + - `direction` (`"asc" | "desc"`): The direction to sort the groups by, `asc` or `desc`. + - `showLabel` (`boolean`): Whether to show the field label in the group header. Default: `true`. + +--- + +### infiniteScrollEnabled + +Whether infinite scroll is enabled instead of pagination. + +- Type: `boolean` + + + +## default_layouts + +The layout types the user can switch between. Each key is a layout type; a type that is not present is not offered in the UI. Each value is a partial view configuration applied when the user switches to that layout type — an empty array means the type is available with no overrides. Common overrides are `layout` (layout-specific configuration, such as per-field column `styles` for tables or `badgeFields` for grids) and view properties like `showMedia`: @@ -74,15 +201,107 @@ $default_layouts = array( ); ``` + +### table + +View overrides applied when the table layout is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `styles` (`{ [ field ]: object }`): The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`). + - `width` (`string | number`): The width of the field column, as a CSS value or a number of pixels. + - `maxWidth` (`string | number`): The maximum width of the field column, as a CSS value or a number of pixels. + - `minWidth` (`string | number`): The minimum width of the field column, as a CSS value or a number of pixels. + - `align` (`"start" | "center" | "end"`): The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`. +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the layout: `compact`, `balanced`, or `comfortable`. +- `enableMoving` (`boolean`): Whether the user can reorder columns. + +--- + +### list + +View overrides applied when the list layout is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the layout: `compact`, `balanced`, or `comfortable`. + +--- + +### grid + +View overrides applied when the grid layout is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `badgeFields` (`[ string ]`): Ids of the fields to display as badges instead of regular fields. +- `previewSize` (`number`): The preview size of the grid. +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the grid layout: `compact`, `balanced`, or `comfortable`. + +--- + +### activity + +View overrides applied when the activity layout is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the layout: `compact`, `balanced`, or `comfortable`. + +--- + +### pickerGrid + +View overrides applied when the grid layout of a picker (DataViewsPicker) is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `badgeFields` (`[ string ]`): Ids of the fields to display as badges instead of regular fields. +- `previewSize` (`number`): The preview size of the grid. +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the grid layout: `compact`, `balanced`, or `comfortable`. + +--- + +### pickerTable + +View overrides applied when the table layout of a picker (DataViewsPicker) is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `styles` (`{ [ field ]: object }`): The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`). + - `width` (`string | number`): The width of the field column, as a CSS value or a number of pixels. + - `maxWidth` (`string | number`): The maximum width of the field column, as a CSS value or a number of pixels. + - `minWidth` (`string | number`): The minimum width of the field column, as a CSS value or a number of pixels. + - `align` (`"start" | "center" | "end"`): The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`. +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the layout: `compact`, `balanced`, or `comfortable`. +- `enableMoving` (`boolean`): Whether the user can reorder columns. + +--- + +### pickerActivity + +View overrides applied when the activity layout of a picker (DataViewsPicker) is selected. In addition to the [view properties](#default_view), the `layout` key accepts: + +- `density` (`"compact" | "balanced" | "comfortable"`): The density of the layout: `compact`, `balanced`, or `comfortable`. + + + ## view_list The preconfigured views displayed in the screen's sidebar (e.g. "All pages", "Published", "Drafts"). Each entry is: -| Property | Description | Type | -| -------- | ----------- | ---- | -| title | Title of the view, displayed in the sidebar. | `string` | -| slug | Unique identifier for the view. Used as the member identity when merging patches. | `string` | -| view | Partial view configuration applied on top of `default_view` when the view is selected — typically locked `filters`, but any view property works. Optional. | `object` | + +### title + +Title of the view, displayed in the sidebar. + +- Type: `string` + +--- + +### slug + +Unique identifier for the view. Used as the member identity when merging patches. + +- Type: `string` + +--- + +### view + +Partial view configuration applied on top of [`default_view`](#default_view) when the view is selected — typically locked `filters`, but any view property works. Optional. + +- Type: `object` + + ```php $view_list = array( @@ -109,24 +328,10 @@ $view_list = array( ## form -> The form structure is shared with the [DataForm component](/packages/dataviews/README.md#form-object); see its documentation for the complete Form Field API, including the additional options of each layout type. +> The form structure is shared with the [DataForm component](/packages/dataviews/README.md#form-object). See the interactive [DataForm Storybook](https://wordpress.github.io/gutenberg/?path=/docs/dataviews-dataform--docs) for examples. The DataForm configuration for the Quick Edit form: which fields are displayed, in which order, and how each one is laid out. -| Property | Description | Type | -| -------- | ----------- | ---- | -| layout | The default layout for the form fields, e.g. `{ 'type' => 'panel' }`. | `{ type }` | -| fields | The fields of the form, in display order. Each entry is a field id, or an object for further configuration (see below). | `[ string \| object ]` | - -A field declared as an object accepts: - -| Property | Description | Type | -| -------- | ----------- | ---- | -| id | Id of the field. Used as the member identity when merging patches. | `string` | -| label | Label displayed for the field, overriding the field's own. | `string` | -| layout | How the field is rendered: `type` is one of `regular`, `panel`, `card`, or `row`, and `labelPosition` is one of `side`, `top`, or `none`. | `{ type, labelPosition }` | -| children | Fields combined under this entry, following the same shape as `fields`. | `[ string \| object ]` | - Only registered fields are rendered: a form entry whose field is not registered for the entity is dropped by the editor. ```php @@ -150,3 +355,80 @@ $form = array( ), ); ``` + + +### layout + +The layout used to render the form fields, discriminated by its `type`. See the form layout types. + +- Type: `object` + +--- + +### fields + +The fields of the form, in display order. Each entry is a field id, or an object for further configuration. + +- Type: `[ string | object ]` +- Item properties: + - `id` (`string`): Id of the field. Used as the member identity when merging patches. + - `label` (`string`): Label displayed for the field, overriding the field's own. + - `description` (`string`): Description displayed for the field. + - `layout` (`object`): The layout used to render the form fields, discriminated by its `type`. See the form layout types. + - `children` (`[ string | object ]`): Fields combined under this entry, following the same shape as `fields`. + +--- + +### Form layout types + +Both the form-level `layout` and a field-level `layout` accept one of the following objects, discriminated by their `type` property: + +#### regular + +The default layout: the field controls are rendered directly in the form, one after another. + +- `type` (`"regular"`): The layout type. +- `labelPosition` (`"top" | "side" | "none"`): Position of the field label: `top`, `side`, or `none`. + +#### panel + +The field is rendered as a button that opens a dropdown or modal with the field controls. + +- `type` (`"panel"`): The layout type. +- `labelPosition` (`"top" | "side" | "none"`): Position of the field label: `top`, `side`, or `none`. +- `openAs` (`"dropdown" | "modal" | object`): How the panel opens: as a `dropdown` or as a `modal`. The object form allows customizing the labels of the modal buttons. + - `type` (`"dropdown" | "modal"`): The type of container to open, `dropdown` or `modal`. + - `applyLabel` (`string`): Label of the modal button that applies the changes. + - `cancelLabel` (`string`): Label of the modal button that discards the changes. +- `summary` (`string | [ string ]`): Id(s) of the field(s) whose values are rendered in the panel button. +- `editVisibility` (`"always" | "on-hover"`): When the edit button is visible: `always` or `on-hover`. + +#### card + +The fields are grouped in a card container. + +- `type` (`"card"`): The layout type. +- `withHeader` (`boolean`): Whether the card renders a header. Defaults to `true`. +- `isOpened` (`boolean`): Whether the card content is opened. Defaults to `true`. +- `isCollapsible` (`boolean`): Whether the card can be collapsed by the user. +- `summary` (`string | [ string | object ]`): Id(s) of the field(s) whose values are rendered in the card header. An entry declared as an object controls when it is visible: `always` or `when-collapsed`. + - `id` (`string`): Id of the field. + - `visibility` (`"always" | "when-collapsed"`): When the field value is visible in the card header: `always` or `when-collapsed`. + +#### row + +The fields are rendered horizontally in a single row. + +- `type` (`"row"`): The layout type. +- `alignment` (`"start" | "center" | "end"`): Vertical alignment of the fields in the row: `start`, `center`, or `end`. +- `styles` (`{ [ field ]: object }`): The styles for the fields in the row, keyed by field id. Each style accepts a `flex` value controlling how the field grows or shrinks. + - `flex` (`string | number`): The CSS `flex` value for the field. + +#### details + +The fields are rendered inside a collapsible disclosure (details) element. + +- `type` (`"details"`): The layout type. +- `summary` (`string`): Label displayed as the summary of the disclosure element. + + diff --git a/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php index f625e357b65da4..50cbe3c02429e8 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php @@ -227,6 +227,12 @@ protected function cast_empty_objects( $value, $schema ) { /** * Retrieves the item's schema, conforming to JSON Schema. * + * The schema is loaded from `view-config-schema.php`, which is generated + * from the canonical JSON Schema at `schemas/json/view-config.json` (see + * `tools/docs/gen-view-config-schema-php.mjs`). The generated file wraps + * the JSON Schema descriptions in `__()` calls so they go through the + * plugin's translation pipeline. + * * @return array Item schema data. */ public function get_item_schema() { @@ -234,578 +240,8 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } - $view_base_properties = $this->get_view_base_schema(); - - $this->schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => 'view-config', - 'type' => 'object', - 'properties' => array( - 'kind' => array( - 'description' => __( 'Entity kind.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - ), - 'name' => array( - 'description' => __( 'Entity name.', 'gutenberg' ), - 'type' => 'string', - 'readonly' => true, - ), - 'version' => array( - 'description' => __( 'The schema version of the configuration.', 'gutenberg' ), - 'type' => 'integer', - 'readonly' => true, - ), - 'default_view' => array( - 'description' => __( 'Default view configuration.', 'gutenberg' ), - 'type' => 'object', - 'readonly' => true, - 'properties' => array_merge( - array( - 'type' => array( - 'type' => 'string', - ), - 'layout' => $this->get_combined_layout_schema(), - ), - $view_base_properties - ), - ), - 'default_layouts' => array( - 'description' => __( 'Default layout configurations.', 'gutenberg' ), - 'type' => 'object', - 'readonly' => true, - 'properties' => array( - 'table' => array( - 'type' => 'object', - 'properties' => array_merge( - $view_base_properties, - array( - 'layout' => $this->get_table_layout_schema(), - ) - ), - ), - 'list' => array( - 'type' => 'object', - 'properties' => array_merge( - $view_base_properties, - array( - 'layout' => $this->get_list_layout_schema(), - ) - ), - ), - 'grid' => array( - 'type' => 'object', - 'properties' => array_merge( - $view_base_properties, - array( - 'layout' => $this->get_grid_layout_schema(), - ) - ), - ), - 'activity' => array( - 'type' => 'object', - 'properties' => array_merge( - $view_base_properties, - array( - 'layout' => $this->get_list_layout_schema(), - ) - ), - ), - 'pickerGrid' => array( - 'type' => 'object', - 'properties' => array_merge( - $view_base_properties, - array( - 'layout' => $this->get_grid_layout_schema(), - ) - ), - ), - 'pickerTable' => array( - 'type' => 'object', - 'properties' => array_merge( - $view_base_properties, - array( - 'layout' => $this->get_table_layout_schema(), - ) - ), - ), - ), - ), - 'view_list' => array( - 'description' => __( 'List of default views.', 'gutenberg' ), - 'type' => 'array', - 'readonly' => true, - 'items' => array( - 'type' => 'object', - 'properties' => array( - 'title' => array( - 'type' => 'string', - ), - 'slug' => array( - 'type' => 'string', - ), - 'view' => array( - 'type' => 'object', - 'properties' => array_merge( - array( - 'type' => array( - 'type' => 'string', - ), - 'layout' => $this->get_combined_layout_schema(), - ), - $view_base_properties - ), - ), - ), - ), - ), - 'form' => array( - 'description' => __( 'Default form configuration.', 'gutenberg' ), - 'type' => 'object', - 'readonly' => true, - 'properties' => $this->get_form_schema(), - ), - ), - ); + $this->schema = require __DIR__ . '/view-config-schema.php'; return $this->add_additional_fields_schema( $this->schema ); } - - /** - * Returns the schema properties shared by all view types (ViewBase), excluding 'type'. - * - * Note that `search` and `page` are not part of the schema: they are managed - * via the URL, which is their only source of truth. - * - * @return array Schema properties for the base view configuration. - */ - protected function get_view_base_schema() { - return array( - 'filters' => array( - 'type' => 'array', - 'items' => array( - 'type' => 'object', - 'properties' => array( - 'field' => array( - 'type' => 'string', - ), - 'operator' => array( - 'type' => 'string', - 'enum' => array( - 'is', - 'isNot', - 'isAny', - 'isNone', - 'isAll', - 'isNotAll', - 'lessThan', - 'greaterThan', - 'lessThanOrEqual', - 'greaterThanOrEqual', - 'before', - 'after', - ), - ), - 'value' => array(), - 'isLocked' => array( - 'type' => 'boolean', - ), - ), - ), - ), - 'sort' => array( - 'type' => 'object', - 'properties' => array( - 'field' => array( - 'type' => 'string', - ), - 'direction' => array( - 'type' => 'string', - 'enum' => array( 'asc', 'desc' ), - ), - ), - ), - 'perPage' => array( - 'type' => 'integer', - ), - 'fields' => array( - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - ), - 'titleField' => array( - 'type' => 'string', - ), - 'mediaField' => array( - 'type' => 'string', - ), - 'descriptionField' => array( - 'type' => 'string', - ), - 'showTitle' => array( - 'type' => 'boolean', - ), - 'showMedia' => array( - 'type' => 'boolean', - ), - 'showDescription' => array( - 'type' => 'boolean', - ), - 'showLevels' => array( - 'type' => 'boolean', - ), - 'groupBy' => array( - 'type' => 'object', - 'properties' => array( - 'field' => array( - 'type' => 'string', - ), - 'direction' => array( - 'type' => 'string', - 'enum' => array( 'asc', 'desc' ), - ), - 'showLabel' => array( - 'type' => 'boolean', - 'default' => true, - ), - ), - ), - 'infiniteScrollEnabled' => array( - 'type' => 'boolean', - ), - ); - } - - /** - * Returns the schema for the ColumnStyle type. - * - * @return array Schema for a column style object. - */ - protected function get_column_style_schema() { - return array( - 'type' => 'object', - 'properties' => array( - 'width' => array( - 'type' => array( 'string', 'number' ), - ), - 'maxWidth' => array( - 'type' => array( 'string', 'number' ), - ), - 'minWidth' => array( - 'type' => array( 'string', 'number' ), - ), - 'align' => array( - 'type' => 'string', - 'enum' => array( 'start', 'center', 'end' ), - ), - ), - ); - } - - /** - * Returns the layout schema for table-type views (ViewTable, ViewPickerTable). - * - * @return array Schema for a table layout object. - */ - protected function get_table_layout_schema() { - return array( - 'type' => 'object', - 'properties' => array( - 'styles' => array( - 'type' => 'object', - 'additionalProperties' => $this->get_column_style_schema(), - ), - 'density' => array( - 'type' => 'string', - 'enum' => array( 'compact', 'balanced', 'comfortable' ), - ), - 'enableMoving' => array( - 'type' => 'boolean', - ), - ), - ); - } - - /** - * Returns the layout schema for list-type views (ViewList, ViewActivity). - * - * @return array Schema for a list layout object. - */ - protected function get_list_layout_schema() { - return array( - 'type' => 'object', - 'properties' => array( - 'density' => array( - 'type' => 'string', - 'enum' => array( 'compact', 'balanced', 'comfortable' ), - ), - ), - ); - } - - /** - * Returns a combined layout schema that accepts properties from all view types. - * - * This is useful for contexts where the view type is not known ahead of time - * (e.g. the `view` override in a view list item), so all possible layout - * properties must be accepted. - * - * @return array Schema for a combined layout object. - */ - protected function get_combined_layout_schema() { - return array( - 'type' => 'object', - 'properties' => array_merge( - $this->get_table_layout_schema()['properties'], - $this->get_grid_layout_schema()['properties'], - $this->get_list_layout_schema()['properties'] - ), - ); - } - - /** - * Returns the layout schema for grid-type views (ViewGrid, ViewPickerGrid). - * - * @return array Schema for a grid layout object. - */ - protected function get_grid_layout_schema() { - return array( - 'type' => 'object', - 'properties' => array( - 'badgeFields' => array( - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - ), - 'previewSize' => array( - 'type' => 'number', - ), - 'density' => array( - 'type' => 'string', - 'enum' => array( 'compact', 'balanced', 'comfortable' ), - ), - ), - ); - } - - /** - * Returns the schema for a form layout object as a discriminated union. - * - * Each variant is discriminated by a single-value enum on its `type` property, - * matching the TypeScript Layout union in dataviews/src/types/dataform.ts. - * - * @return array Schema for a form layout object. - */ - protected function get_form_layout_schema() { - return array( - 'oneOf' => array( - // RegularLayout. - array( - 'type' => 'object', - 'properties' => array( - 'type' => array( - 'type' => 'string', - 'enum' => array( 'regular' ), - ), - 'labelPosition' => array( - 'type' => 'string', - 'enum' => array( 'top', 'side', 'none' ), - ), - ), - ), - // PanelLayout. - array( - 'type' => 'object', - 'properties' => array( - 'type' => array( - 'type' => 'string', - 'enum' => array( 'panel' ), - ), - 'labelPosition' => array( - 'type' => 'string', - 'enum' => array( 'top', 'side', 'none' ), - ), - 'openAs' => array( - 'oneOf' => array( - array( - 'type' => 'string', - 'enum' => array( 'dropdown', 'modal' ), - ), - array( - 'type' => 'object', - 'properties' => array( - 'type' => array( - 'type' => 'string', - 'enum' => array( 'dropdown', 'modal' ), - ), - 'applyLabel' => array( - 'type' => 'string', - ), - 'cancelLabel' => array( - 'type' => 'string', - ), - ), - ), - ), - ), - 'summary' => array( - 'oneOf' => array( - array( 'type' => 'string' ), - array( - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - ), - ), - ), - 'editVisibility' => array( - 'type' => 'string', - 'enum' => array( 'always', 'on-hover' ), - ), - ), - ), - // CardLayout. - array( - 'type' => 'object', - 'properties' => array( - 'type' => array( - 'type' => 'string', - 'enum' => array( 'card' ), - ), - 'withHeader' => array( - 'type' => 'boolean', - ), - 'isOpened' => array( - 'type' => 'boolean', - ), - 'isCollapsible' => array( - 'type' => 'boolean', - ), - 'summary' => array( - 'oneOf' => array( - array( 'type' => 'string' ), - array( - 'type' => 'array', - 'items' => array( - 'oneOf' => array( - array( 'type' => 'string' ), - array( - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'type' => 'string', - ), - 'visibility' => array( - 'type' => 'string', - 'enum' => array( 'always', 'when-collapsed' ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - // RowLayout. - array( - 'type' => 'object', - 'properties' => array( - 'type' => array( - 'type' => 'string', - 'enum' => array( 'row' ), - ), - 'alignment' => array( - 'type' => 'string', - 'enum' => array( 'start', 'center', 'end' ), - ), - 'styles' => array( - 'type' => 'object', - 'additionalProperties' => array( - 'type' => 'object', - 'properties' => array( - 'flex' => array( - 'type' => array( 'string', 'number' ), - ), - ), - ), - ), - ), - ), - // DetailsLayout. - array( - 'type' => 'object', - 'properties' => array( - 'type' => array( - 'type' => 'string', - 'enum' => array( 'details' ), - ), - 'summary' => array( - 'type' => 'string', - ), - ), - ), - ), - ); - } - - /** - * Returns the schema for a form field item (string or object). - * - * @return array Schema for a form field. - */ - protected function get_form_field_schema() { - return array( - 'oneOf' => array( - array( 'type' => 'string' ), - array( - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'type' => 'string', - ), - 'label' => array( - 'type' => 'string', - ), - 'description' => array( - 'type' => 'string', - ), - 'layout' => $this->get_form_layout_schema(), - 'children' => array( - 'type' => 'array', - 'items' => array( - 'oneOf' => array( - array( 'type' => 'string' ), - // This object can have the shape of a form field itself, - // allowing for recursive nesting of form fields. - // There's no easy way to codify this recursion via the JSON Schema draft-04 - // supported by the REST API. - array( 'type' => 'object' ), - ), - ), - ), - ), - ), - ), - ); - } - - /** - * Returns the schema for the form configuration object. - * - * @return array Schema properties for the form configuration. - */ - protected function get_form_schema() { - return array( - 'layout' => $this->get_form_layout_schema(), - 'fields' => array( - 'type' => 'array', - 'items' => $this->get_form_field_schema(), - ), - ); - } } diff --git a/lib/compat/wordpress-7.1/view-config-schema.php b/lib/compat/wordpress-7.1/view-config-schema.php new file mode 100644 index 00000000000000..1acb169e7a4e8a --- /dev/null +++ b/lib/compat/wordpress-7.1/view-config-schema.php @@ -0,0 +1,2094 @@ + 'http://json-schema.org/draft-04/schema#', + 'title' => 'view-config', + 'type' => 'object', + 'properties' => array( + 'kind' => array( + 'description' => __( 'Entity kind (e.g. `postType`).', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'Entity name (e.g. `page`).', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + ), + 'version' => array( + 'description' => __( 'The schema version (currently, 1).', 'gutenberg' ), + 'type' => 'integer', + 'readonly' => true, + ), + 'default_view' => array( + 'description' => __( 'The default DataViews configuration for the screen: layout type, visible fields, sorting, filtering, and pagination.', 'gutenberg' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'table', + 'grid', + 'list', + 'activity', + 'pickerGrid', + 'pickerTable', + 'pickerActivity', + ), + ), + 'layout' => array( + 'description' => __( 'Configuration specific to the selected layout type. Accepts the `layout` options of any layout type; see [`default_layouts`](#default_layouts).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'styles' => array( + 'description' => __( 'The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`).', 'gutenberg' ), + 'type' => 'object', + 'additionalProperties' => array( + 'description' => __( 'The style of a single field column.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'width' => array( + 'description' => __( 'The width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'maxWidth' => array( + 'description' => __( 'The maximum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'minWidth' => array( + 'description' => __( 'The minimum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'align' => array( + 'description' => __( 'The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'start', + 'center', + 'end', + ), + ), + ), + ), + ), + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + 'enableMoving' => array( + 'description' => __( 'Whether the user can reorder columns.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'badgeFields' => array( + 'description' => __( 'Ids of the fields to display as badges instead of regular fields.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'previewSize' => array( + 'description' => __( 'The preview size of the grid.', 'gutenberg' ), + 'type' => 'number', + ), + ), + ), + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + 'default_layouts' => array( + 'description' => __( 'The layout types the user can switch between, and the view overrides each one applies.', 'gutenberg' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'table' => array( + 'description' => __( 'View overrides applied when the table layout is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to table-type layouts (`table`, `pickerTable`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'styles' => array( + 'description' => __( 'The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`).', 'gutenberg' ), + 'type' => 'object', + 'additionalProperties' => array( + 'description' => __( 'The style of a single field column.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'width' => array( + 'description' => __( 'The width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'maxWidth' => array( + 'description' => __( 'The maximum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'minWidth' => array( + 'description' => __( 'The minimum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'align' => array( + 'description' => __( 'The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'start', + 'center', + 'end', + ), + ), + ), + ), + ), + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + 'enableMoving' => array( + 'description' => __( 'Whether the user can reorder columns.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + ), + 'list' => array( + 'description' => __( 'View overrides applied when the list layout is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to list-type layouts (`list`, `activity`, `pickerActivity`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + ), + ), + ), + ), + 'grid' => array( + 'description' => __( 'View overrides applied when the grid layout is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to grid-type layouts (`grid`, `pickerGrid`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'badgeFields' => array( + 'description' => __( 'Ids of the fields to display as badges instead of regular fields.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'previewSize' => array( + 'description' => __( 'The preview size of the grid.', 'gutenberg' ), + 'type' => 'number', + ), + 'density' => array( + 'description' => __( 'The density of the grid layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + ), + ), + ), + ), + 'activity' => array( + 'description' => __( 'View overrides applied when the activity layout is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to list-type layouts (`list`, `activity`, `pickerActivity`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + ), + ), + ), + ), + 'pickerGrid' => array( + 'description' => __( 'View overrides applied when the grid layout of a picker (DataViewsPicker) is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to grid-type layouts (`grid`, `pickerGrid`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'badgeFields' => array( + 'description' => __( 'Ids of the fields to display as badges instead of regular fields.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'previewSize' => array( + 'description' => __( 'The preview size of the grid.', 'gutenberg' ), + 'type' => 'number', + ), + 'density' => array( + 'description' => __( 'The density of the grid layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + ), + ), + ), + ), + 'pickerTable' => array( + 'description' => __( 'View overrides applied when the table layout of a picker (DataViewsPicker) is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to table-type layouts (`table`, `pickerTable`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'styles' => array( + 'description' => __( 'The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`).', 'gutenberg' ), + 'type' => 'object', + 'additionalProperties' => array( + 'description' => __( 'The style of a single field column.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'width' => array( + 'description' => __( 'The width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'maxWidth' => array( + 'description' => __( 'The maximum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'minWidth' => array( + 'description' => __( 'The minimum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'align' => array( + 'description' => __( 'The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'start', + 'center', + 'end', + ), + ), + ), + ), + ), + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + 'enableMoving' => array( + 'description' => __( 'Whether the user can reorder columns.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + ), + 'pickerActivity' => array( + 'description' => __( 'View overrides applied when the activity layout of a picker (DataViewsPicker) is selected.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'layout' => array( + 'description' => __( 'Options specific to list-type layouts (`list`, `activity`, `pickerActivity`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + ), + ), + ), + ), + ), + ), + 'view_list' => array( + 'description' => __( 'The preconfigured views displayed in the screen\'s sidebar.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'title' => array( + 'description' => __( 'Title of the view, displayed in the sidebar.', 'gutenberg' ), + 'type' => 'string', + ), + 'slug' => array( + 'description' => __( 'Unique identifier for the view. Used as the member identity when merging patches.', 'gutenberg' ), + 'type' => 'string', + ), + 'view' => array( + 'description' => __( 'Partial view configuration applied on top of [`default_view`](#default_view) when the view is selected — typically locked `filters`, but any view property works. Optional.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type, one of `table`, `grid`, `list`, `activity`, `pickerGrid`, `pickerTable`, or `pickerActivity`.', 'gutenberg' ), + 'type' => 'string', + ), + 'layout' => array( + 'description' => __( 'Configuration specific to the selected layout type. Accepts the `layout` options of any layout type; see [`default_layouts`](#default_layouts).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'styles' => array( + 'description' => __( 'The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`).', 'gutenberg' ), + 'type' => 'object', + 'additionalProperties' => array( + 'description' => __( 'The style of a single field column.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'width' => array( + 'description' => __( 'The width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'maxWidth' => array( + 'description' => __( 'The maximum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'minWidth' => array( + 'description' => __( 'The minimum width of the field column, as a CSS value or a number of pixels.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + 'align' => array( + 'description' => __( 'The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'start', + 'center', + 'end', + ), + ), + ), + ), + ), + 'density' => array( + 'description' => __( 'The density of the layout: `compact`, `balanced`, or `comfortable`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'compact', + 'balanced', + 'comfortable', + ), + ), + 'enableMoving' => array( + 'description' => __( 'Whether the user can reorder columns.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'badgeFields' => array( + 'description' => __( 'Ids of the fields to display as badges instead of regular fields.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'previewSize' => array( + 'description' => __( 'The preview size of the grid.', 'gutenberg' ), + 'type' => 'number', + ), + ), + ), + 'filters' => array( + 'description' => __( 'Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to filter by.', 'gutenberg' ), + 'type' => 'string', + ), + 'operator' => array( + 'description' => __( 'The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'is', + 'isNot', + 'isAny', + 'isNone', + 'isAll', + 'isNotAll', + 'lessThan', + 'greaterThan', + 'lessThanOrEqual', + 'greaterThanOrEqual', + 'before', + 'after', + ), + ), + 'value' => array( + 'description' => __( 'The value to filter by.', 'gutenberg' ), + ), + 'isLocked' => array( + 'description' => __( 'Whether the filter is locked. A locked filter cannot be changed or removed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + 'sort' => array( + 'description' => __( 'The default sort: the field id and the direction (`asc` or `desc`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to sort by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + ), + ), + 'perPage' => array( + 'description' => __( 'Number of records per page. Also used as the batch size when infinite scroll is enabled.', 'gutenberg' ), + 'type' => 'integer', + ), + 'fields' => array( + 'description' => __( 'Ids of the fields that are visible, in display order.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'titleField' => array( + 'description' => __( 'Id of the field used as the record title.', 'gutenberg' ), + 'type' => 'string', + ), + 'mediaField' => array( + 'description' => __( 'Id of the field used as the record media (e.g. featured image or preview).', 'gutenberg' ), + 'type' => 'string', + ), + 'descriptionField' => array( + 'description' => __( 'Id of the field used as the record description.', 'gutenberg' ), + 'type' => 'string', + ), + 'showTitle' => array( + 'description' => __( 'Whether the title is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showMedia' => array( + 'description' => __( 'Whether the media is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showDescription' => array( + 'description' => __( 'Whether the description is shown. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'showLevels' => array( + 'description' => __( 'Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'groupBy' => array( + 'description' => __( 'The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'field' => array( + 'description' => __( 'The field to group by.', 'gutenberg' ), + 'type' => 'string', + ), + 'direction' => array( + 'description' => __( 'The direction to sort the groups by, `asc` or `desc`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'asc', + 'desc', + ), + ), + 'showLabel' => array( + 'description' => __( 'Whether to show the field label in the group header.', 'gutenberg' ), + 'type' => 'boolean', + 'default' => true, + ), + ), + ), + 'infiniteScrollEnabled' => array( + 'description' => __( 'Whether infinite scroll is enabled instead of pagination.', 'gutenberg' ), + 'type' => 'boolean', + ), + ), + ), + ), + ), + ), + 'form' => array( + 'description' => __( 'The DataForm configuration for the Quick Edit form: which fields are displayed, in which order, and how each one is laid out.', 'gutenberg' ), + 'type' => 'object', + 'readonly' => true, + 'properties' => array( + 'layout' => array( + 'description' => __( 'The layout used to render the form fields, discriminated by its `type`. See the form layout types.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'The default layout: the field controls are rendered directly in the form, one after another.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'regular', + ), + ), + 'labelPosition' => array( + 'description' => __( 'Position of the field label: `top`, `side`, or `none`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'top', + 'side', + 'none', + ), + ), + ), + ), + array( + 'description' => __( 'The field is rendered as a button that opens a dropdown or modal with the field controls.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'panel', + ), + ), + 'labelPosition' => array( + 'description' => __( 'Position of the field label: `top`, `side`, or `none`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'top', + 'side', + 'none', + ), + ), + 'openAs' => array( + 'description' => __( 'How the panel opens: as a `dropdown` or as a `modal`. The object form allows customizing the labels of the modal buttons.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'The type of container to open, `dropdown` or `modal`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'dropdown', + 'modal', + ), + ), + array( + 'description' => __( 'The type of container to open, with custom labels for the modal buttons.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The type of container to open, `dropdown` or `modal`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'dropdown', + 'modal', + ), + ), + 'applyLabel' => array( + 'description' => __( 'Label of the modal button that applies the changes.', 'gutenberg' ), + 'type' => 'string', + ), + 'cancelLabel' => array( + 'description' => __( 'Label of the modal button that discards the changes.', 'gutenberg' ), + 'type' => 'string', + ), + ), + ), + ), + ), + 'summary' => array( + 'description' => __( 'Id(s) of the field(s) whose values are rendered in the panel button.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'A single field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A list of field ids.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + ), + 'editVisibility' => array( + 'description' => __( 'When the edit button is visible: `always` or `on-hover`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'always', + 'on-hover', + ), + ), + ), + ), + array( + 'description' => __( 'The fields are grouped in a card container.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'card', + ), + ), + 'withHeader' => array( + 'description' => __( 'Whether the card renders a header. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'isOpened' => array( + 'description' => __( 'Whether the card content is opened. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'isCollapsible' => array( + 'description' => __( 'Whether the card can be collapsed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'summary' => array( + 'description' => __( 'Id(s) of the field(s) whose values are rendered in the card header. An entry declared as an object controls when it is visible: `always` or `when-collapsed`.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'A single field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A list of field ids, optionally with their visibility.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'oneOf' => array( + array( + 'description' => __( 'A field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A field id with its visibility.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Id of the field.', 'gutenberg' ), + 'type' => 'string', + ), + 'visibility' => array( + 'description' => __( 'When the field value is visible in the card header: `always` or `when-collapsed`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'always', + 'when-collapsed', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + array( + 'description' => __( 'The fields are rendered horizontally in a single row.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'row', + ), + ), + 'alignment' => array( + 'description' => __( 'Vertical alignment of the fields in the row: `start`, `center`, or `end`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'start', + 'center', + 'end', + ), + ), + 'styles' => array( + 'description' => __( 'The styles for the fields in the row, keyed by field id. Each style accepts a `flex` value controlling how the field grows or shrinks.', 'gutenberg' ), + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'object', + 'properties' => array( + 'flex' => array( + 'description' => __( 'The CSS `flex` value for the field.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + ), + ), + ), + ), + ), + array( + 'description' => __( 'The fields are rendered inside a collapsible disclosure (details) element.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'details', + ), + ), + 'summary' => array( + 'description' => __( 'Label displayed as the summary of the disclosure element.', 'gutenberg' ), + 'type' => 'string', + ), + ), + ), + ), + ), + 'fields' => array( + 'description' => __( 'The fields of the form, in display order. Each entry is a field id, or an object for further configuration.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'description' => __( 'A form field: a field id, or an object for further configuration.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'A field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A form field with additional configuration.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Id of the field. Used as the member identity when merging patches.', 'gutenberg' ), + 'type' => 'string', + ), + 'label' => array( + 'description' => __( 'Label displayed for the field, overriding the field\'s own.', 'gutenberg' ), + 'type' => 'string', + ), + 'description' => array( + 'description' => __( 'Description displayed for the field.', 'gutenberg' ), + 'type' => 'string', + ), + 'layout' => array( + 'description' => __( 'The layout used to render the form fields, discriminated by its `type`. See the form layout types.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'The default layout: the field controls are rendered directly in the form, one after another.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'regular', + ), + ), + 'labelPosition' => array( + 'description' => __( 'Position of the field label: `top`, `side`, or `none`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'top', + 'side', + 'none', + ), + ), + ), + ), + array( + 'description' => __( 'The field is rendered as a button that opens a dropdown or modal with the field controls.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'panel', + ), + ), + 'labelPosition' => array( + 'description' => __( 'Position of the field label: `top`, `side`, or `none`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'top', + 'side', + 'none', + ), + ), + 'openAs' => array( + 'description' => __( 'How the panel opens: as a `dropdown` or as a `modal`. The object form allows customizing the labels of the modal buttons.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'The type of container to open, `dropdown` or `modal`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'dropdown', + 'modal', + ), + ), + array( + 'description' => __( 'The type of container to open, with custom labels for the modal buttons.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The type of container to open, `dropdown` or `modal`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'dropdown', + 'modal', + ), + ), + 'applyLabel' => array( + 'description' => __( 'Label of the modal button that applies the changes.', 'gutenberg' ), + 'type' => 'string', + ), + 'cancelLabel' => array( + 'description' => __( 'Label of the modal button that discards the changes.', 'gutenberg' ), + 'type' => 'string', + ), + ), + ), + ), + ), + 'summary' => array( + 'description' => __( 'Id(s) of the field(s) whose values are rendered in the panel button.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'A single field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A list of field ids.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + ), + ), + 'editVisibility' => array( + 'description' => __( 'When the edit button is visible: `always` or `on-hover`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'always', + 'on-hover', + ), + ), + ), + ), + array( + 'description' => __( 'The fields are grouped in a card container.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'card', + ), + ), + 'withHeader' => array( + 'description' => __( 'Whether the card renders a header. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'isOpened' => array( + 'description' => __( 'Whether the card content is opened. Defaults to `true`.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'isCollapsible' => array( + 'description' => __( 'Whether the card can be collapsed by the user.', 'gutenberg' ), + 'type' => 'boolean', + ), + 'summary' => array( + 'description' => __( 'Id(s) of the field(s) whose values are rendered in the card header. An entry declared as an object controls when it is visible: `always` or `when-collapsed`.', 'gutenberg' ), + 'oneOf' => array( + array( + 'description' => __( 'A single field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A list of field ids, optionally with their visibility.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'oneOf' => array( + array( + 'description' => __( 'A field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A field id with its visibility.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'id' => array( + 'description' => __( 'Id of the field.', 'gutenberg' ), + 'type' => 'string', + ), + 'visibility' => array( + 'description' => __( 'When the field value is visible in the card header: `always` or `when-collapsed`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'always', + 'when-collapsed', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + array( + 'description' => __( 'The fields are rendered horizontally in a single row.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'row', + ), + ), + 'alignment' => array( + 'description' => __( 'Vertical alignment of the fields in the row: `start`, `center`, or `end`.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'start', + 'center', + 'end', + ), + ), + 'styles' => array( + 'description' => __( 'The styles for the fields in the row, keyed by field id. Each style accepts a `flex` value controlling how the field grows or shrinks.', 'gutenberg' ), + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'object', + 'properties' => array( + 'flex' => array( + 'description' => __( 'The CSS `flex` value for the field.', 'gutenberg' ), + 'type' => array( + 'string', + 'number', + ), + ), + ), + ), + ), + ), + ), + array( + 'description' => __( 'The fields are rendered inside a collapsible disclosure (details) element.', 'gutenberg' ), + 'type' => 'object', + 'properties' => array( + 'type' => array( + 'description' => __( 'The layout type.', 'gutenberg' ), + 'type' => 'string', + 'enum' => array( + 'details', + ), + ), + 'summary' => array( + 'description' => __( 'Label displayed as the summary of the disclosure element.', 'gutenberg' ), + 'type' => 'string', + ), + ), + ), + ), + ), + 'children' => array( + 'description' => __( 'Fields combined under this entry, following the same shape as `fields`.', 'gutenberg' ), + 'type' => 'array', + 'items' => array( + 'oneOf' => array( + array( + 'description' => __( 'A field id.', 'gutenberg' ), + 'type' => 'string', + ), + array( + 'description' => __( 'A nested form field, following the same shape as an object entry of `fields`.', 'gutenberg' ), + 'type' => 'object', + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), + ), +); diff --git a/package-lock.json b/package-lock.json index 7cb4202040b525..7117b5c36dc4b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55246,6 +55246,7 @@ "@wordpress/prettier-config": "file:../../packages/prettier-config", "@wordpress/rich-text": "file:../../packages/rich-text", "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", "fast-glob": "^3.2.7", "react": "^18.3.1", "rimraf": "^5.0.10" diff --git a/package.json b/package.json index 8b29847b010139..104d870cc6f78f 100644 --- a/package.json +++ b/package.json @@ -108,11 +108,12 @@ "docs:api-ref": "npm run --workspace @wordpress/docs-tools docs:api-ref --", "docs:blocks": "npm run --workspace @wordpress/docs-tools docs:blocks --", "docs:blocks-detail": "npm run --workspace @wordpress/docs-tools docs:blocks-detail --", - "docs:build": "npm-run-all docs:components docs:gen docs:blocks docs:blocks-detail docs:api-ref docs:theme-ref", + "docs:build": "npm-run-all docs:components docs:gen docs:blocks docs:blocks-detail docs:api-ref docs:theme-ref docs:view-config-ref", "docs:check-api-docs-unstaged": "npm run --workspace @wordpress/docs-tools docs:check-api-docs-unstaged --", "docs:components": "npm run --workspace @wordpress/docs-tools docs:components --", "docs:gen": "npm run --workspace @wordpress/docs-tools docs:gen --", "docs:theme-ref": "npm run --workspace @wordpress/docs-tools docs:theme-ref --", + "docs:view-config-ref": "npm run --workspace @wordpress/docs-tools docs:view-config-ref --", "env": "wp-env", "fixtures:clean": "npm run --workspace @wordpress/integration-tests fixtures:clean --", "fixtures:generate": "cross-env GENERATE_MISSING_FIXTURES=y npm run test:unit -- test/integration/full-content/ && npm run format test/integration/fixtures/blocks/*.json", diff --git a/schemas/CHANGELOG.md b/schemas/CHANGELOG.md index 51d33d49707c27..7d6aebf16d958d 100644 --- a/schemas/CHANGELOG.md +++ b/schemas/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased +- Add new `view-config.json` schema. It is the canonical description of the response of the `/wp/v2/view-config` REST API endpoint and the source for the [view configuration reference docs](https://github.com/WordPress/gutenberg/blob/trunk/docs/reference-guides/view-config-reference.md). - Add new properties `settings.typography.fluid` and `settings.typography.fontSizes[n].fluidSize` to theme.json to enable fluid typography ([#39529](https://github.com/WordPress/gutenberg/pull/39529)). - Initial release. diff --git a/schemas/README.md b/schemas/README.md index 28359ba6086c59..13fe5c9e8d77bd 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -4,6 +4,8 @@ The collection of schemas used in WordPress, including the `theme.json`, `block. JSON schemas are used by code editors to offer tooltips, autocomplete, and validation. +Unlike the schemas above, `view-config.json` does not describe a file authored by developers: it documents the response of the `/wp/v2/view-config` REST API endpoint. It is the canonical source for both the endpoint's PHP schema (the generated `lib/compat/wordpress-7.1/view-config-schema.php`) and the [view configuration reference docs](../docs/reference-guides/view-config-reference.md); regenerate both via `npm run docs:view-config-ref`. + ## JSON schema usage Many editors recognize the `$schema` property in JSON files. diff --git a/schemas/json/view-config.json b/schemas/json/view-config.json new file mode 100644 index 00000000000000..06c61999798f72 --- /dev/null +++ b/schemas/json/view-config.json @@ -0,0 +1,973 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "view-config", + "type": "object", + "properties": { + "kind": { + "description": "Entity kind (e.g. `postType`).", + "type": "string", + "readonly": true + }, + "name": { + "description": "Entity name (e.g. `page`).", + "type": "string", + "readonly": true + }, + "version": { + "description": "The schema version (currently, 1).", + "type": "integer", + "readonly": true + }, + "default_view": { + "description": "The default DataViews configuration for the screen: layout type, visible fields, sorting, filtering, and pagination.", + "type": "object", + "readonly": true, + "properties": { + "type": { + "description": "The layout type.", + "type": "string", + "enum": [ + "table", + "grid", + "list", + "activity", + "pickerGrid", + "pickerTable", + "pickerActivity" + ] + }, + + "layout": { + "$ref": "#/definitions/combinedLayout" + }, + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + } + } + }, + "default_layouts": { + "description": "The layout types the user can switch between, and the view overrides each one applies.", + "type": "object", + "readonly": true, + "properties": { + "table": { + "description": "View overrides applied when the table layout is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/tableLayout" + } + } + }, + "list": { + "description": "View overrides applied when the list layout is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/listLayout" + } + } + }, + "grid": { + "description": "View overrides applied when the grid layout is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/gridLayout" + } + } + }, + "activity": { + "description": "View overrides applied when the activity layout is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/listLayout" + } + } + }, + "pickerGrid": { + "description": "View overrides applied when the grid layout of a picker (DataViewsPicker) is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/gridLayout" + } + } + }, + "pickerTable": { + "description": "View overrides applied when the table layout of a picker (DataViewsPicker) is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/tableLayout" + } + } + }, + "pickerActivity": { + "description": "View overrides applied when the activity layout of a picker (DataViewsPicker) is selected.", + "type": "object", + "properties": { + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + }, + "layout": { + "$ref": "#/definitions/listLayout" + } + } + } + } + }, + "view_list": { + "description": "The preconfigured views displayed in the screen's sidebar.", + "type": "array", + "readonly": true, + "items": { + "type": "object", + "properties": { + "title": { + "description": "Title of the view, displayed in the sidebar.", + "type": "string" + }, + "slug": { + "description": "Unique identifier for the view. Used as the member identity when merging patches.", + "type": "string" + }, + "view": { + "description": "Partial view configuration applied on top of [`default_view`](#default_view) when the view is selected — typically locked `filters`, but any view property works. Optional.", + "type": "object", + "properties": { + "type": { + "description": "The layout type, one of `table`, `grid`, `list`, `activity`, `pickerGrid`, `pickerTable`, or `pickerActivity`.", + "type": "string" + }, + "layout": { + "$ref": "#/definitions/combinedLayout" + }, + "filters": { + "$ref": "#/definitions/filters" + }, + "sort": { + "$ref": "#/definitions/sort" + }, + "perPage": { + "$ref": "#/definitions/perPage" + }, + "fields": { + "$ref": "#/definitions/fields" + }, + "titleField": { + "$ref": "#/definitions/titleField" + }, + "mediaField": { + "$ref": "#/definitions/mediaField" + }, + "descriptionField": { + "$ref": "#/definitions/descriptionField" + }, + "showTitle": { + "$ref": "#/definitions/showTitle" + }, + "showMedia": { + "$ref": "#/definitions/showMedia" + }, + "showDescription": { + "$ref": "#/definitions/showDescription" + }, + "showLevels": { + "$ref": "#/definitions/showLevels" + }, + "groupBy": { + "$ref": "#/definitions/groupBy" + }, + "infiniteScrollEnabled": { + "$ref": "#/definitions/infiniteScrollEnabled" + } + } + } + } + } + }, + "form": { + "description": "The DataForm configuration for the Quick Edit form: which fields are displayed, in which order, and how each one is laid out.", + "type": "object", + "readonly": true, + "properties": { + "layout": { + "$ref": "#/definitions/formLayout" + }, + "fields": { + "description": "The fields of the form, in display order. Each entry is a field id, or an object for further configuration.", + "type": "array", + "items": { + "$ref": "#/definitions/formField" + } + } + } + } + }, + "definitions": { + "filters": { + "description": "Filters applied to the dataset. A filter with `isLocked` set cannot be changed or removed by the user.", + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "description": "The field to filter by.", + "type": "string" + }, + "operator": { + "description": "The operator to use, one of `is`, `isNot`, `isAny`, `isNone`, `isAll`, `isNotAll`, `lessThan`, `greaterThan`, `lessThanOrEqual`, `greaterThanOrEqual`, `before`, or `after`.", + "type": "string", + "enum": [ + "is", + "isNot", + "isAny", + "isNone", + "isAll", + "isNotAll", + "lessThan", + "greaterThan", + "lessThanOrEqual", + "greaterThanOrEqual", + "before", + "after" + ] + }, + "value": { + "description": "The value to filter by." + }, + "isLocked": { + "description": "Whether the filter is locked. A locked filter cannot be changed or removed by the user.", + "type": "boolean" + } + } + } + }, + "sort": { + "description": "The default sort: the field id and the direction (`asc` or `desc`).", + "type": "object", + "properties": { + "field": { + "description": "The field to sort by.", + "type": "string" + }, + "direction": { + "description": "The direction to sort by, `asc` or `desc`.", + "type": "string", + "enum": [ "asc", "desc" ] + } + } + }, + "perPage": { + "description": "Number of records per page. Also used as the batch size when infinite scroll is enabled.", + "type": "integer" + }, + "fields": { + "description": "Ids of the fields that are visible, in display order.", + "type": "array", + "items": { + "type": "string" + } + }, + "titleField": { + "description": "Id of the field used as the record title.", + "type": "string" + }, + "mediaField": { + "description": "Id of the field used as the record media (e.g. featured image or preview).", + "type": "string" + }, + "descriptionField": { + "description": "Id of the field used as the record description.", + "type": "string" + }, + "showTitle": { + "description": "Whether the title is shown. Defaults to `true`.", + "type": "boolean" + }, + "showMedia": { + "description": "Whether the media is shown. Defaults to `true`.", + "type": "boolean" + }, + "showDescription": { + "description": "Whether the description is shown. Defaults to `true`.", + "type": "boolean" + }, + "showLevels": { + "description": "Whether to display hierarchical levels for the records (e.g. child pages indented under their parent). Defaults to `false`.", + "type": "boolean" + }, + "groupBy": { + "description": "The grouping configuration: the field to group by, the direction (`asc` or `desc`), and whether to show the field label in each group header (`showLabel`, defaults to `true`).", + "type": "object", + "properties": { + "field": { + "description": "The field to group by.", + "type": "string" + }, + "direction": { + "description": "The direction to sort the groups by, `asc` or `desc`.", + "type": "string", + "enum": [ "asc", "desc" ] + }, + "showLabel": { + "description": "Whether to show the field label in the group header.", + "type": "boolean", + "default": true + } + } + }, + "infiniteScrollEnabled": { + "description": "Whether infinite scroll is enabled instead of pagination.", + "type": "boolean" + }, + "columnStyle": { + "description": "The style of a single field column.", + "type": "object", + "properties": { + "width": { + "description": "The width of the field column, as a CSS value or a number of pixels.", + "type": [ "string", "number" ] + }, + "maxWidth": { + "description": "The maximum width of the field column, as a CSS value or a number of pixels.", + "type": [ "string", "number" ] + }, + "minWidth": { + "description": "The minimum width of the field column, as a CSS value or a number of pixels.", + "type": [ "string", "number" ] + }, + "align": { + "description": "The alignment of the field column: `start`, `center`, or `end`. Defaults to `start`.", + "type": "string", + "enum": [ "start", "center", "end" ] + } + } + }, + "tableLayout": { + "description": "Options specific to table-type layouts (`table`, `pickerTable`).", + "type": "object", + "properties": { + "styles": { + "description": "The styles for the columns, keyed by field id. Each column style accepts `width`, `maxWidth`, and `minWidth` (a CSS value or a number of pixels) and `align` (`start`, `center`, or `end`).", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/columnStyle" + } + }, + "density": { + "description": "The density of the layout: `compact`, `balanced`, or `comfortable`.", + "type": "string", + "enum": [ "compact", "balanced", "comfortable" ] + }, + "enableMoving": { + "description": "Whether the user can reorder columns.", + "type": "boolean" + } + } + }, + "listLayout": { + "description": "Options specific to list-type layouts (`list`, `activity`, `pickerActivity`).", + "type": "object", + "properties": { + "density": { + "description": "The density of the layout: `compact`, `balanced`, or `comfortable`.", + "type": "string", + "enum": [ "compact", "balanced", "comfortable" ] + } + } + }, + "gridLayout": { + "description": "Options specific to grid-type layouts (`grid`, `pickerGrid`).", + "type": "object", + "properties": { + "badgeFields": { + "description": "Ids of the fields to display as badges instead of regular fields.", + "type": "array", + "items": { + "type": "string" + } + }, + "previewSize": { + "description": "The preview size of the grid.", + "type": "number" + }, + "density": { + "description": "The density of the grid layout: `compact`, `balanced`, or `comfortable`.", + "type": "string", + "enum": [ "compact", "balanced", "comfortable" ] + } + } + }, + "combinedLayout": { + "description": "Configuration specific to the selected layout type. Accepts the `layout` options of any layout type; see [`default_layouts`](#default_layouts).", + "type": "object", + "properties": { + "styles": { + "$ref": "#/definitions/tableLayout/properties/styles" + }, + "density": { + "description": "The density of the layout: `compact`, `balanced`, or `comfortable`.", + "type": "string", + "enum": [ "compact", "balanced", "comfortable" ] + }, + "enableMoving": { + "$ref": "#/definitions/tableLayout/properties/enableMoving" + }, + "badgeFields": { + "$ref": "#/definitions/gridLayout/properties/badgeFields" + }, + "previewSize": { + "$ref": "#/definitions/gridLayout/properties/previewSize" + } + } + }, + "formLayout": { + "description": "The layout used to render the form fields, discriminated by its `type`. See the form layout types.", + "oneOf": [ + { + "description": "The default layout: the field controls are rendered directly in the form, one after another.", + "type": "object", + "properties": { + "type": { + "description": "The layout type.", + "type": "string", + "enum": [ "regular" ] + }, + "labelPosition": { + "description": "Position of the field label: `top`, `side`, or `none`.", + "type": "string", + "enum": [ "top", "side", "none" ] + } + } + }, + { + "description": "The field is rendered as a button that opens a dropdown or modal with the field controls.", + "type": "object", + "properties": { + "type": { + "description": "The layout type.", + "type": "string", + "enum": [ "panel" ] + }, + "labelPosition": { + "description": "Position of the field label: `top`, `side`, or `none`.", + "type": "string", + "enum": [ "top", "side", "none" ] + }, + "openAs": { + "description": "How the panel opens: as a `dropdown` or as a `modal`. The object form allows customizing the labels of the modal buttons.", + "oneOf": [ + { + "description": "The type of container to open, `dropdown` or `modal`.", + "type": "string", + "enum": [ "dropdown", "modal" ] + }, + { + "description": "The type of container to open, with custom labels for the modal buttons.", + "type": "object", + "properties": { + "type": { + "description": "The type of container to open, `dropdown` or `modal`.", + "type": "string", + "enum": [ "dropdown", "modal" ] + }, + "applyLabel": { + "description": "Label of the modal button that applies the changes.", + "type": "string" + }, + "cancelLabel": { + "description": "Label of the modal button that discards the changes.", + "type": "string" + } + } + } + ] + }, + "summary": { + "description": "Id(s) of the field(s) whose values are rendered in the panel button.", + "oneOf": [ + { + "description": "A single field id.", + "type": "string" + }, + { + "description": "A list of field ids.", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "editVisibility": { + "description": "When the edit button is visible: `always` or `on-hover`.", + "type": "string", + "enum": [ "always", "on-hover" ] + } + } + }, + { + "description": "The fields are grouped in a card container.", + "type": "object", + "properties": { + "type": { + "description": "The layout type.", + "type": "string", + "enum": [ "card" ] + }, + "withHeader": { + "description": "Whether the card renders a header. Defaults to `true`.", + "type": "boolean" + }, + "isOpened": { + "description": "Whether the card content is opened. Defaults to `true`.", + "type": "boolean" + }, + "isCollapsible": { + "description": "Whether the card can be collapsed by the user.", + "type": "boolean" + }, + "summary": { + "description": "Id(s) of the field(s) whose values are rendered in the card header. An entry declared as an object controls when it is visible: `always` or `when-collapsed`.", + "oneOf": [ + { + "description": "A single field id.", + "type": "string" + }, + { + "description": "A list of field ids, optionally with their visibility.", + "type": "array", + "items": { + "oneOf": [ + { + "description": "A field id.", + "type": "string" + }, + { + "description": "A field id with its visibility.", + "type": "object", + "properties": { + "id": { + "description": "Id of the field.", + "type": "string" + }, + "visibility": { + "description": "When the field value is visible in the card header: `always` or `when-collapsed`.", + "type": "string", + "enum": [ + "always", + "when-collapsed" + ] + } + } + } + ] + } + } + ] + } + } + }, + { + "description": "The fields are rendered horizontally in a single row.", + "type": "object", + "properties": { + "type": { + "description": "The layout type.", + "type": "string", + "enum": [ "row" ] + }, + "alignment": { + "description": "Vertical alignment of the fields in the row: `start`, `center`, or `end`.", + "type": "string", + "enum": [ "start", "center", "end" ] + }, + "styles": { + "description": "The styles for the fields in the row, keyed by field id. Each style accepts a `flex` value controlling how the field grows or shrinks.", + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "flex": { + "description": "The CSS `flex` value for the field.", + "type": [ "string", "number" ] + } + } + } + } + } + }, + { + "description": "The fields are rendered inside a collapsible disclosure (details) element.", + "type": "object", + "properties": { + "type": { + "description": "The layout type.", + "type": "string", + "enum": [ "details" ] + }, + "summary": { + "description": "Label displayed as the summary of the disclosure element.", + "type": "string" + } + } + } + ] + }, + "formField": { + "description": "A form field: a field id, or an object for further configuration.", + "oneOf": [ + { + "description": "A field id.", + "type": "string" + }, + { + "description": "A form field with additional configuration.", + "type": "object", + "properties": { + "id": { + "description": "Id of the field. Used as the member identity when merging patches.", + "type": "string" + }, + "label": { + "description": "Label displayed for the field, overriding the field's own.", + "type": "string" + }, + "description": { + "description": "Description displayed for the field.", + "type": "string" + }, + "layout": { + "$ref": "#/definitions/formLayout" + }, + "children": { + "description": "Fields combined under this entry, following the same shape as `fields`.", + "type": "array", + "items": { + "oneOf": [ + { + "description": "A field id.", + "type": "string" + }, + { + "description": "A nested form field, following the same shape as an object entry of `fields`.", + "type": "object" + } + ] + } + } + } + } + ] + } + } +} diff --git a/test/integration/package.json b/test/integration/package.json index fc9d61eeea0b47..49f012cd91e83c 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -35,6 +35,7 @@ "@wordpress/prettier-config": "file:../../packages/prettier-config", "@wordpress/rich-text": "file:../../packages/rich-text", "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", "fast-glob": "^3.2.7", "react": "^18.3.1", "rimraf": "^5.0.10" diff --git a/test/integration/view-config-schema.test.js b/test/integration/view-config-schema.test.js new file mode 100644 index 00000000000000..2efa60cdfde8ca --- /dev/null +++ b/test/integration/view-config-schema.test.js @@ -0,0 +1,59 @@ +/** + * External dependencies + */ +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import Ajv from 'ajv-draft-04'; + +/** + * Internal dependencies + */ +import viewConfigSchema from '../../schemas/json/view-config.json'; + +describe( 'view-config schema', () => { + // The WP REST API speaks JSON Schema draft-04, so the schema must be + // compiled with the draft-04 Ajv class rather than the default one. + const ajv = new Ajv( { + // Some properties accept several primitive types, e.g. a column + // width declared as a string or a number. + allowUnionTypes: true, + } ); + + // `readonly` (lowercase) is the WP REST API flavor of the `readOnly` + // annotation; register it so the strict compilation accepts it. + ajv.addKeyword( 'readonly' ); + + test( 'strictly adheres to the draft-04 meta schema', () => { + // Use ajv.compile instead of ajv.validateSchema to validate the schema + // because validateSchema only checks syntax, whereas, compile checks + // if the schema is semantically correct with strict mode. + // See https://github.com/ajv-validator/ajv/issues/1434#issuecomment-822982571 + const result = ajv.compile( viewConfigSchema ); + + expect( result.errors ).toBe( null ); + } ); + + test( 'the generated PHP schema file is up to date', () => { + // The REST endpoint consumes the schema through the generated + // lib/compat/wordpress-7.1/view-config-schema.php file. Regenerating + // it must be a no-op, otherwise the two artifacts have drifted. + const { status, stderr } = spawnSync( + process.execPath, + [ + path.join( + __dirname, + '..', + '..', + 'tools', + 'docs', + 'gen-view-config-schema-php.mjs' + ), + '--check', + ], + { encoding: 'utf8' } + ); + + expect( stderr ).toBe( '' ); + expect( status ).toBe( 0 ); + } ); +} ); diff --git a/tools/docs/gen-view-config-reference.mjs b/tools/docs/gen-view-config-reference.mjs new file mode 100644 index 00000000000000..2447964cafe63c --- /dev/null +++ b/tools/docs/gen-view-config-reference.mjs @@ -0,0 +1,413 @@ +/** + * Generates the view configuration documentation using the view-config schema. + * Reads from : schemas/json/view-config.json + * Publishes to: docs/reference-guides/view-config-reference.md + * + * The reference doc is hand-written prose with one autogenerated block per + * top-level schema property (`default_view`, `default_layouts`, `view_list`, + * and `form`), delimited by per-section token pairs: + * + * + * + * + * Each property is documented as its own subsection: the description, + * followed by a list of schema facts (type, enum, default) and, for object + * properties, a nested list of their properties. + */ + +/** + * External dependencies + */ +import fs from 'node:fs/promises'; +import $RefParser from '@apidevtools/json-schema-ref-parser'; +import { fileURLToPath } from 'node:url'; + +/** + * @typedef {import('@apidevtools/json-schema-ref-parser').JSONSchema} JSONSchema + */ + +/** + * Path to view-config json schema file. + * + * @type {string} + */ +const VIEW_CONFIG_SCHEMA_PATH = fileURLToPath( + new URL( '../../schemas/json/view-config.json', import.meta.url ) +); + +/** + * Path to docs file. + * + * @type {string} + */ +const REFERENCE_DOC_PATH = fileURLToPath( + new URL( + '../../docs/reference-guides/view-config-reference.md', + import.meta.url + ) +); + +/** + * Returns the start/end tokens delimiting the autogenerated block of a + * top-level schema property. + * + * @param {string} section Top-level schema property name. + * @return {[string, string]} Start and end tokens. + */ +function getTokens( section ) { + return [ + ``, + ``, + ]; +} + +/** + * Maximum depth of nested property lists. The schema does not nest deeper; + * this is a backstop against unbounded recursion if it ever references + * itself (e.g. form fields nesting form fields). + * + * @type {number} + */ +const MAX_PROPERTY_DEPTH = 2; + +/** + * Whether a schema is a union of objects discriminated by a single-value + * `type` enum (e.g. the form layout variants). Those unions are documented + * with their own per-variant subsections, so their type is summarized as a + * plain `object` and their properties are not listed. + * + * @param {JSONSchema} schema JSON schema. + * @return {boolean} Whether the schema is a discriminated union. + */ +function isDiscriminatedUnion( schema ) { + const branches = schema.oneOf || schema.anyOf; + return ( + Array.isArray( branches ) && + branches.every( + ( branch ) => branch.properties?.type?.enum?.length === 1 + ) + ); +} + +/** + * Serialize the values of an enum as a union of literals. + * + * @param {Array} values Enum values. + * @return {string} Serialized enum. + */ +function serializeEnum( values ) { + return values.map( ( value ) => `"${ value }"` ).join( ' | ' ); +} + +/** + * Serialize a schema type, without the wrapping backticks. Objects are + * summarized as `object`: their properties are listed separately; see + * findObjectProperties(). + * + * @param {JSONSchema} schema JSON schema. + * @return {string} Serialized type. + */ +function serializeType( schema ) { + const branches = schema.oneOf || schema.anyOf; + if ( branches ) { + if ( isDiscriminatedUnion( schema ) ) { + return 'object'; + } + return [ ...new Set( branches.map( serializeType ) ) ].join( ' | ' ); + } + if ( Array.isArray( schema.type ) ) { + return schema.type.join( ' | ' ); + } + if ( schema.enum?.length >= 1 ) { + return serializeEnum( schema.enum ); + } + if ( schema.type === 'array' ) { + return schema.items + ? `[ ${ serializeType( schema.items ) } ]` + : 'array'; + } + if ( schema.type === 'object' ) { + return schema.additionalProperties?.properties + ? '{ [ field ]: object }' + : 'object'; + } + return schema.type ?? 'any'; +} + +/** + * Find the object properties to list for a schema: its own, those of its + * `additionalProperties` (objects keyed by field id), those of the object + * branch of its union, or the same lookup on its array items. Returns null + * when there is nothing to list — scalar types, plain objects, and + * discriminated unions (documented in their own subsections). + * + * @param {JSONSchema} schema JSON schema. + * @return {{properties: Record, viaArray: boolean}|null} + * The properties map, and whether it was found through array items. + */ +function findObjectProperties( schema ) { + if ( isDiscriminatedUnion( schema ) ) { + return null; + } + if ( schema.properties ) { + return { properties: schema.properties, viaArray: false }; + } + if ( schema.additionalProperties?.properties ) { + return { + properties: schema.additionalProperties.properties, + viaArray: false, + }; + } + const branches = schema.oneOf || schema.anyOf; + if ( branches ) { + for ( const branch of branches ) { + const found = findObjectProperties( branch ); + if ( found ) { + return found; + } + } + return null; + } + if ( schema.type === 'array' && schema.items ) { + const found = findObjectProperties( schema.items ); + return found && { ...found, viaArray: true }; + } + return null; +} + +/** + * Generate a nested list of properties: one item per property with its + * type, description, and default, recursing into object properties. + * + * @param {Record} properties Schema properties map. + * @param {string} indent Current list indentation. + * @param {number} depth Current nesting depth. + * @return {string} Markdown list. + */ +function generatePropertyItems( properties, indent = '', depth = 1 ) { + let md = ''; + for ( const [ property, schema ] of Object.entries( properties ) ) { + let item = `${ indent }- \`${ property }\` (\`${ serializeType( + schema + ) }\`)`; + if ( schema.description ) { + item += `: ${ schema.description }`; + } + if ( 'default' in schema ) { + item += ` Default: \`${ JSON.stringify( schema.default ) }\`.`; + } + md += `${ item }\n`; + + if ( depth < MAX_PROPERTY_DEPTH ) { + const nested = findObjectProperties( schema ); + if ( nested ) { + md += generatePropertyItems( + nested.properties, + `${ indent } `, + depth + 1 + ); + } + } + } + return md; +} + +/** + * Generate the subsection of a single property: a heading, the + * description, and a list of schema facts (type, enum, default, and the + * nested properties of objects). + * + * @param {string} property Property name. + * @param {JSONSchema} schema Property JSON schema. + * @param {Object} options Options. + * @param {string} options.headingLevel Markdown heading marker. + * @param {boolean} options.skipProperties Whether to omit the nested + * property list, for objects + * documented in another section. + * @return {string} Markdown content. + */ +function generateProperty( + property, + schema, + { headingLevel = '###', skipProperties = false } = {} +) { + let md = `${ headingLevel } ${ property }\n\n`; + if ( schema.description ) { + md += `${ schema.description }\n\n`; + } + md += `- Type: \`${ + schema.enum ? schema.type : serializeType( schema ) + }\`\n`; + if ( schema.enum?.length > 1 ) { + md += `- Enum: \`${ serializeEnum( schema.enum ) }\`\n`; + } + if ( 'default' in schema ) { + md += `- Default: \`${ JSON.stringify( schema.default ) }\`\n`; + } + if ( ! skipProperties ) { + const nested = findObjectProperties( schema ); + if ( nested ) { + md += `- ${ + nested.viaArray ? 'Item properties' : 'Properties' + }:\n`; + md += generatePropertyItems( nested.properties, ' ' ); + } + } + return md; +} + +/** + * Generate one subsection per property, separated by horizontal rules. + * + * @param {Record} properties Schema properties map. + * @param {string[]} skipProperties Properties whose + * nested property list + * is omitted. + * @return {string} Markdown content. + */ +function generateSubsections( properties, skipProperties = [] ) { + return Object.entries( properties ) + .map( ( [ property, schema ] ) => + generateProperty( property, schema, { + skipProperties: skipProperties.includes( property ), + } ) + ) + .join( '\n---\n\n' ); +} + +/** + * Generate the `default_view` block. + * + * @param {JSONSchema} schema Dereferenced view-config schema. + * @return {string} Markdown content. + */ +function generateDefaultView( schema ) { + // The `layout` keys are documented per layout type in the + // `default_layouts` subsections, which the description links to. + return generateSubsections( schema.properties.default_view.properties, [ + 'layout', + ] ); +} + +/** + * Generate the `default_layouts` block: one subsection per layout type + * with the list of its `layout` options. + * + * @param {JSONSchema} schema Dereferenced view-config schema. + * @return {string} Markdown content. + */ +function generateDefaultLayouts( schema ) { + const layouts = schema.properties.default_layouts.properties; + return Object.entries( layouts ) + .map( ( [ type, subschema ] ) => { + let md = `### ${ type }\n\n`; + md += `${ subschema.description } In addition to the [view properties](#default_view), the \`layout\` key accepts:\n\n`; + md += generatePropertyItems( + subschema.properties.layout.properties + ); + return md; + } ) + .join( '\n---\n\n' ); +} + +/** + * Generate the `view_list` block. + * + * @param {JSONSchema} schema Dereferenced view-config schema. + * @return {string} Markdown content. + */ +function generateViewList( schema ) { + // The `view` keys are the view properties documented in `default_view`, + // which the description links to. + return generateSubsections( schema.properties.view_list.items.properties, [ + 'view', + ] ); +} + +/** + * Generate the `form` block: one subsection per form property, followed by + * one subsection per form layout type. + * + * @param {JSONSchema} schema Dereferenced view-config schema. + * @return {string} Markdown content. + */ +function generateForm( schema ) { + const form = schema.properties.form; + + let md = generateSubsections( form.properties ); + + md += '\n---\n\n### Form layout types\n\n'; + md += + 'Both the form-level `layout` and a field-level `layout` accept one of the following objects, discriminated by their `type` property:\n\n'; + md += form.properties.layout.oneOf + .map( ( variant ) => { + let variantMd = `#### ${ variant.properties.type.enum[ 0 ] }\n\n`; + variantMd += `${ variant.description }\n\n`; + variantMd += generatePropertyItems( variant.properties ); + return variantMd; + } ) + .join( '\n' ); + return md; +} + +/** + * Section generators, keyed by top-level schema property. + * + * @type {Record string>} + */ +const SECTIONS = { + default_view: generateDefaultView, + default_layouts: generateDefaultLayouts, + view_list: generateViewList, + form: generateForm, +}; + +/** + * Escape the special regex characters of a string literal. + * + * @param {string} value String literal. + * @return {string} Escaped string. + */ +function escapeRegExp( value ) { + return value.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); +} + +/** + * Main function. + */ +async function main() { + const schema = await $RefParser.dereference( VIEW_CONFIG_SCHEMA_PATH, { + parse: { binary: false, text: false, yaml: false }, + resolve: { external: false }, + } ); + + let doc = await fs.readFile( REFERENCE_DOC_PATH, { + encoding: 'utf8', + flag: 'r', + } ); + + for ( const [ section, generate ] of Object.entries( SECTIONS ) ) { + const [ startToken, endToken ] = getTokens( section ); + if ( ! doc.includes( startToken ) || ! doc.includes( endToken ) ) { + throw new Error( + `Missing "${ section }" tokens in ${ REFERENCE_DOC_PATH }.` + ); + } + doc = doc.replace( + new RegExp( + `${ escapeRegExp( startToken ) }.*${ escapeRegExp( + endToken + ) }`, + 's' + ), + () => `${ startToken }\n${ generate( schema ) }\n${ endToken }` + ); + } + + await fs.writeFile( REFERENCE_DOC_PATH, doc, { encoding: 'utf8' } ); +} + +main().catch( ( error ) => { + console.error( error ); + process.exit( 1 ); +} ); diff --git a/tools/docs/gen-view-config-schema-php.mjs b/tools/docs/gen-view-config-schema-php.mjs new file mode 100644 index 00000000000000..999ff281627a08 --- /dev/null +++ b/tools/docs/gen-view-config-schema-php.mjs @@ -0,0 +1,191 @@ +/** + * Generates the PHP copy of the view-config REST schema from the canonical + * JSON Schema. + * + * Reads from : schemas/json/view-config.json + * Publishes to: lib/compat/wordpress-7.1/view-config-schema.php + * + * The generated file returns the schema as a PHP array with all local `$ref` + * pointers dereferenced, the `definitions` map dropped, and `description` + * annotations wrapped in `__()` calls so they are picked up by the plugin's + * translation pipeline. + * + * Usage: + * node tools/docs/gen-view-config-schema-php.mjs # (re)generate + * node tools/docs/gen-view-config-schema-php.mjs --check # fail if stale + * + * This script is dependency-free on purpose so the `--check` mode can run + * in any environment (e.g. as part of the integration test suite). + */ + +/** + * External dependencies + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { parseArgs } from 'node:util'; + +/** + * Path to the canonical view-config JSON Schema. + * + * @type {string} + */ +const VIEW_CONFIG_SCHEMA_PATH = fileURLToPath( + new URL( '../../schemas/json/view-config.json', import.meta.url ) +); + +/** + * Path to the generated PHP schema file. + * + * @type {string} + */ +const PHP_SCHEMA_PATH = fileURLToPath( + new URL( + '../../lib/compat/wordpress-7.1/view-config-schema.php', + import.meta.url + ) +); + +/** + * Resolves local `$ref` pointers (e.g. `#/definitions/foo` or + * `#/definitions/foo/properties/bar`) against the schema root. + * + * @param {*} node Schema node to resolve. + * @param {Object} root Schema root the pointers are resolved against. + * @return {*} The resolved node. + */ +function resolveRefs( node, root ) { + if ( Array.isArray( node ) ) { + return node.map( ( item ) => resolveRefs( item, root ) ); + } + if ( ! node || typeof node !== 'object' ) { + return node; + } + if ( typeof node.$ref === 'string' && node.$ref.startsWith( '#/' ) ) { + let target = root; + for ( const rawSegment of node.$ref.slice( 2 ).split( '/' ) ) { + const segment = rawSegment + .replaceAll( '~1', '/' ) + .replaceAll( '~0', '~' ); + if ( ! target || ! ( segment in target ) ) { + throw new Error( `Unresolvable $ref \`${ node.$ref }\`.` ); + } + target = target[ segment ]; + } + return resolveRefs( target, root ); + } + return Object.fromEntries( + Object.entries( node ).map( ( [ key, value ] ) => [ + key, + resolveRefs( value, root ), + ] ) + ); +} + +/** + * Serializes a value as a PHP literal. + * + * `description` annotations are emitted as translatable strings, i.e. + * `__( '…', 'gutenberg' )`. Only string values qualify: a schema *property* + * named `description` (e.g. a form field description) maps to an object, so + * it is serialized as a regular array. + * + * @param {*} value Value to serialize. + * @param {string} indent Current indentation. + * @return {string} PHP literal. + */ +function toPhp( value, indent = '' ) { + if ( value === null ) { + return 'null'; + } + if ( typeof value === 'boolean' || typeof value === 'number' ) { + return JSON.stringify( value ); + } + if ( typeof value === 'string' ) { + return `'${ value + .replaceAll( '\\', '\\\\' ) + .replaceAll( "'", "\\'" ) }'`; + } + const inner = indent + '\t'; + if ( Array.isArray( value ) ) { + if ( value.length === 0 ) { + return 'array()'; + } + const items = value + .map( ( item ) => `${ inner }${ toPhp( item, inner ) },\n` ) + .join( '' ); + return `array(\n${ items }${ indent })`; + } + const entries = Object.entries( value ); + if ( entries.length === 0 ) { + return 'array()'; + } + const items = entries + .map( ( [ key, item ] ) => { + const serialized = + key === 'description' && typeof item === 'string' + ? `__( ${ toPhp( item ) }, 'gutenberg' )` + : toPhp( item, inner ); + return `${ inner }${ toPhp( key ) } => ${ serialized },\n`; + } ) + .join( '' ); + return `array(\n${ items }${ indent })`; +} + +/** + * Generates the content of the PHP schema file. + * + * @return {string} PHP source. + */ +export function generate() { + const schema = JSON.parse( + fs.readFileSync( VIEW_CONFIG_SCHEMA_PATH, 'utf8' ) + ); + const dereferenced = resolveRefs( schema, schema ); + delete dereferenced.definitions; + + return `