diff --git a/alert_dialog.go b/alert_dialog.go index c8ffb9b..378ef2e 100644 --- a/alert_dialog.go +++ b/alert_dialog.go @@ -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 } diff --git a/alert_dialog_test.go b/alert_dialog_test.go index 1a7651b..1653ffb 100644 --- a/alert_dialog_test.go +++ b/alert_dialog_test.go @@ -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) } } diff --git a/date_range_picker.go b/date_range_picker.go index ec6f51a..d659fb9 100644 --- a/date_range_picker.go +++ b/date_range_picker.go @@ -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. diff --git a/date_range_picker_test.go b/date_range_picker_test.go index 0de6cae..41a93d7 100644 --- a/date_range_picker_test.go +++ b/date_range_picker_test.go @@ -17,7 +17,7 @@ func dateRangeTester(value *DateRange, opts DateRangePickerOptions, changes, can if r.Changed() { *changes++ } - if r.Cancelled() { + if r.Canceled() { *cancels++ } }) diff --git a/date_time_picker.go b/date_time_picker.go index 9e7c482..ab1bc04 100644 --- a/date_time_picker.go +++ b/date_time_picker.go @@ -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. diff --git a/date_time_picker_test.go b/date_time_picker_test.go index d10c0dd..6da95c0 100644 --- a/date_time_picker_test.go +++ b/date_time_picker_test.go @@ -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++ } }) diff --git a/datetime.go b/datetime.go index c880efc..728dcaf 100644 --- a/datetime.go +++ b/datetime.go @@ -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) { diff --git a/docs/components/011-text.md b/docs/components/011-text.md index 7c249c2..963b7f9 100644 --- a/docs/components/011-text.md +++ b/docs/components/011-text.md @@ -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` 不变。 ## 键盘操作 diff --git a/docs/components/053-date-range-picker.md b/docs/components/053-date-range-picker.md index 14549cd..a95e3db 100644 --- a/docs/components/053-date-range-picker.md +++ b/docs/components/053-date-range-picker.md @@ -40,7 +40,7 @@ ui.Render(view, 400, 300, 1) ## 事件 -`Changed()`:点击“确定”或“清空”使值改变时为 true。`Cancelled()`:按 Escape、点击“取消”或点击弹层外关闭、草稿被丢弃时为 true。 +`Changed()`:点击“确定”或“清空”使值改变时为 true。`Canceled()`:按 Escape、点击“取消”或点击弹层外关闭、草稿被丢弃时为 true。 ## 键盘操作 diff --git a/docs/components/056-date-time-picker.md b/docs/components/056-date-time-picker.md index f3eaf42..1adcf08 100644 --- a/docs/components/056-date-time-picker.md +++ b/docs/components/056-date-time-picker.md @@ -47,7 +47,7 @@ ui.Render(view, 400, 300, 1) ## 事件 -`Changed()`:确认且时刻改变时为 true,写入的值位于 `Location`。`Cancelled()`:取消、Escape 或点击外部关闭时为 true。 +`Changed()`:确认且时刻改变时为 true,写入的值位于 `Location`。`Canceled()`:取消、Escape 或点击外部关闭时为 true。 ## 键盘操作 diff --git a/docs/components/082-alert-dialog.md b/docs/components/082-alert-dialog.md index b352f42..d3a8a18 100644 --- a/docs/components/082-alert-dialog.md +++ b/docs/components/082-alert-dialog.md @@ -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。 - 点击遮罩不产生事件。 ## 键盘操作 diff --git a/docs/components/089-popconfirm.md b/docs/components/089-popconfirm.md index fbedf74..5d29d3e 100644 --- a/docs/components/089-popconfirm.md +++ b/docs/components/089-popconfirm.md @@ -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、外部点击。 ## 键盘操作 diff --git a/docs/prd/ext-1.md b/docs/prd/ext-1.md new file mode 100644 index 0000000..4726b5c --- /dev/null +++ b/docs/prd/ext-1.md @@ -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 请求用户输入;问题、选项与自由回答;超时提示。 | diff --git a/example_display_test.go b/example_display_test.go index 0bce4e1..6708320 100644 --- a/example_display_test.go +++ b/example_display_test.go @@ -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 } } diff --git a/examples/gallery/composite_files.go b/examples/gallery/composite_files.go index 0ceda62..cf669e8 100644 --- a/examples/gallery/composite_files.go +++ b/examples/gallery/composite_files.go @@ -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") } diff --git a/examples/gallery/pages_datetime.go b/examples/gallery/pages_datetime.go index f547335..e09bf68 100644 --- a/examples/gallery/pages_datetime.go +++ b/examples/gallery/pages_datetime.go @@ -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."). diff --git a/examples/gallery/pages_display.go b/examples/gallery/pages_display.go index 296c0cc..13cee9e 100644 --- a/examples/gallery/pages_display.go +++ b/examples/gallery/pages_display.go @@ -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 }` diff --git a/examples/gallery/pages_overlay.go b/examples/gallery/pages_overlay.go index 76a48ae..2e65b9a 100644 --- a/examples/gallery/pages_overlay.go +++ b/examples/gallery/pages_overlay.go @@ -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") } } diff --git a/popconfirm.go b/popconfirm.go index f12300d..5799756 100644 --- a/popconfirm.go +++ b/popconfirm.go @@ -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 } diff --git a/popconfirm_test.go b/popconfirm_test.go index 1b240ec..0949480 100644 --- a/popconfirm_test.go +++ b/popconfirm_test.go @@ -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) } } diff --git a/text.go b/text.go index e2aa6ca..74f721c 100644 --- a/text.go +++ b/text.go @@ -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 } diff --git a/text_test.go b/text_test.go index d6ead0c..3836edc 100644 --- a/text_test.go +++ b/text_test.go @@ -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 {