@qw-ui/core 0.3.0-next.12 → 0.3.0-next.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -1
- package/dist/search-bar/index.js +1 -1
- package/dist/search-bar/index.js.map +1 -1
- package/dist/search-bar/search-bar.d.ts +2 -0
- package/dist/stepper/index.d.ts +1 -0
- package/dist/stepper/index.js +113 -0
- package/dist/stepper/index.js.map +1 -0
- package/dist/stepper/stepper.d.ts +121 -0
- package/dist/textarea/index.js +2 -2
- package/dist/textarea/index.js.map +1 -1
- package/dist/textarea/textarea.d.ts +4 -0
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ index.ts + __test__/ + docs/{overview.md, accessibility.md, demos.ts, visual.ts}
|
|
|
50
50
|
| `latest`(稳定) | `pnpm add @qw-ui/core` | `main` 通道,Release PR review 后发布 |
|
|
51
51
|
| `next`(预发布) | `pnpm add @qw-ui/core@next` | `release/next` 通道,高频自增 `x.y.z-next.N` |
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
9 个公开包 `fixed` 锁步同版本号。npm 发布固定走 `registry.npmjs.org`;内网装依赖走 `registry.anpm.alibaba-inc.com`(tnpm 代理),发布 registry 不要指向内网。流程详见 [development-workflow.md](../../docs/development-workflow.md)。
|
|
54
54
|
|
|
55
55
|
## 仓库 & License
|
|
56
56
|
|
package/dist/index.d.ts
CHANGED
|
@@ -30,3 +30,5 @@ export { createInputProps } from "./input/index.js";
|
|
|
30
30
|
export type { QwInputProps, InputApi, InputMode, InputReadOnlyOverflow } from "./input/index.js";
|
|
31
31
|
export { SWITCH_PARTS, getSwitchRootProps, getSwitchThumbProps, resolveSwitchState } from "./switch/index.js";
|
|
32
32
|
export type { SwitchPart, QwSwitchProps, ResolvedSwitchState, SwitchRootProps, SwitchThumbProps } from "./switch/index.js";
|
|
33
|
+
export { STEPPER_SIZES, STEPPER_PARTS, clampStepperValue, getStepperRootProps, getStepperDecrementProps, getStepperDecrementValue, getStepperIncrementProps, getStepperIncrementValue, getStepperValueProps, resolveStepperState, } from "./stepper/index.js";
|
|
34
|
+
export type { StepperPart, StepperSize, QwStepperProps, ResolvedStepperState, StepperRootProps, StepperDecrementProps, StepperIncrementProps, StepperValueProps, } from "./stepper/index.js";
|
package/dist/index.js
CHANGED
|
@@ -14,4 +14,5 @@ import { connect as connect$1, createTextAreaProps, machine } from "./textarea/i
|
|
|
14
14
|
import { TAG_PARTS, TAG_SIZES, TAG_VARIANTS } from "./tag/index.js";
|
|
15
15
|
import { createInputProps } from "./input/index.js";
|
|
16
16
|
import { SWITCH_PARTS, getSwitchRootProps, getSwitchThumbProps, resolveSwitchState } from "./switch/index.js";
|
|
17
|
-
|
|
17
|
+
import { STEPPER_PARTS, STEPPER_SIZES, clampStepperValue, getStepperDecrementProps, getStepperDecrementValue, getStepperIncrementProps, getStepperIncrementValue, getStepperRootProps, getStepperValueProps, resolveStepperState } from "./stepper/index.js";
|
|
18
|
+
export { AVATAR_BADGE_TYPES, AVATAR_LOAD_STATUS, AVATAR_PARTS, AVATAR_SHAPES, AVATAR_SIZES, BADGE_COUNT_MAX, BUTTON_ICON_POSITIONS, BUTTON_PARTS, BUTTON_SHAPES, BUTTON_SIZES, BUTTON_TONES, BUTTON_VARIANTS, DEFAULT_OVERLAY_PANEL_HEADER_HEIGHT, DEFAULT_TOAST_DURATION, DEFAULT_TOAST_POSITION, DIVIDER_ORIENTATIONS, DIVIDER_PARTS, DIVIDER_WEIGHTS, EMPTY_PARTS, ICON_FALLBACK_NAME, ICON_PARTS, LOADING_SIZES, OVERLAY_PANEL_PARTS, SKELETON_ANIMATIONS, SKELETON_PARTS, SKELETON_RADII, SKELETON_VARIANTS, STEPPER_PARTS, STEPPER_SIZES, SWITCH_PARTS, TAG_PARTS, TAG_SIZES, TAG_VARIANTS, TOAST_ICON_TYPES, TOAST_PARTS, TOAST_POSITIONS, TOPBAR_VARIANTS, clampStepperValue, connect, createDialogProps, createInputProps, createTabsProps, createTextAreaProps, createTopBarProps, dialogMachine, extractInitials, getAvatarBadgeProps, getAvatarFallbackProps, getAvatarImageElementProps, getAvatarImageProps, getAvatarRootProps, getButtonRootProps, getStepperDecrementProps, getStepperDecrementValue, getStepperIncrementProps, getStepperIncrementValue, getStepperRootProps, getStepperValueProps, getSwitchRootProps, getSwitchThumbProps, isToastIconType, resolveAvatarState, resolveButtonState, resolveIconName, resolveOverlayPanelHeight, resolveStepperState, resolveSwitchState, resolveToastDuration, tabsMachine, connect$1 as textAreaConnect, machine as textAreaMachine };
|
package/dist/search-bar/index.js
CHANGED
|
@@ -195,7 +195,7 @@ function connect(service, normalizeProps) {
|
|
|
195
195
|
"data-part": "clear-button",
|
|
196
196
|
type: "button",
|
|
197
197
|
disabled: disabled || readOnly,
|
|
198
|
-
"aria-label": "清除搜索内容",
|
|
198
|
+
"aria-label": prop("clearLabel") ?? "清除搜索内容",
|
|
199
199
|
"aria-hidden": !showClear,
|
|
200
200
|
hidden: !showClear,
|
|
201
201
|
onClick() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/search-bar/search-bar.ts"],"sourcesContent":["import { createMachine } from \"@zag-js/core\";\nimport type { MachineSchema, Service } from \"@zag-js/core\";\nimport type { PropTypes, NormalizeProps } from \"@zag-js/types\";\nimport type { JSX } from \"@zag-js/types\";\nimport { compact } from \"@zag-js/utils\";\n\n/**\n * 清除按钮显示策略。\n */\nexport type SearchBarClearable = boolean | { showWhen?: \"always\" | \"focus\" | \"non-empty\" };\n\n/**\n * 内部归一化后的显示策略(包含 `never`,表示 `clearable={false}`)。\n */\nexport type SearchBarClearableMode = \"always\" | \"focus\" | \"non-empty\" | \"never\";\n\nexport interface SearchBarClearableConfig {\n showWhen: SearchBarClearableMode;\n}\n\n/**\n * 右侧 action 按钮角色。\n */\nexport type SearchBarActionType = \"cancel\" | \"search\";\n\n/**\n * 右侧 action 按钮配置。\n */\nexport interface SearchBarAction {\n /** 按钮角色。 */\n type: SearchBarActionType;\n /** 展示内容:文案或自定义节点。 */\n content: string | unknown;\n /** 取消时是否清空输入。 */\n clearOnCancel?: boolean;\n /** 按钮可访问名称;当 content 为纯图标时必填。 */\n ariaLabel?: string;\n}\n\n/**\n * SearchBar 公共 props 契约(双栈共享)。\n */\nexport interface QwSearchBarProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。 */\n defaultValue?: string;\n /** 占位符。 */\n placeholder?: string;\n /** 最大字符数。 */\n maxLength?: number;\n /** 禁用态。 */\n disabled?: boolean;\n /** 只读态。 */\n readOnly?: boolean;\n /** 清除按钮配置。 */\n clearable?: SearchBarClearable;\n /** 左侧搜索图标:字符串文案或自定义节点。 */\n searchIcon?: string | unknown;\n /** 左侧按钮配置。 */\n prefix?: SearchBarAction;\n /** 右侧按钮配置。 */\n action?: SearchBarAction;\n /** 输入变化回调。 */\n onChange?: (value: string) => void;\n /** 搜索确认回调(Enter 或点击搜索按钮)。 */\n onSearch?: (value: string) => void;\n /** 清空回调。 */\n onClear?: () => void;\n /** 取消回调。 */\n onCancel?: () => void;\n /** 聚焦回调。 */\n onFocus?: () => void;\n /** 失焦回调。 */\n onBlur?: () => void;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface SearchBarSchema extends MachineSchema {\n props: QwSearchBarProps;\n context: {\n value: string;\n focused: boolean;\n };\n computed: {\n showClear: boolean;\n clearableConfig: SearchBarClearableConfig;\n };\n state: \"idle\";\n event:\n | { type: \"VALUE.SET\"; value: string; src?: string }\n | { type: \"VALUE.CLEAR\" }\n | { type: \"FOCUS.SET\"; focused: boolean }\n | { type: \"SEARCH\" }\n | { type: \"CANCEL\"; src?: \"prefix\" | \"action\" };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnSearch\"\n | \"invokeOnClear\"\n | \"invokeOnCancel\"\n | \"invokeOnFocus\"\n | \"invokeOnBlur\";\n}\n\n/**\n * 解析 clearable 配置为统一对象。\n */\nfunction normalizeClearable(clearable?: SearchBarClearable): SearchBarClearableConfig {\n if (clearable === false) return { showWhen: \"never\" };\n if (clearable === true || clearable == null) return { showWhen: \"non-empty\" };\n return { showWhen: clearable.showWhen ?? \"non-empty\" };\n}\n\n/**\n * SearchBar zag.js machine。\n * 所有交互逻辑唯一来源:value、focus、clear、action、keyboard。\n */\nexport const machine = createMachine<SearchBarSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n disabled: false,\n readOnly: false,\n ...props,\n };\n },\n\n initialState() {\n return \"idle\";\n },\n\n context({ bindable, prop }) {\n return {\n value: bindable<string>(() => {\n const controlledValue = prop(\"value\");\n return {\n defaultValue: prop(\"defaultValue\") ?? \"\",\n sync: true,\n ...(controlledValue !== undefined ? { value: controlledValue } : {}),\n onChange(value, prev) {\n if (value !== prev) {\n prop(\"onChange\")?.(value);\n }\n },\n };\n }),\n focused: bindable(() => ({\n defaultValue: false,\n })),\n };\n },\n\n computed: {\n clearableConfig({ prop }) {\n return normalizeClearable(prop(\"clearable\"));\n },\n showClear({ context, computed }) {\n const cfg = computed(\"clearableConfig\");\n if (cfg.showWhen === \"always\") return true;\n if (cfg.showWhen === \"never\") return false;\n if (cfg.showWhen === \"focus\") return context.get(\"focused\");\n return context.get(\"value\").length > 0;\n },\n },\n\n watch({ track, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n context.set(\"value\", external);\n }\n });\n },\n\n states: {\n idle: {\n on: {\n \"VALUE.SET\": {\n actions: [\"setValue\"],\n },\n \"VALUE.CLEAR\": {\n actions: [\"clearValue\", \"invokeOnClear\"],\n },\n \"FOCUS.SET\": {\n actions: [\"setFocused\"],\n },\n SEARCH: {\n actions: [\"invokeOnSearch\"],\n },\n CANCEL: {\n actions: [\"invokeOnCancel\"],\n },\n },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event, prop }) {\n if (event.type === \"VALUE.SET\") {\n const maxLength = prop(\"maxLength\") ?? 0;\n const current = context.get(\"value\");\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n // 达到 maxLength 后阻止继续输入,保留旧值\n next = current;\n }\n context.set(\"value\", next);\n }\n },\n clearValue({ context }) {\n context.set(\"value\", \"\");\n },\n setFocused({ context, event, prop }) {\n if (event.type === \"FOCUS.SET\") {\n const next = event.focused;\n const prev = context.get(\"focused\");\n context.set(\"focused\", next);\n if (next && !prev) prop(\"onFocus\")?.();\n if (!next && prev) prop(\"onBlur\")?.();\n }\n },\n invokeOnSearch({ context, prop }) {\n prop(\"onSearch\")?.(context.get(\"value\"));\n },\n invokeOnClear({ prop }) {\n prop(\"onClear\")?.();\n },\n invokeOnCancel({ context, prop, event }) {\n const config = event.src === \"prefix\" ? prop(\"prefix\") : prop(\"action\");\n if (config?.type === \"cancel\" && config.clearOnCancel && !prop(\"readOnly\")) {\n context.set(\"value\", \"\");\n }\n prop(\"onCancel\")?.();\n },\n invokeOnFocus() {\n // focus 回调在 setFocused 中已触发\n },\n invokeOnBlur() {\n // blur 回调在 setFocused 中已触发\n },\n },\n },\n});\n\n/**\n * SearchBar api 类型(由 connect 返回)。\n */\nexport interface SearchBarApi<T extends PropTypes = PropTypes> {\n /** 当前输入值。 */\n value: string;\n /** 当前是否聚焦。 */\n focused: boolean;\n /** 是否显示清除按钮。 */\n showClear: boolean;\n /** 清空输入。 */\n clear(): void;\n /** 触发搜索。 */\n search(): void;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** 输入区容器 prop-getter(包含图标、输入框、清除按钮)。 */\n getFieldProps(): T[\"element\"];\n /** 输入框 prop-getter。 */\n getInputProps(): T[\"input\"];\n /** 左侧图标 prop-getter。 */\n getIconProps(): T[\"element\"];\n /** 清除按钮 prop-getter。 */\n getClearButtonProps(): T[\"button\"];\n /** 左侧 prefix 按钮 prop-getter。 */\n getPrefixButtonProps(): T[\"button\"];\n /** 右侧 action 按钮 prop-getter。 */\n getActionButtonProps(): T[\"button\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps)。\n */\nexport function connect<T extends PropTypes>(service: Service<SearchBarSchema>, normalizeProps: NormalizeProps<T>): SearchBarApi<T> {\n const { context, computed, prop, send } = service;\n\n const value = context.get(\"value\");\n const focused = context.get(\"focused\");\n const showClear = computed(\"showClear\");\n const disabled = !!prop(\"disabled\");\n const readOnly = !!prop(\"readOnly\");\n const maxLength = prop(\"maxLength\");\n const prefix = prop(\"prefix\");\n const action = prop(\"action\");\n\n return {\n value,\n focused,\n showClear,\n\n clear() {\n send({ type: \"VALUE.CLEAR\" });\n },\n\n search() {\n send({ type: \"SEARCH\" });\n },\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"root\",\n // readOnly/disabled 时输入区仍可聚焦(readOnly HTML 语义),但不应呈现聚焦视觉态\n \"data-focused\": focused && !readOnly && !disabled ? \"true\" : undefined,\n \"data-disabled\": disabled ? \"true\" : undefined,\n \"data-readonly\": readOnly ? \"true\" : undefined,\n }),\n );\n },\n\n getFieldProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"field\",\n }),\n );\n },\n\n getInputProps() {\n return normalizeProps.input(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"input\",\n type: \"search\" as const,\n role: \"searchbox\" as const,\n value,\n placeholder: prop(\"placeholder\"),\n disabled,\n readOnly,\n maxLength: maxLength && maxLength > 0 ? maxLength : undefined,\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"aria-readonly\": readOnly ? (\"true\" as const) : undefined,\n onFocus() {\n send({ type: \"FOCUS.SET\", focused: true });\n },\n onBlur() {\n send({ type: \"FOCUS.SET\", focused: false });\n },\n onChange(event: JSX.ChangeEvent<HTMLInputElement>) {\n const target = event.target as HTMLInputElement;\n const nativeEvent = (event as { nativeEvent?: { isComposing?: boolean } }).nativeEvent;\n const isComposing = !!nativeEvent?.isComposing;\n let next = target.value;\n // IME composing 期间跳过 maxLength 截断,允许拼音中间态值流过\n if (!isComposing) {\n const maxLen = prop(\"maxLength\") ?? 0;\n if (maxLen > 0 && next.length > maxLen) {\n next = next.slice(0, maxLen);\n }\n }\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", next);\n },\n onKeyDown(event: JSX.KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\") {\n send({ type: \"SEARCH\" });\n }\n },\n }),\n );\n },\n\n getIconProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"icon\",\n \"aria-hidden\": \"true\" as const,\n }),\n );\n },\n\n getClearButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"clear-button\",\n type: \"button\" as const,\n disabled: disabled || readOnly,\n \"aria-label\": \"清除搜索内容\" as const,\n \"aria-hidden\": !showClear,\n hidden: !showClear,\n onClick() {\n send({ type: \"VALUE.CLEAR\" });\n },\n }),\n );\n },\n\n getPrefixButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"prefix-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": prefix?.ariaLabel,\n \"data-action\": prefix?.type,\n onClick() {\n if (prefix?.type === \"cancel\") {\n send({ type: \"CANCEL\", src: \"prefix\" });\n } else if (prefix?.type === \"search\") {\n send({ type: \"SEARCH\" });\n }\n },\n }),\n );\n },\n\n getActionButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"action-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": action?.ariaLabel,\n \"data-action\": action?.type,\n onClick() {\n if (action?.type === \"cancel\") {\n send({ type: \"CANCEL\", src: \"action\" });\n } else if (action?.type === \"search\") {\n send({ type: \"SEARCH\" });\n }\n },\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwSearchBarProps 转成 machine context(默认值收口)。\n */\nexport function createSearchBarProps(props: QwSearchBarProps = {}): Partial<QwSearchBarProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n disabled: props.disabled ?? false,\n readOnly: props.readOnly ?? false,\n ...props,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/search-bar/search-bar.ts"],"sourcesContent":["import { createMachine } from \"@zag-js/core\";\nimport type { MachineSchema, Service } from \"@zag-js/core\";\nimport type { PropTypes, NormalizeProps } from \"@zag-js/types\";\nimport type { JSX } from \"@zag-js/types\";\nimport { compact } from \"@zag-js/utils\";\n\n/**\n * 清除按钮显示策略。\n */\nexport type SearchBarClearable = boolean | { showWhen?: \"always\" | \"focus\" | \"non-empty\" };\n\n/**\n * 内部归一化后的显示策略(包含 `never`,表示 `clearable={false}`)。\n */\nexport type SearchBarClearableMode = \"always\" | \"focus\" | \"non-empty\" | \"never\";\n\nexport interface SearchBarClearableConfig {\n showWhen: SearchBarClearableMode;\n}\n\n/**\n * 右侧 action 按钮角色。\n */\nexport type SearchBarActionType = \"cancel\" | \"search\";\n\n/**\n * 右侧 action 按钮配置。\n */\nexport interface SearchBarAction {\n /** 按钮角色。 */\n type: SearchBarActionType;\n /** 展示内容:文案或自定义节点。 */\n content: string | unknown;\n /** 取消时是否清空输入。 */\n clearOnCancel?: boolean;\n /** 按钮可访问名称;当 content 为纯图标时必填。 */\n ariaLabel?: string;\n}\n\n/**\n * SearchBar 公共 props 契约(双栈共享)。\n */\nexport interface QwSearchBarProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。 */\n defaultValue?: string;\n /** 占位符。 */\n placeholder?: string;\n /** 最大字符数。 */\n maxLength?: number;\n /** 禁用态。 */\n disabled?: boolean;\n /** 只读态。 */\n readOnly?: boolean;\n /** 清除按钮配置。 */\n clearable?: SearchBarClearable;\n /** 清除按钮 aria-label,默认「清除搜索内容」。 */\n clearLabel?: string;\n /** 左侧搜索图标:字符串文案或自定义节点。 */\n searchIcon?: string | unknown;\n /** 左侧按钮配置。 */\n prefix?: SearchBarAction;\n /** 右侧按钮配置。 */\n action?: SearchBarAction;\n /** 输入变化回调。 */\n onChange?: (value: string) => void;\n /** 搜索确认回调(Enter 或点击搜索按钮)。 */\n onSearch?: (value: string) => void;\n /** 清空回调。 */\n onClear?: () => void;\n /** 取消回调。 */\n onCancel?: () => void;\n /** 聚焦回调。 */\n onFocus?: () => void;\n /** 失焦回调。 */\n onBlur?: () => void;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface SearchBarSchema extends MachineSchema {\n props: QwSearchBarProps;\n context: {\n value: string;\n focused: boolean;\n };\n computed: {\n showClear: boolean;\n clearableConfig: SearchBarClearableConfig;\n };\n state: \"idle\";\n event:\n | { type: \"VALUE.SET\"; value: string; src?: string }\n | { type: \"VALUE.CLEAR\" }\n | { type: \"FOCUS.SET\"; focused: boolean }\n | { type: \"SEARCH\" }\n | { type: \"CANCEL\"; src?: \"prefix\" | \"action\" };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnSearch\"\n | \"invokeOnClear\"\n | \"invokeOnCancel\"\n | \"invokeOnFocus\"\n | \"invokeOnBlur\";\n}\n\n/**\n * 解析 clearable 配置为统一对象。\n */\nfunction normalizeClearable(clearable?: SearchBarClearable): SearchBarClearableConfig {\n if (clearable === false) return { showWhen: \"never\" };\n if (clearable === true || clearable == null) return { showWhen: \"non-empty\" };\n return { showWhen: clearable.showWhen ?? \"non-empty\" };\n}\n\n/**\n * SearchBar zag.js machine。\n * 所有交互逻辑唯一来源:value、focus、clear、action、keyboard。\n */\nexport const machine = createMachine<SearchBarSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n disabled: false,\n readOnly: false,\n ...props,\n };\n },\n\n initialState() {\n return \"idle\";\n },\n\n context({ bindable, prop }) {\n return {\n value: bindable<string>(() => {\n const controlledValue = prop(\"value\");\n return {\n defaultValue: prop(\"defaultValue\") ?? \"\",\n sync: true,\n ...(controlledValue !== undefined ? { value: controlledValue } : {}),\n onChange(value, prev) {\n if (value !== prev) {\n prop(\"onChange\")?.(value);\n }\n },\n };\n }),\n focused: bindable(() => ({\n defaultValue: false,\n })),\n };\n },\n\n computed: {\n clearableConfig({ prop }) {\n return normalizeClearable(prop(\"clearable\"));\n },\n showClear({ context, computed }) {\n const cfg = computed(\"clearableConfig\");\n if (cfg.showWhen === \"always\") return true;\n if (cfg.showWhen === \"never\") return false;\n if (cfg.showWhen === \"focus\") return context.get(\"focused\");\n return context.get(\"value\").length > 0;\n },\n },\n\n watch({ track, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n context.set(\"value\", external);\n }\n });\n },\n\n states: {\n idle: {\n on: {\n \"VALUE.SET\": {\n actions: [\"setValue\"],\n },\n \"VALUE.CLEAR\": {\n actions: [\"clearValue\", \"invokeOnClear\"],\n },\n \"FOCUS.SET\": {\n actions: [\"setFocused\"],\n },\n SEARCH: {\n actions: [\"invokeOnSearch\"],\n },\n CANCEL: {\n actions: [\"invokeOnCancel\"],\n },\n },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event, prop }) {\n if (event.type === \"VALUE.SET\") {\n const maxLength = prop(\"maxLength\") ?? 0;\n const current = context.get(\"value\");\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n // 达到 maxLength 后阻止继续输入,保留旧值\n next = current;\n }\n context.set(\"value\", next);\n }\n },\n clearValue({ context }) {\n context.set(\"value\", \"\");\n },\n setFocused({ context, event, prop }) {\n if (event.type === \"FOCUS.SET\") {\n const next = event.focused;\n const prev = context.get(\"focused\");\n context.set(\"focused\", next);\n if (next && !prev) prop(\"onFocus\")?.();\n if (!next && prev) prop(\"onBlur\")?.();\n }\n },\n invokeOnSearch({ context, prop }) {\n prop(\"onSearch\")?.(context.get(\"value\"));\n },\n invokeOnClear({ prop }) {\n prop(\"onClear\")?.();\n },\n invokeOnCancel({ context, prop, event }) {\n const config = event.src === \"prefix\" ? prop(\"prefix\") : prop(\"action\");\n if (config?.type === \"cancel\" && config.clearOnCancel && !prop(\"readOnly\")) {\n context.set(\"value\", \"\");\n }\n prop(\"onCancel\")?.();\n },\n invokeOnFocus() {\n // focus 回调在 setFocused 中已触发\n },\n invokeOnBlur() {\n // blur 回调在 setFocused 中已触发\n },\n },\n },\n});\n\n/**\n * SearchBar api 类型(由 connect 返回)。\n */\nexport interface SearchBarApi<T extends PropTypes = PropTypes> {\n /** 当前输入值。 */\n value: string;\n /** 当前是否聚焦。 */\n focused: boolean;\n /** 是否显示清除按钮。 */\n showClear: boolean;\n /** 清空输入。 */\n clear(): void;\n /** 触发搜索。 */\n search(): void;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** 输入区容器 prop-getter(包含图标、输入框、清除按钮)。 */\n getFieldProps(): T[\"element\"];\n /** 输入框 prop-getter。 */\n getInputProps(): T[\"input\"];\n /** 左侧图标 prop-getter。 */\n getIconProps(): T[\"element\"];\n /** 清除按钮 prop-getter。 */\n getClearButtonProps(): T[\"button\"];\n /** 左侧 prefix 按钮 prop-getter。 */\n getPrefixButtonProps(): T[\"button\"];\n /** 右侧 action 按钮 prop-getter。 */\n getActionButtonProps(): T[\"button\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps)。\n */\nexport function connect<T extends PropTypes>(service: Service<SearchBarSchema>, normalizeProps: NormalizeProps<T>): SearchBarApi<T> {\n const { context, computed, prop, send } = service;\n\n const value = context.get(\"value\");\n const focused = context.get(\"focused\");\n const showClear = computed(\"showClear\");\n const disabled = !!prop(\"disabled\");\n const readOnly = !!prop(\"readOnly\");\n const maxLength = prop(\"maxLength\");\n const prefix = prop(\"prefix\");\n const action = prop(\"action\");\n\n return {\n value,\n focused,\n showClear,\n\n clear() {\n send({ type: \"VALUE.CLEAR\" });\n },\n\n search() {\n send({ type: \"SEARCH\" });\n },\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"root\",\n // readOnly/disabled 时输入区仍可聚焦(readOnly HTML 语义),但不应呈现聚焦视觉态\n \"data-focused\": focused && !readOnly && !disabled ? \"true\" : undefined,\n \"data-disabled\": disabled ? \"true\" : undefined,\n \"data-readonly\": readOnly ? \"true\" : undefined,\n }),\n );\n },\n\n getFieldProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"field\",\n }),\n );\n },\n\n getInputProps() {\n return normalizeProps.input(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"input\",\n type: \"search\" as const,\n role: \"searchbox\" as const,\n value,\n placeholder: prop(\"placeholder\"),\n disabled,\n readOnly,\n maxLength: maxLength && maxLength > 0 ? maxLength : undefined,\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"aria-readonly\": readOnly ? (\"true\" as const) : undefined,\n onFocus() {\n send({ type: \"FOCUS.SET\", focused: true });\n },\n onBlur() {\n send({ type: \"FOCUS.SET\", focused: false });\n },\n onChange(event: JSX.ChangeEvent<HTMLInputElement>) {\n const target = event.target as HTMLInputElement;\n const nativeEvent = (event as { nativeEvent?: { isComposing?: boolean } }).nativeEvent;\n const isComposing = !!nativeEvent?.isComposing;\n let next = target.value;\n // IME composing 期间跳过 maxLength 截断,允许拼音中间态值流过\n if (!isComposing) {\n const maxLen = prop(\"maxLength\") ?? 0;\n if (maxLen > 0 && next.length > maxLen) {\n next = next.slice(0, maxLen);\n }\n }\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", next);\n },\n onKeyDown(event: JSX.KeyboardEvent<HTMLInputElement>) {\n if (event.key === \"Enter\") {\n send({ type: \"SEARCH\" });\n }\n },\n }),\n );\n },\n\n getIconProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"icon\",\n \"aria-hidden\": \"true\" as const,\n }),\n );\n },\n\n getClearButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"clear-button\",\n type: \"button\" as const,\n disabled: disabled || readOnly,\n \"aria-label\": prop(\"clearLabel\") ?? \"清除搜索内容\",\n \"aria-hidden\": !showClear,\n hidden: !showClear,\n onClick() {\n send({ type: \"VALUE.CLEAR\" });\n },\n }),\n );\n },\n\n getPrefixButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"prefix-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": prefix?.ariaLabel,\n \"data-action\": prefix?.type,\n onClick() {\n if (prefix?.type === \"cancel\") {\n send({ type: \"CANCEL\", src: \"prefix\" });\n } else if (prefix?.type === \"search\") {\n send({ type: \"SEARCH\" });\n }\n },\n }),\n );\n },\n\n getActionButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"search-bar\",\n \"data-part\": \"action-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": action?.ariaLabel,\n \"data-action\": action?.type,\n onClick() {\n if (action?.type === \"cancel\") {\n send({ type: \"CANCEL\", src: \"action\" });\n } else if (action?.type === \"search\") {\n send({ type: \"SEARCH\" });\n }\n },\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwSearchBarProps 转成 machine context(默认值收口)。\n */\nexport function createSearchBarProps(props: QwSearchBarProps = {}): Partial<QwSearchBarProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n disabled: props.disabled ?? false,\n readOnly: props.readOnly ?? false,\n ...props,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./stepper.js";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//#region src/stepper/stepper.ts
|
|
2
|
+
/**
|
|
3
|
+
* Stepper 加减(数量步进器)的跨框架行为契约。
|
|
4
|
+
*
|
|
5
|
+
* Stepper 是原型 A(无状态 Core 状态机):交互由原生 `<button>` 承担,
|
|
6
|
+
* core 只负责数值归一化、边界 clamp、按钮可用性计算与 prop-getter 生成。
|
|
7
|
+
*/
|
|
8
|
+
/** 尺寸全集:s = 15×15px,m = 18×18px。 */
|
|
9
|
+
const STEPPER_SIZES = ["s", "m"];
|
|
10
|
+
/** 形态变体全集:default = 裸按钮组合,bordered = 带边框卡片形态。 */
|
|
11
|
+
const STEPPER_VARIANTS = ["default", "bordered"];
|
|
12
|
+
/** 渲染部位全集(data-part 取值)。 */
|
|
13
|
+
const STEPPER_PARTS = [
|
|
14
|
+
"root",
|
|
15
|
+
"decrement",
|
|
16
|
+
"value",
|
|
17
|
+
"increment"
|
|
18
|
+
];
|
|
19
|
+
/** 将数值约束在 [min, max] 范围内;边界未定义时跳过对应侧。 */
|
|
20
|
+
function clampStepperValue(value, min, max) {
|
|
21
|
+
let result = value;
|
|
22
|
+
if (typeof min === "number" && !Number.isNaN(min)) result = Math.max(result, min);
|
|
23
|
+
if (typeof max === "number" && !Number.isNaN(max)) result = Math.min(result, max);
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
/** 规范化 step:仅接受正数,否则回退到 1。 */
|
|
27
|
+
function normalizeStepperStep(step) {
|
|
28
|
+
return typeof step === "number" && step > 0 && !Number.isNaN(step) ? step : 1;
|
|
29
|
+
}
|
|
30
|
+
/** 规范化 min:未指定时按规格默认 0(不做负数默认值)。 */
|
|
31
|
+
function normalizeStepperMin(min) {
|
|
32
|
+
return typeof min === "number" && !Number.isNaN(min) ? min : 0;
|
|
33
|
+
}
|
|
34
|
+
/** 将公开输入归一化为唯一的跨框架状态。 */
|
|
35
|
+
function resolveStepperState(props = {}) {
|
|
36
|
+
const min = normalizeStepperMin(props.min);
|
|
37
|
+
const max = props.max;
|
|
38
|
+
const step = normalizeStepperStep(props.step);
|
|
39
|
+
const disabled = props.disabled ?? false;
|
|
40
|
+
const variant = props.variant ?? "default";
|
|
41
|
+
const value = clampStepperValue(props.value !== void 0 ? props.value : props.defaultValue ?? min, min, max);
|
|
42
|
+
return {
|
|
43
|
+
value,
|
|
44
|
+
min,
|
|
45
|
+
max,
|
|
46
|
+
step,
|
|
47
|
+
disabled,
|
|
48
|
+
size: props.size ?? "m",
|
|
49
|
+
variant,
|
|
50
|
+
selected: value > min,
|
|
51
|
+
canDecrement: !disabled && value > min,
|
|
52
|
+
canIncrement: !disabled && (typeof max !== "number" || value < max),
|
|
53
|
+
decrementLabel: props.decrementLabel ?? "减少",
|
|
54
|
+
incrementLabel: props.incrementLabel ?? "增加"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** 计算减量后的目标值(已 clamp)。 */
|
|
58
|
+
function getStepperDecrementValue(state) {
|
|
59
|
+
return clampStepperValue(state.value - state.step, state.min, state.max);
|
|
60
|
+
}
|
|
61
|
+
/** 计算增量后的目标值(已 clamp)。 */
|
|
62
|
+
function getStepperIncrementValue(state) {
|
|
63
|
+
return clampStepperValue(state.value + state.step, state.min, state.max);
|
|
64
|
+
}
|
|
65
|
+
/** 生成根容器属性。 */
|
|
66
|
+
function getStepperRootProps(state) {
|
|
67
|
+
return {
|
|
68
|
+
role: "group",
|
|
69
|
+
"data-scope": "stepper",
|
|
70
|
+
"data-part": "root",
|
|
71
|
+
"data-size": state.size,
|
|
72
|
+
"data-variant": state.variant,
|
|
73
|
+
...state.selected ? { "data-selected": "true" } : {},
|
|
74
|
+
...state.disabled ? { "data-disabled": "true" } : {}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** 生成减量按钮属性。 */
|
|
78
|
+
function getStepperDecrementProps(state) {
|
|
79
|
+
const disabled = !state.canDecrement;
|
|
80
|
+
return {
|
|
81
|
+
type: "button",
|
|
82
|
+
disabled,
|
|
83
|
+
"data-scope": "stepper",
|
|
84
|
+
"data-part": "decrement",
|
|
85
|
+
"aria-label": state.decrementLabel,
|
|
86
|
+
...disabled ? { "data-disabled": "true" } : {}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** 生成增量按钮属性。 */
|
|
90
|
+
function getStepperIncrementProps(state) {
|
|
91
|
+
const disabled = !state.canIncrement;
|
|
92
|
+
return {
|
|
93
|
+
type: "button",
|
|
94
|
+
disabled,
|
|
95
|
+
"data-scope": "stepper",
|
|
96
|
+
"data-part": "increment",
|
|
97
|
+
"aria-label": state.incrementLabel,
|
|
98
|
+
...disabled ? { "data-disabled": "true" } : {}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** 生成数值展示属性。 */
|
|
102
|
+
function getStepperValueProps() {
|
|
103
|
+
return {
|
|
104
|
+
"data-scope": "stepper",
|
|
105
|
+
"data-part": "value",
|
|
106
|
+
"aria-live": "polite",
|
|
107
|
+
"aria-atomic": "true"
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { STEPPER_PARTS, STEPPER_SIZES, STEPPER_VARIANTS, clampStepperValue, getStepperDecrementProps, getStepperDecrementValue, getStepperIncrementProps, getStepperIncrementValue, getStepperRootProps, getStepperValueProps, resolveStepperState };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/stepper/stepper.ts"],"sourcesContent":["/**\n * Stepper 加减(数量步进器)的跨框架行为契约。\n *\n * Stepper 是原型 A(无状态 Core 状态机):交互由原生 `<button>` 承担,\n * core 只负责数值归一化、边界 clamp、按钮可用性计算与 prop-getter 生成。\n */\n\n/** 尺寸全集:s = 15×15px,m = 18×18px。 */\nexport const STEPPER_SIZES = [\"s\", \"m\"] as const;\n\n/** 形态变体全集:default = 裸按钮组合,bordered = 带边框卡片形态。 */\nexport const STEPPER_VARIANTS = [\"default\", \"bordered\"] as const;\n\n/** 渲染部位全集(data-part 取值)。 */\nexport const STEPPER_PARTS = [\"root\", \"decrement\", \"value\", \"increment\"] as const;\n\nexport type StepperSize = (typeof STEPPER_SIZES)[number];\nexport type StepperVariant = (typeof STEPPER_VARIANTS)[number];\nexport type StepperPart = (typeof STEPPER_PARTS)[number];\n\n/**\n * QwStepperProps — 双栈共享的行为契约(全字段可选)。\n * `value` 未传时走非受控模式,由适配层管理内部数值。\n */\nexport interface QwStepperProps {\n /** 受控值。未传时走非受控模式。 */\n value?: number;\n /** 非受控初始值,默认 `0`(若 `min > 0` 则取 `min`)。仅在 `value` 未传时生效。 */\n defaultValue?: number;\n /** 数值变化回调,参数为新的 value。 */\n onChange?: (value: number) => void;\n /** 最小值。 */\n min?: number;\n /** 最大值。 */\n max?: number;\n /** 步长,默认 1;非法值(非正数)回退到 1。 */\n step?: number;\n /** 禁用状态。 */\n disabled?: boolean;\n /** 按钮尺寸,默认 m。 */\n size?: StepperSize;\n /** 形态变体,默认 default;bordered 为带边框卡片形态。 */\n variant?: StepperVariant;\n /** 减量按钮 aria-label,默认「减少」(适配层可注入语言包取值)。 */\n decrementLabel?: string;\n /** 增量按钮 aria-label,默认「增加」(适配层可注入语言包取值)。 */\n incrementLabel?: string;\n}\n\n/** 适配器唯一可消费的归一化结果。 */\nexport interface ResolvedStepperState {\n /** 当前数值(已按 min/max clamp)。 */\n value: number;\n /** 最小值。 */\n min: number | undefined;\n /** 最大值。 */\n max: number | undefined;\n /** 实际生效的步长。 */\n step: number;\n /** 是否整体禁用。 */\n disabled: boolean;\n /** 按钮尺寸。 */\n size: StepperSize;\n /** 形态变体。 */\n variant: StepperVariant;\n /** 选中态:value > min 派生,仅影响 bordered 视觉。 */\n selected: boolean;\n /** 减量按钮是否可用。 */\n canDecrement: boolean;\n /** 增量按钮是否可用。 */\n canIncrement: boolean;\n /** 减量按钮 aria-label(per-prop > 适配层注入 > 内置「减少」)。 */\n decrementLabel: string;\n /** 增量按钮 aria-label(per-prop > 适配层注入 > 内置「增加」)。 */\n incrementLabel: string;\n}\n\n/** 将数值约束在 [min, max] 范围内;边界未定义时跳过对应侧。 */\nexport function clampStepperValue(\n value: number,\n min: number | undefined,\n max: number | undefined,\n): number {\n let result = value;\n if (typeof min === \"number\" && !Number.isNaN(min)) {\n result = Math.max(result, min);\n }\n if (typeof max === \"number\" && !Number.isNaN(max)) {\n result = Math.min(result, max);\n }\n return result;\n}\n\n/** 规范化 step:仅接受正数,否则回退到 1。 */\nfunction normalizeStepperStep(step: number | undefined): number {\n return typeof step === \"number\" && step > 0 && !Number.isNaN(step) ? step : 1;\n}\n\n/** 规范化 min:未指定时按规格默认 0(不做负数默认值)。 */\nfunction normalizeStepperMin(min: number | undefined): number {\n return typeof min === \"number\" && !Number.isNaN(min) ? min : 0;\n}\n\n/** 将公开输入归一化为唯一的跨框架状态。 */\nexport function resolveStepperState(props: QwStepperProps = {}): ResolvedStepperState {\n const min = normalizeStepperMin(props.min);\n const max = props.max;\n const step = normalizeStepperStep(props.step);\n const disabled = props.disabled ?? false;\n const variant = props.variant ?? \"default\";\n\n const isControlled = props.value !== undefined;\n const rawValue = isControlled\n ? props.value!\n : (props.defaultValue ?? min);\n\n const value = clampStepperValue(rawValue, min, max);\n\n const size = props.size ?? \"m\";\n\n const selected = value > min;\n\n const canDecrement = !disabled && value > min;\n const canIncrement = !disabled && (typeof max !== \"number\" || value < max);\n\n return {\n value,\n min,\n max,\n step,\n disabled,\n size,\n variant,\n selected,\n canDecrement,\n canIncrement,\n decrementLabel: props.decrementLabel ?? \"减少\",\n incrementLabel: props.incrementLabel ?? \"增加\",\n };\n}\n\n/** 计算减量后的目标值(已 clamp)。 */\nexport function getStepperDecrementValue(state: ResolvedStepperState): number {\n return clampStepperValue(state.value - state.step, state.min, state.max);\n}\n\n/** 计算增量后的目标值(已 clamp)。 */\nexport function getStepperIncrementValue(state: ResolvedStepperState): number {\n return clampStepperValue(state.value + state.step, state.min, state.max);\n}\n\n/** Core 生成、由适配器最后覆盖到根元素的属性。 */\nexport interface StepperRootProps {\n role: \"group\";\n \"data-scope\": \"stepper\";\n \"data-part\": \"root\";\n \"data-size\": StepperSize;\n \"data-variant\": StepperVariant;\n \"data-selected\"?: \"true\";\n \"data-disabled\"?: \"true\";\n}\n\n/** 生成根容器属性。 */\nexport function getStepperRootProps(state: ResolvedStepperState): StepperRootProps {\n return {\n role: \"group\",\n \"data-scope\": \"stepper\",\n \"data-part\": \"root\",\n \"data-size\": state.size,\n \"data-variant\": state.variant,\n ...(state.selected ? { \"data-selected\": \"true\" as const } : {}),\n ...(state.disabled ? { \"data-disabled\": \"true\" as const } : {}),\n };\n}\n\n/** 减量按钮属性。 */\nexport interface StepperDecrementProps {\n type: \"button\";\n disabled: boolean;\n \"data-scope\": \"stepper\";\n \"data-part\": \"decrement\";\n \"aria-label\": string;\n \"data-disabled\"?: \"true\";\n}\n\n/** 生成减量按钮属性。 */\nexport function getStepperDecrementProps(state: ResolvedStepperState): StepperDecrementProps {\n const disabled = !state.canDecrement;\n return {\n type: \"button\",\n disabled,\n \"data-scope\": \"stepper\",\n \"data-part\": \"decrement\",\n \"aria-label\": state.decrementLabel,\n ...(disabled ? { \"data-disabled\": \"true\" as const } : {}),\n };\n}\n\n/** 增量按钮属性。 */\nexport interface StepperIncrementProps {\n type: \"button\";\n disabled: boolean;\n \"data-scope\": \"stepper\";\n \"data-part\": \"increment\";\n \"aria-label\": string;\n \"data-disabled\"?: \"true\";\n}\n\n/** 生成增量按钮属性。 */\nexport function getStepperIncrementProps(state: ResolvedStepperState): StepperIncrementProps {\n const disabled = !state.canIncrement;\n return {\n type: \"button\",\n disabled,\n \"data-scope\": \"stepper\",\n \"data-part\": \"increment\",\n \"aria-label\": state.incrementLabel,\n ...(disabled ? { \"data-disabled\": \"true\" as const } : {}),\n };\n}\n\n/** 数值展示属性。 */\nexport interface StepperValueProps {\n \"data-scope\": \"stepper\";\n \"data-part\": \"value\";\n \"aria-live\": \"polite\";\n \"aria-atomic\": \"true\";\n}\n\n/** 生成数值展示属性。 */\nexport function getStepperValueProps(): StepperValueProps {\n return {\n \"data-scope\": \"stepper\",\n \"data-part\": \"value\",\n \"aria-live\": \"polite\",\n \"aria-atomic\": \"true\",\n };\n}\n"],"mappings":";;;;;;;;AAQA,MAAa,gBAAgB,CAAC,KAAK,GAAG;;AAGtC,MAAa,mBAAmB,CAAC,WAAW,UAAU;;AAGtD,MAAa,gBAAgB;CAAC;CAAQ;CAAa;CAAS;AAAW;;AAgEvE,SAAgB,kBACd,OACA,KACA,KACQ;CACR,IAAI,SAAS;CACb,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,MAAM,GAAG,GAC9C,SAAS,KAAK,IAAI,QAAQ,GAAG;CAE/B,IAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,MAAM,GAAG,GAC9C,SAAS,KAAK,IAAI,QAAQ,GAAG;CAE/B,OAAO;AACT;;AAGA,SAAS,qBAAqB,MAAkC;CAC9D,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,CAAC,OAAO,MAAM,IAAI,IAAI,OAAO;AAC9E;;AAGA,SAAS,oBAAoB,KAAiC;CAC5D,OAAO,OAAO,QAAQ,YAAY,CAAC,OAAO,MAAM,GAAG,IAAI,MAAM;AAC/D;;AAGA,SAAgB,oBAAoB,QAAwB,CAAC,GAAyB;CACpF,MAAM,MAAM,oBAAoB,MAAM,GAAG;CACzC,MAAM,MAAM,MAAM;CAClB,MAAM,OAAO,qBAAqB,MAAM,IAAI;CAC5C,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,UAAU,MAAM,WAAW;CAOjC,MAAM,QAAQ,kBALO,MAAM,UAAU,KAAA,IAEjC,MAAM,QACL,MAAM,gBAAgB,KAEe,KAAK,GAAG;CASlD,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,MAbW,MAAM,QAAQ;EAczB;EACA,UAbe,QAAQ;EAcvB,cAZmB,CAAC,YAAY,QAAQ;EAaxC,cAZmB,CAAC,aAAa,OAAO,QAAQ,YAAY,QAAQ;EAapE,gBAAgB,MAAM,kBAAkB;EACxC,gBAAgB,MAAM,kBAAkB;CAC1C;AACF;;AAGA,SAAgB,yBAAyB,OAAqC;CAC5E,OAAO,kBAAkB,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG;AACzE;;AAGA,SAAgB,yBAAyB,OAAqC;CAC5E,OAAO,kBAAkB,MAAM,QAAQ,MAAM,MAAM,MAAM,KAAK,MAAM,GAAG;AACzE;;AAcA,SAAgB,oBAAoB,OAA+C;CACjF,OAAO;EACL,MAAM;EACN,cAAc;EACd,aAAa;EACb,aAAa,MAAM;EACnB,gBAAgB,MAAM;EACtB,GAAI,MAAM,WAAW,EAAE,iBAAiB,OAAgB,IAAI,CAAC;EAC7D,GAAI,MAAM,WAAW,EAAE,iBAAiB,OAAgB,IAAI,CAAC;CAC/D;AACF;;AAaA,SAAgB,yBAAyB,OAAoD;CAC3F,MAAM,WAAW,CAAC,MAAM;CACxB,OAAO;EACL,MAAM;EACN;EACA,cAAc;EACd,aAAa;EACb,cAAc,MAAM;EACpB,GAAI,WAAW,EAAE,iBAAiB,OAAgB,IAAI,CAAC;CACzD;AACF;;AAaA,SAAgB,yBAAyB,OAAoD;CAC3F,MAAM,WAAW,CAAC,MAAM;CACxB,OAAO;EACL,MAAM;EACN;EACA,cAAc;EACd,aAAa;EACb,cAAc,MAAM;EACpB,GAAI,WAAW,EAAE,iBAAiB,OAAgB,IAAI,CAAC;CACzD;AACF;;AAWA,SAAgB,uBAA0C;CACxD,OAAO;EACL,cAAc;EACd,aAAa;EACb,aAAa;EACb,eAAe;CACjB;AACF"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stepper 加减(数量步进器)的跨框架行为契约。
|
|
3
|
+
*
|
|
4
|
+
* Stepper 是原型 A(无状态 Core 状态机):交互由原生 `<button>` 承担,
|
|
5
|
+
* core 只负责数值归一化、边界 clamp、按钮可用性计算与 prop-getter 生成。
|
|
6
|
+
*/
|
|
7
|
+
/** 尺寸全集:s = 15×15px,m = 18×18px。 */
|
|
8
|
+
export declare const STEPPER_SIZES: readonly ["s", "m"];
|
|
9
|
+
/** 形态变体全集:default = 裸按钮组合,bordered = 带边框卡片形态。 */
|
|
10
|
+
export declare const STEPPER_VARIANTS: readonly ["default", "bordered"];
|
|
11
|
+
/** 渲染部位全集(data-part 取值)。 */
|
|
12
|
+
export declare const STEPPER_PARTS: readonly ["root", "decrement", "value", "increment"];
|
|
13
|
+
export type StepperSize = (typeof STEPPER_SIZES)[number];
|
|
14
|
+
export type StepperVariant = (typeof STEPPER_VARIANTS)[number];
|
|
15
|
+
export type StepperPart = (typeof STEPPER_PARTS)[number];
|
|
16
|
+
/**
|
|
17
|
+
* QwStepperProps — 双栈共享的行为契约(全字段可选)。
|
|
18
|
+
* `value` 未传时走非受控模式,由适配层管理内部数值。
|
|
19
|
+
*/
|
|
20
|
+
export interface QwStepperProps {
|
|
21
|
+
/** 受控值。未传时走非受控模式。 */
|
|
22
|
+
value?: number;
|
|
23
|
+
/** 非受控初始值,默认 `0`(若 `min > 0` 则取 `min`)。仅在 `value` 未传时生效。 */
|
|
24
|
+
defaultValue?: number;
|
|
25
|
+
/** 数值变化回调,参数为新的 value。 */
|
|
26
|
+
onChange?: (value: number) => void;
|
|
27
|
+
/** 最小值。 */
|
|
28
|
+
min?: number;
|
|
29
|
+
/** 最大值。 */
|
|
30
|
+
max?: number;
|
|
31
|
+
/** 步长,默认 1;非法值(非正数)回退到 1。 */
|
|
32
|
+
step?: number;
|
|
33
|
+
/** 禁用状态。 */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** 按钮尺寸,默认 m。 */
|
|
36
|
+
size?: StepperSize;
|
|
37
|
+
/** 形态变体,默认 default;bordered 为带边框卡片形态。 */
|
|
38
|
+
variant?: StepperVariant;
|
|
39
|
+
/** 减量按钮 aria-label,默认「减少」(适配层可注入语言包取值)。 */
|
|
40
|
+
decrementLabel?: string;
|
|
41
|
+
/** 增量按钮 aria-label,默认「增加」(适配层可注入语言包取值)。 */
|
|
42
|
+
incrementLabel?: string;
|
|
43
|
+
}
|
|
44
|
+
/** 适配器唯一可消费的归一化结果。 */
|
|
45
|
+
export interface ResolvedStepperState {
|
|
46
|
+
/** 当前数值(已按 min/max clamp)。 */
|
|
47
|
+
value: number;
|
|
48
|
+
/** 最小值。 */
|
|
49
|
+
min: number | undefined;
|
|
50
|
+
/** 最大值。 */
|
|
51
|
+
max: number | undefined;
|
|
52
|
+
/** 实际生效的步长。 */
|
|
53
|
+
step: number;
|
|
54
|
+
/** 是否整体禁用。 */
|
|
55
|
+
disabled: boolean;
|
|
56
|
+
/** 按钮尺寸。 */
|
|
57
|
+
size: StepperSize;
|
|
58
|
+
/** 形态变体。 */
|
|
59
|
+
variant: StepperVariant;
|
|
60
|
+
/** 选中态:value > min 派生,仅影响 bordered 视觉。 */
|
|
61
|
+
selected: boolean;
|
|
62
|
+
/** 减量按钮是否可用。 */
|
|
63
|
+
canDecrement: boolean;
|
|
64
|
+
/** 增量按钮是否可用。 */
|
|
65
|
+
canIncrement: boolean;
|
|
66
|
+
/** 减量按钮 aria-label(per-prop > 适配层注入 > 内置「减少」)。 */
|
|
67
|
+
decrementLabel: string;
|
|
68
|
+
/** 增量按钮 aria-label(per-prop > 适配层注入 > 内置「增加」)。 */
|
|
69
|
+
incrementLabel: string;
|
|
70
|
+
}
|
|
71
|
+
/** 将数值约束在 [min, max] 范围内;边界未定义时跳过对应侧。 */
|
|
72
|
+
export declare function clampStepperValue(value: number, min: number | undefined, max: number | undefined): number;
|
|
73
|
+
/** 将公开输入归一化为唯一的跨框架状态。 */
|
|
74
|
+
export declare function resolveStepperState(props?: QwStepperProps): ResolvedStepperState;
|
|
75
|
+
/** 计算减量后的目标值(已 clamp)。 */
|
|
76
|
+
export declare function getStepperDecrementValue(state: ResolvedStepperState): number;
|
|
77
|
+
/** 计算增量后的目标值(已 clamp)。 */
|
|
78
|
+
export declare function getStepperIncrementValue(state: ResolvedStepperState): number;
|
|
79
|
+
/** Core 生成、由适配器最后覆盖到根元素的属性。 */
|
|
80
|
+
export interface StepperRootProps {
|
|
81
|
+
role: "group";
|
|
82
|
+
"data-scope": "stepper";
|
|
83
|
+
"data-part": "root";
|
|
84
|
+
"data-size": StepperSize;
|
|
85
|
+
"data-variant": StepperVariant;
|
|
86
|
+
"data-selected"?: "true";
|
|
87
|
+
"data-disabled"?: "true";
|
|
88
|
+
}
|
|
89
|
+
/** 生成根容器属性。 */
|
|
90
|
+
export declare function getStepperRootProps(state: ResolvedStepperState): StepperRootProps;
|
|
91
|
+
/** 减量按钮属性。 */
|
|
92
|
+
export interface StepperDecrementProps {
|
|
93
|
+
type: "button";
|
|
94
|
+
disabled: boolean;
|
|
95
|
+
"data-scope": "stepper";
|
|
96
|
+
"data-part": "decrement";
|
|
97
|
+
"aria-label": string;
|
|
98
|
+
"data-disabled"?: "true";
|
|
99
|
+
}
|
|
100
|
+
/** 生成减量按钮属性。 */
|
|
101
|
+
export declare function getStepperDecrementProps(state: ResolvedStepperState): StepperDecrementProps;
|
|
102
|
+
/** 增量按钮属性。 */
|
|
103
|
+
export interface StepperIncrementProps {
|
|
104
|
+
type: "button";
|
|
105
|
+
disabled: boolean;
|
|
106
|
+
"data-scope": "stepper";
|
|
107
|
+
"data-part": "increment";
|
|
108
|
+
"aria-label": string;
|
|
109
|
+
"data-disabled"?: "true";
|
|
110
|
+
}
|
|
111
|
+
/** 生成增量按钮属性。 */
|
|
112
|
+
export declare function getStepperIncrementProps(state: ResolvedStepperState): StepperIncrementProps;
|
|
113
|
+
/** 数值展示属性。 */
|
|
114
|
+
export interface StepperValueProps {
|
|
115
|
+
"data-scope": "stepper";
|
|
116
|
+
"data-part": "value";
|
|
117
|
+
"aria-live": "polite";
|
|
118
|
+
"aria-atomic": "true";
|
|
119
|
+
}
|
|
120
|
+
/** 生成数值展示属性。 */
|
|
121
|
+
export declare function getStepperValueProps(): StepperValueProps;
|
package/dist/textarea/index.js
CHANGED
|
@@ -202,7 +202,7 @@ function connect(service, normalizeProps) {
|
|
|
202
202
|
"data-part": "clear-button",
|
|
203
203
|
type: "button",
|
|
204
204
|
disabled: !showClear || disabled || readOnly,
|
|
205
|
-
"aria-label": "清除内容",
|
|
205
|
+
"aria-label": prop("clearLabel") ?? "清除内容",
|
|
206
206
|
onClick() {
|
|
207
207
|
send({ type: "VALUE.CLEAR" });
|
|
208
208
|
}
|
|
@@ -214,7 +214,7 @@ function connect(service, normalizeProps) {
|
|
|
214
214
|
"data-part": "paste-button",
|
|
215
215
|
type: "button",
|
|
216
216
|
disabled,
|
|
217
|
-
"aria-label": "粘贴并识别",
|
|
217
|
+
"aria-label": prop("pasteLabel") ?? "粘贴并识别",
|
|
218
218
|
onClick() {
|
|
219
219
|
send({ type: "PASTE_RECOGNIZE" });
|
|
220
220
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/textarea/textarea.ts"],"sourcesContent":["import { createMachine } from \"@zag-js/core\";\nimport type { MachineSchema, Service } from \"@zag-js/core\";\nimport type { PropTypes, NormalizeProps } from \"@zag-js/types\";\nimport type { JSX } from \"@zag-js/types\";\nimport { compact } from \"@zag-js/utils\";\n\n/**\n * TextArea 公共 props 契约(双栈共享)。\n */\nexport interface QwTextAreaProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。 */\n defaultValue?: string;\n /** 占位符。 */\n placeholder?: string;\n /** 最大字符数。 */\n maxLength?: number;\n /** 禁用态。 */\n disabled?: boolean;\n /** 只读态。 */\n readOnly?: boolean;\n /** 可视行数(影响最小高度)。 */\n rows?: number;\n /** 自适应高度。 */\n autoSize?: boolean;\n /** 显示底部操作按钮。 */\n showActions?: boolean;\n /** name 属性。 */\n name?: string;\n /** 输入变化回调。 */\n onChange?: (value: string) => void;\n /** 清空回调。 */\n onClear?: () => void;\n /** 粘贴并识别回调,参数为粘贴的文本。 */\n onPasteRecognize?: (value: string) => void;\n /** 聚焦回调。 */\n onFocus?: () => void;\n /** 失焦回调。 */\n onBlur?: () => void;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface TextAreaSchema extends MachineSchema {\n props: QwTextAreaProps;\n context: {\n value: string;\n focused: boolean;\n };\n computed: {\n charCount: number;\n hasMaxLength: boolean;\n showClear: boolean;\n };\n state: \"idle\";\n event:\n | { type: \"VALUE.SET\"; value: string }\n | { type: \"VALUE.CLEAR\" }\n | { type: \"FOCUS.SET\"; focused: boolean }\n | { type: \"PASTE_RECOGNIZE\"; value?: string };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnClear\"\n | \"pasteValue\"\n | \"invokeOnPasteRecognize\"\n | \"invokeOnFocus\"\n | \"invokeOnBlur\";\n}\n\n/**\n * TextArea zag.js machine。\n * 所有交互逻辑唯一来源:value、focus、clear、pasteRecognize。\n */\nexport const machine = createMachine<TextAreaSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n disabled: false,\n readOnly: false,\n showActions: true,\n autoSize: false,\n rows: 4,\n ...props,\n };\n },\n\n initialState() {\n return \"idle\";\n },\n\n context({ bindable, prop }) {\n return {\n value: bindable<string>(() => {\n const controlledValue = prop(\"value\");\n return {\n defaultValue: prop(\"defaultValue\") ?? \"\",\n sync: true,\n ...(controlledValue !== undefined ? { value: controlledValue } : {}),\n onChange(value, prev) {\n if (value !== prev) {\n prop(\"onChange\")?.(value);\n }\n },\n };\n }),\n focused: bindable(() => ({\n defaultValue: false,\n })),\n };\n },\n\n computed: {\n charCount({ context }) {\n return context.get(\"value\").length;\n },\n hasMaxLength({ prop }) {\n const max = prop(\"maxLength\");\n return max != null && max > 0;\n },\n showClear({ context, prop }) {\n return context.get(\"value\").length > 0 && !prop(\"disabled\") && !prop(\"readOnly\");\n },\n },\n\n watch({ track, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n context.set(\"value\", external);\n }\n });\n },\n\n states: {\n idle: {\n on: {\n \"VALUE.SET\": {\n actions: [\"setValue\"],\n },\n \"VALUE.CLEAR\": {\n actions: [\"clearValue\", \"invokeOnClear\"],\n },\n \"FOCUS.SET\": {\n actions: [\"setFocused\"],\n },\n PASTE_RECOGNIZE: {\n actions: [\"pasteValue\", \"invokeOnPasteRecognize\"],\n },\n },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event, prop }) {\n if (event.type === \"VALUE.SET\") {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n clearValue({ context }) {\n context.set(\"value\", \"\");\n },\n setFocused({ context, event, prop }) {\n if (event.type === \"FOCUS.SET\") {\n const next = event.focused;\n const prev = context.get(\"focused\");\n context.set(\"focused\", next);\n if (next && !prev) prop(\"onFocus\")?.();\n if (!next && prev) prop(\"onBlur\")?.();\n }\n },\n invokeOnClear({ prop }) {\n prop(\"onClear\")?.();\n },\n pasteValue({ context, event, prop }) {\n if (event.type === \"PASTE_RECOGNIZE\" && event.value !== undefined) {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n invokeOnPasteRecognize({ prop, event }) {\n if (event.type === \"PASTE_RECOGNIZE\") {\n prop(\"onPasteRecognize\")?.(event.value ?? \"\");\n }\n },\n invokeOnFocus() {\n // focus 回调在 setFocused 中已触发\n },\n invokeOnBlur() {\n // blur 回调在 setFocused 中已触发\n },\n },\n },\n});\n\n/**\n * TextArea api 类型(由 connect 返回)。\n */\nexport interface TextAreaApi<T extends PropTypes = PropTypes> {\n /** 当前输入值。 */\n value: string;\n /** 当前是否聚焦。 */\n focused: boolean;\n /** 当前字符数。 */\n charCount: number;\n /** 是否有字数限制。 */\n hasMaxLength: boolean;\n /** 是否显示清除按钮。 */\n showClear: boolean;\n /** 清空输入。 */\n clear(): void;\n /** 触发粘贴并识别回调(可传入剪贴板文本,自动填入)。 */\n pasteRecognize(value?: string): void;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** textarea 元素 prop-getter。 */\n getTextareaProps(): T[\"textarea\"];\n /** 底部栏 prop-getter。 */\n getBottomBarProps(): T[\"element\"];\n /** 字数统计 prop-getter。 */\n getCounterProps(): T[\"element\"];\n /** 清除按钮 prop-getter。 */\n getClearButtonProps(): T[\"button\"];\n /** 粘贴按钮 prop-getter。 */\n getPasteButtonProps(): T[\"button\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps)。\n */\nexport function connect<T extends PropTypes>(service: Service<TextAreaSchema>, normalizeProps: NormalizeProps<T>): TextAreaApi<T> {\n const { context, computed, prop, send } = service;\n\n const value = context.get(\"value\");\n const focused = context.get(\"focused\");\n const charCount = computed(\"charCount\");\n const hasMaxLength = computed(\"hasMaxLength\");\n const showClear = computed(\"showClear\");\n const disabled = !!prop(\"disabled\");\n const readOnly = !!prop(\"readOnly\");\n const showActions = prop(\"showActions\") ?? true;\n const autoSize = !!prop(\"autoSize\");\n const maxLength = prop(\"maxLength\");\n const rows = prop(\"rows\") ?? 4;\n const isEmpty = value.length === 0;\n\n return {\n value,\n focused,\n charCount,\n hasMaxLength,\n showClear,\n\n clear() {\n send({ type: \"VALUE.CLEAR\" });\n },\n\n pasteRecognize(value?: string) {\n send({ type: \"PASTE_RECOGNIZE\", value });\n },\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"root\",\n \"data-focused\": focused ? \"true\" : undefined,\n \"data-disabled\": disabled ? \"true\" : undefined,\n \"data-readonly\": readOnly ? \"true\" : undefined,\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n \"data-auto-size\": autoSize ? \"true\" : undefined,\n \"data-empty\": isEmpty ? \"true\" : \"false\",\n }),\n );\n },\n\n getTextareaProps() {\n return normalizeProps.textarea(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"textarea\",\n value,\n placeholder: prop(\"placeholder\"),\n disabled,\n readOnly,\n maxLength: maxLength && maxLength > 0 ? maxLength : undefined,\n rows,\n name: prop(\"name\"),\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"aria-readonly\": readOnly ? (\"true\" as const) : undefined,\n onFocus() {\n send({ type: \"FOCUS.SET\", focused: true });\n },\n onBlur() {\n send({ type: \"FOCUS.SET\", focused: false });\n },\n onChange(event: JSX.ChangeEvent<HTMLTextAreaElement>) {\n const target = event.target as unknown as HTMLTextAreaElement;\n const nativeEvent = (event as { nativeEvent?: { isComposing?: boolean } }).nativeEvent;\n const isComposing = !!nativeEvent?.isComposing;\n let next = target.value;\n // IME composing 期间跳过 maxLength 截断,允许拼音中间态值流过\n // 防止 React 受控组件因值未更新而重置 DOM,打断输入法\n if (!isComposing) {\n const maxLen = prop(\"maxLength\") ?? 0;\n if (maxLen > 0 && next.length > maxLen) {\n next = next.slice(0, maxLen);\n }\n }\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", next);\n },\n }),\n );\n },\n\n getBottomBarProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"bottom-bar\",\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n }),\n );\n },\n\n getCounterProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"counter\",\n \"data-has-max-length\": hasMaxLength ? \"true\" : undefined,\n \"aria-live\": \"polite\" as const,\n }),\n );\n },\n\n getClearButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"clear-button\",\n type: \"button\" as const,\n disabled: !showClear || disabled || readOnly,\n \"aria-label\": \"清除内容\" as const,\n onClick() {\n send({ type: \"VALUE.CLEAR\" });\n },\n }),\n );\n },\n\n getPasteButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"paste-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": \"粘贴并识别\" as const,\n onClick() {\n send({ type: \"PASTE_RECOGNIZE\" });\n },\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwTextAreaProps 转成 machine context(默认值收口)。\n */\nexport function createTextAreaProps(props: QwTextAreaProps = {}): Partial<QwTextAreaProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n disabled: props.disabled ?? false,\n readOnly: props.readOnly ?? false,\n showActions: props.showActions ?? true,\n autoSize: props.autoSize ?? false,\n rows: props.rows ?? 4,\n ...props,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/textarea/textarea.ts"],"sourcesContent":["import { createMachine } from \"@zag-js/core\";\nimport type { MachineSchema, Service } from \"@zag-js/core\";\nimport type { PropTypes, NormalizeProps } from \"@zag-js/types\";\nimport type { JSX } from \"@zag-js/types\";\nimport { compact } from \"@zag-js/utils\";\n\n/**\n * TextArea 公共 props 契约(双栈共享)。\n */\nexport interface QwTextAreaProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。 */\n defaultValue?: string;\n /** 占位符。 */\n placeholder?: string;\n /** 最大字符数。 */\n maxLength?: number;\n /** 禁用态。 */\n disabled?: boolean;\n /** 只读态。 */\n readOnly?: boolean;\n /** 可视行数(影响最小高度)。 */\n rows?: number;\n /** 自适应高度。 */\n autoSize?: boolean;\n /** 显示底部操作按钮。 */\n showActions?: boolean;\n /** name 属性。 */\n name?: string;\n /** 清除按钮 aria-label,默认「清除内容」。 */\n clearLabel?: string;\n /** 粘贴按钮 aria-label,默认「粘贴并识别」。 */\n pasteLabel?: string;\n /** 输入变化回调。 */\n onChange?: (value: string) => void;\n /** 清空回调。 */\n onClear?: () => void;\n /** 粘贴并识别回调,参数为粘贴的文本。 */\n onPasteRecognize?: (value: string) => void;\n /** 聚焦回调。 */\n onFocus?: () => void;\n /** 失焦回调。 */\n onBlur?: () => void;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface TextAreaSchema extends MachineSchema {\n props: QwTextAreaProps;\n context: {\n value: string;\n focused: boolean;\n };\n computed: {\n charCount: number;\n hasMaxLength: boolean;\n showClear: boolean;\n };\n state: \"idle\";\n event:\n | { type: \"VALUE.SET\"; value: string }\n | { type: \"VALUE.CLEAR\" }\n | { type: \"FOCUS.SET\"; focused: boolean }\n | { type: \"PASTE_RECOGNIZE\"; value?: string };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnClear\"\n | \"pasteValue\"\n | \"invokeOnPasteRecognize\"\n | \"invokeOnFocus\"\n | \"invokeOnBlur\";\n}\n\n/**\n * TextArea zag.js machine。\n * 所有交互逻辑唯一来源:value、focus、clear、pasteRecognize。\n */\nexport const machine = createMachine<TextAreaSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n disabled: false,\n readOnly: false,\n showActions: true,\n autoSize: false,\n rows: 4,\n ...props,\n };\n },\n\n initialState() {\n return \"idle\";\n },\n\n context({ bindable, prop }) {\n return {\n value: bindable<string>(() => {\n const controlledValue = prop(\"value\");\n return {\n defaultValue: prop(\"defaultValue\") ?? \"\",\n sync: true,\n ...(controlledValue !== undefined ? { value: controlledValue } : {}),\n onChange(value, prev) {\n if (value !== prev) {\n prop(\"onChange\")?.(value);\n }\n },\n };\n }),\n focused: bindable(() => ({\n defaultValue: false,\n })),\n };\n },\n\n computed: {\n charCount({ context }) {\n return context.get(\"value\").length;\n },\n hasMaxLength({ prop }) {\n const max = prop(\"maxLength\");\n return max != null && max > 0;\n },\n showClear({ context, prop }) {\n return context.get(\"value\").length > 0 && !prop(\"disabled\") && !prop(\"readOnly\");\n },\n },\n\n watch({ track, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n context.set(\"value\", external);\n }\n });\n },\n\n states: {\n idle: {\n on: {\n \"VALUE.SET\": {\n actions: [\"setValue\"],\n },\n \"VALUE.CLEAR\": {\n actions: [\"clearValue\", \"invokeOnClear\"],\n },\n \"FOCUS.SET\": {\n actions: [\"setFocused\"],\n },\n PASTE_RECOGNIZE: {\n actions: [\"pasteValue\", \"invokeOnPasteRecognize\"],\n },\n },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event, prop }) {\n if (event.type === \"VALUE.SET\") {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n clearValue({ context }) {\n context.set(\"value\", \"\");\n },\n setFocused({ context, event, prop }) {\n if (event.type === \"FOCUS.SET\") {\n const next = event.focused;\n const prev = context.get(\"focused\");\n context.set(\"focused\", next);\n if (next && !prev) prop(\"onFocus\")?.();\n if (!next && prev) prop(\"onBlur\")?.();\n }\n },\n invokeOnClear({ prop }) {\n prop(\"onClear\")?.();\n },\n pasteValue({ context, event, prop }) {\n if (event.type === \"PASTE_RECOGNIZE\" && event.value !== undefined) {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n invokeOnPasteRecognize({ prop, event }) {\n if (event.type === \"PASTE_RECOGNIZE\") {\n prop(\"onPasteRecognize\")?.(event.value ?? \"\");\n }\n },\n invokeOnFocus() {\n // focus 回调在 setFocused 中已触发\n },\n invokeOnBlur() {\n // blur 回调在 setFocused 中已触发\n },\n },\n },\n});\n\n/**\n * TextArea api 类型(由 connect 返回)。\n */\nexport interface TextAreaApi<T extends PropTypes = PropTypes> {\n /** 当前输入值。 */\n value: string;\n /** 当前是否聚焦。 */\n focused: boolean;\n /** 当前字符数。 */\n charCount: number;\n /** 是否有字数限制。 */\n hasMaxLength: boolean;\n /** 是否显示清除按钮。 */\n showClear: boolean;\n /** 清空输入。 */\n clear(): void;\n /** 触发粘贴并识别回调(可传入剪贴板文本,自动填入)。 */\n pasteRecognize(value?: string): void;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** textarea 元素 prop-getter。 */\n getTextareaProps(): T[\"textarea\"];\n /** 底部栏 prop-getter。 */\n getBottomBarProps(): T[\"element\"];\n /** 字数统计 prop-getter。 */\n getCounterProps(): T[\"element\"];\n /** 清除按钮 prop-getter。 */\n getClearButtonProps(): T[\"button\"];\n /** 粘贴按钮 prop-getter。 */\n getPasteButtonProps(): T[\"button\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps)。\n */\nexport function connect<T extends PropTypes>(service: Service<TextAreaSchema>, normalizeProps: NormalizeProps<T>): TextAreaApi<T> {\n const { context, computed, prop, send } = service;\n\n const value = context.get(\"value\");\n const focused = context.get(\"focused\");\n const charCount = computed(\"charCount\");\n const hasMaxLength = computed(\"hasMaxLength\");\n const showClear = computed(\"showClear\");\n const disabled = !!prop(\"disabled\");\n const readOnly = !!prop(\"readOnly\");\n const showActions = prop(\"showActions\") ?? true;\n const autoSize = !!prop(\"autoSize\");\n const maxLength = prop(\"maxLength\");\n const rows = prop(\"rows\") ?? 4;\n const isEmpty = value.length === 0;\n\n return {\n value,\n focused,\n charCount,\n hasMaxLength,\n showClear,\n\n clear() {\n send({ type: \"VALUE.CLEAR\" });\n },\n\n pasteRecognize(value?: string) {\n send({ type: \"PASTE_RECOGNIZE\", value });\n },\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"root\",\n \"data-focused\": focused ? \"true\" : undefined,\n \"data-disabled\": disabled ? \"true\" : undefined,\n \"data-readonly\": readOnly ? \"true\" : undefined,\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n \"data-auto-size\": autoSize ? \"true\" : undefined,\n \"data-empty\": isEmpty ? \"true\" : \"false\",\n }),\n );\n },\n\n getTextareaProps() {\n return normalizeProps.textarea(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"textarea\",\n value,\n placeholder: prop(\"placeholder\"),\n disabled,\n readOnly,\n maxLength: maxLength && maxLength > 0 ? maxLength : undefined,\n rows,\n name: prop(\"name\"),\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"aria-readonly\": readOnly ? (\"true\" as const) : undefined,\n onFocus() {\n send({ type: \"FOCUS.SET\", focused: true });\n },\n onBlur() {\n send({ type: \"FOCUS.SET\", focused: false });\n },\n onChange(event: JSX.ChangeEvent<HTMLTextAreaElement>) {\n const target = event.target as unknown as HTMLTextAreaElement;\n const nativeEvent = (event as { nativeEvent?: { isComposing?: boolean } }).nativeEvent;\n const isComposing = !!nativeEvent?.isComposing;\n let next = target.value;\n // IME composing 期间跳过 maxLength 截断,允许拼音中间态值流过\n // 防止 React 受控组件因值未更新而重置 DOM,打断输入法\n if (!isComposing) {\n const maxLen = prop(\"maxLength\") ?? 0;\n if (maxLen > 0 && next.length > maxLen) {\n next = next.slice(0, maxLen);\n }\n }\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", next);\n },\n }),\n );\n },\n\n getBottomBarProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"bottom-bar\",\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n }),\n );\n },\n\n getCounterProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"counter\",\n \"data-has-max-length\": hasMaxLength ? \"true\" : undefined,\n \"aria-live\": \"polite\" as const,\n }),\n );\n },\n\n getClearButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"clear-button\",\n type: \"button\" as const,\n disabled: !showClear || disabled || readOnly,\n \"aria-label\": prop(\"clearLabel\") ?? \"清除内容\",\n onClick() {\n send({ type: \"VALUE.CLEAR\" });\n },\n }),\n );\n },\n\n getPasteButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"paste-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": prop(\"pasteLabel\") ?? \"粘贴并识别\",\n onClick() {\n send({ type: \"PASTE_RECOGNIZE\" });\n },\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwTextAreaProps 转成 machine context(默认值收口)。\n */\nexport function createTextAreaProps(props: QwTextAreaProps = {}): Partial<QwTextAreaProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n disabled: props.disabled ?? false,\n readOnly: props.readOnly ?? false,\n showActions: props.showActions ?? true,\n autoSize: props.autoSize ?? false,\n rows: props.rows ?? 4,\n ...props,\n };\n}\n"],"mappings":"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"}
|
|
@@ -26,6 +26,10 @@ export interface QwTextAreaProps {
|
|
|
26
26
|
showActions?: boolean;
|
|
27
27
|
/** name 属性。 */
|
|
28
28
|
name?: string;
|
|
29
|
+
/** 清除按钮 aria-label,默认「清除内容」。 */
|
|
30
|
+
clearLabel?: string;
|
|
31
|
+
/** 粘贴按钮 aria-label,默认「粘贴并识别」。 */
|
|
32
|
+
pasteLabel?: string;
|
|
29
33
|
/** 输入变化回调。 */
|
|
30
34
|
onChange?: (value: string) => void;
|
|
31
35
|
/** 清空回调。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qw-ui/core",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.14",
|
|
4
4
|
"description": "L2 · Headless Core — zag.js machine 薄包装,纯 TS / SSR-safe,零样式。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "qw-ui contributors",
|
|
@@ -99,6 +99,10 @@
|
|
|
99
99
|
"./switch": {
|
|
100
100
|
"types": "./dist/switch/index.d.ts",
|
|
101
101
|
"import": "./dist/switch/index.js"
|
|
102
|
+
},
|
|
103
|
+
"./stepper": {
|
|
104
|
+
"types": "./dist/stepper/index.d.ts",
|
|
105
|
+
"import": "./dist/stepper/index.js"
|
|
102
106
|
}
|
|
103
107
|
},
|
|
104
108
|
"sideEffects": false,
|
|
@@ -107,7 +111,7 @@
|
|
|
107
111
|
"@zag-js/dialog": "^1.42.0",
|
|
108
112
|
"@zag-js/tabs": "^1.42.0",
|
|
109
113
|
"@zag-js/utils": "1.42.0",
|
|
110
|
-
"@qw-ui/icons": "^0.3.0-next.
|
|
114
|
+
"@qw-ui/icons": "^0.3.0-next.14"
|
|
111
115
|
},
|
|
112
116
|
"devDependencies": {
|
|
113
117
|
"@microsoft/api-extractor": "^7.58.9",
|
|
@@ -121,8 +125,8 @@
|
|
|
121
125
|
"typescript": "^6.0.3",
|
|
122
126
|
"unrun": "^0.3.1",
|
|
123
127
|
"vitest": "^4.1.9",
|
|
124
|
-
"@qw-ui/
|
|
125
|
-
"@qw-ui/
|
|
128
|
+
"@qw-ui/tsconfig": "0.0.1",
|
|
129
|
+
"@qw-ui/eslint-config": "0.0.1"
|
|
126
130
|
},
|
|
127
131
|
"size-limit": [
|
|
128
132
|
{
|