Refactor: EditableTextView.Changed() and the Canceled spelling

EditableTextView.Changed is a method like every other result's.
AlertDialogResult, PopconfirmResult and DateTimeResult spell Canceled
the American way, matching EditableTextView and LoadingOverlayResult.
Callers, Gallery and docs updated.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-05 13:50:02 -04:00
1 parent 5d395ad789
commit c0be05d043
22 files changed
+306 -72

No files matched your search

+5 -5
View File
@@ -25,7 +25,7 @@ type AlertDialogOptions struct {
// AlertDialogResult reports the choice made in this frame.
type AlertDialogResult struct {
Confirmed bool
Cancelled bool
Canceled bool
}
// alertDialogState is the typed confirmation, kept while the alert shows.
@@ -76,7 +76,7 @@ func AlertDialog(c *ui.Context, open *bool, opts AlertDialogOptions) AlertDialog
unlocked := opts.ConfirmText == "" || st.typed == opts.ConfirmText
dialogActions(c, func() {
if Button(c, cancelLabel, ButtonOptions{Variant: Secondary}).AutoFocus().Clicked() {
res.Cancelled = true
res.Canceled = true
}
if Button(c, confirmLabel, ButtonOptions{Variant: Danger, Disabled: !unlocked}).Clicked() {
res.Confirmed = true
@@ -85,12 +85,12 @@ func AlertDialog(c *ui.Context, open *bool, opts AlertDialogOptions) AlertDialog
})
focus.shown(panel)
if !shown && !backClicked {
res.Cancelled = true // Escape
res.Canceled = true // Escape
}
if res.Confirmed {
res.Cancelled = false
res.Canceled = false
}
if res.Confirmed || res.Cancelled {
if res.Confirmed || res.Canceled {
slog.Debug("mujica: alert_dialog close", "confirmed", res.Confirmed)
*open = false
}
+13 -13
View File
@@ -7,9 +7,9 @@ import (
)
type alertApp struct {
open bool
opts AlertDialogOptions
confirmed, cancelled int
open bool
opts AlertDialogOptions
confirmed, canceled int
}
func (a *alertApp) view(c *ui.Context) {
@@ -21,8 +21,8 @@ func (a *alertApp) view(c *ui.Context) {
if r.Confirmed {
a.confirmed++
}
if r.Cancelled {
a.cancelled++
if r.Canceled {
a.canceled++
}
}
@@ -40,8 +40,8 @@ func TestAlertDialogFocusesCancelAndEnterCancels(t *testing.T) {
t.Fatalf("open %v, Cancel focused %v", a.open, tt.Focused("Cancel"))
}
tt.Key(0, ui.KeyEnter)
if a.confirmed != 0 || a.cancelled != 1 || a.open {
t.Fatalf("Enter: confirmed %d cancelled %d open %v", a.confirmed, a.cancelled, a.open)
if a.confirmed != 0 || a.canceled != 1 || a.open {
t.Fatalf("Enter: confirmed %d canceled %d open %v", a.confirmed, a.canceled, a.open)
}
if !tt.Focused("Remove") {
t.Fatal("focus not restored")
@@ -51,12 +51,12 @@ func TestAlertDialogFocusesCancelAndEnterCancels(t *testing.T) {
func TestAlertDialogIgnoresBackdropEscapeCancels(t *testing.T) {
a, tt := newAlertApp(AlertDialogOptions{})
tt.ClickAt(8, 392)
if !a.open || a.cancelled != 0 {
t.Fatalf("backdrop click: open %v cancelled %d", a.open, a.cancelled)
if !a.open || a.canceled != 0 {
t.Fatalf("backdrop click: open %v canceled %d", a.open, a.canceled)
}
tt.Key(0, ui.KeyEscape)
if a.open || a.cancelled != 1 || a.confirmed != 0 {
t.Fatalf("Escape: open %v cancelled %d confirmed %d", a.open, a.cancelled, a.confirmed)
if a.open || a.canceled != 1 || a.confirmed != 0 {
t.Fatalf("Escape: open %v canceled %d confirmed %d", a.open, a.canceled, a.confirmed)
}
}
@@ -65,8 +65,8 @@ func TestAlertDialogConfirmOnce(t *testing.T) {
if err := tt.Click("Erase"); err != nil {
t.Fatal(err)
}
if a.confirmed != 1 || a.cancelled != 0 || a.open {
t.Fatalf("confirmed %d cancelled %d open %v", a.confirmed, a.cancelled, a.open)
if a.confirmed != 1 || a.canceled != 0 || a.open {
t.Fatalf("confirmed %d canceled %d open %v", a.confirmed, a.canceled, a.open)
}
}
+3 -3
View File
@@ -66,7 +66,7 @@ func DateRangePicker(c *ui.Context, value *DateRange, opts DateRangePickerOption
cursor := ui.Local(col, "date-range-cursor", func() Date { return Date{} })
hover := ui.Local(col, "date-range-hover", func() Date { return Date{} })
problem := ui.Local(col, "date-range-error", func() string { return "" })
changed, cancelled, done := false, false, false
changed, canceled, done := false, false, false
finish := func(to DateRange, how string) {
if to != *value {
slog.Debug("mujica: date_range_picker change", "from", *value, "to", to, "by", how)
@@ -147,13 +147,13 @@ func DateRangePicker(c *ui.Context, value *DateRange, opts DateRangePickerOption
})
if *wasOpen && !*open && !done {
slog.Debug("mujica: date_range_picker cancel")
cancelled = true
canceled = true
}
*wasOpen = *open
if !*open {
*problem = ""
}
return DateTimeResult{Element: col, changed: changed, cancelled: cancelled}
return DateTimeResult{Element: col, changed: changed, canceled: canceled}
}
// dateRangeProblem checks a whole draft against the current bounds and filter.
+1 -1
View File
@@ -17,7 +17,7 @@ func dateRangeTester(value *DateRange, opts DateRangePickerOptions, changes, can
if r.Changed() {
*changes++
}
if r.Cancelled() {
if r.Canceled() {
*cancels++
}
})
+3 -3
View File
@@ -64,7 +64,7 @@ func DateTimePicker(c *ui.Context, value *time.Time, opts DateTimePickerOptions)
opts.Layout = "2006-01-02"
}
format := func(t time.Time) string { return dateTimeText(c, t.In(loc), opts.Layout, cfg) }
changed, cancelled, done := false, false, false
changed, canceled, done := false, false, false
label := opts.Label
if label == "" {
label = msg(c, "date_time_picker.placeholder")
@@ -137,13 +137,13 @@ func DateTimePicker(c *ui.Context, value *time.Time, opts DateTimePickerOptions)
})
if *wasOpen && !*open && !done {
slog.Debug("mujica: date_time_picker cancel")
cancelled = true
canceled = true
}
*wasOpen = *open
if !*open {
*problem = ""
}
return DateTimeResult{Element: col, changed: changed, cancelled: cancelled}
return DateTimeResult{Element: col, changed: changed, canceled: canceled}
}
// dateTimeConfirm turns the draft into an instant, or explains why it cannot.
+1 -1
View File
@@ -19,7 +19,7 @@ func dateTimeTester(t *testing.T, value *time.Time, opts DateTimePickerOptions,
if r.Changed() {
*changes++
}
if r.Cancelled() {
if r.Canceled() {
*cancels++
}
})
+5 -5
View File
@@ -70,16 +70,16 @@ func (d Date) within(lo, hi Date) bool {
type DateTimeResult struct {
Element *ui.Element
// Input is the text field inside, for labels and focus; nil without one.
Input *ui.Element
changed bool
cancelled bool
Input *ui.Element
changed bool
canceled bool
}
// Changed reports whether the component wrote a new value this frame.
func (r DateTimeResult) Changed() bool { return r.changed }
// Cancelled reports whether the user discarded a pending edit this frame.
func (r DateTimeResult) Cancelled() bool { return r.cancelled }
// Canceled reports whether the user discarded a pending edit this frame.
func (r DateTimeResult) Canceled() bool { return r.canceled }
// datetimeCheckDate panics when a non-empty date is not a real day.
func datetimeCheckDate(name string, d Date) {
+3 -3
View File
@@ -13,7 +13,7 @@ view := func(c *ui.Context) {
mujica.Use(c, mujica.Settings{})
mujica.Text(c, "Order ID: 4821-AX", mujica.TextOptions{Selectable: true})
mujica.Text(c, "A long note that stops after two lines.", mujica.TextOptions{MaxLines: 2})
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed {
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed() {
saveErr = "" // save name here; set saveErr on failure
}
}
@@ -36,7 +36,7 @@ view := func(c *ui.Context) {
| `Error` | `string` | `""` | 调用方保存失败的说明,显示在文本下方并报告给辅助技术。 |
| `Label` | `string` | `""` | 辅助技术读出的名称;同时用在显示状态和编辑输入框上。 |
返回值 `EditableTextView`:`Element`;`Changed` 表示本帧确认了新值;`Canceled` 表示本帧按 Escape 放弃了编辑。
返回值 `EditableTextView`:`Element`;`Changed()` 表示本帧确认了新值;`Canceled` 表示本帧按 Escape 放弃了编辑。
## 状态
@@ -49,7 +49,7 @@ view := func(c *ui.Context) {
## 事件
- `EditableTextView.Changed`:用户确认了新值,`*value` 已更新。调用方在此保存,并通过 `Saving` 与 `Error` 回传结果。
- `EditableTextView.Changed()`:用户确认了新值,`*value` 已更新。调用方在此保存,并通过 `Saving` 与 `Error` 回传结果。
- `EditableTextView.Canceled`:用户按 Escape 放弃编辑,`*value` 不变。
## 键盘操作
+1 -1
View File
@@ -40,7 +40,7 @@ ui.Render(view, 400, 300, 1)
## 事件
`Changed()`:点击“确定”或“清空”使值改变时为 true。`Cancelled()`:按 Escape、点击“取消”或点击弹层外关闭、草稿被丢弃时为 true。
`Changed()`:点击“确定”或“清空”使值改变时为 true。`Canceled()`:按 Escape、点击“取消”或点击弹层外关闭、草稿被丢弃时为 true。
## 键盘操作
+1 -1
View File
@@ -47,7 +47,7 @@ ui.Render(view, 400, 300, 1)
## 事件
`Changed()`:确认且时刻改变时为 true,写入的值位于 `Location`。`Cancelled()`:取消、Escape 或点击外部关闭时为 true。
`Changed()`:确认且时刻改变时为 true,写入的值位于 `Location`。`Canceled()`:取消、Escape 或点击外部关闭时为 true。
## 键盘操作
+2 -2
View File
@@ -37,7 +37,7 @@ ui.Render(view, 400, 300, 1)
| `CancelLabel` | `string` | 内置“取消 / Cancel” | 取消按钮文字。 |
| `ConfirmText` | `string` | `""` | 非空时显示输入框,输入内容与之完全相同后确认按钮才可用。 |
`AlertDialogResult{Confirmed, Cancelled bool}` 只在发生选择的那一帧为 true,之后对话框关闭。
`AlertDialogResult{Confirmed, Canceled bool}` 只在发生选择的那一帧为 true,之后对话框关闭。
## 状态
@@ -47,7 +47,7 @@ ui.Render(view, 400, 300, 1)
## 事件
- `Confirmed`:点击确认,或焦点在已解锁的确认按钮上按 Enter / Space。
- `Cancelled`:点击取消、在取消上按 Enter / Space、或按 Escape。
- `Canceled`:点击取消、在取消上按 Enter / Space、或按 Escape。
- 点击遮罩不产生事件。
## 键盘操作
+2 -2
View File
@@ -33,7 +33,7 @@ ui.Render(view, 400, 300, 1)
| `CancelLabel` | `string` | 内置“取消 / Cancel” | 取消按钮文字。 |
| `Danger` | `bool` | `false` | 确认按钮使用危险变体,图标改为 Danger 色,打开时焦点改在取消。 |
`PopconfirmResult{Confirmed, Cancelled bool}` 只在发生选择的那一帧为 true。
`PopconfirmResult{Confirmed, Canceled bool}` 只在发生选择的那一帧为 true。
## 状态
@@ -42,7 +42,7 @@ ui.Render(view, 400, 300, 1)
## 事件
- `Confirmed`:点击确认或在其上按 Enter / Space。
- `Cancelled`:点击取消、Escape、外部点击。
- `Canceled`:点击取消、Escape、外部点击。
## 键盘操作
+232
View File
@@ -0,0 +1,232 @@
# MujicaUI PRD 扩展一:101—280
版本:1.1
日期:2026-10-05
来源:ELY GPUI 组件清单(`Ely-GPUI-Components/gpui-components.md`)中与本库路线相符的条目。
本扩展沿用 PRD 第 2、3、5、6、8、9、10 节的全部规则。每行一个公开组件;子部件、变体、尺寸不单独计数。图表、行情与媒体组件以 MyGo Painter 绘制,不建图表引擎、音视频解码或富文本编辑引擎:数据、帧与解码由调用方提供。
## 4.12 通用图表一 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 101 | LineChart | 多序列折线;数值与时间轴;缺值断开;悬停读数;序列显隐。 |
| 102 | AreaChart | 面积图;单序列与堆叠;基线可配置;悬停读数。 |
| 103 | BarChart | 纵横向柱状;分组与堆叠;负值;柱标签;键盘逐柱读数。 |
| 104 | PieChart | 饼图与环形;扇区标签;最小角合并为“其他”;中心总计。 |
| 105 | ScatterChart | 散点;点大小映射(气泡);分组颜色;框选。 |
| 106 | RadarChart | 多维雷达;多序列;刻度环;悬停读数。 |
| 107 | HeatmapChart | 二维矩阵热力;色阶图例;单元格读数;空值样式。 |
| 108 | CalendarHeatmap | 按日贡献图;年份切换;周起始可配置;色阶与读数。 |
| 109 | Histogram | 分箱计数;箱宽与箱数可配置;累计模式。 |
| 110 | BoxPlot | 四分位、须线、离群点;多组对比。 |
| 111 | WaterfallChart | 增减累计;小计与总计柱;正负语义色。 |
| 112 | FunnelChart | 漏斗阶段;转化率;阶段读数。 |
| 113 | BulletChart | 实际值、目标值、区间带;纵横向。 |
| 114 | Sparkline | 行内迷你折线、柱形与面积;末值标记;无坐标轴。 |
| 115 | Gauge | 半圆或整圆仪表;阈值分段;数值与单位;不能只靠颜色表达区间。 |
| 116 | Treemap | 层级矩形;点击下钻与返回;标签按面积省略。 |
| 117 | Sunburst | 层级环形;下钻;路径面包屑。 |
| 118 | SankeyChart | 节点与流量带;悬停高亮路径;节点排序稳定。 |
| 119 | NetworkGraph | 节点与边;调用方给定或简单力导布局;拖动节点;缩放平移。 |
| 120 | RealtimeChart | 实时追加数据;固定窗口滚动;暂停;隐藏时停止重绘。 |
## 4.13 通用图表二与图表部件 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 121 | ChartLegend | 序列图例;点击显隐;键盘切换;横纵布局与溢出折叠。 |
| 122 | ChartTooltip | 跟随指针或焦点的读数浮层;多序列同点读数;边缘避让。 |
| 123 | ChartCrosshair | 十字光标;轴上读数标签;吸附最近数据点。 |
| 124 | ChartAxis | 数值、时间、类别轴;刻度自动与指定;格式化函数;对数轴。 |
| 125 | ChartGrid | 网格线;主次刻度;按设备像素对齐。 |
| 126 | ChartBrush | 区间选择与缩放;拖动、调整两端;键盘移动;回调区间。 |
| 127 | ChartAnnotation | 水平线、垂直线、区间带与文字标注;拖动可选。 |
| 128 | ChartExport | 以当前主题渲染图表为 PNG 字节;尺寸与缩放可配置;不写文件。 |
| 129 | ViolinPlot | 核密度轮廓;中位数与四分位;多组。 |
| 130 | ParallelCoordinates | 多维平行轴;轴上刷选;线高亮。 |
| 131 | ChordDiagram | 关系矩阵弦图;悬停高亮;组弧标签。 |
| 132 | SparkBar | 行内迷你柱;正负值;高亮末项。 |
| 133 | TrendIndicator | 趋势箭头与变化值;正负语义;不只靠颜色。 |
| 134 | MetricCard | 指标名、值、变化、迷你图插槽;加载与错误状态。 |
| 135 | ProgressChart | 多目标完成度环或条;目标线;百分比读数。 |
| 136 | StackedPercentBar | 百分比堆叠条;分段标签;悬停读数。 |
| 137 | DualAxisChart | 左右双轴组合(柱与线);各自刻度与格式。 |
| 138 | DensityPlot | 一维核密度曲线;带宽可配置;多组叠加。 |
| 139 | QuadrantChart | 四象限散点;象限标题与中线可配置。 |
| 140 | ChartEmptyState | 图表的无数据、加载、错误与重试占位;保持图表尺寸。 |
## 4.14 行情图表与技术分析 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 141 | CandlestickChart | K 线;涨跌色可配置(红涨或绿涨);缩放平移;万根级只绘可见区。 |
| 142 | OHLCChart | 美国线;与 K 线共用数据接口。 |
| 143 | HeikinAshiChart | 平均 K 线;由调用方 OHLC 计算;与原 K 线可切换。 |
| 144 | LineQuoteChart | 分时图;昨收基线;均价线;盘前盘后分段。 |
| 145 | VolumeChart | 成交量柱;涨跌着色;与主图共享时间轴。 |
| 146 | DepthChart | 买卖深度累计面积;中间价;悬停读数。 |
| 147 | VolumeProfile | 价格分布成交量;价值区与控制点。 |
| 148 | MultiChartLayout | 1、2、4、6 宫格多图;独立品种与周期;布局切换不丢状态。 |
| 149 | ChartSync | 多图十字光标与时间范围同步;可逐图解除。 |
| 150 | TimeRangeSelector | 1D、5D、1M、3M、YTD、1Y、5Y、自定义;键盘导航。 |
| 151 | IntervalSelector | 1m 至 1M 周期;分组;收藏周期。 |
| 152 | ChartTypeSwitcher | K 线、美国线、折线、面积、平均 K 线切换。 |
| 153 | IndicatorOverlay | 主图叠加 MA、EMA、BOLL、VWAP;参数可配;计算由调用方或内置辅助函数。 |
| 154 | IndicatorPane | 副图 MACD、RSI、KDJ、OBV;多副图堆叠与高度调整。 |
| 155 | IndicatorSelector | 指标搜索、添加、移除、参数编辑。 |
| 156 | DrawingTools | 趋势线、水平线、斐波那契、矩形、文字标注;选中、移动、删除、撤销。 |
| 157 | DrawingToolbar | 画线工具栏;当前工具;锁定与清除;键盘快捷键提示。 |
| 158 | CompareSymbol | 叠加对比品种;百分比归一;序列颜色与移除。 |
| 159 | PriceAlertLine | 价格预警线;拖动调整;触发回调;不自行通知。 |
| 160 | CandleCountdown | 当前 K 线收盘倒计时;随周期变化;隐藏时停止计时重绘。 |
## 4.15 报价、盘口与交易 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 161 | TickerTape | 滚动行情条;悬停与聚焦暂停;减少动画时静态分页。 |
| 162 | QuoteCard | 代码、名称、现价、涨跌、迷你图插槽。 |
| 163 | PriceText | 价格精度与千分位;涨跌色;变化闪烁一次(减少动画时无闪烁)。 |
| 164 | PriceChangeBadge | 涨跌额与幅度;箭头与符号;不只靠颜色。 |
| 165 | OrderBook | 买卖档位;深度条;价格聚合档位;点击价格回传。 |
| 166 | Level2Quotes | 多档买卖与委托量;逐档变化高亮。 |
| 167 | TimeAndSales | 逐笔成交;大单标记;虚拟化;暂停跟随。 |
| 168 | DepthLadder | 价格阶梯(DOM);买卖量列;当前价居中;点击下单回调。 |
| 169 | SpreadIndicator | 买卖价差与百分比;阈值提示。 |
| 170 | BidAskBar | 买卖力量对比条;数值标签。 |
| 171 | OrderEntry | 限价、市价、止损、条件单;数量、价格、有效期;校验与预估金额;提交防重入。 |
| 172 | QuickTradeButtons | 快速买卖;数量预设;危险操作确认可配置。 |
| 173 | PositionTable | 持仓;成本、市值、浮动盈亏;平仓操作回调;稳定行 ID。 |
| 174 | OrderTable | 委托;状态筛选;撤单回调;部分成交进度。 |
| 175 | TradeHistoryTable | 成交记录;时间范围筛选;导出回调。 |
| 176 | PnLDisplay | 已实现、未实现、当日盈亏;正负语义;隐私遮蔽切换。 |
| 177 | LeverageSlider | 杠杆档位;风险提示区间;确认后生效。 |
| 178 | MarginIndicator | 保证金率;警戒与强平线;不只靠颜色。 |
| 179 | RiskMeter | 风险等级刻度与说明文字。 |
| 180 | OrderConfirmDialog | 下单摘要与风险提示;默认聚焦取消;二次确认可配置。 |
## 4.16 市场与资产 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 181 | OptionChain | 行权价居中的认购认沽表;到期日切换;价内高亮。 |
| 182 | GreeksTable | Delta、Gamma、Theta、Vega、Rho;精度与说明。 |
| 183 | PayoffDiagram | 期权组合到期损益;盈亏平衡点;当前价标记。 |
| 184 | Watchlist | 自选分组;拖动排序;实时刷新不丢选择;列可配置。 |
| 185 | SymbolSearch | 代码与名称搜索;市场筛选;最近搜索;异步结果由调用方提供。 |
| 186 | SymbolBadge | 代码、市场、类型标签;可点击。 |
| 187 | MarketHeatmap | 板块与个股热力;面积映射市值;涨跌色阶;下钻。 |
| 188 | Screener | 条件构建(字段、比较、值);结果表;保存条件回调。 |
| 189 | MarketOverview | 指数卡片组与涨跌家数;时间戳。 |
| 190 | IndexCard | 指数现价、涨跌、迷你图。 |
| 191 | MarketStatus | 开盘、收盘、盘前、盘后、休市;下一状态时间。 |
| 192 | TradingSessionClock | 多市场交易时段时钟;时区由调用方指定。 |
| 193 | EconomicCalendar | 经济事件;重要性;预期、前值、实际;国家筛选。 |
| 194 | EarningsCalendar | 财报日程;盘前盘后;预期与实际。 |
| 195 | NewsFeed | 新闻流;来源、时间、相关代码;已读状态;虚拟化。 |
| 196 | SentimentGauge | 情绪刻度(恐惧到贪婪);数值与文字。 |
| 197 | PortfolioSummary | 总资产、当日盈亏、累计收益;分账户。 |
| 198 | AssetAllocationChart | 资产配置环形与列表;目标与偏离。 |
| 199 | PerformanceChart | 收益曲线、基准对比、最大回撤区间。 |
| 200 | CurrencyConverter | 币种选择与互换;汇率由调用方提供;精度规则。 |
## 4.17 AI 对话:消息 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 201 | ChatContainer | 消息区、输入区、侧栏插槽;新消息自动滚底,用户上翻时停止跟随。 |
| 202 | MessageList | 虚拟化消息列表;稳定消息 ID;新消息提示;按日期分隔。 |
| 203 | MessageBubble | 用户、助手、系统、错误四种;头像与名称插槽;宽度限制。 |
| 204 | MessageHeader | 模型名、时间、状态。 |
| 205 | StreamingText | 流式追加文本;平滑出字可关闭;流式光标;减少动画时直接显示。 |
| 206 | MarkdownView | Markdown 子集渲染:标题、段落、强调、列表、引用、链接、代码、表格、分隔线;流式增量渲染不重排已完成块。 |
| 207 | CodeBlock | 语言标签、行号、基础语法着色、复制、折叠、换行切换。 |
| 208 | TableBlock | 消息内表格;横向滚动;复制为 TSV。 |
| 209 | ImageGrid | 1 至 9 张图片网格;点击打开 ImageViewer;加载与失败。 |
| 210 | FileMessage | 文件名、类型图标、大小、打开与下载回调。 |
| 211 | AudioMessage | 语音条;波形;播放进度;时长;播放由调用方驱动。 |
| 212 | LinkPreviewCard | 标题、描述、站点、缩略图;元数据由调用方提供。 |
| 213 | QuoteReply | 引用原消息摘要;点击跳转原消息。 |
| 214 | MessageActions | 复制、重新生成、编辑、赞、踩、朗读、分享;按角色显示。 |
| 215 | MessageEditor | 编辑已发送消息;保存与取消;保存后产生新分支回调。 |
| 216 | BranchNavigator | 分支切换(< 2/3 >);键盘操作。 |
| 217 | ErrorMessage | 失败原因、重试;限流倒计时(RateLimit);不吞掉原错误文本。 |
| 218 | ThinkingBlock | 可折叠思考过程;思考中动画与耗时;默认折叠可配置。 |
| 219 | ScrollToBottomButton | 离开底部时出现;未读计数;点击回到底部。 |
| 220 | TypingIndicator | 对方正在输入三点动画;减少动画时显示静态文字。 |
## 4.18 AI 对话:输入与引用 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 221 | PromptComposer | 多行输入;Enter 发送、Shift+Enter 换行可配置;输入法组合期间不发送;附件与工具插槽。 |
| 222 | AttachmentChip | 附件名、类型、大小、上传进度、移除;失败重试。 |
| 223 | PasteImagePreview | 粘贴或拖入图片的预览缩略图;移除。 |
| 224 | DragDropOverlay | 拖入文件时的全区域遮罩与提示;类型检查回调。 |
| 225 | VoiceInputButton | 录音开始与停止;录音时长;权限失败提示;录音由调用方实现。 |
| 226 | VoiceWaveform | 输入电平波形;静音提示。 |
| 227 | SlashCommandMenu | 输入“/”弹出命令;筛选;方向键选择;Tab 补全。 |
| 228 | ContextMentionMenu | 输入“@”选择文件、文档、网页等上下文;分组;异步结果。 |
| 229 | ContextChips | 已附加上下文列表;移除;溢出折叠。 |
| 230 | ModelSelector | 模型列表;能力标签与上下文长度;不可用项说明。 |
| 231 | ModeSelector | 对话、搜索、深度研究、Agent 模式切换。 |
| 232 | ToolToggleMenu | 联网、代码执行等工具开关;需授权项标记。 |
| 233 | ParameterPanel | 温度、Top-p、最大长度等参数;范围与默认值;重置。 |
| 234 | TokenCounter | 当前输入 token 数与上限;估算值由调用方提供。 |
| 235 | ContextWindowMeter | 上下文使用率条;分段(系统、历史、附件);阈值警告。 |
| 236 | SendButton | 发送与停止两态;禁用条件;快捷键提示。 |
| 237 | CitationBadge | 行内引用编号;悬停预览来源;点击定位来源。 |
| 238 | SourceCard | 来源标题、站点、摘要、时间;打开回调。 |
| 239 | SourcesPanel | 来源列表;按引用顺序;与正文引用联动高亮。 |
| 240 | SearchProgress | 搜索中关键词与已读来源数;阶段列表;完成后折叠。 |
## 4.19 AI 对话:会话与引导 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 241 | ConversationList | 按今天、昨天、7 天内、更早分组;置顶;虚拟化。 |
| 242 | ConversationItem | 标题、时间、重命名、删除、置顶、导出回调;行内重命名。 |
| 243 | ConversationSearch | 标题与内容搜索;匹配高亮;异步结果。 |
| 244 | ProjectList | 项目与文件夹;会话归属;拖动移入。 |
| 245 | ProjectKnowledgePanel | 项目知识文件列表;添加、移除、容量提示。 |
| 246 | WelcomeScreen | 问候、能力卡片、起始提示插槽。 |
| 247 | SuggestionChips | 起始提示与追问建议;点击填入或直接发送可配置。 |
| 248 | PromptLibrary | 提示模板分类、搜索、预览、插入;变量占位符填写。 |
| 249 | CapabilityCards | 能力说明卡片;点击示例。 |
| 250 | FeedbackForm | 赞踩后的原因选项与文字;提交与取消。 |
| 251 | DateSeparator | 消息日期分隔;今天、昨天与日期格式本地化。 |
| 252 | ThinkingIndicator | 思考中状态动画与阶段文字;减少动画时静态。 |
| 253 | StopGeneratingButton | 停止生成;生成结束后隐藏;快捷键。 |
| 254 | SystemPromptEditor | 系统提示多行编辑;字数;恢复默认;未保存标记。 |
| 255 | CostEstimator | 输入、输出 token 与费用估算;单价由调用方提供。 |
| 256 | SharedConversationView | 只读会话视图;分享者与时间;复制链接回调。 |
| 257 | ConversationExport | 导出为 Markdown、JSON、纯文本;范围选择;内容由组件生成,写文件由调用方完成。 |
| 258 | RegenerateMenu | 重新生成;换模型重试;更长、更短等变体选项。 |
| 259 | ArtifactCard | 消息内产物卡片(代码、文档、网页);打开产物面板回调。 |
| 260 | ChatEmptyState | 新会话、无搜索结果、离线三种空状态。 |
## 4.20 AI Agent 与工具调用 · 20 个
| 编号 | 组件 | 首版功能 |
| --- | --- | --- |
| 261 | ToolCallCard | 工具名、参数、结果、耗时、状态;参数与结果折叠;JSON 高亮。 |
| 262 | ToolCallGroup | 连续调用折叠为一组;总耗时与失败数。 |
| 263 | ToolApprovalDialog | 执行前审批;参数预览;允许、拒绝;默认聚焦拒绝。 |
| 264 | PermissionPrompt | 允许一次、始终允许、拒绝;作用范围说明。 |
| 265 | AgentStepList | 执行步骤时间线;当前步;步骤展开详情;失败步重试回调。 |
| 266 | AgentPlan | 计划清单;实时勾选;新增与跳过项;进度。 |
| 267 | AgentStatus | 规划中、执行中、等待、完成、失败;不只靠颜色;状态动画可关闭。 |
| 268 | AgentProgress | 总体进度与当前动作文字;不确定模式。 |
| 269 | SubAgentTree | 子 Agent 层级;各自状态;展开查看。 |
| 270 | FileChangeCard | 文件变更预览;增删统计;接受与拒绝;打开差异。 |
| 271 | MultiFileDiffReview | 多文件审阅;逐文件接受拒绝;全部接受;进度。 |
| 272 | CommandExecutionCard | 命令、工作目录、输出(ANSI)、退出码、耗时;中止回调。 |
| 273 | ScreenshotStream | Agent 画面帧序列查看;帧由调用方提供;暂停与逐帧。 |
| 274 | ArtifactPanel | 产物面板;代码、文档、网页预览插槽;复制与导出回调。 |
| 275 | ArtifactVersionSwitcher | 产物版本切换与差异入口。 |
| 276 | SandboxStatus | 沙箱运行状态、资源与剩余时间。 |
| 277 | MCPServerList | 服务器列表;连接状态;启停与重连回调;工具数。 |
| 278 | MemoryPanel | 记忆条目列表;搜索;编辑与删除。 |
| 279 | CheckpointList | 检查点;回滚确认;当前点标记。 |
| 280 | HumanInputRequest | Agent 请求用户输入;问题、选项与自由回答;超时提示。 |
+1 -1
View File
@@ -15,7 +15,7 @@ func ExampleText() {
mujica.Use(c, mujica.Settings{})
mujica.Text(c, "Order ID: 4821-AX", mujica.TextOptions{Selectable: true})
mujica.Text(c, "A long note that stops after two lines.", mujica.TextOptions{MaxLines: 2})
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed {
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed() {
saveErr = "" // save name here; set saveErr on failure
}
}
+1 -1
View File
@@ -351,7 +351,7 @@ func (g *gallery) deleteDialog(c *ui.Context, log func(string)) {
mujica.ShowToast(c, mujica.ToastMessage{ID: "files-deleted", Severity: mujica.SeveritySuccess,
Message: fmt.Sprintf(g.t("Deleted %d files", "已删除 %d 个文件"), len(s.doomed))})
s.doomed = nil
case r.Cancelled:
case r.Canceled:
s.doomed = nil
log("delete canceled")
}
+4 -4
View File
@@ -163,8 +163,8 @@ func dtDateRangeDemo(c *ui.Context, log func(string)) {
if r.Changed() {
log(fmt.Sprintf("DateRangePicker changed: %v – %v (%d days)", s.a.Start, s.a.End, s.a.Days()))
}
if r.Cancelled() {
log("DateRangePicker cancelled")
if r.Canceled() {
log("DateRangePicker canceled")
}
})
}
@@ -264,8 +264,8 @@ func dtDateTimeDemo(c *ui.Context, log func(string)) {
if r.Changed() {
log("DateTimePicker changed: " + s.v.Format(time.RFC3339))
}
if r.Cancelled() {
log("DateTimePicker cancelled")
if r.Canceled() {
log("DateTimePicker canceled")
}
k := mujica.Tokens(c)
ui.Text(c, "Try 2026-03-08 02:30 in America/New_York: the picker reports the DST gap.").
+2 -2
View File
@@ -61,7 +61,7 @@ func textDemo(c *ui.Context, log func(string)) {
})
displaySection(c, "Editable", func() {
e := mujica.EditableText(c, &s.name, mujica.EditableTextOptions{Saving: s.saving, Error: s.saveErr, Label: "File name"})
if e.Changed {
if e.Changed() {
if s.failNext {
s.saveErr = "Save failed: the name is already taken."
log("save failed: " + s.name)
@@ -78,7 +78,7 @@ func textDemo(c *ui.Context, log func(string)) {
const textCode = `mujica.Text(c, "Order ID: 4821-AX", mujica.TextOptions{Selectable: true})
mujica.Text(c, "A long note that stops after two lines.", mujica.TextOptions{MaxLines: 2})
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed {
if mujica.EditableText(c, &name, mujica.EditableTextOptions{Error: saveErr}).Changed() {
saveErr = "" // save name here; set saveErr on failure
}`
+4 -4
View File
@@ -140,8 +140,8 @@ func alertDialogDemo(c *ui.Context, log func(string)) {
if r.Confirmed {
log("confirmed")
}
if r.Cancelled {
log("cancelled")
if r.Canceled {
log("canceled")
}
}
@@ -428,8 +428,8 @@ func popconfirmDemo(c *ui.Context, log func(string)) {
if r.Confirmed {
log("confirmed")
}
if r.Cancelled {
log("cancelled")
if r.Canceled {
log("canceled")
}
}
+4 -4
View File
@@ -22,7 +22,7 @@ type PopconfirmOptions struct {
// PopconfirmResult reports the choice made in this frame.
type PopconfirmResult struct {
Confirmed bool
Cancelled bool
Canceled bool
}
// Popconfirm asks for a light confirmation by anchor; use AlertDialog for high-risk actions.
@@ -65,7 +65,7 @@ func Popconfirm(c *ui.Context, anchor *ui.Element, open *bool, opts PopconfirmOp
cb.AutoFocus()
}
if cb.Clicked() {
res.Cancelled = true
res.Canceled = true
}
ok := Button(c, confirm, ButtonOptions{Variant: variant})
if !opts.Danger {
@@ -77,9 +77,9 @@ func Popconfirm(c *ui.Context, anchor *ui.Element, open *bool, opts PopconfirmOp
})
})
if !shown {
res.Cancelled = true
res.Canceled = true
}
if res.Confirmed || res.Cancelled {
if res.Confirmed || res.Canceled {
slog.Debug("mujica: popconfirm close", "confirmed", res.Confirmed)
*open = false
}
+8 -8
View File
@@ -8,8 +8,8 @@ import (
)
type popconfirmApp struct {
open bool
confirmed, cancelled int
open bool
confirmed, canceled int
}
func (a *popconfirmApp) view(c *ui.Context) {
@@ -23,8 +23,8 @@ func (a *popconfirmApp) view(c *ui.Context) {
if r.Confirmed {
a.confirmed++
}
if r.Cancelled {
a.cancelled++
if r.Canceled {
a.canceled++
}
})
}
@@ -38,8 +38,8 @@ func TestPopconfirmConfirmByKeyboard(t *testing.T) {
t.Fatalf("open %v, Confirm focused %v", a.open, tt.Focused("Confirm"))
}
tt.Key(0, ui.KeyEnter)
if a.confirmed != 1 || a.cancelled != 0 || a.open {
t.Fatalf("confirmed %d cancelled %d open %v", a.confirmed, a.cancelled, a.open)
if a.confirmed != 1 || a.canceled != 0 || a.open {
t.Fatalf("confirmed %d canceled %d open %v", a.confirmed, a.canceled, a.open)
}
if !tt.Focused("Archive") {
t.Fatal("focus not restored to the anchor")
@@ -78,8 +78,8 @@ func TestPopconfirmCancelPaths(t *testing.T) {
tt.Key(0, ui.KeyEscape)
_ = tt.Click("Archive")
tt.ClickAt(390, 290)
if a.cancelled != 3 || a.confirmed != 0 || a.open {
t.Fatalf("cancelled %d confirmed %d open %v", a.cancelled, a.confirmed, a.open)
if a.canceled != 3 || a.confirmed != 0 || a.open {
t.Fatalf("canceled %d confirmed %d open %v", a.canceled, a.confirmed, a.open)
}
}
+7 -5
View File
@@ -49,12 +49,14 @@ type EditableTextOptions struct {
// EditableTextView is a shown EditableText and this frame's outcome.
type EditableTextView struct {
Element *ui.Element
// Changed reports a confirmed new value, already in *value.
Changed bool
// Canceled reports Escape discarded an edit.
Canceled bool
changed bool
}
// Changed reports a confirmed new value, already in *value.
func (v EditableTextView) Changed() bool { return v.changed }
type editableTextState struct {
editing, fresh bool
draft string
@@ -83,7 +85,7 @@ func EditableText(c *ui.Context, value *string, opts EditableTextOptions) Editab
}
})
switch {
case v.Changed:
case v.changed:
slog.Debug("mujica: editabletext changed", "len", len(*value))
case v.Canceled:
slog.Debug("mujica: editabletext canceled")
@@ -131,14 +133,14 @@ func editableTextField(c *ui.Context, k theme.Tokens, value *string, opts Editab
st.fresh = false
in.Focus()
case in.Submitted():
v.Changed = st.draft != *value
v.changed = st.draft != *value
*value = st.draft
done()
case in.Shortcut(0, ui.KeyEscape):
v.Canceled = true
done()
case !in.Focused():
v.Changed = st.draft != *value
v.changed = st.draft != *value
*value = st.draft
st.editing = false
}
+3 -3
View File
@@ -44,7 +44,7 @@ func TestEditableTextBlurConfirms(t *testing.T) {
changed := 0
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
if EditableText(c, &name, EditableTextOptions{}).Changed {
if EditableText(c, &name, EditableTextOptions{}).Changed() {
changed++
}
Button(c, "Other", ButtonOptions{})
@@ -107,7 +107,7 @@ func TestEditableTextConfirm(t *testing.T) {
changed := 0
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
if EditableText(c, &name, EditableTextOptions{Label: "File name"}).Changed {
if EditableText(c, &name, EditableTextOptions{Label: "File name"}).Changed() {
changed++
}
}, 300, 120)
@@ -133,7 +133,7 @@ func TestEditableTextCancel(t *testing.T) {
tt := ui.NewTester(func(c *ui.Context) {
Use(c, Settings{})
v := EditableText(c, &name, EditableTextOptions{})
if v.Changed {
if v.Changed() {
changed++
}
if v.Canceled {