iter(g335): 输入类组件补可访问名
This commit is contained in:
@@ -11,7 +11,7 @@ export function inputNumber(props: InputNumberProps): HTMLElement {
|
||||
const clamp = (v: number): number => Math.min(max, Math.max(min, v)); // 任何来源的值都夹回合法区间
|
||||
const quantize = (n: number): number => (props.precision === undefined ? n : Number(n.toFixed(props.precision))); // precision 归一(顺带消除步进浮点尾差)
|
||||
const emit = (v: number): void => { const n = quantize(clamp(v)); props.value(n); if (props.onChange) props.onChange(n); }; // 写回信号并回调
|
||||
const input = h('input', { class: `ua-input ua-input-number-field${props.size && props.size !== 'md' ? ` is-${props.size}` : ''}`, inputmode: 'decimal', disabled: props.disabled, 'aria-label': '数字输入' }) as HTMLInputElement;
|
||||
const input = h('input', { class: `ua-input ua-input-number-field${props.size && props.size !== 'md' ? ` is-${props.size}` : ''}`, inputmode: 'decimal', disabled: props.disabled, 'aria-label': props.ariaLabel ?? '数字输入' }) as HTMLInputElement;
|
||||
const sync = (): void => { const text = props.precision === undefined ? String(props.value()) : props.value().toFixed(props.precision); if (input.value !== text) input.value = text; }; // 外部变化写回输入框(precision 定长显示)
|
||||
effect(sync);
|
||||
input.addEventListener('change', () => { const n = parseFloat(input.value); if (Number.isNaN(n)) sync(); else emit(n); }); // 非法输入回滚当前值
|
||||
|
||||
@@ -13,8 +13,8 @@ export function slider(props: SliderProps): HTMLElement {
|
||||
const pct = (v: number): number => ((v - min) / (max - min || 1)) * 100; // 分母兜底,避免 min=max 时除零
|
||||
|
||||
/*** 建一条原生 range:区间模式靠 CSS 叠在同一格,只有拇指可命中 ***/
|
||||
const track = (sig: typeof value, onInput: (n: number) => void): HTMLInputElement => {
|
||||
const input = h('input', { type: 'range', class: 'ua-slider-input', min, max, step, disabled }) as HTMLInputElement;
|
||||
const track = (sig: typeof value, onInput: (n: number) => void, label: string): HTMLInputElement => {
|
||||
const input = h('input', { type: 'range', class: 'ua-slider-input', min, max, step, disabled, 'aria-label': label }) as HTMLInputElement; // 原生 range 不自带名字,读屏只念「滑块」
|
||||
input.addEventListener('input', () => onInput(Number(input.value)));
|
||||
effect(() => {
|
||||
const next = String(sig());
|
||||
@@ -30,7 +30,7 @@ export function slider(props: SliderProps): HTMLElement {
|
||||
: null;
|
||||
|
||||
if (!props.range) { // —— 单值:一条原生轨,行为与旧版完全一致 ——
|
||||
const input = track(value, (next) => { value(next); props.onChange?.(next); });
|
||||
const input = track(value, (next) => { value(next); props.onChange?.(next); }, props.ariaLabel ?? '数值');
|
||||
return h(
|
||||
'div',
|
||||
{ class: 'ua-slider' },
|
||||
@@ -42,8 +42,8 @@ export function slider(props: SliderProps): HTMLElement {
|
||||
// —— 区间:两条原生轨叠放,rail 打底、seg 画出两拇指之间的已选段 ——
|
||||
const rail = h('div', { class: 'ua-slider-rail', 'aria-hidden': 'true' });
|
||||
const seg = h('div', { class: 'ua-slider-seg', 'aria-hidden': 'true' }) as HTMLElement;
|
||||
const lowIn = track(value, (next) => { value(Math.min(next, high())); props.onRangeChange?.(value(), high()); }); // 下限越过上限就压平
|
||||
const highIn = track(high, (next) => { high(Math.max(next, value())); props.onRangeChange?.(value(), high()); }); // 上限低于下限就压平
|
||||
const lowIn = track(value, (next) => { value(Math.min(next, high())); props.onRangeChange?.(value(), high()); }, `${props.ariaLabel ?? '数值'}下限`); // 下限越过上限就压平;区间两端各有名字
|
||||
const highIn = track(high, (next) => { high(Math.max(next, value())); props.onRangeChange?.(value(), high()); }, `${props.ariaLabel ?? '数值'}上限`); // 上限低于下限就压平
|
||||
lowIn.classList.add('ua-slider-input-low'); // 供 CSS 区分上下层与拇指命中区
|
||||
highIn.classList.add('ua-slider-input-high');
|
||||
effect(() => { seg.style.left = `${pct(value())}%`; seg.style.width = `${pct(high()) - pct(value())}%`; }); // 已选段随两端重算
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { TextFieldProps } from './types.js';
|
||||
|
||||
/*** 文本输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调;prefix/suffix 包附加组,clearable 一键清空,password 密码显隐切换,maxCount 显示字数统计 ***/
|
||||
export function textField(props: TextFieldProps): HTMLElement {
|
||||
const el = h('input', { class: `ua-input${props.size && props.size !== 'md' ? ` is-${props.size}` : ''}`, placeholder: props.placeholder ?? '', disabled: props.disabled }) as HTMLInputElement; // false/undefined 移除属性,Signal 走响应式
|
||||
const el = h('input', { class: `ua-input${props.size && props.size !== 'md' ? ` is-${props.size}` : ''}`, placeholder: props.placeholder ?? '', disabled: props.disabled, 'aria-label': props.ariaLabel ?? '文本输入' }) as HTMLInputElement; // false/undefined 移除属性,Signal 走响应式
|
||||
if (props.value) {
|
||||
const signal = props.value;
|
||||
el.addEventListener('input', () => signal(el.value)); // 用户输入写回信号
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { TextareaProps } from './types.js';
|
||||
|
||||
/*** 多行输入:value 信号双向绑定,disabled 支持布尔或信号禁用,onInput 为输入回调;prefix/suffix 包纵向附加组,maxCount 显示字数统计 ***/
|
||||
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 走响应式
|
||||
const el = h('textarea', { class: 'ua-textarea', rows: props.rows ?? 3, placeholder: props.placeholder ?? '', disabled: props.disabled, 'aria-label': props.ariaLabel ?? '多行文本' }) as HTMLTextAreaElement; // false/undefined 移除属性,Signal 走响应式
|
||||
if (props.value) {
|
||||
const signal = props.value;
|
||||
el.addEventListener('input', () => signal(el.value)); // 用户输入写回信号
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface ButtonProps {
|
||||
|
||||
/*** 文本输入入参 ***/
|
||||
export interface TextFieldProps {
|
||||
ariaLabel?: string; // 读屏用的字段名(如「收件人」);缺省「文本输入」
|
||||
size?: 'sm' | 'md' | 'lg'; // 高度档位:走 --ua-control-h-* 三档;缺省 md
|
||||
placeholder?: string; // 占位文案
|
||||
value?: Signal<string>; // 双向绑定的信号
|
||||
@@ -189,6 +190,7 @@ export interface AccordionProps {
|
||||
|
||||
/*** 滑块入参 ***/
|
||||
export interface SliderProps {
|
||||
ariaLabel?: string; // 读屏用的字段名(如「音量」);缺省「数值」,区间模式自动带上限/下限
|
||||
value: Signal<number>; // 当前值信号,双向绑定(拖动写回、外部赋值同步)
|
||||
min?: number; // 最小值;缺省 0
|
||||
max?: number; // 最大值;缺省 100
|
||||
@@ -204,6 +206,7 @@ export interface SliderProps {
|
||||
|
||||
/*** 多行输入入参 ***/
|
||||
export interface TextareaProps {
|
||||
ariaLabel?: string; // 读屏用的字段名(如「备注」);缺省「多行文本」
|
||||
placeholder?: string; // 占位文案
|
||||
rows?: number; // 行数;缺省 3
|
||||
value?: Signal<string>; // 双向绑定的信号
|
||||
@@ -217,6 +220,7 @@ export interface TextareaProps {
|
||||
|
||||
/*** 数字输入入参 ***/
|
||||
export interface InputNumberProps {
|
||||
ariaLabel?: string; // 读屏用的字段名(如「数量」);缺省「数字输入」
|
||||
size?: 'sm' | 'md' | 'lg'; // 高度档位:走 --ua-control-h-* 三档;缺省 md
|
||||
value: Signal<number>; // 当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)
|
||||
min?: number; // 最小值;缺省无下限
|
||||
|
||||
@@ -315,6 +315,12 @@
|
||||
"t": "boolean",
|
||||
"d": "密码框:原生打点渲染,内置「显示/隐藏」切换按钮(aria-label 同步);缺省普通文本"
|
||||
}
|
||||
,
|
||||
{
|
||||
"n": "ariaLabel",
|
||||
"t": "string",
|
||||
"d": "无可见 label 时的可访问名(如「收件人」);缺省「文本输入」"
|
||||
}
|
||||
],
|
||||
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
|
||||
"steps": [
|
||||
@@ -383,6 +389,12 @@
|
||||
"t": "'sm' | 'md' | 'lg'",
|
||||
"d": "高度档位:sm 紧凑 / md 默认 / lg 大触控目标,走 --ua-control-h-* 三档;缺省 md"
|
||||
}
|
||||
,
|
||||
{
|
||||
"n": "ariaLabel",
|
||||
"t": "string",
|
||||
"d": "无可见 label 时的可访问名(如「数量」);缺省「数字输入」"
|
||||
}
|
||||
],
|
||||
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
||||
"steps": [
|
||||
@@ -445,6 +457,12 @@
|
||||
"t": "boolean | { minRows?, maxRows? }",
|
||||
"d": "自适应内容高度:true 跟随内容涨落,对象态钳最小/最大行数,超出 maxRows 内部滚动;缺省固定行数"
|
||||
}
|
||||
,
|
||||
{
|
||||
"n": "ariaLabel",
|
||||
"t": "string",
|
||||
"d": "无可见 label 时的可访问名(如「备注」);缺省「多行文本」"
|
||||
}
|
||||
],
|
||||
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
|
||||
"steps": [
|
||||
@@ -522,6 +540,12 @@
|
||||
"k": 1,
|
||||
"d": "拖动回调"
|
||||
}
|
||||
,
|
||||
{
|
||||
"n": "ariaLabel",
|
||||
"t": "string",
|
||||
"d": "无可见 label 时的可访问名(如「音量」);缺省「数值」,区间模式自动带「下限 / 上限」"
|
||||
}
|
||||
],
|
||||
"example": "const vol = signal(40); slider({ value: vol, min: 0, max: 100, step: 5, onChange: (v) => console.log(v) })",
|
||||
"steps": [
|
||||
|
||||
Reference in New Issue
Block a user