此预发布版本依赖 WordPress 核心私有 API,并被构建为在 WordPress 核心内运行。尚不能安全地作为独立 npm 依赖从外部插件安装和运行。
用于小工具仪表盘的无状态渲染引擎。WidgetDashboard 在使用方控制的编辑模式下渲染可编辑的小工具实例网格:拖拽重新排序、调整大小、模态插入器、每个小工具的设置,以及命令面板集成。
该引擎不拥有任何数据。小工具类型通过 widgetTypes prop 传入(参见 @wordpress/widget-primitives),已提交的 layout 数组由使用方拥有,进行中的编辑会累积在内部暂存层中,直到用户提交;此时会触发 onLayoutChange,并传入更新后的数组。网格布局通过 @wordpress/grid 渲染。
有关小工具系统如何协同工作(创作、构建、服务器注册表、宿主),请参阅 仪表盘小工具系统架构文档。
安装
安装模块:
npm install @wordpress/widget-dashboard --save
此软件包假定您的代码将运行在 ES2015+ 环境中。
如果您使用的环境对此类语言特性和 API 的支持有限或完全不支持,
您应在代码中包含 由 @wordpress/babel-preset-default 提供的 polyfill。
设置
组件样式是 CSS Modules,会在组件挂载时于运行时注入;没有需要入队或导入的样式表。
视觉默认值会读取 @wordpress/theme 以 --wpds-* CSS 自定义属性发布的设计令牌。在由 Gutenberg 管理的 WordPress 界面中,令牌样式表会集中加载,无需任何设置。在其他环境中,请在您的应用中安装并加载它:
npm install @wordpress/theme
import '@wordpress/theme/design-tokens.css';
用法
import { useState } from '@wordpress/element';
import { WidgetDashboard } from '@wordpress/widget-dashboard';
function Dashboard() {
const [ layout, setLayout ] = useState( defaultLayout );
return (
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
/>
);
}
<WidgetDashboard> 默认渲染 <WidgetDashboard.Widgets />。传入 children 以围绕网格组合仪表盘——页眉、空状态、页脚:
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
>
<WidgetDashboard.NoWidgetsState>
<p>{ __( 'No widgets yet.' ) }</p>
</WidgetDashboard.NoWidgetsState>
<WidgetDashboard.Widgets />
</WidgetDashboard>
组合
该仪表盘由两类部分构成:
- 由您安排的触发器和外观组件。
Actions、Widgets、WidgetChrome、NoWidgetsState和Commands是复合组件;将它们作为children组合,以把它们放置到您的布局中。 - 引擎挂载的覆盖层。 小工具插入器、每个小工具的设置编辑器,以及重置确认框由引擎挂载,并由共享 UI 状态驱动。触发器只通过该状态打开它们——“添加小工具”按钮和命令面板都会打开插入器——因此树中没有需要放置的覆盖层。
省略 children 会渲染默认布局。传入 children 时,无论您组合什么内容,覆盖层都会挂载。
属性
layout: DashboardWidget[]
要渲染的小工具实例。每个实例都带有稳定的 uuid、type 引用、可选 attributes,以及描述其在网格中插槽位置的 placement。此状态由使用方拥有。
onLayoutChange: ( layout: DashboardWidget[] ) => void
当用户通过“完成”操作提交进行中的编辑时调用。接收应持久化的完整布局数组。进行中的变更(重新排序、调整大小、添加、移除、属性编辑)会累积在仪表盘的内部暂存层中,在提交前不会触发此回调。
onLayoutReset: () => void
可选。由 <WidgetDashboard.Actions /> 和命令面板显示的重置操作。省略时,重置入口将被禁用。
widgetTypes: WidgetType[]
仪表盘可用的小工具类型。仪表盘从不直接查询 store——使用方通过此 prop 限定范围和过滤。
isResolvingWidgetTypes: boolean
可选。当为 true 时,小工具类型仍在加载:类型尚不在 widgetTypes 中的实例会显示加载状态,而不是缺失状态。
editMode: boolean
当为 true 时,网格启用拖拽和调整大小。默认为 false。
onEditChange: ( next: boolean ) => void
可选。当通过 WidgetDashboard.Actions(或任何使用方自建的切换控件)切换编辑模式时调用。省略时,WidgetDashboard.Actions 不渲染任何内容。
resolveWidgetModule: ( moduleId: string ) => Promise< { default: ComponentType } >
可选。将 WidgetType.renderModule id 映射到渲染该小工具的 React 组件。默认为动态 import( /* webpackIgnore */ moduleId )。可在测试、Storybook 或远程 URL 加载时覆盖。
gridSettings: WidgetGridSettings
可选。网格模型配置;参见 网格设置。默认为 DEFAULT_GRID。
children: ReactNode
可选。用于仪表盘触发器和外观组件的组合插槽。省略时,引擎渲染默认布局:空状态、操作、小工具网格,以及命令面板集成。无论如何,引擎挂载的覆盖层都会存在。
复合组件
<WidgetDashboard.Widgets />
遍历 layout,通过 <WidgetDashboard.WidgetChrome /> 渲染每个条目,并将生成的树传入底层网格(@wordpress/grid)。
<WidgetDashboard.WidgetChrome />
每个实例的包装器。它通过 context 向渲染树提供小工具身份,并在 Suspense 边界和错误边界下承载该小工具的渲染模块。实例从 layout 读取;使用方无需手动传入。
<WidgetDashboard.NoWidgetsState>
仅当 layout 为空时渲染其子元素。将其与 <WidgetDashboard.Widgets /> 搭配使用,以便在添加小工具之前,空状态显示在网格的位置。
<WidgetDashboard.Actions />
编辑模式切换器:在 editMode 关闭时显示“自定义”按钮,在开启时显示“添加小工具”“取消”“完成”。这些按钮和更多操作菜单都是触发器:“自定义”和“完成”触发 onEditChange,“添加小工具”打开插入器,“恢复默认”打开重置确认框。当仪表盘挂载时没有传入 onEditChange,则返回 null,因此不公开编辑模式的界面也可以无条件地在树中保留 Actions。自定义按钮还需要策略允许 customize;在已经处于自定义状态时,完成和取消保持可用。
<WidgetDashboard.Commands />
命令面板集成。它通过 @wordpress/commands 注册仪表盘的命令(自定义、添加小工具、恢复默认),并设置当前命令上下文。它不渲染任何内容,而会在宿主应用挂载命令面板的位置显示。它包含在默认布局中;传入自定义子元素时,请组合它以保留该集成。自定义命令,以及编辑模式外的添加小工具命令,遵循策略的 customize 答复。
<WidgetDashboard.Policy>
管控其下方仪表盘上用户可以执行的操作:是否提供自定义、插入器列出哪些小工具类型,以及哪些实例可以被移除、移动、调整大小或编辑。与其他复合组件不同,它挂载在 <WidgetDashboard> 外围。参见 治理。
来自 @wordpress/admin-ui 的 <Page> 公开了一个在管理界面(DataViews、WidgetDashboard 等)中使用的 actions 插槽。直接将 Actions 接入其中:
import { Page } from '@wordpress/admin-ui';
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
editMode={ editMode }
onEditChange={ setEditMode }
>
<Page
title={ __( 'My Dashboard' ) }
actions={ <WidgetDashboard.Actions /> }
>
<WidgetDashboard.Widgets />
</Page>
<WidgetDashboard.Commands />
</WidgetDashboard>;
<Page> 是可选的。该复合组件可以渲染在任何容器内部,因此简单的 <header> 或自定义外壳同样适用。
插入小工具
<WidgetDashboard.Actions /> 中的“添加小工具”按钮会打开一个模态插入器。它将 widgetTypes prop 列为实时预览网格(每个预览通过该类型自己的渲染模块渲染该类型的 example 属性),支持搜索,并提供支持批量的“选择”操作,因此用户可以在一次布局更改中插入一个或多个小工具。仪表盘上方的 <WidgetDashboard.Policy> 会通过 insert 操作收窄列表;如果没有它,则会提供所有条目。
确认后,插入器会创建实例(使用每个类型的 example.attributes 作为初始值),并将它们追加到暂存布局中。成功插入后或用户关闭时,对话框会关闭。
治理
引擎知道用户可以对其执行哪些操作。它不知道用户是谁,也不知道应用程序允许什么。<WidgetDashboard.Policy> 是应用程序通过单个回调作出回答的衔接点:
type CanPerformDashboardOperation = (
request: DashboardOperationRequest
) => boolean;
type DashboardOperationRequest =
| { operation: 'customize' }
| { operation: 'reset' }
| { operation: 'insert'; widgetType: WidgetType }
| {
operation: 'remove' | 'move' | 'resize' | 'edit';
widget: DashboardWidget;
widgetType?: WidgetType;
};
每个请求都会指明操作并携带其主体,因此基于 request.operation 的分支可以收窄对象其余部分。实例请求携带已放置的小工具及其类型;当类型未注册时,该字段不存在:
<WidgetDashboard.Policy
canPerform={ ( request ) => {
switch ( request.operation ) {
case 'customize':
return canEditLayout;
case 'insert':
return request.widgetType.category === activeSection;
case 'remove':
return ! request.widget.attributes?.pinned;
default:
return true;
}
} }
>
<WidgetDashboard { ...props } />
</WidgetDashboard.Policy>
| 操作 | 主体 | 它控制的内容 |
|---|---|---|
customize | 无 | 自定义按钮、core/dashboard/customize 命令、编辑模式之外的 core/dashboard/add-widgets 命令,以及空布局时自动进入自定义模式。 |
reset | 无 | 更多菜单中的“重置为默认”条目、core/dashboard/reset-to-default 命令,以及它们打开的确认提示。被拒绝的重置会被隐藏,而不是禁用。 |
insert | widgetType | 插入器是否提供该类型;被拒绝的类型会继续在已放置的位置渲染。仅当存在可插入的已注册类型时,“添加小工具”按钮和命令才会显示。 |
remove | widget, widgetType? | 自定义模式下的移除控件。 |
move | widget, widgetType? | 自定义模式下拖动磁贴。被拒绝的磁贴会被固定:当其他磁贴在其周围重新排序时,它保持自己的索引;它前方的变化仍可能重新排布它最终落入的单元格。 |
resize | widget, widgetType? | 调整大小手柄和宽度菜单。 |
edit | widget, widgetType? | 属性编辑:头部中的内联字段和设置触发器、设置界面,以及小工具的 setAttributes;被拒绝时该属性不存在,因此小工具以只读方式渲染。 |
引擎在其 Provider 中解析一次策略,所有界面都会查询该已解析答案,因此更多来源可在同一位置接入,而无需改动界面。
暂存层会强制执行相同的判定。无论哪个触发器暂存了变更,每项变更到达时都会按实例进行差异比较,并且被策略拒绝的每项更改都会在落地前被重新强制保持:被移除的锁定实例会返回其索引,被拒绝的移动会保持其索引,被拒绝的调整大小会保持其跨度,被拒绝的编辑会保持其属性,被拒绝类型的新实例会被丢弃。界面所隐藏的内容,暂存层会拒绝。
实例操作仅在该类型仍位于 widgetTypes 中时才携带 widgetType。已移除的插件或加载失败的渲染模块会使该字段不存在:引擎仍会询问,因为实例仍在面板上;但基于类型设置锁的策略没有可匹配的内容。必须在这种情形下仍然有效的锁应放在实例本身。同样由于该字段不存在,未注册类型的新实例不会被丢弃:没有可询问的类型。
约定规则:
- 对你不管控的操作返回
true。策略以限制方式组合,因此默认false会拒绝之后添加的每一项操作。 - 将其挂载在
<WidgetDashboard>外层,而不是内部。引擎会将插入器挂载在children子树之外,因此放置在children内部的策略不会生效。一个 Provider 可以覆盖多个仪表盘。 - 嵌套策略只会收窄。只有当每个外层策略都允许某个操作时,该操作才被允许;内层策略不能重新授予外层策略拒绝的操作。如果没有策略,所有操作都被允许。
- 回调会在渲染期间被调用。保持其同步;当它派生自状态时,将其记忆化;即使插入器处于打开状态,新的函数也会导致仪表盘重新求值。
策略管控的是界面:它决定仪表盘提供什么,而不是服务器接受什么。必须强制实施权限的宿主应在布局持久化的地方执行。它也不会触达小工具主体:小工具向服务器查询自己的实体,并仅将应用程序的决定读取为宿主提供给它的内容的存在或缺失。
网格设置
仪表盘支持两种网格模型,通过 gridSettings prop 配置:二维打包的 grid 模型,其中磁贴在统一行上声明明确跨度;以及内容驱动的 masonry 模型,其中高度随内容变化,调整大小仅限水平方向。这些设置对仪表盘而言是只读的:仪表盘内没有编辑界面。使用方拥有这些值及其持久化。
用于处理这些设置的导出工具集:
WidgetGridSettings—— 各模型设置结构的可辨识联合。DEFAULT_GRID—— 规范默认设置,在省略gridSettings时应用。normalizeGridSettings( settings, defaultRowHeight )—— 将旧的自由格式行高强制转换为最接近的预设。在传入存储的数据前对其运行该函数。ROW_HEIGHT_PRESETS/DEFAULT_ROW_HEIGHT——rowHeight值会被规范化到的行高预设(small、medium、large)。WIDGET_DASHBOARD_COLUMN_COUNT—— 当宿主未设置gridSettings.columns时,宽容器使用的列数。它是一个默认值,而不是上限:有限的columns会被设置下限,下限为1,并按要求渲染。有效列数会根据容器宽度递减:在960px及以上为该列数,低于该宽度时为min( 2, count ),低于600px时为一列。
<WidgetDashboard
layout={ layout }
onLayoutChange={ setLayout }
widgetTypes={ widgetTypes }
gridSettings={ { model: 'masonry' } }
/>
磁贴间距
磁贴外壳是一个 Card,其内边距默认为 --wpds-dimension-padding-lg。宿主可以通过在 :root 上设置两个自定义属性来收紧或放宽它:
:root {
--wp-widget-dashboard-tile-padding: var( --wpds-dimension-padding-2xl );
}
--wp-widget-dashboard-tile-padding 控制磁贴表面的内边距。--wp-widget-dashboard-tile-header-gap 控制磁贴头部与主体之间的间距;除非单独设置,否则它跟随磁贴内边距。请使用 --wpds-* 间距令牌作为值。全出血磁贴的浮动头部和选择器预览也遵循相同属性。
对选择器而言,重要的是 :root,而不是仪表盘包裹器:它挂载在 document.body 下的对话框中,包裹器的自定义属性永远无法到达那里。
此宿主如何转换该约定
此引擎是 @wordpress/widget-primitives 的一个宿主实现。它将契约字段映射到宿主自有 UI,如下所示。
身份与帮助
当小工具类型声明 help 时,磁贴界面会在标题旁的信息提示中展示其 content 和可选的 links。
属性编辑
小工具通过 relevance('high' | 'medium' | 'low',缺省表示 'low')为每个属性声明重要性。该声明说明重要性,而不是放置位置;此宿主将其映射到两个界面:
- 突出界面:
relevance: 'high'字段在磁贴头部渲染为无装饰的内联控件,便于在上下文中编辑。 - 设置界面:完整模式,从设置触发器打开。只有当某些属性未被提升时才会显示该触发器;否则它会重复突出界面。
突出界面只有在容纳得下时才保持。头部会测量它可分配给工具栏的空间;当被提升字段的自然宽度超出该空间时,它们会折叠到一个下拉菜单中,并以表单形式呈现。设置触发器不参与折叠:只要存在未提升属性,它就保留在工具栏中。呈现会双向跟随测量结果:拉宽磁贴,字段会恢复为内联。
任何界面上的编辑都会经由引擎内部层暂存,并在提交时到达 onLayoutChange。突出界面和下拉菜单中的编辑通过共享自动保存防抖发布;设置界面在保存时发布。
操作
操作带有相同的 relevance 等级,并映射到突出程度递减的界面:
- 页脚,起始侧:
relevance: 'high'操作在小工具主体下方的常驻条带中渲染为文本链接,声明的图标会作为前缀显示。 - 页脚,末尾侧:
relevance: 'medium'操作渲染为紧凑操作元素:如果操作声明了图标,则为仅图标链接,否则为文本链接。 - 更多菜单:其余操作折叠到磁贴头部的三点菜单中。
每个链接形式的操作元素都是真实的锚点。全出血小工具没有页脚,因此它们的所有操作都保留在菜单中。
已挂载的小工具会在运行时通过来自 @wordpress/widget-primitives 的 useWidgetActions 向该列表添加内容。引擎会按 id 将其声明的内容与类型的操作合并,并按相同等级路由结果。
callback 操作会挂载一个按钮,或更多菜单中的一个菜单项;在其返回的 promise 落定前,该按钮或菜单项处于禁用状态。
编写小工具
小工具渲染模块只接收渲染和编辑所需的内容:
interface WidgetRenderProps< Item = unknown > {
attributes: Item;
setAttributes?: ( next: Partial< Item > ) => void;
}
setAttributes 会回流经过暂存层,并在提交时到达 onLayoutChange。移除、徽章和错误外观不属于此契约——这些属于使用方。
类型
DashboardWidget—— 小工具在仪表盘上的一个放置实例。包含uuid、type、attributes、placement。WidgetGridSettings—— 网格模型配置;参见 网格设置。DashboardOperationRequest/CanPerformDashboardOperation—— 策略契约;参见 治理。DashboardInstanceOperation和DashboardInstanceOperationRequest表示按实例划分的部分。
小工具契约类型(WidgetName、WidgetType、WidgetRenderProps、ResolveWidgetModule)定义于 @wordpress/widget-primitives,并直接从该包导入;此引擎不会重新导出它们。
为此包做贡献
这是一个独立包,是 Gutenberg 项目的一部分。
该项目以 monorepo 形式组织。它由多个
自包含软件包组成,每个包都有特定用途。此
monorepo 中的包发布到 npm
并被 WordPress 以及
其他软件项目使用。
要了解更多关于为此包或整个 Gutenberg 做贡献的信息,
请阅读项目的主要
贡献者指南。