此软件包仍处于实验阶段。“实验性”意味着这是一个早期实现,可能发生重大且不兼容的变更。在其以 0.x 版本发布期间,不兼容变更可能会随次版本发布。

此预发布版本依赖 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 命令,以及它们打开的确认提示。被拒绝的重置会被隐藏,而不是禁用。
insertwidgetType插入器是否提供该类型;被拒绝的类型会继续在已放置的位置渲染。仅当存在可插入的已注册类型时,“添加小工具”按钮和命令才会显示。
removewidget, widgetType?自定义模式下的移除控件。
movewidget, widgetType?自定义模式下拖动磁贴。被拒绝的磁贴会被固定:当其他磁贴在其周围重新排序时,它保持自己的索引;它前方的变化仍可能重新排布它最终落入的单元格。
resizewidget, widgetType?调整大小手柄和宽度菜单。
editwidget, 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 做贡献的信息,
请阅读项目的主要
贡献者指南。