Skip to content

Commit e2c3df7

Browse files
committed
sync widget docs with the example field
1 parent 4cba7d7 commit e2c3df7

2 files changed

Lines changed: 17 additions & 16 deletions

File tree

‎docs/explanations/architecture/dashboard-widgets.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ A widget is a directory under `widgets/`, discovered by convention; there is no
2222

2323
```
2424
widgets/hello-world/
25-
├── widget.json static metadata (name, title, description, help, icon, actions, attributes, keywords, category, presentation, textdomain)
26-
├── widget.ts optional metadata module: default-exports example, and attribute props that need code
25+
├── widget.json static metadata (name, title, description, help, icon, actions, attributes, example, keywords, category, presentation, textdomain)
26+
├── widget.ts optional metadata module: default-exports the attribute props and example values that need code
2727
├── render.tsx render module: default-exports the React component
2828
├── style.module.css optional, injected at runtime by the build
2929
└── report.csv optional static asset; action `href`s resolve it only where `widgets/` ships
@@ -39,7 +39,9 @@ Unlike the other translatable strings, `help` is an object: `content` plus optio
3939

4040
`attributes`: the schema hosts render settings from, one entry per attribute with the JSON-expressible subset of a DataViews `Field` (`id`, `type`, `label`, `elements`, `isValid` without `custom`, ...) plus an optional `relevance` hint. `type` may name a registered field type. Labels, headers, descriptions, placeholders, and option labels and descriptions are translated server-side.
4141

42-
`widget.ts` is the live half of the metadata: values that only exist in JavaScript, such as `example` data, or an attribute's `Edit` component or custom validator. It is optional: a widget declared entirely by its manifest ships none, and resolves from its record alone.
42+
`example`: the attributes hosts preview the widget with, and apply to a new instance created without any, as `{ "attributes": { … } }`. JSON values only, and not translated: example values are data.
43+
44+
`widget.ts` is the live half of the metadata: values that only exist in JavaScript, such as an attribute's `Edit` component or custom validator, or `example` values that need code. It is optional: a widget declared entirely by its manifest ships none, and resolves from its record alone.
4345

4446
Its render component receives the widget's `attributes` and, optionally, `setAttributes`:
4547

@@ -58,13 +60,13 @@ export default function HelloWorld( { attributes } ) { ... }
5860

5961
## The server registry
6062

61-
`WP_Widget_Type_Registry` (`lib/experimental/dashboard-widgets/`) is a singleton, hydrated at `init` from the manifest. Each entry becomes a `WP_Widget_Type` with `name`, `render_module`, `widget_module`, `presentation`, `category`, `icon`, and the translatable `title`, `description`, `help`, `actions`, `attributes`, and `keywords` (localized at registration time using the widget's `textdomain`).
63+
`WP_Widget_Type_Registry` (`lib/experimental/dashboard-widgets/`) is a singleton, hydrated at `init` from the manifest. Each entry becomes a `WP_Widget_Type` with `name`, `render_module`, `widget_module`, `presentation`, `category`, `icon`, `example`, and the translatable `title`, `description`, `help`, `actions`, `attributes`, and `keywords` (localized at registration time using the widget's `textdomain`).
6264

6365
The hydration is a deterministic copy, with no filters in between. The `widgets/` folder is the single source of widget authorship in this codebase.
6466

6567
The registry is the server's authoritative list of widget types for the site. Two consumers read it:
6668

67-
- The REST controller (`WP_REST_Widget_Modules_Controller`) exposes it at `/wp/v2/widget-modules`, returning `{ name, render_module, widget_module, presentation, category, title, description, help, icon, actions, attributes, keywords }` per record.
69+
- The REST controller (`WP_REST_Widget_Modules_Controller`) exposes it at `/wp/v2/widget-modules`, returning `{ name, render_module, widget_module, presentation, category, title, description, help, icon, actions, attributes, example, keywords }` per record.
6870
- The dashboard page hooks its `dashboard-wp-admin_boot_dependencies` filter, a per-page instance of the generic `{page-slug}-wp-admin_boot_dependencies`, and adds every registered module to its import map as a `dynamic` dependency. A dynamic dependency is reachable by `import()` but never executed eagerly.
6971

7072
Registration only makes the modules known to WordPress; loading them is a separate, per-host decision. Dynamic `import()` against the import map is how the dashboard loads widgets today. A host can load them another way: enqueue a module eagerly (`wp_enqueue_script_module()`), declare it as a `static` dependency of its own module, or, outside WordPress, skip the import map and resolve modules through its own `ResolveWidgetModule`.
@@ -75,7 +77,7 @@ The registry exists as a class, rather than having REST read the manifest direct
7577

7678
Everything after the REST record is the job of [`@wordpress/widget-primitives`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/widget-primitives), the contract both widget authors and hosts share. Its full surface (the contract types, the discovery hook, and the render component) is covered in the _Widget Primitives / Introduction_ story. In the pipeline it does two things.
7779

78-
`useWidgetTypes( records )` takes the host-supplied records, imports each record's `widget_module` for the live metadata, and merges it with the record into `WidgetType[]`. A record without a metadata module resolves from its own fields, as long as it carries a `render_module`. The record's `presentation`, `category`, `title`, `description`, `help`, `actions`, `attributes`, and `keywords`, all sourced from `widget.json` (with `title`, `description`, `help`, `actions`, `attributes`, and `keywords` localized server-side), win over the module's value. `attributes` merges per entry by `id`: the record wins a shared key, a module entry adds what JSON cannot express, `isValid` merges rule by rule, and module-only entries follow. The record's `icon` is a registered icon name: the hook resolves it through the application-registered resolver (`registerIconResolver`), and the resolved element wins over a module's element. The hook reaches for no store or endpoint; a host such as the dashboard reads its own `widgetModule` core-data entity (backed by `/wp/v2/widget-modules`) and passes the records in.
80+
`useWidgetTypes( records )` takes the host-supplied records, imports each record's `widget_module` for the live metadata, and merges it with the record into `WidgetType[]`. A record without a metadata module resolves from its own fields, as long as it carries a `render_module`. The record's `presentation`, `category`, `title`, `description`, `help`, `actions`, `attributes`, and `keywords`, all sourced from `widget.json` (with `title`, `description`, `help`, `actions`, `attributes`, and `keywords` localized server-side), win over the module's value. `attributes` merges per entry by `id`: the record wins a shared key, a module entry adds what JSON cannot express, `isValid` merges rule by rule, and module-only entries follow. `example` is the exception: the module's wins, and the record's stands in only when the module declares none, so values that need code stay in the module. The record's `icon` is a registered icon name: the hook resolves it through the application-registered resolver (`registerIconResolver`), and the resolved element wins over a module's element. The hook reaches for no store or endpoint; a host such as the dashboard reads its own `widgetModule` core-data entity (backed by `/wp/v2/widget-modules`) and passes the records in.
7981

8082
An attribute may also reference a field type by name (`"type": "location"`). The application registers those definitions up front through `registerFieldType()` (the dashboard route registers its own on boot), and `useWidgetTypes` resolves every named reference through that registry while building each `WidgetType`: the registered definition supplies the field's behavior on top of its DataViews `baseType`, and hosts receive plain DataViews fields. The widget declaration stays serializable; resolution happens once, at this boundary.
8183

‎packages/wp-build/README.md‎

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -613,9 +613,10 @@ Widgets use a dual-entry pattern, similar in spirit to how blocks split metadata
613613
| Translatable metadata (`title`, `description`, `help`, `keywords`) | `widget.json` | translated server-side via `textdomain`, so the host receives localized strings without a JS runtime |
614614
| Framing (`category`, `presentation`) | `widget.json` | plain JSON the host reads without a JS runtime |
615615
| Attribute schema (types, options, labels, `relevance`) | `widget.json` | plain JSON; labels translated server-side via `textdomain`; a `widget.ts` entry with the same `id` adds what JSON cannot express |
616-
| `example`, attribute props that need code | `widget.ts` | runtime values |
616+
| `example` attributes | `widget.json` | plain JSON; a `widget.ts` `example` wins when its values need code |
617+
| Attribute props that need code | `widget.ts` | runtime values |
617618

618-
Rule of thumb: anything the host needs before loading a widget's module — identity, translatable copy, framing, attribute schema — goes in `widget.json`, which the build forwards to `build/widgets/registry.php` and exposes through the REST API. Anything that needs code (`example`, an attribute's `Edit` component or custom validator) goes in `widget.ts`.
619+
Rule of thumb: anything the host needs before loading a widget's module — identity, translatable copy, framing, attribute schema, example attributes — goes in `widget.json`, which the build forwards to `build/widgets/registry.php` and exposes through the REST API. Anything that needs code (an attribute's `Edit` component or custom validator, an `example` with runtime values) goes in `widget.ts`.
619620

620621
### `widget.json` — static discovery metadata
621622

@@ -627,6 +628,9 @@ Rule of thumb: anything the host needs before loading a widget's module — iden
627628
"help": {
628629
"content": "A short note for compact surfaces such as tooltips."
629630
},
631+
"example": {
632+
"attributes": { "greeting": "World", "showDate": true }
633+
},
630634
"category": "demo",
631635
"textdomain": "my-plugin"
632636
}
@@ -640,13 +644,14 @@ Rule of thumb: anything the host needs before loading a widget's module — iden
640644
- **`help`** (optional): Contextual help note for compact surfaces. An object with `content` (may carry `<em>`/`<strong>`) and optional `links` (`label`, `href`). Translated server-side using `textdomain`.
641645
- **`keywords`** (optional): Search aliases. Translated server-side using `textdomain`.
642646
- **`attributes`** (optional): Attribute schema, one entry per attribute with the JSON-expressible subset of a DataViews `Field` (`id`, `type`, `label`, `elements`, `isValid` without `custom`, ...) plus an optional `relevance` (`high`, `medium`, `low`). `type` may name a registered field type. `label`, `header`, `description`, `placeholder`, and option labels and descriptions are translated server-side using `textdomain`.
647+
- **`example`** (optional): Structured example, `{ "attributes": { … } }`: the attributes hosts preview the widget with, and apply to a new instance created without any. JSON values only; an `example` in `widget.ts` wins over it.
643648
- **`category`** (optional): Grouping category for filtering
644649
- **`presentation`** (optional): Rendering intent (`framed`, `content-bleed`, `full-bleed`)
645650
- **`textdomain`** (optional): Gettext text domain for translating `title`, `description`, `help`, `keywords`, and attribute labels
646651

647652
### `widget.ts` — runtime values
648653

649-
Optional. Exports a default object with what needs code: `example` data, and attribute entries that add an `Edit` component or a custom validator to a `widget.json` attribute with the same `id`. The build system injects the `render_module` handle at registration time, so authors don't need to declare it.
654+
Optional. Exports a default object with what needs code: attribute entries that add an `Edit` component or a custom validator to a `widget.json` attribute with the same `id`, and an `example` whose values only exist at runtime, which wins over the `widget.json` one. The build system injects the `render_module` handle at registration time, so authors don't need to declare it.
650655

651656
```ts
652657
import type { WidgetAttributeField } from '@wordpress/widget-primitives';
@@ -662,18 +667,12 @@ const widget = {
662667
attributes: [ { id: 'greeting', Edit: GreetingControl } ] satisfies Partial<
663668
WidgetAttributeField< HelloWorldAttributes >
664669
>[],
665-
example: {
666-
attributes: {
667-
greeting: 'World',
668-
showDate: true,
669-
},
670-
},
671670
};
672671

673672
export default widget;
674673
```
675674

676-
Entries merge by `id` with the `widget.json` schema: the JSON value wins a shared key, the module adds the rest, and `isValid` merges rule by rule so JSON rules and a module `custom` validator coexist. A widget whose attributes are plain data needs no `widget.ts`.
675+
Entries merge by `id` with the `widget.json` schema: the JSON value wins a shared key, the module adds the rest, and `isValid` merges rule by rule so JSON rules and a module `custom` validator coexist. A widget whose attributes and example are plain data needs no `widget.ts`.
677676

678677
### `render.tsx` — UI component
679678

0 commit comments

Comments
 (0)