文档 / 原生 UI 组件

原生 UI 组件

Metrix.Bar .bar v0 原生 UI 组件库、属性反射、布局与事件回调完整参考。

概述

在 .bar v0 中,Worker 的 render(env) 钩子直接返回 JSON 树形结构来描述界面:

javascript
{
  type: "Column",
  props: { gap: 8, padding: 12 },
  children: [
    { type: "Text", props: { text: "你好", bold: true } },
    { type: "Button", props: { text: "点我", onClick: () => { ... } } }
  ]
}

每个 type 对应由 mygo 驱动的跨平台桌面原生组件。在 props 中传入的属性通过反射自动映射到底层原生组件的方法中。


组件目录

布局与容器

组件名描述常用属性
Box通用矩形容器padding, background, border, radius, width, height
Column垂直弹性布局gap, padding, alignItems, justify
Row水平弹性布局gap, padding, alignItems, justify
Grid网格布局columns, columnTracks, rowTracks, gap, padding
Scroll垂直滚动容器height, maxHeight, padding
ScrollHorizontal水平滚动容器width, maxWidth, padding
ScrollBoth双向滚动容器width, height
Spacer弹性占位空白—
Divider水平或垂直分割线orientation ("horizontal", "vertical")
Form表单分组容器gap, onSubmit
Accordion手风琴折叠面板title, open
AccordionItem手风琴折叠子项title, open, onChange
Field带标签的表单字段包装器label, description, error
Fieldset表单控件集合legend
Collapsible可展开/收起的内容面板open
Overlay浮层遮罩容器children
Split水平可调分栏容器size, onChange(接收左右两个子节点)
SplitPercent水平百分比分栏容器size, onChange(接收左右两个子节点)
SplitVertical垂直可调分栏容器size, onChange(接收上下两个子节点)
SplitVerticalPercent垂直百分比分栏容器size, onChange(接收上下两个子节点)
Sidebar侧边栏导航容器value, onChange
SidebarSection侧边栏分组区块title, open
SidebarItem侧边栏导航条目id, label, icon, badge
Toolbar顶部或底部工具栏gap, padding
ToggleGroup互斥或多选切换按钮组value, onChange
CheckboxGroup复选框组合容器label, gap

Split 与 SplitVertical 的 size 表示第一个面板的宽或高,单位为 DIP,默认 200。onChange(size) 返回调整后的 DIP 值。SplitPercent 与 SplitVerticalPercent 接收相同结构的两个子节点,size 为 0..100 的百分数(默认 50),onChange(value) 回传调整后的原生百分数值,面板随可用空间同比缩放。组件仅渲染前两个子节点;缺失的面板留空,额外子节点忽略。

展示与导航

组件名描述常用属性
Text文本标签text, fontSize, bold, italic, color, wrap, letterSpacing
RichText富文本块(支持多区间样式)spans, fontSize, color
Badge徽标胶囊标签text, color, background
Link超链接文本text, url, color
Icon原生系统或远程图标name, svg, size, color
Image图片展示(支持 URL 或内联数据)src, svg, name, width, height
Avatar用户头像(支持缩写文本回退)name, src, size
Breadcrumbs面包屑导航items, value, onChange
Progress进度条(负值表示不确定加载,支持反向)value, reverse, width, color
Meter标量计量计value, min, max, low, high, optimum
Spinner加载动画指示器size

Image 与 Avatar 让你可以在面板中直接显示远程图片或内联图标:

javascript
{
  type: "Image",
  props: {
    src: "https://example.com/a.png",
    width: 64,
    height: 64
  }
}
  • 来源与优先级:src 支持 HTTPS 与已授权的局域网 HTTP 地址或 data:image/... 内联数据(PNG、JPEG、GIF、WebP、SVG)。属性优先级为 src > svg > name。
  • 头像展示:Avatar 支持位图格式(src)。加载中、加载失败或传入 SVG 时,会自动回退显示由 name 生成的首字母缩写。
  • 网络白名单:Image、Avatar、Icon(name)与 tray.icon 中的远程图片均必须在 <permissions><network> 声明的来源内。允许已授权的局域网 HTTP 来源,单张图片限制为 8 MB 且单边不超过 4096 像素(总像素不超过 1600 万)。data: 内联图片与 Iconify 名称(如 mdi:...)无需配置网络权限。

数据展示与表格

组件名描述常用属性
Table数据表格(支持列宽与表头排序)columns, items, value, sort, onSort, onChange, onSubmit
List原生列表视图(支持自定义行与拖拽重排)items, children, value, followEnd, onChange, onSubmit, onReorder
GridView虚拟化网格项目视图items, minWidth, height, value, onChange, onSubmit, onReorder
Outline树形分层大纲items, open, onChange
OutlineTable带多列属性的树形大纲表格columns, items, open, onChange
Tree树形结构容器children
TreeItem树形可展开子节点label, open, onChange

List 自定义行与拖拽重排

List 支持通过子节点自定义行布局,且 List 与 GridView 均支持拖拽重排:

  • 自定义子节点行:向 List 传入子节点时,每个子节点渲染为独立的一行(此时 items 属性被忽略),并保留子节点自身声明的 key:
    javascript
    {
      type: "List",
      props: { onReorder: (rows, to) => handleReorder(rows, to) },
      children: items.map(item => ({
        type: "Row",
        props: { key: item.id, padding: 8, gap: 12, alignItems: "center" },
        children: [
          { type: "Text", props: { text: item.name } },
          { type: "Badge", props: { text: item.status } }
        ]
      }))
    }
  • 受控拖拽重排 (onReorder):onReorder(rows, to) 回调接收被移动的行下标数组(rows)与目标插入下标(to)。排序为受控模式:宿主通知 Worker 而不擅自修改数据,由脚本更新状态后重新渲染。拖拽释放必须落在列表内容区域内。

交互控件

事件回调(onClick、onChange、onSubmit 等)可直接传入 JavaScript 函数。

组件名描述常用属性
Button原生常规按钮(支持 children 自定义内容)text, children, icon, onClick, disabled
PrimaryButton主操作强调按钮(支持 children 自定义内容)text, children, icon, onClick, disabled
Switch原生滑动开关value, onChange
Checkbox带标签的复选框label, value, onChange
Toggle按压状态切换按钮label, value, onChange
Radio单选框选项label, value, selected, onChange
RadioGroup单选框组合value, onChange
Select下拉选择器(mygo 原生组件)options, items, value, onChange
Combobox可搜索组合框options, value, onChange
Segmented分段控制器(mygo 原生组件)options, items, value, onChange
Tabs选项卡容器(mygo 原生组件)items, labels, options, value, onChange
TextInput单行文本输入框value, placeholder, onChange, onSubmit
TextArea多行文本编辑框(支持行数约束)value, rows, lines ([min, max]), onChange
SearchField原生搜索框value, placeholder, onChange
Slider连续数值滑块value, min, max, step, onChange
StepSlider步进滑块value, steps, onChange
Stepper增减步进器value, min, max, step, onChange
NumberInput数字输入框value, min, max, step, onChange
RangeSlider双手柄范围滑块value, min, max, onChange
Rating星级评分控件value, max, onChange
ColorPicker交互式颜色选择器color, onChange
ColorWell色块展示color
MenuButton弹出菜单按钮text, items
EditableText行内可编辑文本value, placeholder, onChange
Autocomplete自动补全输入框value, suggestions, onChange, onSubmit
TokenField标签词输入框value, suggestions, onChange
Editor原生代码编辑器value, language, readOnly, lineNumbers, softWrap, tabSize, onChange

Tabs 的 items 接受字符串或 { label } 对象,也可通过 labels / options 传入字符串数组。value 和 onChange 的参数都是选中项的下标。

Segmented 和 Select 的 items 接受字符串或 { label, value } 对象,也可通过 options 传入字符串数组。每项的 value 缺省等于 label;组件按每项的 value 匹配 value 属性,onChange 回传选中项原始的 JSON 值。选项内部使用下标标识,因此允许重复标签;标签改名或重排后,只要值仍匹配就能保持选择。值未匹配时,Select 显示空标签,Segmented 遵循 MyGO 显示首项。

这三种组件只支持普通文本标签,不支持选项图标、徽标、禁用状态、分组标题或分割线。

时间与日期控件

组件名描述常用属性
Calendar原生月历视图value, onChange
DateInput日期选择输入框value ("YYYY-MM-DD"), onChange
TimeInput时间选择输入框value ("HH:mm"), onChange

浮层与弹窗

组件名描述常用属性
Modal原生模态浮层卡片open, onDismiss
Popover气泡弹出卡片open, anchor, onDismiss
TooltipBase悬浮自定义提示anchor, children
AlertDialog警告确认对话框open, title, message, buttons, onAction, onDismiss
FindBar原生查找搜索条open, value, current, matches, onChange, onDismiss
Toast浮动轻提示通知open, title, description, type, timeout, onClose

图表组件 (Chart)

Chart 组件可在面板中直接渲染折线图、面积图、柱状图与饼图:

javascript
{
  type: "Chart",
  props: {
    type: "area",          // "line" | "area" | "bar" | "pie" | "radar" | "candlestick" | "sankey"
    height: 120,
    appear: true,          // 开启首次加载动效(默认 true)
    appearKey: "v1",       // 触发重播的业务 key(可选)
    theme: {
      colors: ["#3b82f6", "#10b981"], // 自定义调色板(可选)
      gridColor: "border",            // 网格线颜色(可选)
      appearDuration: 1000,           // 登场动效时长毫秒(0..5000,默认 1000)
      hoverDuration: 120,             // 悬停过渡与淡出时长毫秒(0..500,默认 120)
    },
    xKind: "time",         // "time" | "number" | "category"
    curve: "monotone",     // "linear" | "monotone" | "step" | "stepBefore" | "stepAfter"
    strokeWidth: 1.5,
    grid: true,
    gridStyle: "dashed",   // "dashed" | "solid"
    legend: false,         // pie 默认 true,其余默认 false
    dots: false,           // 为 line/area 绘制数据点
    hover: true,           // 开启原生 Overlay 悬停提示与键盘导览
    xTicks: 5,             // 2..12,基于文本测量动态抽稀
    yTicks: 4,             // 2..12
    referenceLines: [
      { value: 100, label: "Target", color: "accent" }
    ],
    series: [
      {
        name: "请求数",
        fill: { from: 0.28, to: 0.02 },
        points: [
          { x: 1700000000000, y: 120, valueLabel: "120 req/s" },
          { x: 1700003600000, y: 240, valueLabel: "240 req/s" },
          { x: 1700007200000, y: 190, valueLabel: "190 req/s" }
        ]
      }
    ]
  }
}
  • 图表类型、主题与登场动效:

    • type:"line"(默认)、"area"、"bar"、"pie"、"radar"、"candlestick" 或 "sankey"。
    • theme:图表主题样式对象(可选):
      • colors:调色板颜色字符串数组;未显式指定系列/数据点颜色时依序轮转;
      • gridColor:网格线颜色,支持十六进制或主题语义色名,缺省由当前主题 Border.Alpha(0.55) 派生;
      • riseColor 与 fallColor:K 线图涨跌颜色,顶层显式 riseColor/fallColor 优先于 theme 对应字段,未配置时回退默认色(#10b981 与 #ef4444);
      • 颜色优先级:系列/数据项/节点显式颜色 > theme.colors > 系统明暗默认调色板;
      • appearDuration:初始登场动效时长(毫秒),有限数值限制在 0..5000 区间,默认 1000;
      • hoverDuration:悬停过渡与淡出时长(毫秒),有限数值限制在 0..500 区间,默认 120;
      • 动效降级:时长设为 0 或系统开启减弱动态效果(ReduceMotion)时瞬间切换;系统浅深外观变化会更新图表颜色。
    • appear:布尔值(默认 true)。控制首次挂载时的登场动效(四次缓出曲线 1 - (1 - t)^4)和数据更新过渡。设为 false 时数据立即切换。
    • 数据更新:line、area、bar 与 pie(含圆环图)在系列、标签及点数等结构兼容时,以 300ms 同步过渡数值和坐标域;期间再次更新会从当前显示值继续。整张图超过 4096 个数据点、结构不兼容或系统开启 ReduceMotion 时立即切换。radar、candlestick 与 sankey 的数据更新直接生效。
    • appearKey:字符串(可选)。用于在特定业务 key 变化时重播登场动效;缺省时仅在首次挂载时播放。
    • legend:布尔值,控制是否在图表底部或右侧渲染图例。pie 默认 true,其他类型默认 false(sankey 设为 true 时渲染节点图例)。
    • dots:布尔值,默认 false。为 line、area 与 radar 在各数据点绘制点标记。
    • axes:布尔值或 { x: boolean, y: boolean }。设置 axes: false 即可隐藏全部坐标轴与刻度标签,呈现极为紧凑的 sparkline 迷你走势图。
    • height:组件高度 DIP,默认 120(pie 正方形边长默认 120)。
  • 坐标轴、刻度与参考线:

    • xKind:"number"(默认,普通数值)、"time"(Unix 毫秒时间戳)或 "category"(离散分类)。
    • labels:分类轴标签字符串数组 string[],配合 xKind: "category" 或 type: "radar" 使用。X 坐标为从 0 起的整数索引;缺省 labels 时自动生成从 0 到最大索引的数字字符串。
    • xTicks / yTicks:刻度目标数,默认分别为 5 与 4,有效范围在 2..12。X 轴刻度自动消除相邻重叠,在空间允许时尽量保留首尾刻度。
    • xDomain / yDomain:显示域范围 [lo, hi] 二元数组。仅接受有限且 lo < hi 的数值;提供显式域后不再执行 Nice 刻度扩展。柱状图自动 Y 域始终包含 0,正负柱基线均为 0。
    • grid 与 gridStyle:grid 布尔值控制是否绘制 Y 刻度网格线(默认 true);gridStyle 支持 "dashed"(默认轻量虚线)与 "solid"(实线)。网格线颜色可由 theme.gridColor 自定义。
    • referenceLines:参考线列表 [{ value, label?, color? }]。以固定的 Y 轴值(横向柱状图对应 X 轴值)绘制实线,支持自定义标签文字与主题色。
  • 柱状图扩展:

    • horizontal:布尔值,默认 false。仅在 type: "bar" 时生效,渲染横向条形图,X 与 Y 轴互换,柱子沿水平方向延展,基线对齐 X = 0。
    • barRadius:柱子圆角半径 DIP,默认 3,非负数。
    • barGap:柱间间距比例,默认 0.25,范围在 0..0.9 之间。
  • 系列填充与渐变:

    • series 中的每个系列支持 fill。可设置为布尔值(true 为 0.24 透明度)、0..1 透明度数值,或 { from: number, to: number } 垂直渐变填充(角度 180 度,顶部透明度为 from,底部为 to,缺省为 { from: 0.24, to: 0 })。渐变填充优先于数值 fill;area 默认采用垂直渐变填充。
    • color:系列颜色,可传十六进制色值或主题语义色;缺省时轮转使用 theme.colors 或内置调色板。
    • points:数据点集合,每个点支持 label(覆盖 X 轴/时间/分类提示文本)与 valueLabel(覆盖数值提示文本)。
  • 饼图与环形图(Donut):

    • 当 type: "pie" 时,数据通过 data: [{ label, value, valueLabel?, color? }] 传入。valueLabel 仅覆盖浮动提示中的数值文本;外侧标签使用 label。
    • innerRadius:0..1 浮点数,大于 0 时将饼图变为环形图,默认 0。
    • padAngle:浮点数(默认 0.02,取值限制在 0..π),控制扇区之间的夹角间隔。修改 padAngle 仅重构饼图扇区 Path,不重算其他几何。
    • pieLabels:布尔值(默认 false)。开启后在饼图外侧绘制双列文字标签与连接引线,在左右列内夹紧排布,不进行循环回绕;空间不足时自动省略低优先级小扇区标签,优先保证大扇区可读性与边界留白,不强制挤满。
    • 饼图交互与占比:浮动提示统一展示扇区数值(或 valueLabel)与占比份额(Share: XX%);分母仅累计有效有限正值(value > 0),所有有效正值均先按最大值缩放,再以同一缩放后总和计算占比和扇区,杜绝除零或 NaN。悬停时选中扇区沿径向平移 6 DIP,其余扇区透明度降为 0.35;支持键盘左右键、Home、End 与 Escape 键导览,映射安全索引回原始数据项。
    • 默认展示图例(legend: true)。
  • 雷达图(Radar):

    • 当 type: "radar" 时,复用 labels、series、yDomain、legend、dots、fill、strokeWidth、yTicks、grid、hover、appear、appearKey、height 与 theme;忽略 xDomain、xKind、horizontal 与 referenceLines。
    • labels 至少声明 3 个维度(labels.length >= 3);每个系列的 points 必须完整覆盖所有维度且 x 对应离散分类整数索引(0..labels.length-1),不允许重复或缺失。
    • 标签预算:雷达图外围标签使用固定布局预算:宽度为 min(80 DIP, 绘图区宽度 × 0.4),高度为字号的 2.5 倍;标签盒按绘图区边界夹紧,超出盒子预算的文字被裁剪,长中文或多行标签可能仅显示一部分。
    • 默认 fill 为 0.12,显式指定的 fill(数值或渐变)优先;环网与辐条自适应绘图区。
    • 支持按角度分区的指针命中、原生 Overlay 悬停数值提示与键盘导览。
  • K 线图(Candlestick / OHLC):

    • 当 type: "candlestick" 时,通过 candles: [{ x, open, high, low, close, label?, openLabel?, highLabel?, lowLabel?, closeLabel? }] 传入数据,不消费 series。
    • riseColor 与 fallColor:涨跌颜色,支持主题语义色名或十六进制;顶层显式设置优先于 theme 对应字段,未配置时回退默认色(#10b981 与 #ef4444)。close >= open 时采用 riseColor,close < open 时采用 fallColor。
    • 复用 xKind("time" | "number" | "category")、labels、xDomain、yDomain、xTicks、yTicks、axes、grid、gridStyle、referenceLines、barGap、hover、appear、appearKey、height 与 theme。
    • 自动 Y 域由数据中 low 与 high 的极值决定,平值向两侧各延展 1;柱宽由最近正相邻间距计算(回退 12 DIP,限制在 1..30 DIP 之间)。
    • 不对输入数据重新排序;当 X 轴数据无序时自动关闭交互与选择。
  • 桑基图(Sankey):

    • 当 type: "sankey" 时,通过 sankey: { nodes, links } 传入预布局数据。

    • 预布局设计原则:宿主与 UI 侧不做原生 DAG 自动布局。节点坐标 [x0, y0, x1, y1] 及连线几何 y0, y1, width 均为 [0, 1] 归一化浮点数,由 Worker 脚本使用前端布局库(如 d3-sankey)预计算,并将 source/target 投影为字符串,归一化几何作为纯数据交付给宿主。所有 Y 坐标和宽度按绘图区实际高度转换。

    • 节点与边契约:保留首个合法重复 id 节点;节点 value 缺省或无效时由关联有效连线自动计算 max(in, out)。

    • 标签留白预算:按两端节点标签的实际文本宽度保留带上限的水平留白,仅在标签文本或字体族改变且留白实际变化时才使连线几何失效。

    • 交互与高亮:节点优先命中;连线支持 24 次二分贝塞尔曲线命中检测(命中计算零堆分配),悬停高亮关联的入度/出度连线与相邻节点($O(V + E)$)。

    • Worker 预布局示例(结合 d3-sankey 归一化):

      javascript
      import { sankey as d3Sankey } from "https://esm.sh/d3-sankey@0.12.3";
      
      const rawGraph = {
        nodes: [
          { id: "direct", label: "Direct" },
          { id: "signup", label: "Sign Up" },
        ],
        links: [{ source: "direct", target: "signup", value: 30 }],
      };
      
      const layout = d3Sankey()
        .extent([
          [0, 0],
          [1, 1],
        ])
        .nodeWidth(0.03)
        .nodePadding(0.06)
        .nodeId((d) => d.id);
      
      const graph = layout(rawGraph);
      
      const sankeyData = {
        nodes: graph.nodes.map((n) => ({
          id: n.id,
          label: n.label,
          value: n.value,
          x0: n.x0,
          y0: Math.max(0, n.y0),
          x1: n.x1,
          y1: Math.min(1, n.y1),
        })),
        links: graph.links.map((l) => ({
          source: typeof l.source === "object" ? l.source.id : l.source,
          target: typeof l.target === "object" ? l.target.id : l.target,
          value: l.value,
          y0: l.y0,
          y1: l.y1,
          width: l.width,
        })),
      };
  • 原生悬停提示(Overlay Tooltip)与交互:

    • 开启 hover: true 时,7 类图表统一使用原生 ui.Overlay 与 AttachTo 呈现浮层,锚定在数据标记或指针位置,自动根据窗口边界向内翻转与夹紧(不被图表绘图区 Bounds 裁剪)。
    • 非干扰交互:使用 PassThrough() 与 KeepFocus(),不抢夺指针焦点,不干扰其他浮层或快捷键。
    • 布局与排版:两列分栏排版(标题、数据项名称、数值与色点),宽度按窗口可用空间上限夹紧至 320 DIP,高度按可用预算裁剪;超出预算时截断显示,但完整文本始终保存在无障碍 Label 与读屏公告中。
    • 平滑过渡与淡出:浮层在指针移动时平滑过渡至新锚点,指针移出时平滑淡出(时长由 theme.hoverDuration 控制,默认 120ms,duration 0 或 ReduceMotion 即时生效);按 Escape 键立即清除浮层与选中标记。
    • 缺失点行为:首个非空系列作为导览基准轴,必须为有限且单调递增,否则安全关闭悬停选择。多系列悬停按首系列实际 X 坐标精确匹配(有序系列采用二分查找,无序系列精确线性查找);只有真实存在该 X 坐标数据点的系列才会出现在浮动提示与吸附高亮点中,缺失系列自动省略,不按下标对齐,亦不进行前后插值。
    • 键盘与无障碍:支持键盘左右箭头、Home、End 与 Escape 键导览,焦点切换时触发无障碍读屏播报(c.Announce)。
    • 横向柱状图沿 Y 轴分类吸附最近柱;饼图/环形图通过指针位置进行扇区命中检测;雷达图按角度扇区吸附;桑基图执行节点优先及连线二分曲线命中。

状态样式与交互动画

请在 Worker 中限制图表数据数组的长度。密集折线与面积图可简化绘制,悬停查看仍使用原始读数。

伪状态样式(State Props)

组件支持声明多种交互状态下的覆盖样式。当元素处于对应状态时,嵌套对象中的属性会自动与基础属性合并:

状态属性触发时机
hover鼠标悬浮在组件上方时
pressed鼠标按住该组件时
focused组件获得键盘焦点时
focusVisible通过键盘导航等明确途径获得焦点时
focusWithin组件自身或其任一子元素获得焦点时
disabled组件处于禁用状态时(亦可直接传入布尔值)
highlighted列表或菜单项处于高亮选择态时
javascript
{
  type: "Box",
  props: {
    padding: 12,
    background: "surface",
    hover: { background: "surfaceHover" },
    pressed: { background: "surfacePressed" }
  }
}

动画与平滑过渡

  • 过渡动画 (transition):配置 { duration: 200, ease: "easeInOut" } 为属性变更添加平滑过渡。
  • 姿态与弹簧 (animatePose):支持声明动画目标与阻尼预设(如 animatePose: { pose: { scale: 1.1 }, animation: "bouncy" })。
  • 逐帧时间线 (keyframes):采样驱动 Scale、Translate、Rotate 与 Opacity:
    javascript
    keyframes: {
      duration: 300,
      frames: [
        { at: 0, scale: 0.8, opacity: 0 },
        { at: 1, scale: 1, opacity: 1 }
      ]
    }
  • 视觉变换 (transform):对象简写(如 { translate: [0, -4], scale: 1.05 })或按步骤组合变换。
  • 背景与边框渐变:
    • 线性渐变 (linearGradient / borderLinearGradient):配置 { from: "#3b82f6", to: "#8b5cf6", angle: 45 },或通过多断点 { stops: [{ color: "#3b82f6", at: 0 }, { color: "#8b5cf6", at: 1 }], angle: 90 } 精确控制(at 为 0..1 的位置)。
    • 角度渐变 (conicGradient / borderConicGradient):配置 { stops: [{ color: "#3b82f6", at: 0 }, { color: "#8b5cf6", at: 1 }], center: { x: 0.5, y: 0.5 }, angle: 0 }(center 相对宽高比例,默认居中;angle 顺时针旋转角度)。
  • 布局方向与逻辑锚点 (direction / 逻辑 Anchor):
    • 支持 direction: "ltr" | "rtl" 控制元素及其子元素的阅读与布局方向。
    • attach 属性支持逻辑锚点(start、end、topStart、topEnd、bottomStart、bottomEnd)与物理锚点(top、bottom、left、right 等);在 RTL 布局下,逻辑锚点会按方向自适应。
  • 投影与阴影 (shadow / insetShadow):配置 { x: 0, y: 4, blur: 12, color: "#00000020" }。

事件回调与快捷键

除了常规的 onChange(value) 与 onSubmit(value),Metrix.Bar 还提供了丰富的桌面级交互事件:

  • 增强点击事件 (onClick):回调参数携带点击次数与按键修饰符:
    javascript
    onClick: (e) => {
      // e = { clicks: 1, mods: { meta: true, ctrl: false, alt: false, shift: false } }
    };
  • 键盘监听 (onKeyDown):组件绑定 onKeyDown 后会自动成为可聚焦元素(可配合 autoFocus: true 使用):
    javascript
    onKeyDown: (e) => {
      // e = { key: "Enter", mods: { meta: false, ... } }
    };
  • 局部快捷键 (shortcuts):在任意容器或组件上绑定快捷键字典。当该元素自身或其焦点子树内的任意子元素获得焦点时生效(可配合 autoFocus: true 绑定面板级快捷键):
    javascript
    shortcuts: {
      "CmdOrCtrl+R": () => refresh(),
      "CmdOrCtrl+1": () => selectTab(0),
      "Escape": () => back()
    }
    shortcuts 优先于 onKeyDown:若按键匹配焦点链上声明的快捷键,将优先执行快捷键回调,且不会向 onKeyDown 重复派发。
  • 文本输入观察事件:onComposingChange(composing) 在 MyGO 输入法预编辑状态变化时触发,初始 false 不派发。onTextSelectionChange({ start, end, anchor, caret }) 在首次观察和选区变化时触发,偏移量按 Unicode 码点计算。onPaste(text) 观察粘贴文本并保留原生插入行为,不能阻止粘贴。这些事件支持 TextInput、TextArea 及其 Base、Buffer、BufferBase 变体;SearchField、Combobox、NumberInput 等组合控件不支持。Editor 与 RichTextEditor 使用各自的文档协议。这些是状态观察,不提供浏览器 composition 事件;onKeyDown 不包含 isComposing。
  • 滑动手势 (onSwipe):参数为 "left" | "right" | "up" | "down"。
  • 其他鼠标与视口事件:onDoubleClick(双击)、onRightClick(右键)、onInView(visible)(可见性切换)、onPressedOutside(外部点击)以及 onDismiss(模态关闭)。

属性映射与样式

tw 原生样式工具类

在节点 props.tw 中用空白分隔工具类;也可用于 Vue h()、HTM 等构树结果。tw 在每次 render 时解析,支持条件表达式、拼接和模板字符串,无需 Tailwind 安装、CSS、扫描源码或构建步骤。它是借鉴 Tailwind 命名的独立原生样式语法,支持范围以此处为准。

javascript
const tileSize = 106.25;
const selected = true;
const node = {
  type: "Box",
  props: {
    tw: `size-[${tileSize}px] p-2 rounded-lg ${selected ? "bg-accent" : "bg-surface"} hover:bg-accentHover active:opacity-80`,
    paddingX: 0,
    hover: { background: "surfaceHover" },
  },
  children: [{ type: "Text", props: { text: "42", tw: "text-2xl font-semibold tabular-nums" } }],
};

数值与颜色

  • 通常的长度数字乘以 4 DIP:p-2.5 为 10 DIP,w-20 为 80 DIP;支持非负小数,不限于预设整数刻度。[npx] 直接表示 DIP,可保留小数:w-[106.25px]。这里的 px 是原生逻辑像素,1 px = 1 DIP,不是屏幕物理像素,也不接受 rem、em、pt、CSS 表达式或变量。
  • 尺寸、basis、定位边与 inset 支持 [n%];尺寸与 basis 还支持 n/d 分数(0 <= n <= d,d > 0),如 w-1/2。w-full、h-full、size-full 为 100%。这些长度以及 gap 支持 px 简写(如 w-px 为 1 DIP);padding / margin 用 [1px]。
  • margin、定位边、inset、translate 支持负号前缀或括号内负数,如 -mt-2、left-[-3px];tracking、scale、rotate、skew 也支持负值。padding、gap 与尺寸不接受负值。只有 margin 支持 auto,如 mx-auto、mt-auto;显式 margin 属性也接受 "auto"。
  • 下表的 n 为数字;网格及行数必须是 1..1024 的整数。所有数值须为有限十进制数,能由原生 float32 表达;拒绝指数写法、溢出和非零下溢。倍率、角度、线宽等例外见表。
  • 颜色前缀 bg-、text-、border-、decoration-、selection- 支持主题色或 [#rgb]、[#rgba]、[#rrggbb]、[#rrggbbaa]。主题色为 background、surface、surfaceHover、surfacePressed、border、text、textMuted、accent、accentSubtle、accentHover、accentPressed、accentText、danger、warning、success、selection、focus、inverse、inverseText、scrollbar;忽略大小写、横线与下划线差异,如 text-text-muted。不支持 bg-red-500 等 CSS 调色板。

支持类别

类别工具类与取值
间距p/px/py/pt/pr/pb/pl-n、m/mx/my/mt/mr/mb/ml-n;gap-n、gap-x-n、gap-y-n。x/y 为物理水平/垂直轴,t/r/b/l 为物理上/右/下/左
布局flex-row、flex-col 及各自的 -reverse;flex-wrap、flex-wrap-reverse、grid;flex-1 为 grow=1、shrink=1、basis=0;grow、shrink 为 1,grow-n、shrink-n 或 [n] 为非负原生倍率;basis-n
对齐items-start/center/end/stretch;justify-start/center/end/between/around/evenly;content-start/center/end/stretch/between/around/evenly;self-、justify-items-、justify-self- 后接 start/center/end/stretch。justify-* 对应显式 justify
网格grid-cols-n、grid-rows-n、col-span-n、row-span-n、col-start-n、row-start-n;col-span-full、row-span-full 为原生全跨度
尺寸w-n、h-n、size-n(两轴)、min-w-n、min-h-n、max-w-n、max-h-n
定位absolute、relative;left-n、top-n、right-n、bottom-n;inset-n、inset-x-n、inset-y-n
字号text-xs/sm/base/lg/xl/2xl/3xl/4xl/5xl/6xl/7xl/8xl/9xl = 12/14/16/18/20/24/30/36/48/60/72/96/128 DIP;text-[npx]
字体与字重font-sans/mono/serif = system-ui/monospace/serif;font-thin/extralight/light/normal/medium/semibold/bold/extrabold/black = 100/200/300/400/500/600/700/800/900;font-[n] 为 100..900 整数;italic
文字布局text-left/center/right/start/end,left/right 对应原生 start/end;whitespace-nowrap、whitespace-normal;truncate 为单行,line-clamp-n 为最大行数;tracking-[npx] 为字间距
行高leading-none/tight/snug/normal/relaxed/loose = 1/1.25/1.375/1.5/1.625/2 倍;leading-[n] 为非负原生倍率;leading-n 为 n×4 DIP 固定行高;leading-[npx] 为固定 DIP 行高
字形与装饰tabular-nums、proportional-nums、ordinal、slashed-zero 分别选择 tnum/pnum/ordn/zero;后写的整组替换前者。underline、decoration-wavy、line-through;decoration-n 或 decoration-[npx] 为线厚,裸 n 直接为 DIP;decoration-颜色、selection-颜色
背景与文字色bg-颜色、text-颜色
边框与圆角border = 1 DIP;border-n 或 border-[npx],裸 n 直接为 DIP;border-solid/dashed、border-颜色。rounded = 4 DIP;rounded-none/sm/md/lg/xl/2xl/3xl/full = 0/2/6/8/12/16/24/9999 DIP;rounded-[npx]
裁剪与外观overflow-hidden、overflow-x-hidden、overflow-y-hidden;invisible;opacity-n 为 0..100(可为小数)除以 100;blur-n 或 blur-[npx],裸 n 为原生 sigma DIP
图像object-contain/cover/fill/none/scale-down;aspect-square/video = 1 与 16/9
变换translate-x-n、translate-y-n 为 n×4 DIP,或 [npx];scale-n、scale-x-n、scale-y-n 为 n/100,或 [n] 直接倍率;rotate-n、rotate-[ndeg]、rotate-[n] 为角度;skew-x-n、skew-y-n 或 [n] 为角度;origin-center/top/bottom/left/right/top-left/top-right/bottom-left/bottom-right
阴影shadow、shadow-sm/md/lg/xl/2xl;inset-shadow 及同名等级。每个是单个原生阴影,参数见下表
鼠标cursor- 后接 auto/default/pointer/text/move/grab/grabbing/not-allowed/crosshair/nwse-resize/nesw-resize/n-resize/e-resize/s-resize/w-resize/vertical-text/ew-resize/ns-resize/col-resize/row-resize/copy/alias/context-menu/none;auto 等于 default

阴影参数顺序为 x、y、blur、spread(DIP)、color;内外阴影使用相同刻度,不等同于 CSS 多层阴影。

等级参数
默认0, 1, 3, 0, #00000033
sm0, 1, 2, 0, #0000000d
md0, 4, 6, -1, #0000001a
lg0, 10, 15, -3, #0000001a
xl0, 20, 25, -5, #0000001a
2xl0, 25, 50, -12, #00000040

状态、覆盖与错误

  • 支持单层 hover:、active: / pressed:、focus:、focus-visible:、focus-within:、disabled:、highlighted:,分别对应 hover、pressed、focused、focusVisible、focusWithin、disabled、highlighted 状态。状态仍遵循原生优先级,pressed 高于 hover。
  • tw 只允许在节点 props;hover: { tw: "p-2" } 等状态对象(包括嵌套状态对象)非法,应写为节点上的 tw: "hover:p-2"。普通业务数据里的同名 tw 字段不作为样式解析。
  • 工具类从左到右,同一目标后写覆盖前写:p-2 px-4 为上/下 8、左/右 16 DIP。仅修改声明的边,保留其他边的组件默认值;p-2 hover:px-4 active:pt-3 同时 hover+pressed 时为上/右/下/左 12/16/8/16 DIP。
  • 显式 props 覆盖对应工具类,显式状态对象覆盖该状态的工具类,数值 0 同样有效。上例 paddingX: 0 只覆盖左右边,hover.background 覆盖 hover 的背景色;显式 padding: 0 覆盖全部四边。显式属性之间仍按原生命名顺序应用(例如 paddingX 晚于 paddingLeft);复合属性按对应字段覆盖,如 border: [2, "danger"] 覆盖工具类的边框宽度与颜色。
  • 显式 logical spacing(paddingStart/End、marginStart/End)与物理边混用时,先应用工具类,再按既有原生属性名称顺序调用该组显式属性;LTR/RTL 由原生方向决定。例如 paddingStart: 20, paddingX: 4 最终遵循原生 paddingStart → paddingX 顺序。
  • 同一基础层或同一状态层中,truncate / 为真的 singleLine 与 line-clamp-* / maxLines 相互替代,后写生效;后写 clamp 不保留 truncate 的最小宽度行为。显式属性按原生顺序先 maxLines 后 singleLine,singleLine: false 只跳过调用。跨基础层与状态层仍为原生 overlay:状态 clamp 可以覆盖基础层行数,但无法撤销基础层 SingleLine 的最小宽度标志。
  • 零参数快捷属性的 false 表示不调用:w-full 配合 fillWidth: false 仍为 100% 宽;用 width: 0 等具体值覆盖。center: false、bold: false 同理。relative 与 whitespace-normal 可重置同次 tw 的标志,但无法撤销组件本身启用的标志。
  • relative、whitespace-normal、所有 transform / origin / shadow / inset-shadow 工具类仅支持基础样式,不能加状态前缀。变换按原生方法顺序组合(rotate → scale → skew → translate),不按工具类书写顺序;同名双轴变换保留另一轴,显式变换/阴影属性覆盖对应工具类。
  • 状态名若同时用于非对象组件属性,不能再声明同名状态工具类,例如 Chart 的 hover: true 与 hover:bg-surface 冲突。
  • 未知工具类、错误类型、无效数值与不支持的状态组合会使本次 render 失败,错误包含节点类型、具体 token 或状态路径;不静默忽略。空字符串合法。
  • 不支持响应式断点、dark:、group/peer、组合变体、important、CSS 变量、插件、calc()、flex / flex-auto/initial/none、auto 尺寸、logical ps/pe/ms/me/start/end、fixed/sticky/z-index、单边 border / 单角 rounded、visible/hidden、overflow-auto/scroll/visible、反向文字标志、渐变或动画工具类。复杂材质、渐变、动画、事件与业务数据继续用显式 props。

节点引用

activeDescendant: "result" 指定无障碍活动项,键盘焦点仍留在源元素上。scrollWith: "body" 让源元素跟随目标的横向滚动;用于固定表头时,应为源元素设置 clip: true。

这两个属性引用同一面板内显式声明的节点 key。目标可写在源节点之前或之后,但必须在同一次构建中实际生成,并且 key 在面板内唯一。不同父节点下可重复使用局部 key,但这样的 key 不能作为明确的引用目标;同级节点的 key 不可重复。缺失或有歧义的目标以及自身引用会被忽略并记录诊断。引用不能放入 hover 等状态样式。

javascript
{
  type: "Column",
  children: [
    { type: "TextInput", props: { activeDescendant: "result", value: query } },
    { type: "Text", key: "result", props: { text: "建议结果" } }
  ]
}

驼峰到帕斯卡命名反射

在 JavaScript 对象中使用 camelCase 属性名,底层自动映射为原生组件的 PascalCase 方法:

  • fontSize: 14 → .FontSize(14)
  • letterSpacing: 1.5 → .LetterSpacing(1.5)(字间距,单位 DIP)
  • gap: 12 → .Gap(12)
  • padding: 16 → .Padding(16)
  • width: 280 → .Width(280)
  • bold: true → .Bold(true)

尺寸与百分比

长度数值使用 DIP(原生逻辑像素),与 tw 任意长度中的 px 相同。百分比字符串使用对应的百分比布局:

javascript
props: {
  width: "100%",  // 调用 WidthPercent(100)
  height: 200     // 调用 Height(200)
}

面板打开方式

看板可在设置中为菜单栏、三侧 Sidebar 与快捷键全部五个触发来源各自配置启用开关与打开方式(Flyout 或 Dialog,例如菜单栏打开 Flyout,快捷键与右侧 Sidebar 打开 Dialog)。XML 根属性 <bar open-mode="flyout"> 或 <bar open-mode="dialog"> 仅提供作者默认值。两种方式显示同一棵 panel 树;宿主 Dialog 模式独立于树内的 Modal、Popover 等组件。

Dialog 的遮罩覆盖当前显示器完整 Bounds,面板内容居中,不进入系统全屏 Space。遮罩采用约 12% 黑色的高透明遮罩(系统高对比度模式下为约 40% 黑色);内容卡片带圆角与阴影。界面无“X”关闭按钮。panel 根节点的 width 和 height 以设备独立像素(DIP)定义内容请求尺寸,默认 320×480。左右边距各至少为屏幕宽度的 10%,上下边距各至少为屏幕高度的 10%,内容区域宽高最多为屏幕宽高的 80%,超大内容内部滚动;内容尺寸动态变化不会缩小遮罩窗口。点击内容外或按 Esc 可关闭,内容内点击不关闭;有内部浮层时 Esc 优先关闭该浮层。注意:Windows 与 Linux 桌面 GUI 实机验收仍待完成。

面板材质

通过 XML 根属性 <bar material="glass">(缺省)或 <bar material="solid"> 选择宿主面板内容的材质,包括空白与加载、错误状态区域。Dialog 模式下,材质仅作用于居中的内容区域,遮罩保持独立外观。solid 使用不透明主题 Surface 和常规主题控件。glass 保留系统材质或 Glass 回退,控件使用透明派生主题。macOS 托盘的两种材质均保留原生 NSPopover 气泡箭头、圆角和阴影;材质由 Host 外壳管理,Worker 仅负责内部内容。macOS 14 及以上的 solid 外壳(含箭头)使用不透明主题 Surface;macOS 12/13 的 solid 正文不透明,箭头保留系统材质。其他平台保持既有浮窗样式。

保存源码并成功重载后,新材质会在下次打开面板时生效;重载失败保留当前运行定义和材质。Worker 节点更新不改变宿主材质,预览跟随当前运行定义。根节点、子节点和状态样式的局部 material 均按插件绘制,不控制宿主;所有节点 props 均不接受 "solid"。

玻璃材质

在根组件、子组件或状态样式中,通过 material: "glass" 启用 MyGO 官方 Glass 插件,也可以显式配置:

javascript
props: {
  material: { type: "glass", style: "regular", tint: "surface", interactive: false },
  radius: 12
}

style 支持 "regular"(默认)或 "clear";tint 接受主题 token 或十六进制颜色,interactive 默认为 false。同一组件的基础 material 会覆盖 background,也可用于 hover 等状态样式。

Glass 采样同一窗口内已经绘制的内容,不能模糊桌面。看板面板优先保留系统 Vibrancy,不支持系统材质时使用带主题 tint 的 Glass 回退。自定义看板的根节点如果设置不透明背景,会遮住系统材质。

颜色系统

颜色属性支持以下取值:

  • 十六进制颜色代码:#RGB、#RRGGBB、#RRGGBBAA(如 "#3b82f6")。
  • 语义化主题 Token:
    • background, surface, surfaceHover, surfacePressed
    • border
    • text, textMuted
    • accent, accentHover, accentPressed, accentText
    • danger, warning, success
    • selection, focus
    • inverse, inverseText

使用 htm 简化 UI 树编写

如果你希望用类似 JSX 的语法编写界面,可在 Worker 中通过 ESM 导入 htm:

bar
<permissions>
  <network origin="https://esm.sh" />
</permissions>

<script>
let html;

export default {
  async scheduled() {
    if (!html) {
      const htm = await import("https://esm.sh/htm");
      const h = (type, props, ...children) => ({
        type,
        props: props || {},
        children: children.flat().filter(Boolean)
      });
      html = htm.default.bind(h);
    }
  },

  render(env) {
    return {
      tray: { title: "监控指标" },
      panel: html`
        <Column gap=${12} padding=${16} width=${300}>
          <Row justify="space-between" alignItems="center">
            <Text text="服务运行状态" bold=${true} fontSize=${14} />
            <Badge text="正常" color="success" />
          </Row>
          <Button
            text="立即刷新"
            onClick=${() => $metrix.invalidate()}
          />
        </Column>
      `
    };
  }
};
</script>

文档与导航

RichText 通过 spans 显示静态富文本;编辑文档使用 RichTextEditor。以下组件直接使用 MyGO 原生渲染器,不执行网页内容。

组件属性与回调
Markdown、MarkdownStreamcontent、wrapCode、shareSelection、reveal:{unit,catchUp,fade}、onLink(url)
RichTextEditorvalue、format:"plain"|"markdown"|"html"(默认 markdown)、readOnly(默认 true)、noMarkdown、placeholder、label、onChange(serializedText)、onLink(url)、onLinkCommand()、onSelectionChange({anchor,caret})
DiffVieworiginal、modified、language、editable、layout:"split"|"unified"、wrap、fitContent、hunkActions、contextLines、expandUnchanged、lineNumbers、tabSize、expansionLines、indicators、noBackground、inline、annotations;onChange(modified)、onOriginalChange(original)、onLineSelection({side,start,end})、onGutterButton({side,start,end})
Routerpath(默认 /)、routeTransition:"auto"|"fade"|"none"、routeAnimation、onChange(location);子节点为含 path、title 的 Route
Route使用第一个匹配项的 children;路径必须为本地路径,不得带 scheme、host 或以 // 开头
BackButton、ForwardButton操作所在 Router 的历史;在 Router 外禁用

Markdown reveal 的 unit 为 word、character、sentence、line、paragraph 或 atOnce,catchUp/fade 单位毫秒。图片遵循声明权限。每张图片最多 8 MiB、单边 4096 像素与总计 1600 万像素。无 loader 时显示 alt。编辑器受控回显保留 caret 与 undo,Router 回显保留历史。Diff annotations 为 {key?,side:"original"|"modified",line,content:Node},行号从零起;支持 contextLines:-1。inline 接受 wordAlt(默认)、word、wordLine、char、none,indicators 接受 bars、classic、none。

Editor:身份、增量与动作

同一 surface 和稳定 key 保留原生编辑状态。受控 value echo 保留选区、滚动和 undo;真正外部替换会换 Buffer,不回声 onChange/onEdit。移除重挂、切换 surface 或 Worker 会产生新的 instanceId。文本统一 LF;Editor 的选区、edit、completion、hover、diagnostics、inlays 偏移都是 UTF-8 字节边界,不是 JS 字符串下标。value/diffBase 最多 8 MiB,有效 UTF-8;Worker 导出时 JS 未配对 surrogate 规范化为 U+FFFD,合法 U+FFFD 允许,panel 层仅拒非法 UTF-8。

onState({instanceId,version,stateRevision,byteLength,lineCount,selections:[{anchor,head}],cursor:{line,column},readOnly}) 首次及状态变化时握手。version 标识文档;stateRevision 还随选区、焦点与输入上下文变化。onEdit({instanceId,baseVersion,version,edits:[{start,end,text}]}) 报告相对旧 snapshot 的真实增量;未订阅 onChange 可避免生成全文,增量比较最坏仍扫描全文,全替换也可能输出全文。

action 共有 {instanceId,id,version},id 为递增正 safe integer,每实例首次尝试即消费,失败不重放;错误身份不消费当前实例 id。支持 {kind:"edit",edits}、{kind:"select",stateRevision,selections}、{kind:"command",stateRevision,command};后两者必须匹配当前 stateRevision,command 使用原生 CommandNamed 的 lowerCamel 名称。编辑必须按旧 snapshot 排序且不重叠,最多 256 项、插入共 1 MiB、结果 8 MiB,整批可单步 undo;选区为 1–256 项。只读允许导航、选择、查找、复制、折叠和显示的受支持命令,拒绝编辑及 FindReplace。onAction({instanceId,id,version,stateRevision,ok,error?}) 报告 stale-instance/stale-version/stale-state/read-only/invalid-range。非法结构由 ValidateView 定位到 props 路径。

onCompletionRequest({instanceId,requestId,version,offset,prefix,explicit}) / onHoverRequest({instanceId,requestId,version,offset}) 可异步回传 completions:{instanceId,requestId,version,start,items} / hover:{instanceId,requestId,version,start,end,text}。completion item 为 {label,detail?,kind?,insert?,documentation?,filterText?};kind 支持 text/keyword/function/method/variable/field/type/module/constant/snippet。每类仅最新 pending 请求可应用一次,且文档、输入 generation、完整选区、焦点仍须匹配。输入、Escape、指针移动、失焦或移除 handler 取消;移除 handler 也关闭浮层,重复 render 不重开。

diagnostics:{instanceId,version,items:[{start,end,severity,message,source?}]}(severity 为 error/warning/info/hint)与 inlays:{instanceId,version,items:[{offset,text}]} 只接受当前身份和版本,移除 prop 或外部全文替换清标记;diffBase 为独立差异基线,移除即清除。diagnostics/inlays 各最多 1024 项,completion 最多 512 项,LSP 各文本字段最多 64 KiB,仍受 Worker 树预算限制。

以下为完整 <script> 模块内容;按钮在 onState 握手后请求一次可撤销的字节插入。普通 render 不会恢复初始文本;需要保存全文时再订阅 onChange。

js
let state,
  action,
  nextId = 0;
export default {
  render() {
    return {
      panel: {
        type: "Column",
        children: [
          {
            type: "Editor",
            key: "code",
            props: {
              value: "hello\n",
              readOnly: false,
              ...(action ? { action } : {}),
              onState(s) {
                state = s;
              },
              onAction(result) {
                action = undefined;
              },
            },
          },
          {
            type: "Button",
            props: {
              text: "Insert",
              keepFocus: true,
              onClick() {
                if (!state) return;
                action = {
                  instanceId: state.instanceId,
                  version: state.version,
                  id: ++nextId,
                  kind: "edit",
                  edits: [{ start: 0, end: 0, text: "// " }],
                };
              },
            },
          },
        ],
      },
    };
  },
};

RichTextEditor 与 TextBuffer

RichTextEditor 的 onState({instanceId,version,stateRevision,length,anchor,caret,readOnly,formatting}) 使用 rune 长度与选区。formatting 提供 bold/italic/underline/strikethrough/code 的 off|on|mixed、link、heading(0–6)、list(none/bullets/numbers/tasks)、quote、codeBlock、paragraphsMixed、canUndo/canRedo。选区或 typing 格式变化也推进 stateRevision。先握手,再传 action:{instanceId,id,version,stateRevision,...},支持 kind:"format" 加 command(bold/italic/underline/strikethrough/code/quote/codeBlock/undo/redo)、kind:"heading",level、kind:"list",list、kind:"link",url、kind:"select",anchor,caret。id 为一次性正 safe integer,失败也消费;onAction 使用 EditorActionResult,只读仅允许 select。工具栏普通 Button 可用 keepFocus:true 保持选区;例如依据最新 state 构造 {instanceId:s.instanceId,version:s.version,stateRevision:s.stateRevision,id:++id,kind:"format",command:"bold"},不能复用旧状态。

受控 echo 保留文档、选区和 undo,外部内容或 format 变化替换文档。未订阅 onChange 时普通帧不序列化;echo 比较可序列化。要保存 underline 使用 HTML,Markdown 不保证保留 underline;原生 HTML 不执行网页。value 上限 8 MiB 有效 UTF-8,link 上限 64 KiB;非法选区拒绝,不 clamp。JS surrogate 的 U+FFFD 规范化规则同 Editor。

四种 TextBuffer 控件支持 onState({instanceId,version,length,byteLength,lineCount})、onEdit({instanceId,baseVersion,version,edits})、action:{instanceId,id,version,edits} 与 onAction;所有编辑坐标及 length 为 rune。握手与一次性 id 同上,不要求 stateRevision。增量为旧 snapshot 的单段替换,未订阅 onChange/onSubmit 可避免全文字符串;最坏比较扫描全文。整批有序不重叠 edits 最多 256 项、插入共 1 MiB、结果 8 MiB,逆序应用后报告一个 onEdit;只读拒绝。程序替换清 undo/IME,需要保留历史时使用 Editor。可编辑十万行文本,但不保证每次变更恒定耗时或帧率;外部 value 替换仅回报新 state。

Base 控件与命名 Part

ButtonBase、CheckboxBase、SwitchBase、ToggleBase、SliderBase、RadioBase、TextInputBase、TextAreaBase 沿用对应控件的值和事件契约。ButtonBase 支持 children 与 href。TextInputBuffer、TextAreaBuffer、TextInputBufferBase、TextAreaBufferBase 通过 value、onChange、onSubmit 收发字符串,并在回显时保留原生 TextBuffer。

仅在匹配的 Base 内使用 {type:"Part",props:{name:"..."},children:[...]}:

BasePart
CollapsibleBasetrigger, panel
ComboboxBaseinput, popup, item (value)
SelectBasetrigger, popup, item (value)
TabsBaselist, tab (index)
SegmentedBasetrack, segment (index)
DialogBasebackdrop, panel
PopoverBasepanel
ToastBaseroot, actionButton, closeButton
ToastViewportBaseviewport, toast

Collapsible/Dialog/Popover 使用 open、onOpenChange(boolean);ComboboxBase 还支持字符串 value/onChange。SelectBase 报告 onOpenChange,展开由 trigger 管理。TabsBase/SegmentedBase 使用整数 value/onChange(index)。ToastBase 支持 title、description、action、type、timeout(秒)、onAction、onClose;关闭或超时后消失,移除再加入节点可重新弹出。ToastViewportBase 每个活跃 toast 仅渲染一次,其 toast Part 内可使用 ToastBase 的 Part。

结构化表格

Table 的 items[].cells[] 接受字符串或显式 Node,保留事件回调。columnLayout:{order:string[],widths:Record<string,number>} 与 onColumnLayoutChange(layout) 保存列顺序和宽度。列支持 fixed、minWidth、maxWidth、width、align、sortable。selection 为行 key 数组,onSelectionChange(keys) 按可见行顺序回传已选 key。sortBy:[{column,descending?}] 与 onSortBy(orders) 支持多列排序。headerMenu 接受标准菜单项,onHeaderMenu(columnIndex) 标识列头;既有 sort/onSort 保留。

材质与时间线

除 Glass 外,material 接受 {type:"blur",radius,mask?}(radius 为有限非负数,mask 为 LinearGradient)或 {type:"scrollEdge",hard?,bottom?,background?};拒绝未知字段与类型。

timeline 接受非空 tracks:[{property,from,to,duration,ease?,at?}],property 为 x、y、scale、scaleX、scaleY、rotate、opacity、fade。duration 与绝对 at 偏移以毫秒计,省略 at 时顺序衔接。选项包含 run(非负整数)、repeat(≥ -1 的整数)、alternate、paused、reverse、delay、repeatDelay,以及互斥的 seek(非负毫秒)或 progress(0–1)。所有轨道参与总时长计算,按 playhead 合并通道,包括平移和缩放。

本地与系统拖放

drag 提供同窗口 JSON 快照(不含 handler,最多 64 KiB),与 dragData:{text?,html?,markdown?} 互斥。后者仅接受字符串,导出单个 Copy 条目,总量最多 64 KiB。本地 onDrop 收到 {kind:"local",value}。

dropFormats 仅允许 text/plain、text/html、text/markdown。系统 onDrop 在全部读取成功后收到 {kind:"data",items:[{types:string[],data:{[mime]:string}}],operation:"copy"}:最多 64 items、总计 64 KiB UTF-8 文本,失败不部分交付。onDragOver 在变化时回传 {over:boolean,kind?:"local"|"data",types?:string[]},离开为 {over:false};仅有悬停回调也能观察本地拖入/移出。系统悬停检查排序后的白名单交集,不调用数据 provider。

Canvas:受限 2D 绘制与连续输入

Canvas 使用原生 Painter,坐标与目标尺寸为 DIP,绘制裁剪到视口。稳定 key 保持输入与时钟;动态列表、条件替换和重排中的 Canvas 必须使用树内唯一的稳定 key,无 key 时仅按 Children/props 结构位置识别。props:commands、onFrame、paused(默认 false)、fps(1..60,默认60)、stepMs(1..250ms,默认1000/60)、maxSteps(整数1..8,默认5),以及 width/height/autoFocus 等现有布局属性。

js
let x = 0;
export default {
  render() {
    return {
      panel: {
        type: "Canvas",
        key: "scene",
        props: {
          width: 320,
          height: 180,
          autoFocus: true,
          commands: [{ op: "rect", rect: { x, y: 20, width: 24, height: 24 }, color: "accent" }],
          onFrame(frame) {
            x = (x + frame.steps * 2) % 296;
          },
        },
      },
    };
  },
};

指令按 op 区分:rect(rect/color/radius)、line(x/y/x2/y2/width/color)、circle(x/y/radius/color)、path(path/color/width,width=0 填充)、text(x/y/text/size/color)、image(src/rect/可选srcRect/flipX/flipY)、clip(rect/commands)、transform(可选translate:[x,y]、scale:[x,y]、rotate弧度及commands)。rect统一为 {x,y,width,height};path包含 ['M',x,y]、['L',x,y]、['Q',cx,cy,x,y]、['C',cx1,cy1,cx2,cy2,x,y]、['Z']。transform依次组合平移、缩放、旋转。srcRect使用位图像素,目标rect使用DIP;SVG不支持区域裁剪和翻转。颜色接受十六进制和主题名称;图片复用Image资源缓存及权限加载,未加载时暂不显示。

每树上限8192指令、16层嵌套、65536路径点(含曲线控制点)。数值须有限且绝对值不超过1000000,scale每轴绝对值不超过4;尺寸/半径/线宽非负,字号为正。结构诊断与渲染使用同一decoder拒绝非法数据。精确类型由 /v0/types 导出 CanvasCommand、CanvasFrame、CanvasInput。

onFrame({time,delta,stepMs,steps,alpha,droppedMs,width,height,input}) 提供单调毫秒时间、最大250ms的delta、固定步长次数和[0,1)插值比例;超出maxSteps的整步计入droppedMs。handler完成后宿主自动render,每Canvas最多一个invoke+render在途,且等待该版本或更新视图提交后再发帧,慢脚本产生背压。失败清held并设reset;执行前按目标从最新已提交视图解析 onFrame,原始输入参数保持不变;旧 Worker 实例、已移除或歧义目标拒绝派发并等待新视图。invoke 成功后即使 render 失败也不重放该帧输入。无完成型dispatcher的headless/静态预览仅绘图,不自动调用onFrame。

input包含keys:string[]、buttons:number[]、pointer:{x,y}、events、reset。聚焦后的keydown/keyup包含key、physicalKey、repeat、mods:{alt,ctrl,meta,shift};未知逻辑键回退物理键名。pointerdown/move/up包含局部x/y、button、mods;scroll另含dx/dy/precise。原生pointer-down捕获窗外移动/释放;Tab与系统快捷键保持正常路由。事件最多256条,相邻move合并;溢出优先丢旧move,否则清held并reset。事件仅随帧交付。失焦、出视口、paused、隐藏和销毁清输入与累计时钟,失焦后暂停派帧直到重新聚焦,初始未设置 autoFocus 仍可启动动画;恢复首帧delta=0,已有在途帧仍须完成后才能再派帧;无onFrame时不默认获取键盘焦点。

Canvas 多 pass Shader

shaders:Record<string,string> 声明 Kage 源码;passes:[{id,shader,width?,height?,scale?,uniforms?,images?}] 顺序生成中间纹理;最终 commands 使用 {op:"shader",shader,rect,uniforms?,images?}。shader 引用本 Canvas 的声明;pass id 唯一,仅能引用前序 pass,最终 command 可引用任意 pass,形成前序 DAG。images 最多四项,严格为 {src:string} 或 {pass:string};外部纹理仅 bitmap,复用图片权限、加载与预览资源。

以下是完整 <script> 模块内容,两级 pass 后绘制到 Canvas;不需要外部资源权限。

js
const shaders = {
  paint: `package main
var Tint vec4
func Fragment(dst vec4, pos vec2) vec4 { return Tint }`,
  copy: `package main
func Fragment(dst vec4, pos vec2) vec4 {
  return image0Sample(pos / rectSize() * image0Size())
}`,
};
export default {
  render() {
    return {
      panel: {
        type: "Canvas",
        key: "shader-scene",
        props: {
          width: 256,
          height: 128,
          shaders,
          passes: [
            {
              id: "base",
              shader: "paint",
              width: 128,
              height: 64,
              uniforms: { Tint: [0.2, 0.4, 0.8, 1] },
            },
            { id: "filtered", shader: "copy", scale: 0.5, images: [{ pass: "base" }] },
          ],
          commands: [
            {
              op: "shader",
              shader: "copy",
              rect: { x: 0, y: 0, width: 256, height: 128 },
              images: [{ pass: "filtered" }],
            },
          ],
        },
      },
    };
  },
};

显式 width/height 必须成对为 1–2048 的整数物理像素,且不能带 scale;否则目标为 round(viewportDIP * pixelScale * scale),scale 默认 1、范围 (0,2],viewport 使用祖先变换前布局。pass 坐标为物理像素,最终 draw.rect 为 Canvas 局部 DIP。零尺寸或超限拒绝整树 Shader。可逐帧调整分辨率,仍受预算限制。

uniforms 为 Record<string,number|boolean|number[]>,标量为单值,向量/矩阵/数组为完整展平数值数组;必须匹配声明名称、类型和形状,省略为零。数值须有限且可表示为 float32;int/uint 须为各自 32 位整数。仅允许 Fragment,禁止 ColorEffect/DistortionEffect/LayerEffect/backdrop。每源码最多 64 KiB、16000 AST 节点、64 层表达式、64 函数;展开数组共最多 4096 scalar,单数组也最多 4096。禁止递归、range、动态 loop;只允许常量起止、常量步长的规范计数循环(</<= 递增或对应递减),每循环最多 1024 次,循环体不能改计数器。动态 count 可手动改为静态容量循环并用 if i >= Count { break },同时在脚本限制 Count。最坏路径展开用户调用并按迭代计费,每源码函数静态 cost 合计最多 500000;这是复杂度门槛,不保证 GPU 耗时或编译器硬内存隔离。

整树(含 props 内嵌节点、重复源码声明)最多 8 shader、256 KiB 源码、8 pass、16 shader draw(含 pass)、声明和传入 uniform 各 4096 scalar。布局后分配前检查目标单边 2048、目标总计 8 × 1024² 像素、目标加最终 draw 覆盖 16 × 1024² 像素。覆盖保守计算 Canvas、祖先完整 world affine 及嵌套 command transform 的包围矩形,每个最终 draw 至少计 Canvas 变换后包围面积,动画变换也计入。Shader 仅支持有限 affine 2D,拒绝透视等非 affine;布局超限跳过整树 Shader 并显示提示,普通 2D 继续。

需要 GPU 加速;无 GPU 仍校验源码/类型,显示提示并保留普通 2D,不执行 pass 或 Shader.Draw,也无昂贵 CPU fallback。onFrame 的 accelerated:boolean 可用于选择内容,headless 预览不保证 GPU 图像。

跨次渲染保持 Canvas key 与 pass id 稳定。制作动画时,优先更新 uniform,保持 Shader 源码不变。

Loading 与行列选择

Loading 提供原生加载动画,variant 默认 arc,可选:arc, atom, blocks, bouncingDots, cascade, circularDots, classic, classicV2, clock, comet, compass, dual, eclipse, flip, gather, leap, linearDots, loading, morph, orbit, pulse, radar, ring, ripple, slide, snake, swirl, trace, wave。size 是 DIP 高度(默认 20),durationMs 是一个动画周期的毫秒数;二者须为有限非负数,0 使用默认值。支持主题 color、paused、无障碍 label 与通用 Element 属性。easing 为 linear|easeInOut|stacked,cap 为 round|flat,sweep 为 diagonal|rows|columns,rippleDirection 为 out|in,waveOrigin 为 center|bottom。direction 仍控制布局方向;系统减少动态效果与隐藏窗口暂停由原生运行时处理。

List 与 Table 支持 selection 行 key 数组、onSelectionChange(keys)、dragSelect 和 menuKeepsSelection。List children 使用节点 key(缺省为索引),items 使用 item key;回调按当前行顺序返回。dragSelect 需要 selection;List 与 onReorder 同时配置时会诊断并保留 reorder、禁用拖选。

Table 另支持 cellSelect(需要 selection)、cells:{cursor?:string,anchor?:string,all?:boolean} 与 onCellsChange(cells,columns);columns 是当前显示顺序的列 ID 数组。受控值只在 prop 改变时重新初始化。columns[].rowHeader 标识行表头列。cellMenu 使用标准菜单项,onCellMenu(row,column) 提供源 items/columns 索引;先展示当前已提交菜单,回调可更新后续状态。Outline/OutlineTable 不开放此单元格多选协议。