Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ee05251
add version number graphic to welcome banner
retrofox May 24, 2026
682417f
Merge branch 'update/add-welcome-widget-banner-bg' into trunk
retrofox May 25, 2026
b5d6eb9
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
3a3ff5f
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
a897825
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
f2917e0
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
4625fb1
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
c683724
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
853b9c8
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
0780c53
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 25, 2026
75810ab
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 26, 2026
9d8276a
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 26, 2026
b19ab13
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 26, 2026
1a142a3
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 26, 2026
59b8492
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 26, 2026
393837e
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 27, 2026
9c43e72
Merge branch 'trunk' of github.com:WordPress/gutenberg into trunk
retrofox May 27, 2026
6f5e846
rename widget-types to widget-kit
retrofox May 27, 2026
95af0d6
move render contract and resolver to widget-kit
retrofox May 27, 2026
8fc6899
consume widget-kit contract from its barrel
retrofox May 27, 2026
3c2e8d1
rewrite the widget-kit README
retrofox May 27, 2026
fcea0bc
rename widget-kit to widget-primitives
retrofox May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion routes/dashboard/stage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { store as viewportStore } from '@wordpress/viewport';
import { useDashboardGridSettings, useDashboardLayout } from './hooks';
import { WidgetDashboard } from './widget-dashboard';
import type { DashboardWidget } from './widget-dashboard';
import { useWidgetTypes } from './widget-types';
import { useWidgetTypes } from './widget-primitives';

function Dashboard() {
const [ layout, setLayout, resetLayout ] = useDashboardLayout(
Expand Down
2 changes: 1 addition & 1 deletion routes/dashboard/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"types": [ "style-imports" ]
},
"include": [ "**/*.ts", "**/*.tsx" ],
"exclude": [ "**/test/**", "build", "node_modules", "widget-types" ]
"exclude": [ "**/test/**", "build", "node_modules", "widget-primitives" ]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useDashboardInternalContext } from '../../context/dashboard-context';
import { useDashboardUIContext } from '../../context/ui-context';
import { createDashboardWidget } from '../../utils/create-dashboard-widget';
import { WidgetPicker } from '../widget-picker';
import type { WidgetType } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

/**
* Modal-based widget inserter. The dialog stays hidden until `inserterOpen`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { useDashboardInternalContext } from '../../context/dashboard-context';
import { WidgetContextProvider } from '../../context/widget-context';
import { WidgetRender } from '../widget-render';
import styles from './widget-chrome.module.css';
import type { DashboardWidget, WidgetType } from '../../types';
import type { DashboardWidget } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

interface ErrorBoundaryProps {
children: ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { useDashboardInternalContext } from '../../context/dashboard-context';
import { createDashboardWidget } from '../../utils/create-dashboard-widget';
import { WidgetRender } from '../widget-render';
import styles from './widget-picker.module.css';
import type { WidgetType } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

const DEFAULT_VIEW: View = {
type: 'pickerGrid',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { useCallback } from '@wordpress/element';
* Internal dependencies
*/
import { useDashboardInternalContext } from '../../context/dashboard-context';
import { getLazyWidgetComponent } from '../../utils/get-lazy-widget-component';
import type { DashboardWidget, WidgetType } from '../../types';
import { getLazyWidgetComponent } from '../../../widget-primitives';
import type { DashboardWidget } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

interface WidgetRenderInternalProps {
widget: DashboardWidget< unknown >;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { sprintf, __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { WidgetType } from '../../../../widget-types/types';
import type { WidgetType } from '../../../../widget-primitives';

/**
* Localized "<Widget> settings" label, falling back to a generic title
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import { WidgetToolbar } from '../widget-toolbar';
import { WidgetSettingsTrigger } from './widget-settings-trigger';
import styles from './widget-settings-toolbar.module.css';
import type { DashboardWidget, WidgetType } from '../../types';
import type { DashboardWidget } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

export interface WidgetSettingsToolbarProps {
/** The instance whose settings this toolbar configures. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { IconButton } from '@wordpress/ui';
*/
import { useDashboardUIContext } from '../../context/ui-context';
import { getAdminMenuInset } from './utils';
import type { DashboardWidget, WidgetType } from '../../types';
import type { DashboardWidget } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

export interface WidgetSettingsTriggerProps {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import type {
DashboardWidget,
GridTilePlacement,
MasonryTilePlacement,
WidgetName,
} from '../../types';
import type { WidgetName } from '../../../widget-primitives';

// Floor applied as `minColumnWidth` on every surface render. Acts as a
// safety net for stored settings that predate the layered model (where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import {
*/
import { computeGridModelChange } from '../utils/grid-model-change';
import type {
ResolveWidgetModule,
WidgetGridModel,
WidgetGridSettings,
DashboardWidget,
WidgetType,
} from '../types';
import type { ResolveWidgetModule, WidgetType } from '../../widget-primitives';

/*
* Defaults for the active grid model. Applied when the consumer omits
Expand Down
2 changes: 1 addition & 1 deletion routes/dashboard/widget-dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { WidgetDashboard } from './widget-dashboard';
export type { DashboardWidget, WidgetType } from './types';
export type { DashboardWidget } from './types';
3 changes: 2 additions & 1 deletion routes/dashboard/widget-dashboard/test/actions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { useState } from '@wordpress/element';
* Internal dependencies
*/
import { WidgetDashboard } from '../widget-dashboard';
import type { DashboardWidget, WidgetType } from '../types';
import type { DashboardWidget } from '../types';
import type { WidgetType } from '../../widget-primitives';

const widgetTypes: WidgetType[] = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Internal dependencies
*/
import { createDashboardWidget } from '../utils/create-dashboard-widget';
import type { WidgetType } from '../types';
import type { WidgetType } from '../../widget-primitives';

const baseType: WidgetType = {
apiVersion: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import { store as commandsStore } from '@wordpress/commands';
*/
import { WidgetDashboard } from '../widget-dashboard';
import { DASHBOARD_COMMAND_CONTEXT } from '../components/dashboard-commands';
import type { DashboardWidget, WidgetType } from '../types';
import type { DashboardWidget } from '../types';
import type { WidgetType } from '../../widget-primitives';

const widgetTypes: WidgetType[] = [];

Expand Down
3 changes: 2 additions & 1 deletion routes/dashboard/widget-dashboard/test/staging.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { useEffect, useState } from '@wordpress/element';
*/
import { useDashboardInternalContext } from '../context/dashboard-context';
import { WidgetDashboard } from '../widget-dashboard';
import type { DashboardWidget, WidgetGridSettings, WidgetType } from '../types';
import type { DashboardWidget, WidgetGridSettings } from '../types';
import type { WidgetType } from '../../widget-primitives';

const widgetTypes: WidgetType[] = [];

Expand Down
52 changes: 8 additions & 44 deletions routes/dashboard/widget-dashboard/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
/**
* Widget type definitions for the dashboard engine.
* Dashboard-specific types: `DashboardWidget`, grid settings, and the
* `WidgetDashboard` prop bag.
*
* The widget identity types (`WidgetName`, `WidgetTypeMetadata`,
* `WidgetType`) live in `routes/dashboard/widget-types/types` and are
* re-exported here so dashboard internals can pull every type they need
* from a single module. The local declarations below cover the
* dashboard-specific surface area: `DashboardWidget`, render props,
* module resolver, grid settings, and the `WidgetDashboard` prop bag.
* The widget contract types (`WidgetName`, `WidgetType`, `WidgetRenderProps`,
* `ResolveWidgetModule`) live in `widget-primitives` and are imported from there
* directly; this module does not re-export them.
*/

/**
* External dependencies
*/
import type { ComponentType, ReactNode } from 'react';
import type { ReactNode } from 'react';

/**
* WordPress dependencies
Expand All @@ -27,11 +25,9 @@ import type {
*/
import type {
WidgetName,
WidgetTypeMetadata,
WidgetType,
} from '../widget-types/types';

export type { WidgetName, WidgetTypeMetadata, WidgetType };
ResolveWidgetModule,
} from '../widget-primitives';

export type GridTilePlacement = Omit< DashboardGridLayoutItem, 'key' >;
export type MasonryTilePlacement = Omit< DashboardLanesLayoutItem, 'key' >;
Expand Down Expand Up @@ -87,22 +83,6 @@ export interface DashboardWidget< Item = unknown > {
placement?: DashboardTilePlacement;
}

/**
* Props passed to every widget render component.
*/
export interface WidgetRenderProps< Item = unknown > {
/**
* Widget attributes configured by the user.
*/
attributes: Item;

/**
* Update the attributes of this instance. Fires `onLayoutChange` on the
* dashboard with the updated layout.
*/
setAttributes?: ( next: Partial< Item > ) => void;
}

/**
* Identity of a widget within the rendering tree. Returned by
* `useWidgetContext()`; `null` when called outside a widget render subtree.
Expand All @@ -124,22 +104,6 @@ export interface WidgetContextValue {
index: number;
}

/**
* Widget render module shape returned by the module resolver.
*/
export interface WidgetModule {
default: ComponentType< WidgetRenderProps< unknown > >;
}

/**
* Resolver hook: maps a `WidgetType.renderModule` id to a React component.
* Defaults to a dynamic `import()`; override for tests, Storybook, or to load
* from a non-URL source.
*/
export type ResolveWidgetModule = (
moduleId: string
) => Promise< WidgetModule >;

/**
* Identifier for the active grid model. Drives which `@wordpress/grid`
* surface the dashboard mounts and which per-model settings the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/**
* Internal dependencies
*/
import type {
DashboardWidget,
WidgetType,
GridTilePlacement,
} from '../../types';
import type { DashboardWidget, GridTilePlacement } from '../../types';
import type { WidgetType } from '../../../widget-primitives';

const DEFAULT_PLACEMENT: GridTilePlacement = {
width: 1,
Expand Down
1 change: 0 additions & 1 deletion routes/dashboard/widget-dashboard/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { createDashboardWidget } from './create-dashboard-widget';
export { getLazyWidgetComponent } from './get-lazy-widget-component';
export { migrateLayout } from './migrate-layout';
2 changes: 1 addition & 1 deletion routes/dashboard/widget-dashboard/widget-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { NoWidgetsState } from './components/no-widgets-state';
*
* The consumer owns `layout` and `editMode` state; every mutation fires
* `onLayoutChange` with the fully updated array. The engine never queries a
* widget-types store types flow in via the `widgetTypes` prop.
* widget-primitives store; types flow in via the `widgetTypes` prop.
*
* ```tsx
* import { WidgetDashboard } from '@wordpress/dashboard';
Expand Down
75 changes: 75 additions & 0 deletions routes/dashboard/widget-primitives/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# `widget-primitives`

The host-agnostic toolkit for dashboard widgets. It is the single source of
truth for what a widget *is* (the contract types) and the runtime for working
with widgets (discovering the registered widget types and resolving their render
modules). It is not tied to any host.

## Purpose

A widget is a self-contained unit; a *host* is any context that renders one (the
dashboard today, a sidebar or an inspector tomorrow). `widget-primitives` sits between
the build pipeline that produces widgets and the hosts that render them, so
neither side has to know about the other:

- **Contract.** It defines the widget type shape (`WidgetType`,
`WidgetTypeMetadata`, `WidgetName`) and the render contract
(`WidgetRenderProps`). Authors type their `widget.ts` / `render.tsx` against
these, and hosts consume the same types. Nothing re-exports them: every
consumer imports from `widget-primitives` directly, so the source of truth stays in
one place.
- **Discovery.** `useWidgetTypes()` returns the `WidgetType[]` registered on the
current page.
- **Resolution.** `getLazyWidgetComponent()` resolves a widget's render module
to a cached `lazy()` component, so any host can mount a widget body under
Suspense.

## Public API

- `useWidgetTypes()` → `[ widgetTypes, isResolvingWidgetTypes ]`: the
`WidgetType[]` available on the current page, plus a flag that is true while
they are still resolving.
- `getLazyWidgetComponent( renderModule, resolveWidgetModule )`: a cached
`lazy()` React component for a widget's render module.
- Contract types: `WidgetType`, `WidgetTypeMetadata`, `WidgetName`,
`WidgetRenderProps`, `ResolveWidgetModule`.

Anything not exported here (for example the internal `WidgetModule` shape) is an
implementation detail and should not be imported from outside the kit.

## How discovery works

The data flow uses `@wordpress/core-data` and dynamic module imports. There is
no custom data store and no client-side registration step.

1. **Server (PHP).** `WP_Widget_Type_Registry` is hydrated at `init` from the
build manifest. One entry per widget folder under `widgets/`.
2. **REST endpoint.** `/wp/v2/widget-modules` exposes the registry. Each record
returns `{ name, render_module, widget_module }`.
3. **core-data entity.** A `widgetModule` entity reads the endpoint via
`getEntityRecords( 'root', 'widgetModule' )`.
4. **Hook.** `useWidgetTypes()` reads those records and `await import(
record.widget_module )` to fetch each widget's metadata, merging it with
`name` and `renderModule` into a `WidgetType`.

## Identity vs host

A widget type is metadata plus a render module. It belongs to no host in
particular.

The same `core/on-this-day` widget can render in a dashboard grid, a sidebar
within another page, a modal inserter, or a plugin panel. The choice of where to
render belongs to the consumer; the registry knows nothing about it.

For `import( widget.renderModule )` to resolve at runtime, the render module
needs to be available to the browser. The build pipeline registers each widget's
script module with WordPress at `init`, which makes it loadable. Hosts decide
when to import; they do not register widgets.

## Status

This module lives inside the dashboard route while its API stabilizes. Because
both hosts and widget authors consume it, its destination is a top-level,
private (unpublished) package, `@wordpress/widget-primitives`, in the same vein as
`@wordpress/grid`. The current layout (`index.ts`, `types.ts`, `hooks/`,
`tools/`) already matches that future shape.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
*/
export { useWidgetTypes } from './hooks';

/**
* Tools
*/
export { getLazyWidgetComponent } from './tools/get-lazy-widget-component';

/**
* Types
*/
Expand All @@ -11,5 +16,5 @@ export type {
WidgetTypeMetadata,
WidgetType,
WidgetRenderProps,
ResolveWidgetModule,
} from './types';
export type { UseWidgetTypesResult } from './hooks/use-widget-types';
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
/**
* External dependencies
*/
import type { ComponentProps } from 'react';
import type { ComponentProps, ComponentType } from 'react';
import type { Field } from '@wordpress/dataviews';
import type { Icon } from '@wordpress/ui';

Expand Down Expand Up @@ -162,3 +162,19 @@ export interface WidgetRenderProps< Item = unknown > {
*/
setAttributes?: ( next: Partial< Item > ) => void;
}

/**
* Widget render module shape returned by the module resolver.
*/
export interface WidgetModule {
default: ComponentType< WidgetRenderProps< unknown > >;
}

/**
* Resolver hook: maps a `WidgetType.renderModule` id to a React component.
* Defaults to a dynamic `import()`; override for tests, Storybook, or to load
* from a non-URL source.
*/
export type ResolveWidgetModule = (
moduleId: string
) => Promise< WidgetModule >;
Loading
Loading