Files
UI/packages/skin/ui/src/textField.ts
T

66 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { effect, h } from '@ui-arch/frame';
import { addonWrap } from './addons.js';
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 走响应式
if (props.value) {
const signal = props.value;
el.addEventListener('input', () => signal(el.value)); // 用户输入写回信号
effect(() => {
if (el.value !== signal()) el.value = signal(); // 外部变化写回输入框
});
}
if (props.onInput) el.addEventListener('input', () => props.onInput!(el.value));
let clearBtn: HTMLElement | null = null; // 一键清空按钮;clearable 关闭时不创建
if (props.clearable) {
clearBtn = h(
'button',
{
type: 'button',
class: 'ua-input-clear is-hidden', // 初始无值隐藏
'aria-label': '清空输入',
tabIndex: -1, // 不进 Tab 序,清空走鼠标/触屏
onClick: () => { el.value = ''; props.value?.(''); props.onInput?.(''); el.focus(); }, // 清空并同步信号与回调
},
'×',
);
const syncClear = (): void => { clearBtn!.classList.toggle('is-hidden', el.value === ''); }; // 有值才显示
el.addEventListener('input', syncClear); // 用户输入时同步
effect(() => { if (props.value) props.value(); syncClear(); }); // 外部写回时同步(读信号建依赖)
}
let counter: HTMLElement | null = null; // 字数统计块;maxCount 未传不创建
if (props.maxCount) {
el.setAttribute('maxlength', String(props.maxCount)); // 原生拦截超限输入
counter = h('span', { class: 'ua-input-count' });
const syncCount = (): void => { counter!.textContent = `${el.value.length}/${props.maxCount}`; }; // 实时 n/limit
el.addEventListener('input', syncCount); // 用户输入时同步
effect(() => { if (props.value) props.value(); syncCount(); }); // 初始与外部写回时同步(读信号建依赖)
}
let eyeBtn: HTMLElement | null = null; // 密码显隐按钮;password 未开不创建
if (props.password) {
el.type = 'password'; // 原生打点渲染,避免自绘遮罩的安全坑
let shown = false; // 当前是否明文
eyeBtn = h(
'button',
{
type: 'button',
class: 'ua-input-clear', // 复用清空按钮样式:同尺寸同位置,零新增 CSS
'aria-label': '显示密码',
onClick: () => {
shown = !shown;
el.type = shown ? 'text' : 'password';
eyeBtn!.textContent = shown ? '隐藏' : '显示'; // 文案即状态:按钮永远写「下一步动作」
eyeBtn!.setAttribute('aria-label', shown ? '隐藏密码' : '显示密码');
},
},
'显示',
);
}
const suffix = clearBtn || counter || eyeBtn
? h('span', { class: 'ua-input-clear-group' }, clearBtn, eyeBtn, counter, props.suffix ?? null) // 清空 / 显隐 / 计数并入后置附加组
: props.suffix;
return addonWrap(el, props.prefix, suffix);
}