Scheduler 资源日程
发布状态:Client-side Editable Public Preview。 默认仍为
editable=false的只读本地完整快照;宿主显式开启编辑后,组件提供客户端本地 CRUD、默认 Canvas Event Editor、Day/Week 空白时间槽创建,以及 Day/Week timed Event 的指针/受限键盘移动、资源重分配和起止时间拉伸。Public Preview 不代表 GA 或完整商用承诺;本地提交不代表服务端保存,也不表示 all-day/Month/Agenda 空间编辑、权限、远程数据、recurrence engine、生产级时区适配、跨浏览器或屏幕阅读能力已经可用。
通用布局能力:
RenderScheduler支持width、height、min/max、margin和槽位对齐。时间轴、资源表头和事件区域在组件给定的有限尺寸内完成布局;详见组件通用布局属性。
RenderScheduler 是面向后台管理系统的 Canvas 资源日程组件。它使用本地完整数据
快照,在 Canvas 中绘制资源时间线或 Agenda 列表,并提供滚动、定位、选择、
激活、overflow、状态反馈和可见窗口虚拟化。默认 editable=false;开启后可通过
同步公共方法、默认事件编辑器或 Timeline 指针/键盘交互修改组件当前持有的本地快照,
但不会写入服务端。
适合排班查看、预约总览、床位与设备占用、人员计划等场景。需要表格编辑和 任意列操作时使用 GridView;需要聚合趋势时使用 Charts;需要自由业务轨道时评估 ClinicalFlowSheet。需要远程分页、权限、 服务端持久化或规则引擎时,当前 Scheduler 不适用。
API 总览
所有 Scheduler API 都从 ds-ui 包入口导入。
运行时类
| API | 类型 | 用途 |
|---|---|---|
RenderScheduler |
class | 构造和绘制资源日程,并提供可选本地 CRUD。 |
RenderSchedulerConfigurationError |
class | 报告缺少数据、无效数据、无效 domain、formatter 或 editing 配置。 |
组件契约类型
| API | 用途 |
|---|---|
RenderSchedulerOptions |
构造参数。 |
RenderSchedulerDebugState |
稳定的标量诊断摘要。 |
SchedulerTimelineView |
Day / Week / Month 资源时间线视图。 |
SchedulerView |
Day / Week / Month / Agenda 视图联合类型。 |
SchedulerZoomPreset |
compact、standard、comfortable 密度。 |
SchedulerScrollAlignment |
定位时使用的 start、center、end、nearest 对齐。 |
SchedulerStatus |
当前加载、刷新、就绪、空、错误或释放状态。 |
SchedulerHeaderFormatter |
Month、Day 和 time header 文本格式化协议。 |
SchedulerReadonlyItem |
选择和激活回调使用的稳定只读数据。 |
SchedulerInteractionSource |
交互来源:API、editor、pointer、keyboard 或 reconcile。 |
SchedulerInteractionRejection |
before 事件取消时可选的稳定 code 与用户可见 message。 |
SchedulerCancelableInteractionEvent |
统一的同步 preventDefault() 交互事件基础契约。 |
SchedulerSelectionChangeReason |
select、clear、activation、editor、mutation、data-replacement 或 projection-loss 选择变更原因。 |
SchedulerSelectionChangeDescriptor |
选择变更的来源、原因、前值、候选值与 forced 标记。 |
SchedulerBeforeSelectionChangeEvent |
可同步取消的选择变更事件。 |
SchedulerSelectionChangeEvent |
已提交或强制协调后的选择变更通知。 |
SchedulerEventActivationSurface |
Event 激活所在的 canvas 或 overflow 表面。 |
SchedulerEventActivateDescriptor |
Event 激活的来源、表面与只读 Event 投影。 |
SchedulerBeforeEventActivateEvent |
可同步取消的 Event 激活事件。 |
SchedulerEventActivateEvent |
Event 已激活通知。 |
SchedulerViewChangeDescriptor |
视图切换的来源、前视图与候选视图。 |
SchedulerBeforeViewChangeEvent |
可同步取消的视图切换事件。 |
SchedulerViewChangeEvent |
已提交的视图切换通知。 |
SchedulerZoomChangeDescriptor |
密度切换的来源、前密度与候选密度。 |
SchedulerBeforeZoomChangeEvent |
可同步取消的密度切换事件。 |
SchedulerZoomChangeEvent |
已提交的密度切换通知。 |
SchedulerNavigationRequest |
date、resource 或 page 语义导航请求。 |
SchedulerNavigateDescriptor |
导航来源、请求与导航前 viewport。 |
SchedulerBeforeNavigateEvent |
可同步取消的语义导航事件。 |
SchedulerNavigateEvent |
导航提交后的请求与 viewport 通知。 |
SchedulerViewport |
当前滚动偏移和可见时间、资源窗口。 |
SchedulerViewportChangeEvent |
布局周期内合并后的 viewport 变更通知。 |
SchedulerSetDataOptions |
完整快照替换时的 query key 和 domain。 |
SchedulerEventDraft |
创建 Event 及其 Assignments 的原子草稿。 |
SchedulerCreateEventContext |
后续交互创建入口使用的时间与 Resource 上下文。 |
SchedulerUpdateEventCommand |
更新标题、时间和单个 Assignment Resource 的命令。 |
SchedulerChangedEvent |
数据变更回调中的最小 Event 记录。 |
SchedulerChangedAssignment |
数据变更回调中的最小 Assignment 记录。 |
SchedulerDataAction |
create、update、remove 数据动作。 |
SchedulerDataChangeKind |
一次提交中可组合的变更字段类别。 |
SchedulerDataGesture |
move、resize、reassign 空间编辑手势。 |
SchedulerDataChangeSource |
api、editor、pointer、keyboard 来源。 |
SchedulerDataChangeDescriptor |
proposed 与 committed change 共用的 action、changeKinds、gesture、source 和前后记录。 |
SchedulerProposedDataChange |
提交前深度冻结的候选变更,包含 baseRevision。 |
SchedulerBeforeDataChangeEvent |
通过 preventDefault() 同步取消候选提交。 |
SchedulerDataChangeRejection |
可选的稳定取消 code 与用户可见 message。 |
SchedulerDataChange |
已提交本地变更的深度冻结描述。 |
SchedulerMutationResult |
同步成功结果或稳定拒绝原因。 |
本地快照记录
| API | 用途 |
|---|---|
SchedulerKey |
Event、Resource 和 Assignment 的 string | number 身份。 |
SchedulerVersion |
可选业务版本值。 |
SchedulerInstantRange |
epoch milliseconds 半开时间区间。 |
SchedulerAllDayRange |
使用日历日期表达的全天半开区间。 |
SchedulerEventTime |
timed 或 all-day Event 时间。 |
SchedulerWallDateTime |
recurrence 记录中的墙钟日期时间。 |
SchedulerTimeDisambiguation |
重复或缺失墙钟时间的消歧策略记录。 |
SchedulerOccurrenceIdentity |
已展开 occurrence 的原始身份。 |
SchedulerRecurrenceDefinition |
调用方 recurrence 定义记录;组件不负责展开。 |
SchedulerEvent |
日程事件。 |
SchedulerResource |
资源记录。 |
SchedulerAssignment |
Event 与 Resource 的关联。 |
SchedulerTimeRangeKind |
working、unavailable、background、blocked 区间类型。 |
SchedulerTimeRange |
全局或资源级背景时间区间。 |
SchedulerDataSnapshot |
Events、Resources、Assignments 和 TimeRanges 的完整本地快照。 |
开发者数据字段
SchedulerDataSnapshot 是传给 RenderScheduler 的完整客户端数据边界:
| 字段 | 类型 | 说明 |
|---|---|---|
events |
readonly SchedulerEvent[] |
已经展开到可直接绘制的 Event 记录。 |
resources |
readonly SchedulerResource[] |
资源及其可选父子关系。 |
assignments |
readonly SchedulerAssignment[] |
Event 与 Resource 的关联;同一 Event 可以有多个 Assignment。 |
timeRanges |
readonly SchedulerTimeRange[] |
全局或资源级工作、不可用、背景和阻塞区间。 |
SchedulerEvent:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
SchedulerKey |
是 | Event 身份。 |
title |
string |
是 | Canvas 和 Editor 中显示的标题。 |
time |
SchedulerEventTime |
是 | timed 或 all-day 时间。 |
recurrence |
SchedulerRecurrenceDefinition |
否 | recurrence master 定义;RenderScheduler 的本地快照采用 expanded-occurrences 模式,不接受 master,详见时间、recurrence 和时区边界。 |
recurrenceParentId |
SchedulerKey |
否 | 已展开 occurrence 的外部 series 身份,必须与 originalOccurrenceStart 同时提供。 |
originalOccurrenceStart |
SchedulerOccurrenceIdentity |
否 | 已展开 occurrence 的稳定原始起点身份。 |
readOnly |
boolean |
否 | 可选择和查看,但不可本地写入或空间编辑。 |
disabled |
boolean |
否 | 不参与选择、激活或编辑。 |
version |
SchedulerVersion |
否 | 调用方业务版本。 |
metadata |
unknown |
否 | 调用方业务数据;组件保留但不放入通用 change DTO。 |
SchedulerResource:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
SchedulerKey |
是 | Resource 身份。 |
title |
string |
是 | Resource header 或 Agenda 中显示的标题。 |
parentId |
SchedulerKey |
否 | 父 Resource 身份。 |
color |
string |
否 | Resource 语义颜色。 |
readOnly |
boolean |
否 | 该 Resource 上的 Event 不允许本地写入。 |
expanded |
boolean |
否 | 层级资源的初始展开状态。 |
sortOrder |
number |
否 | 同级 Resource 排序值。 |
version |
SchedulerVersion |
否 | 调用方业务版本。 |
metadata |
unknown |
否 | 调用方业务数据。 |
SchedulerAssignment:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
SchedulerKey |
是 | Assignment 身份。 |
eventId |
SchedulerKey |
是 | 必须引用当前 snapshot 中存在的 Event。 |
resourceId |
SchedulerKey |
是 | 必须引用当前 snapshot 中存在的 Resource。 |
units |
number |
否 | 调用方容量或占用单位。 |
version |
SchedulerVersion |
否 | 调用方业务版本。 |
metadata |
unknown |
否 | 调用方业务数据。 |
SchedulerTimeRange:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
SchedulerKey |
是 | 时间区间身份。 |
resourceId |
SchedulerKey |
否 | 省略时作用于全部 Resource。 |
kind |
working | unavailable | background | blocked |
是 | 绘制和本地编辑校验使用的区间语义。 |
time |
SchedulerEventTime |
是 | timed 或 all-day 时间。 |
readOnly |
boolean |
否 | 调用方只读标记。 |
metadata |
unknown |
否 | 调用方业务数据。 |
时间字段均使用半开区间:
| 类型 | 字段 | 说明 |
|---|---|---|
SchedulerInstantRange |
startEpochMs、endEpochMs |
timed Event 的 epoch milliseconds 起点和排他终点。 |
timed SchedulerEventTime |
kind: 'timed'、range、可选 timeZone |
timeZone 只作为业务记录保留,不触发时区换算。 |
SchedulerAllDayRange |
start、endExclusive |
两端均为 { year, month, day },结束日期不包含在 Event 中。 |
all-day SchedulerEventTime |
kind: 'all-day'、range |
不隐式转换成浏览器本地时区 instant。 |
最小示例
下面的时间值都使用 epoch milliseconds。domain 和 Event range 都采用
[startEpochMs, endEpochMs) 半开区间。
import { RenderScheduler, type SchedulerDataSnapshot, type SchedulerInstantRange,} from 'ds-ui' const startEpochMs = Date.UTC(2026, 6, 28, 0, 0, 0)const endEpochMs = Date.UTC(2026, 7, 4, 0, 0, 0) const domain: SchedulerInstantRange = { startEpochMs, endEpochMs,} const schedulerData: SchedulerDataSnapshot = { resources: [ { id: 'room-a', title: '会议室 A', color: '#2563eb' }, ], events: [ { id: 'event-1', title: '项目评审', time: { kind: 'timed', range: { startEpochMs: Date.UTC(2026, 6, 28, 9, 0, 0), endEpochMs: Date.UTC(2026, 6, 28, 10, 30, 0), }, }, readOnly: true, }, ], assignments: [ { id: 'assignment-1', eventId: 'event-1', resourceId: 'room-a', }, ], timeRanges: [],} const scheduler = new RenderScheduler({ data: schedulerData, domain, height: 520, title: '资源排期', view: 'resourceTimelineWeek', zoomPreset: 'standard', onEventActivate: event => { console.log( event.item.eventId, event.item.resourceId, event.surface, ) },})
全天 Event 使用日历日期而不是浏览器本地时间:
const allDayData: SchedulerDataSnapshot = { resources: [{ id: 'team-a', title: '交付团队 A' }], events: [{ id: 'release-window', title: '版本发布窗口', time: { kind: 'all-day', range: { start: { year: 2026, month: 7, day: 31 }, endExclusive: { year: 2026, month: 8, day: 2 }, }, }, }], assignments: [{ id: 'release-window-team-a', eventId: 'release-window', resourceId: 'team-a', }], timeRanges: [],}
该 Event 覆盖 7 月 31 日和 8 月 1 日,不包含 8 月 2 日。
组件必须由父布局提供有限宽高。构造完成后,把 scheduler 作为普通 render
object 放入页面布局树即可。
构造参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
data |
SchedulerDataSnapshot |
必填 | 完整本地数据快照。 |
domain |
SchedulerInstantRange |
必填 | 有限显示时间范围。 |
height |
number |
420 |
Scheduler 未由父布局强制高度时使用的组件默认高度;其他通用布局属性见上方链接。 |
view |
SchedulerView |
resourceTimelineDay |
初始 Day、Week、Month 或 Agenda 视图。 |
zoomPreset |
SchedulerZoomPreset |
standard |
时间轴密度。 |
slotDurationMs |
number |
1_800_000(30 分钟) |
单个时间槽持续毫秒数。 |
slotWidth |
number |
72 |
standard 密度下的时间槽宽度。 |
monthDayWidth |
number |
64 |
Month standard 密度下每个 UTC 日历日的显示宽度。 |
resourceRowHeight |
number |
36 |
资源行高度。 |
resourceHeaderWidth |
number |
180 |
冻结资源表头宽度。 |
timeHeaderHeight |
number |
48 |
冻结时间表头高度。 |
title |
string |
'Resources' |
资源表头标题。 |
queryKey |
string |
'render-scheduler' |
当前数据查询身份。 |
headerFormatter |
SchedulerHeaderFormatter |
内置 formatter | Month、Month day、Day 和 time header 文本格式化。formatMonth 与 formatMonthDay 可选。 |
editable |
boolean |
false |
是否允许公共本地 mutation API 和默认事件编辑器写入当前快照。 |
snapDurationMs |
number |
当前时间槽时长 | 指针创建、移动和受限键盘空间编辑使用的正整数毫秒步长。 |
defaultEventDurationMs |
number |
max(3_600_000, snapDurationMs) |
空白时间槽创建使用的默认时长,不能小于 snap。 |
createEventDraft |
(context) => draft | null |
undefined |
双击空白 Timeline 时间槽时调用一次的同步草稿工厂;返回 null 表示取消创建。 |
onStateChange |
(state) => void |
undefined |
加载状态、视图、滚动位置、视口/内容尺寸、可见投影、编辑开关或 Event Editor 状态变化后接收最新 RenderSchedulerDebugState。 |
onBeforeDataChange |
(event) => void |
undefined |
本地 transaction 校验成功、Store 提交前同步触发;调用 event.preventDefault(rejection?) 可取消提交。 |
onDataChange |
(change) => void |
undefined |
每次成功本地提交后接收一次深度冻结的变更。 |
onBeforeSelectionChange |
(event) => void |
undefined |
选择提交前同步触发;调用 preventDefault() 可取消非强制选择变化。 |
onSelectionChange |
(event) => void |
undefined |
选择提交后接收 previous、current、source、reason 和 forced。 |
onBeforeEventActivate |
(event) => void |
undefined |
Event 激活前同步触发;取消后不执行默认激活行为。 |
onEventActivate |
(event) => void |
undefined |
普通 Canvas 与 overflow Event 的统一激活通知;使用 surface 区分来源表面。 |
onBeforeViewChange / onViewChange |
(event) => void |
undefined |
View 切换前后的业务事件。 |
onBeforeZoomChange / onZoomChange |
(event) => void |
undefined |
Zoom 切换前后的业务事件。 |
onBeforeNavigate / onNavigate |
(event) => void |
undefined |
日期、资源和键盘分页语义导航前后的业务事件。 |
onViewportChange |
(event) => void |
undefined |
实际可视区域变化后的合并通知,不对滚轮的每个像素提供同步拦截。 |
缺少 data、数据关系无效、domain 无效或 formatter 协议无效时,
RenderSchedulerConfigurationError.reason 分别为:
missing-local-datainvalid-local-datainvalid-domaininvalid-header-formatterinvalid-editing-optionsinvalid-interaction-options
实例属性
以下是业务开发者可能直接读取或修改的 Scheduler 自有属性;通用布局属性和框架
焦点路由仍遵循 RenderBox / render tree 契约。
| 属性 | 类型 | 可写 | 说明 |
|---|---|---|---|
resourceHeaderWidth |
number |
否 | 构造时解析后的 Resource header 宽度。 |
timeHeaderHeight |
number |
否 | 构造时解析后的 time header 高度。 |
title |
string |
否 | Resource header 标题。 |
headerFormatter |
SchedulerHeaderFormatter |
否 | 构造时验证后的 header formatter。 |
editable |
boolean |
是 | 运行时可动态开启或关闭本地编辑;关闭时会取消未提交交互和编辑器。 |
queryKey |
string |
否 | 当前查询身份;使用 setQueryKey() 更新。 |
isFocused |
boolean |
否 | 当前是否持有框架焦点;通常由 runtime 输入路由维护。 |
Header formatter
formatDay() 和 formatTime() 必须提供;Month 使用的 formatMonth() 与
formatMonthDay() 可选,省略时使用内置 UTC 文本。
| 方法 | 输入 | 返回 |
|---|---|---|
formatMonth(input) |
{ monthIndex, startEpochMs } |
月份表头文本。 |
formatMonthDay(input) |
{ dayIndex, startEpochMs } |
Month 日期槽文本。 |
formatDay(input) |
{ dayIndex, startEpochMs } |
Day / Week 日期表头文本。 |
formatTime(input) |
{ slotIndex, startEpochMs } |
Timeline 时间槽文本。 |
monthIndex、dayIndex 和 slotIndex 是当前 domain 内从 0 开始的投影索引;
startEpochMs 是对应槽位的 UTC instant。
const scheduler = new RenderScheduler({ data: schedulerData, domain, headerFormatter: { formatMonth: ({ startEpochMs }) => { const date = new Date(startEpochMs) return `${date.getUTCFullYear()}-${date.getUTCMonth() + 1}` }, formatMonthDay: ({ startEpochMs }) => String(new Date(startEpochMs).getUTCDate()), formatDay: ({ dayIndex }) => `Day ${dayIndex + 1}`, formatTime: ({ startEpochMs }) => { const date = new Date(startEpochMs) return `${String(date.getUTCHours()).padStart(2, '0')}:` + String(date.getUTCMinutes()).padStart(2, '0') }, },})
数据模型和身份
SchedulerDataSnapshot 是一次完整替换的数据边界:
resources描述资源及可选层级、颜色和顺序;events描述 timed 或 all-day 事件;assignments使用 ID 把 Event 分配到 Resource;timeRanges描述 working、unavailable、background 或 blocked 背景区间。
SchedulerKey 保留调用方传入的 string | number 类型和值,因此字符串
'1' 和数字 1 是不同身份。Event、Resource 和 Assignment ID 应在各自集合
内稳定且唯一,Assignment 引用的 Event 和 Resource 必须存在。
timed Event 使用 epoch milliseconds;endEpochMs 不包含在事件区间中。all-day
Event 使用 SchedulerAllDayRange.start 和 endExclusive 的日历日期,不应把
日期字符串隐式转换成浏览器本地时区的 instant。
视图、密度和导航
SchedulerView 支持:
resourceTimelineDay:单日资源时间线;resourceTimelineWeek:多日资源时间线;resourceTimelineMonth:按 UTC 日历日压缩显示的资源月时间线,使用 Month / Day 双层表头;timed Event 按覆盖的 UTC 日历日视觉槽排列,all-day Event 按原始SchedulerAllDayRange排列。两者都支持选择、激活和 Event Editor,且不会因为 Month 的视觉压缩而修改底层 Event 时间;agenda:按资源和时间排序的列表视图。
Month 不会把 domain 自动扩展为自然月;调用方传入的半开 domain 仍是唯一显示
边界。若要显示完整自然月,应传入该月第一天零点到下月第一天零点。Month 的
compact、standard、comfortable 分别调整日宽,不改变原始事件时间。
Month 的 UTC 月份与日期计算覆盖 Scheduler 接受的安全整数 epoch,不依赖
JavaScript Date 的 0–99 年特殊规则或 TimeClip 范围。
SchedulerZoomPreset 支持 compact、standard、comfortable。以下方法返回
true 表示状态实际发生变化;无效参数、无匹配项、无变化或组件已释放时通常
返回 false。
| 方法 | 用途 |
|---|---|
setView(view) |
切换 Day、Week、Month 或 Agenda。 |
setZoomPreset(preset, anchorViewportRatio?) |
围绕视口锚点切换密度。 |
setScroll(scrollX, scrollY) |
设置并裁剪双轴滚动位置。 |
scrollToDate(epochMs, alignment?) |
定位日期或时间。 |
scrollToResource(resourceId, alignment?) |
定位资源。 |
setNowEpochMs(epochMs | null) |
设置或清除当前时间指示线。 |
clearSelection() |
清除当前选择并回调 null。 |
定位对齐可以使用 start、center、end 或 nearest。指针和框架键盘交互都
使用当前可见的只读目标;这属于 Canvas 组件交互,不表示存在语义 DOM。
交互可发现性和光标
Scheduler 在 Canvas 内直接绘制 hover、pressed、selection、active 和不可操作状态。 鼠标进入目标后,可先根据填充、边框、状态标记和光标判断交互,不需要猜测整个 网格都是按钮。单击静态区域只会让组件获得框架焦点,不会自动给第一个 Event 绘制 active 边框;active 边框来自实际选择或键盘导航。
| 区域或状态 | 可见反馈与光标 | 单击 | 双击或拖动 |
|---|---|---|---|
可写 timed Event,editable=true 且当前 projection 已就绪 |
hover/pressed 填充;主体为 grab,拖动后为 grabbing;左右边缘为 ew-resize |
选择并激活 Event | 双击打开编辑器;拖主体移动/改派,拖边缘拉伸 |
| Month timed / all-day Event | hover/pressed 填充;可用行为 pointer,disabled 行为 not-allowed |
选择并激活可用 Event | 编辑开启时双击打开 edit 或 view-only editor;不支持空间拖动、改派或拉伸 |
| Month 空白日格 | default,不绘制创建预览 |
清除当前 Event 选择 | 不调用 createEventDraft,不创建 Event |
普通 Event,editable=false |
hover/pressed 填充;pointer |
选择并激活 Event | 不进入本地编辑 |
| readonly Event 或 readonly Resource 上的 Event | 锁定标记;hover/pressed 填充;pointer |
选择并激活 Event | 编辑开启时双击以 view-only 模式查看;不能移动、改派或拉伸 |
| disabled Event | 禁用标记;not-allowed |
不选择、不激活 | 不打开编辑器,也不开始拖动 |
| 可创建的 Timeline 空白时间格 | 按默认 Event 时长绘制预览框;crosshair |
清除当前 Event 选择 | 双击调用 createEventDraft 并在草稿有效时打开 create editor |
| readonly Resource 或 blocked 区间中的空白时间格 | readonly/blocked 预览框;not-allowed |
仅清除当前 Event 选择 | 不创建 Event |
未开启编辑或未提供 createEventDraft 的空白时间格 |
不绘制创建预览;default |
清除当前 Event 选择 | 不创建 Event |
| time header、resource header 和 Resource 标题 | default,无业务 hover |
无选择或激活动作 | 无业务动作 |
| overflow 指示器 | hover/pressed 填充;pointer |
打开隐藏 Event 列表 | 不适用 |
| overflow 可用行 / disabled 行 | 可用行为 pointer;disabled 行为 not-allowed |
可用行触发 overflow activation;编辑开启时继续打开 Event Editor | disabled 行不响应 |
| scrollbar thumb | hover 为 grab,按住为 grabbing |
开始捕获并拖动 | 拖动滚动 |
| scrollbar track | pointer;按下显示 pressed |
向目标方向翻页一次 | 不适用 |
| Agenda Event | hover/pressed 填充;可用行为 pointer,disabled 行为 not-allowed |
选择并激活可用 Event | 编辑开启时双击打开 edit 或 view-only editor;不支持空间拖动 |
Event 的 selection/active 描边与 hover/pressed 填充可以同时出现,因此已选 Event
仍能表达当前鼠标位置。指针离开、取消、失焦、视图或数据切换、隐藏、卸载和释放
都会清理临时 hover、pressed、tooltip 与 cursor;不会留下“看起来还能操作”的旧状态。
刷新期间如果保留了上一份 projection,旧内容仍可查看和选择,但不会显示
grab、resize 或空白创建光标,也不能开始新的本地编辑;当前 projection 再次
进入 ready 后才恢复这些编辑反馈。
事件和回调
选择、激活、View、Zoom 和语义导航使用统一的同步 before/after 管线。before
事件可以调用 preventDefault({ code, message? }) 取消操作;before 回调抛错时
采用 fail-closed,状态不提交。after 回调在状态提交后触发,回调抛错不会回滚
Scheduler。事件对象和其中的 SchedulerReadonlyItem、viewport 均为冻结对象。
import { RenderScheduler, type SchedulerDataSnapshot, type SchedulerInstantRange,} from 'ds-ui' const interactionData: SchedulerDataSnapshot = { resources: [{ id: 'room-a', title: '会议室 A' }], events: [], assignments: [], timeRanges: [],}const interactionDomain: SchedulerInstantRange = { startEpochMs: Date.UTC(2026, 6, 28), endEpochMs: Date.UTC(2026, 6, 29),}const interactionScheduler = new RenderScheduler({ data: interactionData, domain: interactionDomain, onBeforeSelectionChange: event => { if (event.current?.readOnly) { event.preventDefault({ code: 'readonly-selection', message: '当前业务不允许选择只读日程。', }) } }, onSelectionChange: event => { console.log(event.previous, event.current, event.source) }, onBeforeEventActivate: event => { if (event.item.disabled) event.preventDefault() }, onEventActivate: event => { console.log(event.item.eventId, event.surface) },})
回调数据保留原始 ID 类型,并包含标题、Resource 标题、当前视图、半开时间区间、
readOnly 和 disabled。它不返回业务 metadata 或内部布局对象。disabled
Event 不触发 activation;清除选择时 onSelectionChange.current 为 null。
普通 Event 与 overflow Event 共用 onEventActivate,通过 surface 的
canvas | overflow 区分,不再提供独立的 overflow activation 回调。
onSelectionChange.reason 使用以下稳定值:
| reason | 含义 |
|---|---|
select |
pointer、keyboard 或 API 选择了 Event。 |
clear |
空白区交互或 clearSelection() 清除了选择。 |
activation |
Event 激活流程同步了选择目标。 |
editor |
打开 Event Editor 时同步了选择目标。 |
mutation |
本地创建、更新、改派或删除后协调选择。 |
data-replacement |
setData() 后按新 snapshot 协调原选择身份。 |
projection-loss |
原选择目标不再存在于当前可见 projection。 |
reconcile 是 SchedulerInteractionSource,不是 selection reason;它表示组件为
维持公开选择状态一致性而执行的强制协调。
setView()、setZoomPreset()、scrollToDate()、scrollToResource() 和键盘
分页分别通过 View、Zoom 和 Navigate 事件暴露。滚轮、滚动条和实际布局引起的
可视区域变化使用合并后的 onViewportChange;它是提交后通知,不会在每个像素
变化前同步阻塞绘制。
SchedulerViewport 同时包含像素窗口和业务可用的语义窗口:
view表示快照对应的当前视图;scrollX、scrollY、bodyWidth、bodyHeight、contentWidth和contentHeight表示 Canvas 内部滚动与尺寸;visibleTimeRange是当前可见的半开时间区间;过渡状态无法可靠投影时为null;visibleResourceIds保留原始 Resource ID 类型,并按当前可见顺序返回。
onBeforeNavigate 的 previousViewport 是导航提交前快照;onNavigate 同时
保留该快照,并通过 viewport 提供提交后的最终快照。onViewportChange 在
浏览器绘制帧内合并多次滚动、布局或 projection 更新:previous 保留该帧最早
快照,current 使用最后快照;若最终语义与像素窗口均未变化,则不发送事件。
setData() 引起的 domain、内容尺寸或可见资源变化也遵守同一通知规则。
在 before 回调中同步执行 setData()、本地 CRUD、View、Zoom、滚动或其他已
提交交互时,外层旧提案会按 revision 失效,不会把旧 Store 的选择或激活状态
提交到新快照。已选 Event 的标题、时间、Resource、状态或当前 View 变化时,
后续回调读取到的 SchedulerReadonlyItem 会同步刷新;Event 被删除时,强制
选择清理发生在对应 onDataChange 之前。
选择目标因数据替换或 projection 丢失而消失时,Scheduler 必须清除无效身份。
这种协调只发送 forced: true 的 onSelectionChange,不调用可取消的 before
事件。其他 pointer、keyboard、editor 和 API 选择变化均可同步取消。
Month all-day Event 的 startEpochMs / endEpochMs 是其日历日期范围对应的 UTC
零点半开投影,仅用于统一的可见区间回调;Event Editor 和本地 CRUD 始终读取并
写回原始 SchedulerAllDayRange。
可选客户端本地 CRUD
设置 editable: true 后,可以通过同步 API 原子修改 Scheduler 当前持有的客户端
快照:
| 方法 | 用途 |
|---|---|
createEvent(draft) |
一次创建一个 Event 和至少一个 Assignment。 |
updateEvent(command) |
更新标题、时间,以及可选的单个 Assignment Resource。 |
removeEvent(eventId) |
一次删除 Event 及其全部 Assignments。 |
openEventEditor(eventId, assignmentId?) |
为当前可见 projection 中的 Event 打开默认编辑器;成功返回 true。 |
cancelEdit() |
取消当前编辑器;实际关闭时返回 true,没有 editor 时返回 false。 |
createEvent() 接收 { event, assignments },并要求至少一个 Assignment;
createEventDraft(context) 使用相同草稿结构。空白 Timeline 创建时的 context
包含 view、resourceId、startEpochMs 和 endEpochMs。
SchedulerUpdateEventCommand 只修改显式提供的字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
eventId |
SchedulerKey |
是 | 要更新的 Event。 |
title |
string |
否 | 新标题。 |
time |
SchedulerEventTime |
否 | 新 timed 或 all-day 时间。 |
assignment |
{ assignmentId, resourceId } |
否 | 把指定 Assignment 改派到目标 Resource。 |
import { RenderScheduler, type SchedulerDataChange, type SchedulerDataSnapshot, type SchedulerInstantRange,} from 'ds-ui' const domain: SchedulerInstantRange = { startEpochMs: Date.UTC(2026, 6, 28), endEpochMs: Date.UTC(2026, 6, 29),}const schedulerData: SchedulerDataSnapshot = { resources: [ { id: 'room-a', title: '会议室 A' }, { id: 'room-b', title: '会议室 B' }, ], events: [], assignments: [], timeRanges: [],}const changes: SchedulerDataChange[] = []const scheduler = new RenderScheduler({ data: schedulerData, domain, editable: true, onBeforeDataChange: event => { if (event.change.action === 'remove') { event.preventDefault({ code: 'demo-delete-policy', message: '当前客户端规则不允许删除该日程。', }) } }, onDataChange: change => { changes.push(change) },}) const created = scheduler.createEvent({ event: { id: 'event-2', title: '设计评审', time: { kind: 'timed', range: { startEpochMs: Date.UTC(2026, 6, 28, 11, 0, 0), endEpochMs: Date.UTC(2026, 6, 28, 12, 0, 0), }, }, }, assignments: [{ id: 'assignment-2', eventId: 'event-2', resourceId: 'room-a', }],}) if (created.ok) { scheduler.updateEvent({ eventId: 'event-2', title: '设计评审(已确认)', time: { kind: 'timed', range: { startEpochMs: Date.UTC(2026, 6, 28, 13, 0, 0), endEpochMs: Date.UTC(2026, 6, 28, 14, 30, 0), }, }, assignment: { assignmentId: 'assignment-2', resourceId: 'room-b', }, }) scheduler.openEventEditor('event-2', 'assignment-2')}
成功时,SchedulerMutationResult 包含 revision 和同一个
SchedulerDataChange;失败时包含稳定 reason,且不会增加 revision 或触发
onDataChange。同时修改 Event 时间与 Assignment Resource 仍只产生一次提交和
一次回调。完整顺序为“transaction 校验 → onBeforeDataChange → revision 再确认
→ 本地提交 → 选择状态协调 → onDataChange”。before 回调取消会返回
reason: 'cancelled',其 code / message 会随 rejection 返回;before 回调
抛错返回 callback-error,回调期间数据身份或 revision 失效返回 stale。三种
情况都保持零提交。回调内重入 mutation 返回 busy。
业务代码应按以下类别处理失败结果:
| 类别 | reason |
说明 |
|---|---|---|
| 生命周期或状态 | disposed、editing-disabled、not-ready、busy |
当前组件不能接受新的本地提交。 |
| 身份或可写性 | readonly、disabled、not-found、duplicate |
目标不可写、找不到或身份重复。 |
| 数据校验 | invalid-event、invalid-range、invalid-relation、outside-domain、blocked-range、unsupported-recurrence、no-change |
候选数据不满足 Scheduler 本地约束。 |
| 宿主回调或并发 | cancelled、callback-error、stale、internal-error |
before 回调取消/抛错、候选 revision 失效或内部提交失败。 |
失败结果不会增加 revision 或调用 onDataChange。cancelled 可以带
rejection: { code, message? };其他 reason 不应依赖异常文本做业务分支。
SchedulerDataChangeDescriptor 不再用一个 operation 混合描述数据动作和交互
手势:
action为create、update或remove;changeKinds可同时包含title、time、recurrence、state、version、metadata和assignment;gesture仅在空间编辑时出现,为move、resize或reassign;source表示api、editor、pointer或keyboard。
例如编辑器同时修改标题与 Resource 时,change 为 action: 'update'、
changeKinds: ['title', 'assignment'],不会再被错误归类为一次 reassign。
change DTO 会深度冻结,并保留 string | number ID;它只包含变更需要的 Event
与 Assignment 字段,不包含业务 metadata、内部 token、布局或绘制对象。组件内部
记录仍保留原始 metadata。此 API 只修改当前 Web 客户端组件实例,不提供权限、
服务端保存、optimistic rollback 或冲突解决。默认 editable=false 时,调用
create/update/delete 会返回 editing-disabled。
本地提交不代表服务端保存。业务宿主可以在 onDataChange 中自行持久化,但
Scheduler 不等待网络结果,也不提供 pending、rollback 或 conflict 状态。
Timeline 指针创建、移动、重分配和拉伸
设置 editable: true 后,Day 和 Week Timeline 提供以下 Canvas 指针交互:
- 双击空白时间槽:按
snapDurationMs对齐时间,并以defaultEventDurationMs生成SchedulerCreateEventContext;组件同步调用一次createEventDraft(context),返回草稿时打开 create editor,返回null或抛错 时不创建记录; - 在 timed Event 上按下并移动至少 4px:进入拖动预览;水平移动按 snap 调整 start/end,并保持原 duration;
- 拖到另一个可写 Resource 行:在同一次候选中移动时间并重分配当前 Assignment;
- 悬停在 timed Event 已物化的左/右边缘:显示 Canvas resize handle 和
ew-resize游标;按下后立即捕获指针,拖动左边缘只修改 start,拖动右边缘只 修改 end; - 拉伸使用与移动相同的
snapDurationMs,Event 最短可缩到一个 snap;原本短于 snap 的合法 Event 保留其原时长作为最小值。交叉、零时长、domain 外和 blocked 候选会被拒绝,不做静默裁剪; - 指针靠近 Timeline 边缘时:由一个内部计时器同时处理水平和垂直自动滚动, 预览按最新 scroll offset 重新投影。拉伸只启用水平自动滚动,不改变 Resource 轴位置。
拖动期间只绘制预览,不修改本地快照。松开指针时进行最后一次校验;合法候选最多
提交一个 revision 并触发一次 onDataChange,未变化、被拒绝、Escape、失焦、
取消 capture、切换 view/data 或释放组件都不会提交。
移动/重分配预览会弱化当前 Assignment 对应的源 Event,显示目标 Resource、snap guide 和 ghost;拉伸预览会同步弱化当前可见窗口内同一 Event 的全部 Assignment 投影,并在各自原 Resource 行绘制同一 start/end ghost。合法候选使用 selection/accent 语义色;readonly、disabled、recurrence、blocked range、domain 外或其他无效候选使用 danger 语义色并保持零写入。
指针空间编辑只适用于 Timeline 中的 timed Event。all-day、Agenda 和 copy/paste 不属于指针 candidate;这些记录仍可在适用时通过公共 CRUD 或默认 Event Editor 处理。
Timeline 键盘本地编辑
设置 editable: true、让 Scheduler 获得框架焦点并把 active 目标移动到可写的
Timeline timed Event 后,可以使用以下固定键位:
| 键位 | 本地操作 |
|---|---|
Enter |
打开当前 Event 的默认编辑器;Agenda 也支持。 |
Delete |
打开已有的二次删除确认;第一次按键不会删除。 |
Ctrl+← / Ctrl+→ |
按一个 snapDurationMs 向前/向后移动,保持 duration。 |
Ctrl+↑ / Ctrl+↓ |
把当前 Assignment 改派到扁平资源顺序中的上一个/下一个可写 Resource;跳过 readonly Resource。 |
Ctrl+Shift+← / Ctrl+Shift+→ |
按一个 snap 调整 start。 |
Alt+← / Alt+→ |
按一个 snap 调整 end。 |
组合键使用精确修饰键匹配;额外修饰键、输入法 composition 和未列出的键不会触发
本地修改。移动、改派和拉伸直接复用指针编辑的吸附候选与本地事务校验,不建立
pointer capture、拖动预览或 auto-scroll timer。成功命令恰好增加一次 revision,
触发一次 onDataChange,其 source 为 keyboard;移动、改派和拉伸的
action 均为 update,gesture 分别为 move、reassign 或 resize,删除的
action 为 remove。readonly、disabled、recurrence、
blocked range、domain 外、最小时长和资源边界候选均保持零写入,不做静默裁剪。
直接空间键位只适用于 Day/Week Timeline 的 timed Event。all-day Event 与 Agenda
不执行 move/reassign/resize,但仍支持 Enter 打开编辑器和 Delete 二次确认。
这些键位是 Canvas 后台排班组件的效率交互,不表示存在语义 DOM、ARIA item
projection 或屏幕阅读能力。
默认事件编辑器
设置 editable: true 后,Timeline、Agenda 和 dense overflow 共用同一个 Canvas
Event Editor。可以通过以下方式打开:
- 在 Timeline Event 上双击;
- 在 Month Event 上双击;
- 在 Agenda Event 上双击;
- 在 overflow popup 中选择 Event;
- 让 Scheduler 获得焦点后,对当前 active Event 按
Enter; - 调用
openEventEditor(eventId, assignmentId?)。
openEventEditor() 只为当前 ready、可见 projection 中能够解析到 Event、
Assignment 和 Resource 的目标打开 editor。组件已释放、编辑未开启、目标不存在、
目标不可见或 Event 为 disabled 时返回 false。再次打开另一个 Event 会先取消
前一个未提交会话。
编辑器提供:
- Title;
- start/end 日期;
- timed Event 的 start/end 时间;
- all-day 开关;
- 当前 Assignment 的 Resource;
- Save、Cancel 和经过二次确认的 Delete。
Save 使用与 public CRUD 相同的同步本地 command pipeline。标题、时间和 Resource
同时变化时仍只产生一个 revision 和一次 onDataChange。无变化的 Save 直接
关闭且不提交;无效字段保持 editor 打开并显示错误;Cancel、Escape 和 outside
dismiss 都不会产生 callback。Delete 第一次只进入确认状态,第二次确认才删除
Event 及其全部 Assignments,并协调当前 selection。
readonly Event、带 recurrence identity 的 Event,以及当前 Assignment 指向 readonly Resource 时,editor 以 view-only 模式打开。view-only 允许查看和关闭, 不显示写入动作。disabled Event 不打开 editor。
UTC、all-day 和 Resource 语义
默认 editor 把 timed Event 的 epoch milliseconds 按 UTC 拆成日期和时间字段,
保存时再按 UTC 组合;它不会使用浏览器本地时区猜测业务时间。原 Event 上已有的
timeZone 记录会保留,但当前仍不提供生产级 IANA timezone / DST adapter。
all-day 表单中的结束日期是用户可见的包含式日期;写回
SchedulerAllDayRange.endExclusive 时转换成下一日的排他边界。例如,UI 中
“2026-07-31 至 2026-07-31”会保存为:
const savedAllDayTime = { kind: 'all-day', range: { start: { year: 2026, month: 7, day: 31 }, endExclusive: { year: 2026, month: 8, day: 1 }, },} as const
Resource 选择保留原始 string | number ID,因此数字 1 与字符串 '1' 不会
碰撞。readonly Resource 可以显示,但不能成为可写提交目标。
Event 和 Assignment 的业务 metadata 会在组件内部原记录中保留,但不会进入
SchedulerDataChange。change DTO 只返回已经公开的最小字段,避免把业务 metadata
或内部布局对象意外暴露给通用组件层。
取消与宿主切换
Scheduler 把尚未提交的 pointer candidate、Event Editor 会话和被动 popup 分成
独立 owner。取消只清理临时 UI 状态,不产生 Store revision 或
onDataChange:
| 原因 | 处理结果 |
|---|---|
pointercancel、touch cancel、window blur |
释放 pointer capture,停止 auto-scroll,清除 preview、resize cursor、pressed/hover 和 tooltip;不提交候选。 |
Scheduler focusOut() |
取消仍由 Scheduler 持有的 pointer sequence,但不关闭 Event Editor;焦点从 Scheduler 转入 popup 表单属于正常流程。 |
Escape |
最内层 Editor popup / Delete confirmation 优先,其次是 Event Editor、overflow、active pointer 和 tooltip;一次按键只关闭一层,且不提交。 |
editable=false |
取消 pointer,关闭 Editor、Delete confirmation、overflow 和 tooltip;保留仍有效的 selection 身份。 |
setView()、setZoomPreset() |
先取消所有未提交编辑,再切换 view 或重新投影密度;zoom 保留有效 selection,view 重新建立 active navigation。 |
成功的 setData() |
先完整验证候选快照,验证成功后才清理旧 owner 并原子替换;旧 pointerup、Save 或 Delete confirmation 随后均失效。 |
setQueryKey()、refresh() |
取消未提交交互并等待新 projection;不产生本地 mutation。 |
visible=false、detach()、dispose() |
清理 capture、timer、preview、popup、tooltip 和框架焦点注册,不尝试把焦点恢复到已隐藏或已卸载的 Scheduler。 |
无效 setData() 候选在清理之前失败,因此当前数据、Editor、pointer owner、view、
zoom、scroll 和 selection 都保持原样。普通滚动不属于全局取消:captured drag
可继续自动滚动,Editor anchor 可见时继续跟随;目标离开 projection 后才关闭
Editor。
键盘 move、reassign 和 resize 是当前调用栈内完成的同步命令,不留下可取消的 capture、timer 或 preview。Agenda 与 all-day 仍是 editor-only,不执行空间 pointer 或键盘 mutation。
完整快照替换
使用 setData() 原子替换整个本地快照:
const accepted = scheduler.setData(nextData, {
queryKey: 'week-2026-08-03',
domain: nextDomain,
})
- 返回
true表示新快照已通过同步校验并完成替换; - invalid data 或 domain 会同步抛出 configuration error;
- 校验失败时旧 data、domain、query key、view、zoom、scroll 和 selection 保持 不变;
- view 和 zoom 会保留,时间与资源锚点会裁剪到新数据边界;
- 原身份仍存在时保留选择,否则在新快照就绪后清除;
- hover、pressed、tooltip、overflow 和 pointer capture 不跨数据代际;
- 组件已释放时返回
false。
setData() 是完整替换,不是增量 patch。调用方应先在业务层构造一致的新数组,
再一次提交。
状态、刷新和错误
| API | 用途 |
|---|---|
setQueryKey(queryKey) |
改变当前查询身份并请求重新布局。 |
refresh() |
重新处理当前本地快照的可见范围。 |
retry() |
当前为可重试的 error 或 refreshing-error 时重试。 |
whenSettled() |
等待当前可见更新全部收敛。 |
debugState() |
读取稳定的只读标量摘要。 |
SchedulerStatus 包含 idle、loading、refreshing、refreshing-error、
ready、empty、error 和 disposed。刷新时可以保留上一份可见内容;错误面
是否提供 retry 由 debugState().retryable 表示。保留内容不等同于当前数据已
ready:刷新或刷新失败期间不会接受新的指针、键盘或 Editor 本地编辑。
当前数据源仍是本地 snapshot。以上状态 API 不代表存在远程 Provider、server paging 或网络请求契约。
Theme、高对比和绘制
Scheduler 使用框架统一主题,支持 light、dark 和 high-contrast palette。业务层 通过应用主题切换外观,不应覆盖内部 Event、header、selection、拖动 ghost、 target highlight、snap guide 或 scrollbar 绘制状态。High Contrast 的合法与 拒绝预览同时依靠语义颜色和明确边框区分。
组件按可见资源行和时间窗口执行 layout、paint 和 hit-test。长时间范围或大量
Event 不会要求每帧物化全部记录;setData() 本身仍允许进行 O(input size) 的
关系校验和索引重建。
RenderSchedulerDebugState 只暴露:
- status、retryable、disposed;
- view、zoom、scroll、content/body/max-scroll 尺寸;
- visible rows、events、overflow;
- pending updates、active requests;
- layout、paint、hit-test 次数;
- transient surface、focus、pointer capture 和 overflow popup 状态。
- editing enabled、editor open/mode、mutation commit/reject 和 change callback 计数。
- pointer interaction、edit gesture phase、preview mode/validity、preview/ validation/commit/cancel 计数、draft factory 计数,以及 auto-scroll tick/ active timer 计数。
这些字段用于稳定诊断,不提供内部 rect、token、owner、完整布局快照或 painter 对象。
时间、recurrence 和时区边界
domain必须是固定、有限且有效的SchedulerInstantRange;- timed Event 和时间定位统一使用 epoch milliseconds;
- instant 和 all-day 区间都使用结束端 exclusive 的半开语义;
SchedulerRecurrenceDefinition是共享的 recurrence master 记录,但RenderScheduler的本地数据入口采用 expanded-occurrences 模式,不接受 master;- Scheduler 不执行 recurrence expansion;调用方必须先生成需要显示的
occurrences,并为每条记录同时提供
recurrenceParentId和originalOccurrenceStart; - 当前不提供生产级 IANA timezone / DST adapter;
- 调用方必须在构造 snapshot 前确定 display zone 和消歧规则,不能依赖浏览器 本地时区猜测业务时间。
例如,调用方把外部 series 展开后,可以把单次 timed occurrence 写成:
import type { SchedulerEvent } from 'ds-ui' const expandedOccurrence: SchedulerEvent = { id: 'series-42@2026-07-28T09:00Z', title: '每日交接班', time: { kind: 'timed', range: { startEpochMs: Date.UTC(2026, 6, 28, 9), endEpochMs: Date.UTC(2026, 6, 28, 10), }, timeZone: 'UTC', }, recurrenceParentId: 'series-42', originalOccurrenceStart: { kind: 'timed', originalWallStart: { date: { year: 2026, month: 7, day: 28 }, hour: 9, minute: 0, second: 0, millisecond: 0, }, timeZone: 'UTC', originalOffsetMinutes: 0, fold: 'unambiguous', },}
recurrenceParentId 可以引用 snapshot 外部的 series 身份;不要同时把带
recurrence 的 master 混入同一个 RenderScheduler 本地快照。带 recurrence
identity 的 occurrence 当前只读,不进入 Editor 或空间 mutation。
生命周期
Scheduler 跟随 render tree 的 attach、detach 和 dispose 生命周期。宿主页面
销毁时应释放持有它的 render tree。dispose() 会清理当前更新、popup、tooltip、
pointer capture、拖动预览、auto-scroll timer 和内部 owned resources;释放后
业务方法不会重新创建资源。
在替换页面或执行确定性截图前,可以先等待 whenSettled(),再读取
debugState() 或进行下一步操作。
当前产品限制
- 仅支持 local
SchedulerDataSnapshot; - 默认仍是只读浏览;opt-in 客户端编辑提供 create/update/delete 和默认 Canvas Event Editor,并提供 Timeline blank-slot create、timed Event move 与 Resource reassign、start/end resize,以及上述受限键盘空间编辑,但不提供 all-day/Agenda 空间编辑或 copy/paste;
- 不提供 remote provider、server paging 或 optimistic mutation;
- 不提供 recurrence engine/editor,recurrence 必须由调用方预先展开;
- 不提供生产级 IANA timezone / DST adapter;
- Canvas 绘制和交互当前只支持 Google Chrome / Chromium;
- 浏览器兼容范围与屏幕阅读能力是两个独立边界;
- Scheduler 在任何浏览器上都不提供屏幕阅读能力,也不生成供屏幕阅读器消费的 Event/Resource 语义或 ARIA item projection。