@qw-ui/core 0.2.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"}
package/dist/index.d.ts CHANGED
@@ -22,3 +22,7 @@ export { AVATAR_BADGE_TYPES, AVATAR_LOAD_STATUS, AVATAR_PARTS, AVATAR_SHAPES, AV
22
22
  export type { AvatarBadgeProps, AvatarContentPresence, AvatarFallbackProps, AvatarImageElementProps, AvatarImageProps, AvatarLoadStatus, AvatarPart, AvatarRootProps, AvatarShape, AvatarSize, BadgeConfig, BadgeType, FallbackMode, QwAvatarProps, ResolvedAvatarState, } from "./avatar/index.js";
23
23
  export { machine as textAreaMachine, connect as textAreaConnect, createTextAreaProps } from "./textarea/index.js";
24
24
  export type { QwTextAreaProps, TextAreaApi } from "./textarea/index.js";
25
+ export { TAG_PARTS, TAG_SIZES, TAG_VARIANTS } from "./tag/index.js";
26
+ export type { TagPart, TagSize, TagVariant, QwTagProps } from "./tag/index.js";
27
+ export { createInputProps } from "./input/index.js";
28
+ export type { QwInputProps, InputApi, InputMode, InputReadOnlyOverflow } from "./input/index.js";
package/dist/index.js CHANGED
@@ -10,4 +10,6 @@ import { TOPBAR_VARIANTS, createTopBarProps } from "./top-bar/index.js";
10
10
  import { ICON_FALLBACK_NAME, ICON_PARTS, resolveIconName } from "./icon/index.js";
11
11
  import { AVATAR_BADGE_TYPES, AVATAR_LOAD_STATUS, AVATAR_PARTS, AVATAR_SHAPES, AVATAR_SIZES, BADGE_COUNT_MAX, extractInitials, getAvatarBadgeProps, getAvatarFallbackProps, getAvatarImageElementProps, getAvatarImageProps, getAvatarRootProps, resolveAvatarState } from "./avatar/index.js";
12
12
  import { connect as connect$1, createTextAreaProps, machine } from "./textarea/index.js";
13
- 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_TOAST_DURATION, DEFAULT_TOAST_POSITION, DIVIDER_ORIENTATIONS, DIVIDER_PARTS, DIVIDER_WEIGHTS, EMPTY_PARTS, ICON_FALLBACK_NAME, ICON_PARTS, LOADING_SIZES, SKELETON_ANIMATIONS, SKELETON_PARTS, SKELETON_RADII, SKELETON_VARIANTS, TOAST_ICON_TYPES, TOAST_PARTS, TOAST_POSITIONS, TOPBAR_VARIANTS, connect, createDialogProps, createTabsProps, createTextAreaProps, createTopBarProps, dialogMachine, extractInitials, getAvatarBadgeProps, getAvatarFallbackProps, getAvatarImageElementProps, getAvatarImageProps, getAvatarRootProps, getButtonRootProps, isToastIconType, resolveAvatarState, resolveButtonState, resolveIconName, resolveToastDuration, tabsMachine, connect$1 as textAreaConnect, machine as textAreaMachine };
13
+ import { TAG_PARTS, TAG_SIZES, TAG_VARIANTS } from "./tag/index.js";
14
+ import { createInputProps } from "./input/index.js";
15
+ 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_TOAST_DURATION, DEFAULT_TOAST_POSITION, DIVIDER_ORIENTATIONS, DIVIDER_PARTS, DIVIDER_WEIGHTS, EMPTY_PARTS, ICON_FALLBACK_NAME, ICON_PARTS, LOADING_SIZES, SKELETON_ANIMATIONS, SKELETON_PARTS, SKELETON_RADII, SKELETON_VARIANTS, TAG_PARTS, TAG_SIZES, TAG_VARIANTS, TOAST_ICON_TYPES, TOAST_PARTS, TOAST_POSITIONS, TOPBAR_VARIANTS, connect, createDialogProps, createInputProps, createTabsProps, createTextAreaProps, createTopBarProps, dialogMachine, extractInitials, getAvatarBadgeProps, getAvatarFallbackProps, getAvatarImageElementProps, getAvatarImageProps, getAvatarRootProps, getButtonRootProps, isToastIconType, resolveAvatarState, resolveButtonState, resolveIconName, resolveToastDuration, tabsMachine, connect$1 as textAreaConnect, machine as textAreaMachine };
@@ -0,0 +1 @@
1
+ export * from "./input.js";
@@ -0,0 +1,139 @@
1
+ import { createMachine } from "@zag-js/core";
2
+ import { compact } from "@zag-js/utils";
3
+ //#region src/input/input.ts
4
+ /**
5
+ * Input zag.js machine。
6
+ * 唯一状态来源:value(受控/非受控由 bindable 收敛)。
7
+ * readOnly / overflow / maxLines 为纯派生,不进入状态。
8
+ */
9
+ const machine = createMachine({
10
+ props({ props }) {
11
+ return {
12
+ defaultValue: "",
13
+ readOnly: false,
14
+ readOnlyOverflow: "wrap",
15
+ ...props
16
+ };
17
+ },
18
+ initialState() {
19
+ return "idle";
20
+ },
21
+ context({ bindable, prop }) {
22
+ return { value: bindable(() => {
23
+ const controlledValue = prop("value");
24
+ return {
25
+ defaultValue: prop("defaultValue") ?? "",
26
+ sync: true,
27
+ ...controlledValue !== void 0 ? { value: controlledValue } : {},
28
+ onChange(value, prev) {
29
+ if (value !== prev) prop("onChange")?.(value);
30
+ }
31
+ };
32
+ }) };
33
+ },
34
+ computed: {
35
+ mode({ prop }) {
36
+ return prop("readOnly") ? "readonly" : "input";
37
+ },
38
+ overflow({ prop }) {
39
+ if (!prop("readOnly")) return void 0;
40
+ return prop("readOnlyOverflow") ?? "wrap";
41
+ },
42
+ clampLines({ prop, computed }) {
43
+ if (computed("mode") !== "readonly") return void 0;
44
+ if (computed("overflow") !== "wrap") return void 0;
45
+ const max = prop("maxLines");
46
+ return typeof max === "number" && max > 0 ? Math.floor(max) : void 0;
47
+ }
48
+ },
49
+ watch({ track, prop, context }) {
50
+ track([() => prop("value")], () => {
51
+ const external = prop("value");
52
+ if (external !== void 0 && external !== context.get("value")) context.set("value", external);
53
+ });
54
+ },
55
+ states: { idle: { on: { "VALUE.SET": { actions: ["setValue"] } } } },
56
+ implementations: { actions: { setValue({ context, event }) {
57
+ if (event.type === "VALUE.SET") context.set("value", event.value);
58
+ } } }
59
+ });
60
+ /**
61
+ * connect:把 service 接成业务友好的 prop-getter。
62
+ * 适配层只需调用 connect(service, normalizeProps),按 api.mode 选择渲染子树。
63
+ */
64
+ function connect(service, normalizeProps) {
65
+ const { context, computed, prop } = service;
66
+ const value = context.get("value");
67
+ const mode = computed("mode");
68
+ const overflow = computed("overflow");
69
+ const clampLines = computed("clampLines");
70
+ return {
71
+ value,
72
+ mode,
73
+ overflow,
74
+ getRootProps() {
75
+ return normalizeProps.element(compact({
76
+ "data-scope": "input",
77
+ "data-part": "root",
78
+ "data-mode": mode
79
+ }));
80
+ },
81
+ getTitleProps() {
82
+ return normalizeProps.element(compact({
83
+ "data-scope": "input",
84
+ "data-part": "title"
85
+ }));
86
+ },
87
+ getFieldProps() {
88
+ return normalizeProps.element(compact({
89
+ "data-scope": "input",
90
+ "data-part": "field"
91
+ }));
92
+ },
93
+ getInputProps() {
94
+ return normalizeProps.input(compact({
95
+ "data-scope": "input",
96
+ "data-part": "input",
97
+ type: "text",
98
+ value,
99
+ placeholder: prop("placeholder"),
100
+ onChange(event) {
101
+ const target = event.target;
102
+ context.set("value", target.value);
103
+ }
104
+ }));
105
+ },
106
+ getIconProps() {
107
+ return normalizeProps.element(compact({
108
+ "data-scope": "input",
109
+ "data-part": "icon",
110
+ onClick() {
111
+ prop("onIconClick")?.();
112
+ }
113
+ }));
114
+ },
115
+ getReadonlyProps() {
116
+ return normalizeProps.element(compact({
117
+ "data-scope": "input",
118
+ "data-part": "readonly",
119
+ "data-overflow": overflow,
120
+ style: clampLines ? { ["--qw-input-max-lines"]: String(clampLines) } : void 0
121
+ }));
122
+ }
123
+ };
124
+ }
125
+ /**
126
+ * 把 QwInputProps 转成 machine props(默认值收口)。
127
+ */
128
+ function createInputProps(props = {}) {
129
+ return {
130
+ defaultValue: props.defaultValue ?? "",
131
+ readOnly: props.readOnly ?? false,
132
+ readOnlyOverflow: props.readOnlyOverflow ?? "wrap",
133
+ ...props
134
+ };
135
+ }
136
+ //#endregion
137
+ export { connect, createInputProps, machine };
138
+
139
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/input/input.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 * - `wrap`:换行展示(默认),可配 `maxLines` 限制最大行数。\n * - `ellipsis`:单行超出省略。\n */\nexport type InputReadOnlyOverflow = \"wrap\" | \"ellipsis\";\n\n/**\n * 渲染模式(由 readOnly 派生)。\n */\nexport type InputMode = \"input\" | \"readonly\";\n\n/**\n * Input 公共 props 契约(双栈共享,不含框架特有节点类型)。\n */\nexport interface QwInputProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** true=只读模式,false=输入模式。默认 false。 */\n readOnly?: boolean;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。默认 \"\"。 */\n defaultValue?: string;\n /** 输入变化回调(仅输入模式触发)。 */\n onChange?: (value: string) => void;\n /** 输入模式提示文本(右对齐)。 */\n placeholder?: string;\n /** 左侧标题文本(宽度自适应)。 */\n title?: string;\n /** icon 点击回调(仅输入模式)。 */\n onIconClick?: () => void;\n /** 只读模式溢出策略。默认 \"wrap\"。 */\n readOnlyOverflow?: InputReadOnlyOverflow;\n /** 只读换行最大行数(仅 wrap 生效;缺省不限行数)。 */\n maxLines?: number;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface InputSchema extends MachineSchema {\n props: QwInputProps;\n context: {\n value: string;\n };\n computed: {\n mode: InputMode;\n overflow: InputReadOnlyOverflow | undefined;\n clampLines: number | undefined;\n };\n state: \"idle\";\n event: { type: \"VALUE.SET\"; value: string };\n action: \"setValue\";\n}\n\n/**\n * Input zag.js machine。\n * 唯一状态来源:value(受控/非受控由 bindable 收敛)。\n * readOnly / overflow / maxLines 为纯派生,不进入状态。\n */\nexport const machine = createMachine<InputSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n readOnly: false,\n readOnlyOverflow: \"wrap\",\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 };\n },\n\n computed: {\n mode({ prop }) {\n return prop(\"readOnly\") ? \"readonly\" : \"input\";\n },\n overflow({ prop }) {\n if (!prop(\"readOnly\")) return undefined;\n return prop(\"readOnlyOverflow\") ?? \"wrap\";\n },\n clampLines({ prop, computed }) {\n if (computed(\"mode\") !== \"readonly\") return undefined;\n if (computed(\"overflow\") !== \"wrap\") return undefined;\n const max = prop(\"maxLines\");\n return typeof max === \"number\" && max > 0 ? Math.floor(max) : undefined;\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 },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event }) {\n if (event.type === \"VALUE.SET\") {\n context.set(\"value\", event.value);\n }\n },\n },\n },\n});\n\n/**\n * Input api 类型(由 connect 返回)。\n */\nexport interface InputApi<T extends PropTypes = PropTypes> {\n /** 当前值。 */\n value: string;\n /** 渲染模式:input | readonly。 */\n mode: InputMode;\n /** 只读溢出策略(输入模式为 undefined)。 */\n overflow: InputReadOnlyOverflow | undefined;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** 左侧标题 prop-getter。 */\n getTitleProps(): 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 getReadonlyProps(): T[\"element\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps),按 api.mode 选择渲染子树。\n */\nexport function connect<T extends PropTypes>(service: Service<InputSchema>, normalizeProps: NormalizeProps<T>): InputApi<T> {\n const { context, computed, prop } = service;\n\n const value = context.get(\"value\");\n const mode = computed(\"mode\");\n const overflow = computed(\"overflow\");\n const clampLines = computed(\"clampLines\");\n\n return {\n value,\n mode,\n overflow,\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"input\",\n \"data-part\": \"root\",\n \"data-mode\": mode,\n }),\n );\n },\n\n getTitleProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"input\",\n \"data-part\": \"title\",\n }),\n );\n },\n\n getFieldProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"input\",\n \"data-part\": \"field\",\n }),\n );\n },\n\n getInputProps() {\n return normalizeProps.input(\n compact({\n \"data-scope\": \"input\",\n \"data-part\": \"input\",\n type: \"text\" as const,\n value,\n placeholder: prop(\"placeholder\"),\n onChange(event: JSX.ChangeEvent<HTMLInputElement>) {\n const target = event.target as HTMLInputElement;\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", target.value);\n },\n }),\n );\n },\n\n getIconProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"input\",\n \"data-part\": \"icon\",\n onClick() {\n prop(\"onIconClick\")?.();\n },\n }),\n );\n },\n\n getReadonlyProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"input\",\n \"data-part\": \"readonly\",\n \"data-overflow\": overflow,\n style: clampLines ? ({ [\"--qw-input-max-lines\"]: String(clampLines) } as Record<string, string>) : undefined,\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwInputProps 转成 machine props(默认值收口)。\n */\nexport function createInputProps(props: QwInputProps = {}): Partial<QwInputProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n readOnly: props.readOnly ?? false,\n readOnlyOverflow: props.readOnlyOverflow ?? \"wrap\",\n ...props,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import type { MachineSchema, Service } from "@zag-js/core";
2
+ import type { PropTypes, NormalizeProps } from "@zag-js/types";
3
+ /**
4
+ * 只读模式溢出策略。
5
+ * - `wrap`:换行展示(默认),可配 `maxLines` 限制最大行数。
6
+ * - `ellipsis`:单行超出省略。
7
+ */
8
+ export type InputReadOnlyOverflow = "wrap" | "ellipsis";
9
+ /**
10
+ * 渲染模式(由 readOnly 派生)。
11
+ */
12
+ export type InputMode = "input" | "readonly";
13
+ /**
14
+ * Input 公共 props 契约(双栈共享,不含框架特有节点类型)。
15
+ */
16
+ export interface QwInputProps {
17
+ /** 唯一 id 前缀。 */
18
+ id?: string;
19
+ /** true=只读模式,false=输入模式。默认 false。 */
20
+ readOnly?: boolean;
21
+ /** 受控值。 */
22
+ value?: string;
23
+ /** 非受控初始值。默认 ""。 */
24
+ defaultValue?: string;
25
+ /** 输入变化回调(仅输入模式触发)。 */
26
+ onChange?: (value: string) => void;
27
+ /** 输入模式提示文本(右对齐)。 */
28
+ placeholder?: string;
29
+ /** 左侧标题文本(宽度自适应)。 */
30
+ title?: string;
31
+ /** icon 点击回调(仅输入模式)。 */
32
+ onIconClick?: () => void;
33
+ /** 只读模式溢出策略。默认 "wrap"。 */
34
+ readOnlyOverflow?: InputReadOnlyOverflow;
35
+ /** 只读换行最大行数(仅 wrap 生效;缺省不限行数)。 */
36
+ maxLines?: number;
37
+ }
38
+ /**
39
+ * 内部 schema,用于 createMachine 类型推导。
40
+ */
41
+ interface InputSchema extends MachineSchema {
42
+ props: QwInputProps;
43
+ context: {
44
+ value: string;
45
+ };
46
+ computed: {
47
+ mode: InputMode;
48
+ overflow: InputReadOnlyOverflow | undefined;
49
+ clampLines: number | undefined;
50
+ };
51
+ state: "idle";
52
+ event: {
53
+ type: "VALUE.SET";
54
+ value: string;
55
+ };
56
+ action: "setValue";
57
+ }
58
+ /**
59
+ * Input zag.js machine。
60
+ * 唯一状态来源:value(受控/非受控由 bindable 收敛)。
61
+ * readOnly / overflow / maxLines 为纯派生,不进入状态。
62
+ */
63
+ export declare const machine: import("@zag-js/core").Machine<InputSchema>;
64
+ /**
65
+ * Input api 类型(由 connect 返回)。
66
+ */
67
+ export interface InputApi<T extends PropTypes = PropTypes> {
68
+ /** 当前值。 */
69
+ value: string;
70
+ /** 渲染模式:input | readonly。 */
71
+ mode: InputMode;
72
+ /** 只读溢出策略(输入模式为 undefined)。 */
73
+ overflow: InputReadOnlyOverflow | undefined;
74
+ /** 根容器 prop-getter。 */
75
+ getRootProps(): T["element"];
76
+ /** 左侧标题 prop-getter。 */
77
+ getTitleProps(): T["element"];
78
+ /** 输入区容器 prop-getter(输入模式)。 */
79
+ getFieldProps(): T["element"];
80
+ /** 输入框 prop-getter(输入模式)。 */
81
+ getInputProps(): T["input"];
82
+ /** 右侧图标容器 prop-getter(输入模式,承载点击)。 */
83
+ getIconProps(): T["element"];
84
+ /** 只读展示容器 prop-getter(只读模式)。 */
85
+ getReadonlyProps(): T["element"];
86
+ }
87
+ /**
88
+ * connect:把 service 接成业务友好的 prop-getter。
89
+ * 适配层只需调用 connect(service, normalizeProps),按 api.mode 选择渲染子树。
90
+ */
91
+ export declare function connect<T extends PropTypes>(service: Service<InputSchema>, normalizeProps: NormalizeProps<T>): InputApi<T>;
92
+ /**
93
+ * 把 QwInputProps 转成 machine props(默认值收口)。
94
+ */
95
+ export declare function createInputProps(props?: QwInputProps): Partial<QwInputProps>;
96
+ export {};
@@ -31,6 +31,7 @@ const machine = createMachine({
31
31
  const controlledValue = prop("value");
32
32
  return {
33
33
  defaultValue: prop("defaultValue") ?? "",
34
+ sync: true,
34
35
  ...controlledValue !== void 0 ? { value: controlledValue } : {},
35
36
  onChange(value, prev) {
36
37
  if (value !== prev) prop("onChange")?.(value);
@@ -52,10 +53,10 @@ const machine = createMachine({
52
53
  return context.get("value").length > 0;
53
54
  }
54
55
  },
55
- watch({ track, action, prop, context }) {
56
+ watch({ track, prop, context }) {
56
57
  track([() => prop("value")], () => {
57
58
  const external = prop("value");
58
- if (external !== void 0 && external !== context.get("value")) action(["setValue"]);
59
+ if (external !== void 0 && external !== context.get("value")) context.set("value", external);
59
60
  });
60
61
  },
61
62
  states: { idle: { on: {
@@ -130,7 +131,7 @@ function connect(service, normalizeProps) {
130
131
  return normalizeProps.element(compact({
131
132
  "data-scope": "search-bar",
132
133
  "data-part": "root",
133
- "data-focused": focused ? "true" : void 0,
134
+ "data-focused": focused && !readOnly && !disabled ? "true" : void 0,
134
135
  "data-disabled": disabled ? "true" : void 0,
135
136
  "data-readonly": readOnly ? "true" : void 0
136
137
  }));
@@ -167,11 +168,14 @@ function connect(service, normalizeProps) {
167
168
  });
168
169
  },
169
170
  onChange(event) {
170
- const next = event.target.value;
171
- send({
172
- type: "VALUE.SET",
173
- value: next
174
- });
171
+ const target = event.target;
172
+ const isComposing = !!event.nativeEvent?.isComposing;
173
+ let next = target.value;
174
+ if (!isComposing) {
175
+ const maxLen = prop("maxLength") ?? 0;
176
+ if (maxLen > 0 && next.length > maxLen) next = next.slice(0, maxLen);
177
+ }
178
+ context.set("value", next);
175
179
  },
176
180
  onKeyDown(event) {
177
181
  if (event.key === "Enter") send({ type: "SEARCH" });
@@ -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 ...(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, action, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n action([\"setValue\"]);\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 next = target.value;\n send({ type: \"VALUE.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 /** 左侧搜索图标:字符串文案或自定义节点。 */\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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"}
@@ -0,0 +1 @@
1
+ export * from "./tag.js";
@@ -0,0 +1,22 @@
1
+ //#region src/tag/tag.ts
2
+ /**
3
+ * Tag 契约 — 原型 A(无状态),core 无 machine,只承载双栈共享的类型契约。
4
+ * 渲染契约:`data-scope="tag"`,`data-part="root"`;
5
+ * `data-size` / `data-variant` / `data-bold` 摊平枚举。
6
+ *
7
+ * 尺寸 / 变体 / 部位词表以运行时常量数组为唯一事实源(类型从常量推导),__test__ 直接锁死。
8
+ */
9
+ /** 尺寸全集:small(小)/ medium(中,默认)/ large(大)。 */
10
+ const TAG_SIZES = [
11
+ "small",
12
+ "medium",
13
+ "large"
14
+ ];
15
+ /** 变体全集:solid 填充(默认)/ outlined 框线。 */
16
+ const TAG_VARIANTS = ["solid", "outlined"];
17
+ /** Tag 各渲染部位全集(data-part 取值)。 */
18
+ const TAG_PARTS = ["root"];
19
+ //#endregion
20
+ export { TAG_PARTS, TAG_SIZES, TAG_VARIANTS };
21
+
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/tag/tag.ts"],"sourcesContent":["/**\n * Tag 契约 — 原型 A(无状态),core 无 machine,只承载双栈共享的类型契约。\n * 渲染契约:`data-scope=\"tag\"`,`data-part=\"root\"`;\n * `data-size` / `data-variant` / `data-bold` 摊平枚举。\n *\n * 尺寸 / 变体 / 部位词表以运行时常量数组为唯一事实源(类型从常量推导),__test__ 直接锁死。\n */\n\n/** 尺寸全集:small(小)/ medium(中,默认)/ large(大)。 */\nexport const TAG_SIZES = [\"small\", \"medium\", \"large\"] as const;\n\n/** 变体全集:solid 填充(默认)/ outlined 框线。 */\nexport const TAG_VARIANTS = [\"solid\", \"outlined\"] as const;\n\n/** Tag 各渲染部位全集(data-part 取值)。 */\nexport const TAG_PARTS = [\"root\"] as const;\n\nexport type TagSize = (typeof TAG_SIZES)[number];\nexport type TagVariant = (typeof TAG_VARIANTS)[number];\nexport type TagPart = (typeof TAG_PARTS)[number];\n\n/**\n * QwTagProps — 双栈共享的行为契约(全字段可选)。\n * 颜色完全由业务方传入,组件不内置任何主题。\n */\nexport interface QwTagProps {\n /** 尺寸,默认 medium。 */\n size?: TagSize;\n /** 变体,默认 solid。 */\n variant?: TagVariant;\n /** 文字是否加粗,默认 false。 */\n bold?: boolean;\n /** 背景色,未传入时使用默认中性灰。 */\n bgcolor?: string;\n /** 文字色,未传入时使用默认灰。 */\n textcolor?: string;\n /** 边框色,仅在 variant=\"outlined\" 时生效;未传入时使用默认灰。 */\n borderColor?: string;\n}\n"],"mappings":";;;;;;;;;AASA,MAAa,YAAY;CAAC;CAAS;CAAU;AAAO;;AAGpD,MAAa,eAAe,CAAC,SAAS,UAAU;;AAGhD,MAAa,YAAY,CAAC,MAAM"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tag 契约 — 原型 A(无状态),core 无 machine,只承载双栈共享的类型契约。
3
+ * 渲染契约:`data-scope="tag"`,`data-part="root"`;
4
+ * `data-size` / `data-variant` / `data-bold` 摊平枚举。
5
+ *
6
+ * 尺寸 / 变体 / 部位词表以运行时常量数组为唯一事实源(类型从常量推导),__test__ 直接锁死。
7
+ */
8
+ /** 尺寸全集:small(小)/ medium(中,默认)/ large(大)。 */
9
+ export declare const TAG_SIZES: readonly ["small", "medium", "large"];
10
+ /** 变体全集:solid 填充(默认)/ outlined 框线。 */
11
+ export declare const TAG_VARIANTS: readonly ["solid", "outlined"];
12
+ /** Tag 各渲染部位全集(data-part 取值)。 */
13
+ export declare const TAG_PARTS: readonly ["root"];
14
+ export type TagSize = (typeof TAG_SIZES)[number];
15
+ export type TagVariant = (typeof TAG_VARIANTS)[number];
16
+ export type TagPart = (typeof TAG_PARTS)[number];
17
+ /**
18
+ * QwTagProps — 双栈共享的行为契约(全字段可选)。
19
+ * 颜色完全由业务方传入,组件不内置任何主题。
20
+ */
21
+ export interface QwTagProps {
22
+ /** 尺寸,默认 medium。 */
23
+ size?: TagSize;
24
+ /** 变体,默认 solid。 */
25
+ variant?: TagVariant;
26
+ /** 文字是否加粗,默认 false。 */
27
+ bold?: boolean;
28
+ /** 背景色,未传入时使用默认中性灰。 */
29
+ bgcolor?: string;
30
+ /** 文字色,未传入时使用默认灰。 */
31
+ textcolor?: string;
32
+ /** 边框色,仅在 variant="outlined" 时生效;未传入时使用默认灰。 */
33
+ borderColor?: string;
34
+ }
@@ -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.2.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",
@@ -83,6 +83,14 @@
83
83
  "./textarea": {
84
84
  "types": "./dist/textarea/index.d.ts",
85
85
  "import": "./dist/textarea/index.js"
86
+ },
87
+ "./tag": {
88
+ "types": "./dist/tag/index.d.ts",
89
+ "import": "./dist/tag/index.js"
90
+ },
91
+ "./input": {
92
+ "types": "./dist/input/index.d.ts",
93
+ "import": "./dist/input/index.js"
86
94
  }
87
95
  },
88
96
  "sideEffects": false,
@@ -91,7 +99,7 @@
91
99
  "@zag-js/dialog": "^1.42.0",
92
100
  "@zag-js/tabs": "^1.42.0",
93
101
  "@zag-js/utils": "1.42.0",
94
- "@qw-ui/icons": "^0.2.0"
102
+ "@qw-ui/icons": "^0.3.0-next.2"
95
103
  },
96
104
  "devDependencies": {
97
105
  "@microsoft/api-extractor": "^7.58.9",