@qw-ui/core 0.3.0-next.0 → 0.3.0-next.2

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 ADDED
@@ -0,0 +1,59 @@
1
+ # @qw-ui/core
2
+
3
+ > L2 · Headless Core — zag.js machine 薄包装,纯 TS / SSR-safe,零样式。
4
+
5
+ **层级 L2 · 行为唯一事实源**。所有组件的交互逻辑、状态机、ARIA 语义、键盘与 focus 行为都只写在这里;React/Vue 适配层零业务逻辑。纯 TypeScript,无样式、SSR 安全。
6
+
7
+ ## 安装
8
+
9
+ ```bash
10
+ pnpm add @qw-ui/core
11
+ ```
12
+
13
+ 无框架 peer 依赖(内部封装 `@zag-js/*`)。通常你不直接装它——`@qw-ui/react` / `@qw-ui/vue` 已把它作为依赖带上;只有自建适配层或直接消费状态机时才单独装。
14
+
15
+ ## 最小示例
16
+
17
+ 按子路径导入某组件的 machine / 契约类型(每个组件一个 export 入口):
18
+
19
+ ```ts
20
+ import * as button from "@qw-ui/core/button";
21
+ import type { QwDialogProps, QwDialogApi } from "@qw-ui/core/dialog";
22
+ import { resolveIconName } from "@qw-ui/core/icon";
23
+ ```
24
+
25
+ 子路径与组件 slug 一一对应:`dialog / tabs / toast / button / empty / divider / loading / skeleton / top-bar / icon / search-bar / avatar / image / textarea / tag / input`。
26
+
27
+ ## 目录契约
28
+
29
+ 每个组件在 `src/<slug>/` 下必须齐备(G1 `verify:demos` 强制):
30
+
31
+ ```
32
+ index.ts + __test__/ + docs/{overview.md, accessibility.md, demos.ts, visual.ts}
33
+ ```
34
+
35
+ `docs/` 是文档站与双栈 demo 的唯一清单来源;行为改一处(core),双栈同步生效。
36
+
37
+ ## 关联包
38
+
39
+ | 包 | 关系 |
40
+ |---|---|
41
+ | [@qw-ui/react](../react/README.md) | 基于本包 machine 的 React 适配 |
42
+ | [@qw-ui/vue](../vue/README.md) | 基于本包 machine 的 Vue 适配 |
43
+ | [@qw-ui/tokens](../tokens/README.md) | 组件视觉所依赖的 T3 token(本包自身零样式) |
44
+ | [@qw-ui/icons](../icons/README.md) | `icon` 子路径的图标名解析依赖 |
45
+
46
+ ## 版本通道
47
+
48
+ | 通道 | 安装 | 说明 |
49
+ |---|---|---|
50
+ | `latest`(稳定) | `pnpm add @qw-ui/core` | `main` 通道,Release PR review 后发布 |
51
+ | `next`(预发布) | `pnpm add @qw-ui/core@next` | `release/next` 通道,高频自增 `x.y.z-next.N` |
52
+
53
+ 8 个公开包 `fixed` 锁步同版本号。npm 发布固定走 `registry.npmjs.org`;内网装依赖走 `registry.anpm.alibaba-inc.com`(tnpm 代理),发布 registry 不要指向内网。流程详见 [development-workflow.md](../../docs/development-workflow.md)。
54
+
55
+ ## 仓库 & License
56
+
57
+ - 主仓库:<http://gitlab.alibaba-inc.com/qianwen/qw-ui>
58
+ - 问题反馈:<http://gitlab.alibaba-inc.com/qianwen/qw-ui/issues>
59
+ - License:MIT
@@ -7,7 +7,7 @@
7
7
  */
8
8
  /** 方向全集:horizontal 水平(默认)/ vertical 垂直。 */
9
9
  export declare const DIVIDER_ORIENTATIONS: readonly ["horizontal", "vertical"];
10
- /** 粗细全集:thin 细 1px(默认)/ thick 粗 2px。 */
10
+ /** 粗细全集:thin 细 0.5px(默认)/ thick 粗 1px。 */
11
11
  export declare const DIVIDER_WEIGHTS: readonly ["thin", "thick"];
12
12
  /** Divider 各渲染部位全集(data-part 取值)。 */
13
13
  export declare const DIVIDER_PARTS: readonly ["root"];
@@ -18,6 +18,6 @@ export type DividerPart = (typeof DIVIDER_PARTS)[number];
18
18
  export interface QwDividerProps {
19
19
  /** 方向,默认 horizontal。 */
20
20
  orientation?: DividerOrientation;
21
- /** 粗细,默认 thin(细,1px)。 */
21
+ /** 粗细,默认 thin(细,0.5px)。 */
22
22
  weight?: DividerWeight;
23
23
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
  /** 方向全集:horizontal 水平(默认)/ vertical 垂直。 */
10
10
  const DIVIDER_ORIENTATIONS = ["horizontal", "vertical"];
11
- /** 粗细全集:thin 细 1px(默认)/ thick 粗 2px。 */
11
+ /** 粗细全集:thin 细 0.5px(默认)/ thick 粗 1px。 */
12
12
  const DIVIDER_WEIGHTS = ["thin", "thick"];
13
13
  /** Divider 各渲染部位全集(data-part 取值)。 */
14
14
  const DIVIDER_PARTS = ["root"];
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/divider/divider.ts"],"sourcesContent":["/**\n * Divider 契约 — 原型 A(无状态),core 无 machine,只承载双栈共享的类型契约。\n * 渲染契约:`role=\"separator\"`,`data-scope=\"divider\"`,`data-part=\"root\"`;\n * `data-orientation` / `data-weight` 摊平枚举,`vertical` 额外输出 `aria-orientation`。\n *\n * 方向 / 粗细词表以运行时常量数组为唯一事实源(类型从常量推导),__test__ 直接锁死。\n */\n\n/** 方向全集:horizontal 水平(默认)/ vertical 垂直。 */\nexport const DIVIDER_ORIENTATIONS = [\"horizontal\", \"vertical\"] as const;\n\n/** 粗细全集:thin 细 1px(默认)/ thick 粗 2px。 */\nexport const DIVIDER_WEIGHTS = [\"thin\", \"thick\"] as const;\n\n/** Divider 各渲染部位全集(data-part 取值)。 */\nexport const DIVIDER_PARTS = [\"root\"] as const;\n\nexport type DividerOrientation = (typeof DIVIDER_ORIENTATIONS)[number];\nexport type DividerWeight = (typeof DIVIDER_WEIGHTS)[number];\nexport type DividerPart = (typeof DIVIDER_PARTS)[number];\n\n/** QwDividerProps — 双栈共享的行为契约(全字段可选)。 */\nexport interface QwDividerProps {\n /** 方向,默认 horizontal。 */\n orientation?: DividerOrientation;\n /** 粗细,默认 thin(细,1px)。 */\n weight?: DividerWeight;\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,uBAAuB,CAAC,cAAc,UAAU;;AAG7D,MAAa,kBAAkB,CAAC,QAAQ,OAAO;;AAG/C,MAAa,gBAAgB,CAAC,MAAM"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/divider/divider.ts"],"sourcesContent":["/**\n * Divider 契约 — 原型 A(无状态),core 无 machine,只承载双栈共享的类型契约。\n * 渲染契约:`role=\"separator\"`,`data-scope=\"divider\"`,`data-part=\"root\"`;\n * `data-orientation` / `data-weight` 摊平枚举,`vertical` 额外输出 `aria-orientation`。\n *\n * 方向 / 粗细词表以运行时常量数组为唯一事实源(类型从常量推导),__test__ 直接锁死。\n */\n\n/** 方向全集:horizontal 水平(默认)/ vertical 垂直。 */\nexport const DIVIDER_ORIENTATIONS = [\"horizontal\", \"vertical\"] as const;\n\n/** 粗细全集:thin 细 0.5px(默认)/ thick 粗 1px。 */\nexport const DIVIDER_WEIGHTS = [\"thin\", \"thick\"] as const;\n\n/** Divider 各渲染部位全集(data-part 取值)。 */\nexport const DIVIDER_PARTS = [\"root\"] as const;\n\nexport type DividerOrientation = (typeof DIVIDER_ORIENTATIONS)[number];\nexport type DividerWeight = (typeof DIVIDER_WEIGHTS)[number];\nexport type DividerPart = (typeof DIVIDER_PARTS)[number];\n\n/** QwDividerProps — 双栈共享的行为契约(全字段可选)。 */\nexport interface QwDividerProps {\n /** 方向,默认 horizontal。 */\n orientation?: DividerOrientation;\n /** 粗细,默认 thin(细,0.5px)。 */\n weight?: DividerWeight;\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,uBAAuB,CAAC,cAAc,UAAU;;AAG7D,MAAa,kBAAkB,CAAC,QAAQ,OAAO;;AAG/C,MAAa,gBAAgB,CAAC,MAAM"}
@@ -131,7 +131,7 @@ function connect(service, normalizeProps) {
131
131
  return normalizeProps.element(compact({
132
132
  "data-scope": "search-bar",
133
133
  "data-part": "root",
134
- "data-focused": focused ? "true" : void 0,
134
+ "data-focused": focused && !readOnly && !disabled ? "true" : void 0,
135
135
  "data-disabled": disabled ? "true" : void 0,
136
136
  "data-readonly": readOnly ? "true" : void 0
137
137
  }));
@@ -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 \"data-focused\": focused ? \"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":";;;;;;AAiHA,SAAS,mBAAmB,WAA0D;CACpF,IAAI,cAAc,OAAO,OAAO,EAAE,UAAU,QAAQ;CACpD,IAAI,cAAc,QAAQ,aAAa,MAAM,OAAO,EAAE,UAAU,YAAY;CAC5E,OAAO,EAAE,UAAU,UAAU,YAAY,YAAY;AACvD;;;;;AAMA,MAAa,UAAU,cAA+B;CACpD,MAAM,EAAE,SAAS;EACf,OAAO;GACL,cAAc;GACd,UAAU;GACV,UAAU;GACV,GAAG;EACL;CACF;CAEA,eAAe;EACb,OAAO;CACT;CAEA,QAAQ,EAAE,UAAU,QAAQ;EAC1B,OAAO;GACL,OAAO,eAAuB;IAC5B,MAAM,kBAAkB,KAAK,OAAO;IACpC,OAAO;KACL,cAAc,KAAK,cAAc,KAAK;KACtC,MAAM;KACN,GAAI,oBAAoB,KAAA,IAAY,EAAE,OAAO,gBAAgB,IAAI,CAAC;KAClE,SAAS,OAAO,MAAM;MACpB,IAAI,UAAU,MACZ,KAAK,UAAU,CAAC,GAAG,KAAK;KAE5B;IACF;GACF,CAAC;GACD,SAAS,gBAAgB,EACvB,cAAc,MAChB,EAAE;EACJ;CACF;CAEA,UAAU;EACR,gBAAgB,EAAE,QAAQ;GACxB,OAAO,mBAAmB,KAAK,WAAW,CAAC;EAC7C;EACA,UAAU,EAAE,SAAS,YAAY;GAC/B,MAAM,MAAM,SAAS,iBAAiB;GACtC,IAAI,IAAI,aAAa,UAAU,OAAO;GACtC,IAAI,IAAI,aAAa,SAAS,OAAO;GACrC,IAAI,IAAI,aAAa,SAAS,OAAO,QAAQ,IAAI,SAAS;GAC1D,OAAO,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS;EACvC;CACF;CAEA,MAAM,EAAE,OAAO,MAAM,WAAW;EAC9B,MAAM,OAAO,KAAK,OAAO,CAAC,SAAS;GACjC,MAAM,WAAW,KAAK,OAAO;GAC7B,IAAI,aAAa,KAAA,KAAa,aAAa,QAAQ,IAAI,OAAO,GAC5D,QAAQ,IAAI,SAAS,QAAQ;EAEjC,CAAC;CACH;CAEA,QAAQ,EACN,MAAM,EACJ,IAAI;EACF,aAAa,EACX,SAAS,CAAC,UAAU,EACtB;EACA,eAAe,EACb,SAAS,CAAC,cAAc,eAAe,EACzC;EACA,aAAa,EACX,SAAS,CAAC,YAAY,EACxB;EACA,QAAQ,EACN,SAAS,CAAC,gBAAgB,EAC5B;EACA,QAAQ,EACN,SAAS,CAAC,gBAAgB,EAC5B;CACF,EACF,EACF;CAEA,iBAAiB,EACf,SAAS;EACP,SAAS,EAAE,SAAS,OAAO,QAAQ;GACjC,IAAI,MAAM,SAAS,aAAa;IAC9B,MAAM,YAAY,KAAK,WAAW,KAAK;IACvC,MAAM,UAAU,QAAQ,IAAI,OAAO;IACnC,IAAI,OAAO,MAAM;IACjB,IAAI,YAAY,KAAK,KAAK,SAAS,WAEjC,OAAO;IAET,QAAQ,IAAI,SAAS,IAAI;GAC3B;EACF;EACA,WAAW,EAAE,WAAW;GACtB,QAAQ,IAAI,SAAS,EAAE;EACzB;EACA,WAAW,EAAE,SAAS,OAAO,QAAQ;GACnC,IAAI,MAAM,SAAS,aAAa;IAC9B,MAAM,OAAO,MAAM;IACnB,MAAM,OAAO,QAAQ,IAAI,SAAS;IAClC,QAAQ,IAAI,WAAW,IAAI;IAC3B,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,GAAG;IACrC,IAAI,CAAC,QAAQ,MAAM,KAAK,QAAQ,CAAC,GAAG;GACtC;EACF;EACA,eAAe,EAAE,SAAS,QAAQ;GAChC,KAAK,UAAU,CAAC,GAAG,QAAQ,IAAI,OAAO,CAAC;EACzC;EACA,cAAc,EAAE,QAAQ;GACtB,KAAK,SAAS,CAAC,GAAG;EACpB;EACA,eAAe,EAAE,SAAS,MAAM,SAAS;GACvC,MAAM,SAAS,MAAM,QAAQ,WAAW,KAAK,QAAQ,IAAI,KAAK,QAAQ;GACtE,IAAI,QAAQ,SAAS,YAAY,OAAO,iBAAiB,CAAC,KAAK,UAAU,GACvE,QAAQ,IAAI,SAAS,EAAE;GAEzB,KAAK,UAAU,CAAC,GAAG;EACrB;EACA,gBAAgB,CAEhB;EACA,eAAe,CAEf;CACF,EACF;AACF,CAAC;;;;;AAoCD,SAAgB,QAA6B,SAAmC,gBAAoD;CAClI,MAAM,EAAE,SAAS,UAAU,MAAM,SAAS;CAE1C,MAAM,QAAQ,QAAQ,IAAI,OAAO;CACjC,MAAM,UAAU,QAAQ,IAAI,SAAS;CACrC,MAAM,YAAY,SAAS,WAAW;CACtC,MAAM,WAAW,CAAC,CAAC,KAAK,UAAU;CAClC,MAAM,WAAW,CAAC,CAAC,KAAK,UAAU;CAClC,MAAM,YAAY,KAAK,WAAW;CAClC,MAAM,SAAS,KAAK,QAAQ;CAC5B,MAAM,SAAS,KAAK,QAAQ;CAE5B,OAAO;EACL;EACA;EACA;EAEA,QAAQ;GACN,KAAK,EAAE,MAAM,cAAc,CAAC;EAC9B;EAEA,SAAS;GACP,KAAK,EAAE,MAAM,SAAS,CAAC;EACzB;EAEA,eAAe;GACb,OAAO,eAAe,QACpB,QAAQ;IACN,cAAc;IACd,aAAa;IACb,gBAAgB,UAAU,SAAS,KAAA;IACnC,iBAAiB,WAAW,SAAS,KAAA;IACrC,iBAAiB,WAAW,SAAS,KAAA;GACvC,CAAC,CACH;EACF;EAEA,gBAAgB;GACd,OAAO,eAAe,QACpB,QAAQ;IACN,cAAc;IACd,aAAa;GACf,CAAC,CACH;EACF;EAEA,gBAAgB;GACd,OAAO,eAAe,MACpB,QAAQ;IACN,cAAc;IACd,aAAa;IACb,MAAM;IACN,MAAM;IACN;IACA,aAAa,KAAK,aAAa;IAC/B;IACA;IACA,WAAW,aAAa,YAAY,IAAI,YAAY,KAAA;IACpD,iBAAiB,WAAY,SAAmB,KAAA;IAChD,iBAAiB,WAAY,SAAmB,KAAA;IAChD,UAAU;KACR,KAAK;MAAE,MAAM;MAAa,SAAS;KAAK,CAAC;IAC3C;IACA,SAAS;KACP,KAAK;MAAE,MAAM;MAAa,SAAS;KAAM,CAAC;IAC5C;IACA,SAAS,OAA0C;KACjD,MAAM,SAAS,MAAM;KAErB,MAAM,cAAc,CAAC,CADA,MAAsD,aACxC;KACnC,IAAI,OAAO,OAAO;KAElB,IAAI,CAAC,aAAa;MAChB,MAAM,SAAS,KAAK,WAAW,KAAK;MACpC,IAAI,SAAS,KAAK,KAAK,SAAS,QAC9B,OAAO,KAAK,MAAM,GAAG,MAAM;KAE/B;KAEA,QAAQ,IAAI,SAAS,IAAI;IAC3B;IACA,UAAU,OAA4C;KACpD,IAAI,MAAM,QAAQ,SAChB,KAAK,EAAE,MAAM,SAAS,CAAC;IAE3B;GACF,CAAC,CACH;EACF;EAEA,eAAe;GACb,OAAO,eAAe,QACpB,QAAQ;IACN,cAAc;IACd,aAAa;IACb,eAAe;GACjB,CAAC,CACH;EACF;EAEA,sBAAsB;GACpB,OAAO,eAAe,OACpB,QAAQ;IACN,cAAc;IACd,aAAa;IACb,MAAM;IACN,UAAU,YAAY;IACtB,cAAc;IACd,eAAe,CAAC;IAChB,QAAQ,CAAC;IACT,UAAU;KACR,KAAK,EAAE,MAAM,cAAc,CAAC;IAC9B;GACF,CAAC,CACH;EACF;EAEA,uBAAuB;GACrB,OAAO,eAAe,OACpB,QAAQ;IACN,cAAc;IACd,aAAa;IACb,MAAM;IACN;IACA,cAAc,QAAQ;IACtB,eAAe,QAAQ;IACvB,UAAU;KACR,IAAI,QAAQ,SAAS,UACnB,KAAK;MAAE,MAAM;MAAU,KAAK;KAAS,CAAC;UACjC,IAAI,QAAQ,SAAS,UAC1B,KAAK,EAAE,MAAM,SAAS,CAAC;IAE3B;GACF,CAAC,CACH;EACF;EAEA,uBAAuB;GACrB,OAAO,eAAe,OACpB,QAAQ;IACN,cAAc;IACd,aAAa;IACb,MAAM;IACN;IACA,cAAc,QAAQ;IACtB,eAAe,QAAQ;IACvB,UAAU;KACR,IAAI,QAAQ,SAAS,UACnB,KAAK;MAAE,MAAM;MAAU,KAAK;KAAS,CAAC;UACjC,IAAI,QAAQ,SAAS,UAC1B,KAAK,EAAE,MAAM,SAAS,CAAC;IAE3B;GACF,CAAC,CACH;EACF;CACF;AACF;;;;AAKA,SAAgB,qBAAqB,QAA0B,CAAC,GAA8B;CAC5F,OAAO;EACL,cAAc,MAAM,gBAAgB;EACpC,UAAU,MAAM,YAAY;EAC5B,UAAU,MAAM,YAAY;EAC5B,GAAG;CACL;AACF"}
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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"}
@@ -58,7 +58,7 @@ const machine = createMachine({
58
58
  "VALUE.SET": { actions: ["setValue"] },
59
59
  "VALUE.CLEAR": { actions: ["clearValue", "invokeOnClear"] },
60
60
  "FOCUS.SET": { actions: ["setFocused"] },
61
- PASTE_RECOGNIZE: { actions: ["invokeOnPasteRecognize"] }
61
+ PASTE_RECOGNIZE: { actions: ["pasteValue", "invokeOnPasteRecognize"] }
62
62
  } } },
63
63
  implementations: { actions: {
64
64
  setValue({ context, event, prop }) {
@@ -84,8 +84,16 @@ const machine = createMachine({
84
84
  invokeOnClear({ prop }) {
85
85
  prop("onClear")?.();
86
86
  },
87
- invokeOnPasteRecognize({ prop }) {
88
- prop("onPasteRecognize")?.();
87
+ pasteValue({ context, event, prop }) {
88
+ if (event.type === "PASTE_RECOGNIZE" && event.value !== void 0) {
89
+ const maxLength = prop("maxLength") ?? 0;
90
+ let next = event.value;
91
+ if (maxLength > 0 && next.length > maxLength) next = next.slice(0, maxLength);
92
+ context.set("value", next);
93
+ }
94
+ },
95
+ invokeOnPasteRecognize({ prop, event }) {
96
+ if (event.type === "PASTE_RECOGNIZE") prop("onPasteRecognize")?.(event.value ?? "");
89
97
  },
90
98
  invokeOnFocus() {},
91
99
  invokeOnBlur() {}
@@ -118,8 +126,11 @@ function connect(service, normalizeProps) {
118
126
  clear() {
119
127
  send({ type: "VALUE.CLEAR" });
120
128
  },
121
- pasteRecognize() {
122
- send({ type: "PASTE_RECOGNIZE" });
129
+ pasteRecognize(value) {
130
+ send({
131
+ type: "PASTE_RECOGNIZE",
132
+ value
133
+ });
123
134
  },
124
135
  getRootProps() {
125
136
  return normalizeProps.element(compact({
@@ -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?: () => 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\" };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnClear\"\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: [\"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 invokeOnPasteRecognize({ prop }) {\n prop(\"onPasteRecognize\")?.();\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(): 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() {\n send({ type: \"PASTE_RECOGNIZE\" });\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 /** 输入变化回调。 */\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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"}
@@ -30,8 +30,8 @@ export interface QwTextAreaProps {
30
30
  onChange?: (value: string) => void;
31
31
  /** 清空回调。 */
32
32
  onClear?: () => void;
33
- /** 粘贴并识别回调。 */
34
- onPasteRecognize?: () => void;
33
+ /** 粘贴并识别回调,参数为粘贴的文本。 */
34
+ onPasteRecognize?: (value: string) => void;
35
35
  /** 聚焦回调。 */
36
36
  onFocus?: () => void;
37
37
  /** 失焦回调。 */
@@ -62,8 +62,9 @@ interface TextAreaSchema extends MachineSchema {
62
62
  focused: boolean;
63
63
  } | {
64
64
  type: "PASTE_RECOGNIZE";
65
+ value?: string;
65
66
  };
66
- action: "setValue" | "clearValue" | "setFocused" | "invokeOnClear" | "invokeOnPasteRecognize" | "invokeOnFocus" | "invokeOnBlur";
67
+ action: "setValue" | "clearValue" | "setFocused" | "invokeOnClear" | "pasteValue" | "invokeOnPasteRecognize" | "invokeOnFocus" | "invokeOnBlur";
67
68
  }
68
69
  /**
69
70
  * TextArea zag.js machine。
@@ -86,8 +87,8 @@ export interface TextAreaApi<T extends PropTypes = PropTypes> {
86
87
  showClear: boolean;
87
88
  /** 清空输入。 */
88
89
  clear(): void;
89
- /** 触发粘贴并识别回调。 */
90
- pasteRecognize(): void;
90
+ /** 触发粘贴并识别回调(可传入剪贴板文本,自动填入)。 */
91
+ pasteRecognize(value?: string): void;
91
92
  /** 根容器 prop-getter。 */
92
93
  getRootProps(): T["element"];
93
94
  /** textarea 元素 prop-getter。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qw-ui/core",
3
- "version": "0.3.0-next.0",
3
+ "version": "0.3.0-next.2",
4
4
  "description": "L2 · Headless Core — zag.js machine 薄包装,纯 TS / SSR-safe,零样式。",
5
5
  "license": "MIT",
6
6
  "author": "qw-ui contributors",
@@ -99,7 +99,7 @@
99
99
  "@zag-js/dialog": "^1.42.0",
100
100
  "@zag-js/tabs": "^1.42.0",
101
101
  "@zag-js/utils": "1.42.0",
102
- "@qw-ui/icons": "^0.3.0-next.0"
102
+ "@qw-ui/icons": "^0.3.0-next.2"
103
103
  },
104
104
  "devDependencies": {
105
105
  "@microsoft/api-extractor": "^7.58.9",