From bfa70132da880f48d2a0e93ab6e3426f90e723e9 Mon Sep 17 00:00:00 2001 From: HIsheR Date: Fri, 23 Oct 2026 19:04:27 +0800 Subject: [PATCH] =?UTF-8?q?iter(g03):=20=E8=BE=93=E5=85=A5=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E8=A1=A5=20prefix/suffix=20=E9=99=84=E5=8A=A0?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/index.json | 47 ++++++++++++++++++++++++----- apps/demo/main.ts | 7 ++++- llms-full.txt | 17 ++++++++--- llms.txt | 6 ++-- packages/skin/ui/src/addons.ts | 13 ++++++++ packages/skin/ui/src/inputNumber.ts | 5 +-- packages/skin/ui/src/textField.ts | 7 +++-- packages/skin/ui/src/textarea.ts | 7 +++-- packages/skin/ui/src/types.ts | 6 ++++ packages/skin/ui/ua.manifest.json | 45 +++++++++++++++++++++++---- packages/skin/ui/ui.css | 39 ++++++++++++++++++++++++ 11 files changed, 170 insertions(+), 29 deletions(-) create mode 100644 packages/skin/ui/src/addons.ts diff --git a/.ua/index.json b/.ua/index.json index ade3b34..7e22330 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T10:58:48.833Z", + "generatedAt": "2026-10-23T11:03:12.689Z", "repoRoot": ".", "packages": [ { @@ -143,9 +143,9 @@ "entry": "src/index.ts", "exports": [ "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用", - "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用", - "inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取", - "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", + "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块", + "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块", + "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块", "slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", @@ -325,13 +325,24 @@ "t": "(value: string) => void", "k": 1, "d": "输入回调,每次按键触发" + }, + { + "n": "prefix", + "t": "string | HTMLElement", + "d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染" + }, + { + "n": "suffix", + "t": "string | HTMLElement", + "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" } ], "example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })", "steps": [ "先用 signal('') 创建字符串信号", "传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显", - "在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`" + "在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`", + "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, { @@ -371,13 +382,24 @@ "t": "(value: number) => void", "k": 1, "d": "值变化回调(已夹取到合法区间)" + }, + { + "n": "prefix", + "t": "string | HTMLElement", + "d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染" + }, + { + "n": "suffix", + "t": "string | HTMLElement", + "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" } ], "example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })", "steps": [ "用 signal(1) 创建数字信号并传入 value 完成双向绑定", "点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析", - "非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用" + "非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用", + "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, { @@ -411,13 +433,24 @@ "t": "(value: string) => void", "k": 1, "d": "输入回调" + }, + { + "n": "prefix", + "t": "string | HTMLElement", + "d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染" + }, + { + "n": "suffix", + "t": "string | HTMLElement", + "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" } ], "example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })", "steps": [ "创建信号并传入 value", "用 rows 控制初始高度,用户可纵向拉伸", - "字数统计等派生逻辑直接在响应式文本里读 bio().length" + "字数统计等派生逻辑直接在响应式文本里读 bio().length", + "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, { diff --git a/apps/demo/main.ts b/apps/demo/main.ts index b8e6f74..f4072ef 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -127,6 +127,8 @@ const volume = signal(40); // —— 数字输入:购买数量演示,与禁用态实例共用同一信号 —— const qty = signal(1); +const price = signal('9.9'); // 演示 suffix 附加块 +const amount = signal(2); // 演示 inputNumber 前后缀 // —— 步骤条:下单流程演示,按钮推进、点已完成步骤回跳 —— const stepIdx = signal(0); @@ -169,8 +171,10 @@ const content = h( section( 'sec-bind', '双向绑定', - 'textField 与信号双向同步:输入即回显。', + 'textField 与信号双向同步:输入即回显;prefix/suffix 渲染为输入框内侧附加块。', textField({ placeholder: '输入名字…', value: name }), + textField({ placeholder: '搜索…', value: name, prefix: '🔍' }), + textField({ value: price, suffix: '元' }), h('span', { class: 'out' }, () => (name() ? `你好,${name()}!` : '等待输入…')), ), section( @@ -267,6 +271,7 @@ const content = h( { class: 'row' }, inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => toast.info(`数量 ${v}`) }), inputNumber({ value: qty, disabled: locked }), + inputNumber({ value: amount, min: 0, prefix: '¥', suffix: '件' }), ), ), section( diff --git a/llms-full.txt b/llms-full.txt index b11e5f4..422beb4 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T10:58:48.833Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T11:03:12.689Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -104,6 +104,8 @@ button({ label: '提交', variant: 'primary', onClick: () => console.log('clicke - placeholder:string —— 占位文案 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onInput(回调):(value: string) => void —— 输入回调,每次按键触发 +- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 +- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 示例: @@ -116,6 +118,7 @@ const name = signal(''); const el = textField({ value: name, placeholder: '输 1. 先用 signal('') 创建字符串信号 2. 传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显 3. 在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}` +4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载 现场演示:apps/docs/demo.html#sec-bind @@ -129,6 +132,8 @@ const name = signal(''); const el = textField({ value: name, placeholder: '输 - step(默认 1):number —— 步长;缺省 1 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间) +- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 +- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 示例: @@ -141,6 +146,7 @@ const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onC 1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定 2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析 3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用 +4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载 现场演示:apps/docs/demo.html#sec-inputnumber @@ -153,6 +159,8 @@ const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onC - placeholder:string —— 占位文案 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onInput(回调):(value: string) => void —— 输入回调 +- prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 +- suffix:string | HTMLElement —— 后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染 示例: @@ -165,6 +173,7 @@ const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话 1. 创建信号并传入 value 2. 用 rows 控制初始高度,用户可纵向拉伸 3. 字数统计等派生逻辑直接在响应式文本里读 bio().length +4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载 现场演示:apps/docs/demo.html#sec-form @@ -892,9 +901,9 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓 API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 -- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用 -- inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取 -- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 +- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块 +- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块 +- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块 - slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 diff --git a/llms.txt b/llms.txt index 7818f1f..4cf0c7d 100644 --- a/llms.txt +++ b/llms.txt @@ -48,9 +48,9 @@ - 描述: DOM 皮肤组件库:button / textField / textarea / slider / checkbox / switch / select / menu / radioGroup / accordion / breadcrumb / card / badge / image / tag / avatar / alert / toast / tooltip / popover / skeleton / spinner / progress / tabs / pagination / modal / drawer / form / formItem / dataTable,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌。 - API: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 - - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - - inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取 - - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 + - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块 + - inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块 + - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块 - slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 diff --git a/packages/skin/ui/src/addons.ts b/packages/skin/ui/src/addons.ts new file mode 100644 index 0000000..689611c --- /dev/null +++ b/packages/skin/ui/src/addons.ts @@ -0,0 +1,13 @@ +import { h } from '@ui-arch/frame'; + +/*** 输入附加块包装:prefix/suffix 存在时把控件包进附加组,否则原样返回(零侵入,结构不变) ***/ +export function addonWrap( + el: HTMLElement, + prefix?: string | HTMLElement, + suffix?: string | HTMLElement, + column = false, // 多行控件用纵向布局:prefix 在上、suffix 在下 +): HTMLElement { + if (!prefix && !suffix) return el; // 卫语句:无附加不包裹 + const addon = (v?: string | HTMLElement) => (v == null ? null : h('span', { class: 'ua-input-addon' }, v)); + return h('div', { class: column ? 'ua-input-group ua-input-group-column' : 'ua-input-group' }, addon(prefix), el, addon(suffix)); +} diff --git a/packages/skin/ui/src/inputNumber.ts b/packages/skin/ui/src/inputNumber.ts index 7b94b7c..c0fd8eb 100644 --- a/packages/skin/ui/src/inputNumber.ts +++ b/packages/skin/ui/src/inputNumber.ts @@ -1,9 +1,10 @@ import { effect, h } from '@ui-arch/frame'; import { button } from './button.js'; +import { addonWrap } from './addons.js'; import type { InputNumberProps } from './types.js'; /*** 数字输入:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节 ***/ -export function inputNumber(props: InputNumberProps): HTMLDivElement { +export function inputNumber(props: InputNumberProps): HTMLElement { const min = props.min ?? -Infinity; // 无下限缺省 const max = props.max ?? Infinity; // 无上限缺省 const step = props.step ?? 1; // 步长缺省 1 @@ -22,5 +23,5 @@ export function inputNumber(props: InputNumberProps): HTMLDivElement { const inc = button({ label: '+', variant: 'ghost', disabled: props.disabled, onClick: () => emit(props.value() + step) }); dec.setAttribute('aria-label', '减少'); inc.setAttribute('aria-label', '增加'); - return h('div', { class: 'ua-input-number', role: 'group' }, dec, input, inc) as HTMLDivElement; + return addonWrap(h('div', { class: 'ua-input-number', role: 'group' }, dec, input, inc) as HTMLDivElement, props.prefix, props.suffix); } diff --git a/packages/skin/ui/src/textField.ts b/packages/skin/ui/src/textField.ts index b2db21e..e5efcea 100644 --- a/packages/skin/ui/src/textField.ts +++ b/packages/skin/ui/src/textField.ts @@ -1,8 +1,9 @@ import { effect, h } from '@ui-arch/frame'; +import { addonWrap } from './addons.js'; import type { TextFieldProps } from './types.js'; -/*** 文本输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调 ***/ -export function textField(props: TextFieldProps): HTMLInputElement { +/*** 文本输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调;prefix/suffix 包附加组 ***/ +export function textField(props: TextFieldProps): HTMLElement { const el = h('input', { class: 'ua-input', placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLInputElement; // false/undefined 移除属性,Signal 走响应式 if (props.value) { const signal = props.value; @@ -12,5 +13,5 @@ export function textField(props: TextFieldProps): HTMLInputElement { }); } if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value)); - return el; + return addonWrap(el, props.prefix, props.suffix); } diff --git a/packages/skin/ui/src/textarea.ts b/packages/skin/ui/src/textarea.ts index 593875b..0df2ca7 100644 --- a/packages/skin/ui/src/textarea.ts +++ b/packages/skin/ui/src/textarea.ts @@ -1,8 +1,9 @@ import { effect, h } from '@ui-arch/frame'; +import { addonWrap } from './addons.js'; import type { TextareaProps } from './types.js'; -/*** 多行输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调 ***/ -export function textarea(props: TextareaProps): HTMLTextAreaElement { +/*** 多行输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调;prefix/suffix 包纵向附加组 ***/ +export function textarea(props: TextareaProps): HTMLElement { const el = h('textarea', { class: 'ua-textarea', rows: props.rows ?? 3, placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLTextAreaElement; // false/undefined 移除属性,Signal 走响应式 if (props.value) { const signal = props.value; @@ -12,5 +13,5 @@ export function textarea(props: TextareaProps): HTMLTextAreaElement { }); } if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value)); - return el; + return addonWrap(el, props.prefix, props.suffix, true); } diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index b250bf3..2f99caa 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -32,6 +32,8 @@ export interface TextFieldProps { value?: Signal; // 双向绑定的信号 disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 onInput?: (value: string) => void; // 输入回调 + prefix?: string | HTMLElement; // 输入框内侧前置附加块(如图标、货币符);缺省不渲染 + suffix?: string | HTMLElement; // 输入框内侧后置附加块(如单位);缺省不渲染 } /*** 卡片入参 ***/ @@ -163,6 +165,8 @@ export interface TextareaProps { value?: Signal; // 双向绑定的信号 disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 onInput?: (value: string) => void; // 输入回调 + prefix?: string | HTMLElement; // 上方附加块(如标题、计数说明);缺省不渲染 + suffix?: string | HTMLElement; // 下方附加块(如字数提示);缺省不渲染 } /*** 数字输入入参 ***/ @@ -173,6 +177,8 @@ export interface InputNumberProps { step?: number; // 步长;缺省 1 disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 onChange?: (value: number) => void; // 值变化回调(已夹取到合法区间) + prefix?: string | HTMLElement; // −/+ 按钮前的附加块(如货币符);缺省不渲染 + suffix?: string | HTMLElement; // +/+ 按钮后的附加块(如单位);缺省不渲染 } /*** 步骤条条目 ***/ diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 3cfad23..749d1d9 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -14,9 +14,9 @@ "entry": "src/index.ts", "exports": [ "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用", - "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用", - "inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取", - "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", + "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块", + "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节,min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块", + "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块", "slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", @@ -196,13 +196,24 @@ "t": "(value: string) => void", "k": 1, "d": "输入回调,每次按键触发" + }, + { + "n": "prefix", + "t": "string | HTMLElement", + "d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染" + }, + { + "n": "suffix", + "t": "string | HTMLElement", + "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" } ], "example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })", "steps": [ "先用 signal('') 创建字符串信号", "传入 value 完成双向绑定:用户输入自动写回,外部赋值自动回显", - "在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`" + "在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`", + "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, { @@ -242,13 +253,24 @@ "t": "(value: number) => void", "k": 1, "d": "值变化回调(已夹取到合法区间)" + }, + { + "n": "prefix", + "t": "string | HTMLElement", + "d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染" + }, + { + "n": "suffix", + "t": "string | HTMLElement", + "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" } ], "example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })", "steps": [ "用 signal(1) 创建数字信号并传入 value 完成双向绑定", "点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析", - "非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用" + "非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用", + "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, { @@ -282,13 +304,24 @@ "t": "(value: string) => void", "k": 1, "d": "输入回调" + }, + { + "n": "prefix", + "t": "string | HTMLElement", + "d": "前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染" + }, + { + "n": "suffix", + "t": "string | HTMLElement", + "d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染" } ], "example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })", "steps": [ "创建信号并传入 value", "用 rows 控制初始高度,用户可纵向拉伸", - "字数统计等派生逻辑直接在响应式文本里读 bio().length" + "字数统计等派生逻辑直接在响应式文本里读 bio().length", + "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, { diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 2b95fdf..728b48e 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -1608,3 +1608,42 @@ button.ua-step-dot:hover { transition: none !important; } } + +/* ---- 输入附加组(prefix/suffix) ---- */ +.ua-input-group { + display: inline-flex; + align-items: center; + border: 1px solid var(--ua-color-border); + border-radius: var(--ua-radius-md); + background: var(--ua-color-bg); + transition: border-color 0.15s, box-shadow 0.15s; +} +.ua-input-group:focus-within { + border-color: var(--ua-color-accent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--ua-color-accent) 18%, transparent); +} +.ua-input-group .ua-input, +.ua-input-group .ua-textarea { + border: none; /* 组已带边框,内层去边防双线 */ + border-radius: 0; + min-width: 160px; + background: transparent; +} +.ua-input-group .ua-input:focus, +.ua-input-group .ua-textarea:focus { + box-shadow: none; /* 聚焦光环由组统一承载 */ + outline: none; +} +.ua-input-group-column { + flex-direction: column; + align-items: stretch; +} +.ua-input-addon { + display: inline-flex; + align-items: center; + padding: 6px 10px; + color: var(--ua-color-text-muted); + background: color-mix(in srgb, var(--ua-color-text) 5%, transparent); + font-size: var(--ua-font-size-md); + user-select: none; +}