@qw-ui/core 0.2.0-next.0 → 0.2.0
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/dist/index.d.ts +2 -0
- package/dist/index.js +2 -1
- package/dist/textarea/index.d.ts +1 -0
- package/dist/textarea/index.js +231 -0
- package/dist/textarea/index.js.map +1 -0
- package/dist/textarea/textarea.d.ts +113 -0
- package/package.json +8 -4
package/dist/index.d.ts
CHANGED
|
@@ -20,3 +20,5 @@ export { ICON_FALLBACK_NAME, ICON_PARTS, resolveIconName } from "./icon/index.js
|
|
|
20
20
|
export type { IconName, IconNameInput, IconPart } from "./icon/index.js";
|
|
21
21
|
export { 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";
|
|
22
22
|
export type { AvatarBadgeProps, AvatarContentPresence, AvatarFallbackProps, AvatarImageElementProps, AvatarImageProps, AvatarLoadStatus, AvatarPart, AvatarRootProps, AvatarShape, AvatarSize, BadgeConfig, BadgeType, FallbackMode, QwAvatarProps, ResolvedAvatarState, } from "./avatar/index.js";
|
|
23
|
+
export { machine as textAreaMachine, connect as textAreaConnect, createTextAreaProps } from "./textarea/index.js";
|
|
24
|
+
export type { QwTextAreaProps, TextAreaApi } from "./textarea/index.js";
|
package/dist/index.js
CHANGED
|
@@ -9,4 +9,5 @@ import { EMPTY_PARTS } from "./empty/index.js";
|
|
|
9
9
|
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 };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./textarea.js";
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { createMachine } from "@zag-js/core";
|
|
2
|
+
import { compact } from "@zag-js/utils";
|
|
3
|
+
//#region src/textarea/textarea.ts
|
|
4
|
+
/**
|
|
5
|
+
* TextArea zag.js machine。
|
|
6
|
+
* 所有交互逻辑唯一来源:value、focus、clear、pasteRecognize。
|
|
7
|
+
*/
|
|
8
|
+
const machine = createMachine({
|
|
9
|
+
props({ props }) {
|
|
10
|
+
return {
|
|
11
|
+
defaultValue: "",
|
|
12
|
+
disabled: false,
|
|
13
|
+
readOnly: false,
|
|
14
|
+
showActions: true,
|
|
15
|
+
autoSize: false,
|
|
16
|
+
rows: 4,
|
|
17
|
+
...props
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
initialState() {
|
|
21
|
+
return "idle";
|
|
22
|
+
},
|
|
23
|
+
context({ bindable, prop }) {
|
|
24
|
+
return {
|
|
25
|
+
value: bindable(() => {
|
|
26
|
+
const controlledValue = prop("value");
|
|
27
|
+
return {
|
|
28
|
+
defaultValue: prop("defaultValue") ?? "",
|
|
29
|
+
sync: true,
|
|
30
|
+
...controlledValue !== void 0 ? { value: controlledValue } : {},
|
|
31
|
+
onChange(value, prev) {
|
|
32
|
+
if (value !== prev) prop("onChange")?.(value);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
focused: bindable(() => ({ defaultValue: false }))
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
computed: {
|
|
40
|
+
charCount({ context }) {
|
|
41
|
+
return context.get("value").length;
|
|
42
|
+
},
|
|
43
|
+
hasMaxLength({ prop }) {
|
|
44
|
+
const max = prop("maxLength");
|
|
45
|
+
return max != null && max > 0;
|
|
46
|
+
},
|
|
47
|
+
showClear({ context, prop }) {
|
|
48
|
+
return context.get("value").length > 0 && !prop("disabled") && !prop("readOnly");
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
watch({ track, prop, context }) {
|
|
52
|
+
track([() => prop("value")], () => {
|
|
53
|
+
const external = prop("value");
|
|
54
|
+
if (external !== void 0 && external !== context.get("value")) context.set("value", external);
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
states: { idle: { on: {
|
|
58
|
+
"VALUE.SET": { actions: ["setValue"] },
|
|
59
|
+
"VALUE.CLEAR": { actions: ["clearValue", "invokeOnClear"] },
|
|
60
|
+
"FOCUS.SET": { actions: ["setFocused"] },
|
|
61
|
+
PASTE_RECOGNIZE: { actions: ["invokeOnPasteRecognize"] }
|
|
62
|
+
} } },
|
|
63
|
+
implementations: { actions: {
|
|
64
|
+
setValue({ context, event, prop }) {
|
|
65
|
+
if (event.type === "VALUE.SET") {
|
|
66
|
+
const maxLength = prop("maxLength") ?? 0;
|
|
67
|
+
let next = event.value;
|
|
68
|
+
if (maxLength > 0 && next.length > maxLength) next = next.slice(0, maxLength);
|
|
69
|
+
context.set("value", next);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
clearValue({ context }) {
|
|
73
|
+
context.set("value", "");
|
|
74
|
+
},
|
|
75
|
+
setFocused({ context, event, prop }) {
|
|
76
|
+
if (event.type === "FOCUS.SET") {
|
|
77
|
+
const next = event.focused;
|
|
78
|
+
const prev = context.get("focused");
|
|
79
|
+
context.set("focused", next);
|
|
80
|
+
if (next && !prev) prop("onFocus")?.();
|
|
81
|
+
if (!next && prev) prop("onBlur")?.();
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
invokeOnClear({ prop }) {
|
|
85
|
+
prop("onClear")?.();
|
|
86
|
+
},
|
|
87
|
+
invokeOnPasteRecognize({ prop }) {
|
|
88
|
+
prop("onPasteRecognize")?.();
|
|
89
|
+
},
|
|
90
|
+
invokeOnFocus() {},
|
|
91
|
+
invokeOnBlur() {}
|
|
92
|
+
} }
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* connect:把 service 接成业务友好的 prop-getter。
|
|
96
|
+
* 适配层只需调用 connect(service, normalizeProps)。
|
|
97
|
+
*/
|
|
98
|
+
function connect(service, normalizeProps) {
|
|
99
|
+
const { context, computed, prop, send } = service;
|
|
100
|
+
const value = context.get("value");
|
|
101
|
+
const focused = context.get("focused");
|
|
102
|
+
const charCount = computed("charCount");
|
|
103
|
+
const hasMaxLength = computed("hasMaxLength");
|
|
104
|
+
const showClear = computed("showClear");
|
|
105
|
+
const disabled = !!prop("disabled");
|
|
106
|
+
const readOnly = !!prop("readOnly");
|
|
107
|
+
const showActions = prop("showActions") ?? true;
|
|
108
|
+
const autoSize = !!prop("autoSize");
|
|
109
|
+
const maxLength = prop("maxLength");
|
|
110
|
+
const rows = prop("rows") ?? 4;
|
|
111
|
+
const isEmpty = value.length === 0;
|
|
112
|
+
return {
|
|
113
|
+
value,
|
|
114
|
+
focused,
|
|
115
|
+
charCount,
|
|
116
|
+
hasMaxLength,
|
|
117
|
+
showClear,
|
|
118
|
+
clear() {
|
|
119
|
+
send({ type: "VALUE.CLEAR" });
|
|
120
|
+
},
|
|
121
|
+
pasteRecognize() {
|
|
122
|
+
send({ type: "PASTE_RECOGNIZE" });
|
|
123
|
+
},
|
|
124
|
+
getRootProps() {
|
|
125
|
+
return normalizeProps.element(compact({
|
|
126
|
+
"data-scope": "textarea",
|
|
127
|
+
"data-part": "root",
|
|
128
|
+
"data-focused": focused ? "true" : void 0,
|
|
129
|
+
"data-disabled": disabled ? "true" : void 0,
|
|
130
|
+
"data-readonly": readOnly ? "true" : void 0,
|
|
131
|
+
"data-show-actions": showActions ? "true" : "false",
|
|
132
|
+
"data-auto-size": autoSize ? "true" : void 0,
|
|
133
|
+
"data-empty": isEmpty ? "true" : "false"
|
|
134
|
+
}));
|
|
135
|
+
},
|
|
136
|
+
getTextareaProps() {
|
|
137
|
+
return normalizeProps.textarea(compact({
|
|
138
|
+
"data-scope": "textarea",
|
|
139
|
+
"data-part": "textarea",
|
|
140
|
+
value,
|
|
141
|
+
placeholder: prop("placeholder"),
|
|
142
|
+
disabled,
|
|
143
|
+
readOnly,
|
|
144
|
+
maxLength: maxLength && maxLength > 0 ? maxLength : void 0,
|
|
145
|
+
rows,
|
|
146
|
+
name: prop("name"),
|
|
147
|
+
"aria-disabled": disabled ? "true" : void 0,
|
|
148
|
+
"aria-readonly": readOnly ? "true" : void 0,
|
|
149
|
+
onFocus() {
|
|
150
|
+
send({
|
|
151
|
+
type: "FOCUS.SET",
|
|
152
|
+
focused: true
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
onBlur() {
|
|
156
|
+
send({
|
|
157
|
+
type: "FOCUS.SET",
|
|
158
|
+
focused: false
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
onChange(event) {
|
|
162
|
+
const target = event.target;
|
|
163
|
+
const isComposing = !!event.nativeEvent?.isComposing;
|
|
164
|
+
let next = target.value;
|
|
165
|
+
if (!isComposing) {
|
|
166
|
+
const maxLen = prop("maxLength") ?? 0;
|
|
167
|
+
if (maxLen > 0 && next.length > maxLen) next = next.slice(0, maxLen);
|
|
168
|
+
}
|
|
169
|
+
context.set("value", next);
|
|
170
|
+
}
|
|
171
|
+
}));
|
|
172
|
+
},
|
|
173
|
+
getBottomBarProps() {
|
|
174
|
+
return normalizeProps.element(compact({
|
|
175
|
+
"data-scope": "textarea",
|
|
176
|
+
"data-part": "bottom-bar",
|
|
177
|
+
"data-show-actions": showActions ? "true" : "false"
|
|
178
|
+
}));
|
|
179
|
+
},
|
|
180
|
+
getCounterProps() {
|
|
181
|
+
return normalizeProps.element(compact({
|
|
182
|
+
"data-scope": "textarea",
|
|
183
|
+
"data-part": "counter",
|
|
184
|
+
"data-has-max-length": hasMaxLength ? "true" : void 0,
|
|
185
|
+
"aria-live": "polite"
|
|
186
|
+
}));
|
|
187
|
+
},
|
|
188
|
+
getClearButtonProps() {
|
|
189
|
+
return normalizeProps.button(compact({
|
|
190
|
+
"data-scope": "textarea",
|
|
191
|
+
"data-part": "clear-button",
|
|
192
|
+
type: "button",
|
|
193
|
+
disabled: !showClear || disabled || readOnly,
|
|
194
|
+
"aria-label": "清除内容",
|
|
195
|
+
onClick() {
|
|
196
|
+
send({ type: "VALUE.CLEAR" });
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
},
|
|
200
|
+
getPasteButtonProps() {
|
|
201
|
+
return normalizeProps.button(compact({
|
|
202
|
+
"data-scope": "textarea",
|
|
203
|
+
"data-part": "paste-button",
|
|
204
|
+
type: "button",
|
|
205
|
+
disabled,
|
|
206
|
+
"aria-label": "粘贴并识别",
|
|
207
|
+
onClick() {
|
|
208
|
+
send({ type: "PASTE_RECOGNIZE" });
|
|
209
|
+
}
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 把 QwTextAreaProps 转成 machine context(默认值收口)。
|
|
216
|
+
*/
|
|
217
|
+
function createTextAreaProps(props = {}) {
|
|
218
|
+
return {
|
|
219
|
+
defaultValue: props.defaultValue ?? "",
|
|
220
|
+
disabled: props.disabled ?? false,
|
|
221
|
+
readOnly: props.readOnly ?? false,
|
|
222
|
+
showActions: props.showActions ?? true,
|
|
223
|
+
autoSize: props.autoSize ?? false,
|
|
224
|
+
rows: props.rows ?? 4,
|
|
225
|
+
...props
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
export { connect, createTextAreaProps, machine };
|
|
230
|
+
|
|
231
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { MachineSchema, Service } from "@zag-js/core";
|
|
2
|
+
import type { PropTypes, NormalizeProps } from "@zag-js/types";
|
|
3
|
+
/**
|
|
4
|
+
* TextArea 公共 props 契约(双栈共享)。
|
|
5
|
+
*/
|
|
6
|
+
export interface QwTextAreaProps {
|
|
7
|
+
/** 唯一 id 前缀。 */
|
|
8
|
+
id?: string;
|
|
9
|
+
/** 受控值。 */
|
|
10
|
+
value?: string;
|
|
11
|
+
/** 非受控初始值。 */
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
/** 占位符。 */
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
/** 最大字符数。 */
|
|
16
|
+
maxLength?: number;
|
|
17
|
+
/** 禁用态。 */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** 只读态。 */
|
|
20
|
+
readOnly?: boolean;
|
|
21
|
+
/** 可视行数(影响最小高度)。 */
|
|
22
|
+
rows?: number;
|
|
23
|
+
/** 自适应高度。 */
|
|
24
|
+
autoSize?: boolean;
|
|
25
|
+
/** 显示底部操作按钮。 */
|
|
26
|
+
showActions?: boolean;
|
|
27
|
+
/** name 属性。 */
|
|
28
|
+
name?: string;
|
|
29
|
+
/** 输入变化回调。 */
|
|
30
|
+
onChange?: (value: string) => void;
|
|
31
|
+
/** 清空回调。 */
|
|
32
|
+
onClear?: () => void;
|
|
33
|
+
/** 粘贴并识别回调。 */
|
|
34
|
+
onPasteRecognize?: () => void;
|
|
35
|
+
/** 聚焦回调。 */
|
|
36
|
+
onFocus?: () => void;
|
|
37
|
+
/** 失焦回调。 */
|
|
38
|
+
onBlur?: () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 内部 schema,用于 createMachine 类型推导。
|
|
42
|
+
*/
|
|
43
|
+
interface TextAreaSchema extends MachineSchema {
|
|
44
|
+
props: QwTextAreaProps;
|
|
45
|
+
context: {
|
|
46
|
+
value: string;
|
|
47
|
+
focused: boolean;
|
|
48
|
+
};
|
|
49
|
+
computed: {
|
|
50
|
+
charCount: number;
|
|
51
|
+
hasMaxLength: boolean;
|
|
52
|
+
showClear: boolean;
|
|
53
|
+
};
|
|
54
|
+
state: "idle";
|
|
55
|
+
event: {
|
|
56
|
+
type: "VALUE.SET";
|
|
57
|
+
value: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: "VALUE.CLEAR";
|
|
60
|
+
} | {
|
|
61
|
+
type: "FOCUS.SET";
|
|
62
|
+
focused: boolean;
|
|
63
|
+
} | {
|
|
64
|
+
type: "PASTE_RECOGNIZE";
|
|
65
|
+
};
|
|
66
|
+
action: "setValue" | "clearValue" | "setFocused" | "invokeOnClear" | "invokeOnPasteRecognize" | "invokeOnFocus" | "invokeOnBlur";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* TextArea zag.js machine。
|
|
70
|
+
* 所有交互逻辑唯一来源:value、focus、clear、pasteRecognize。
|
|
71
|
+
*/
|
|
72
|
+
export declare const machine: import("@zag-js/core").Machine<TextAreaSchema>;
|
|
73
|
+
/**
|
|
74
|
+
* TextArea api 类型(由 connect 返回)。
|
|
75
|
+
*/
|
|
76
|
+
export interface TextAreaApi<T extends PropTypes = PropTypes> {
|
|
77
|
+
/** 当前输入值。 */
|
|
78
|
+
value: string;
|
|
79
|
+
/** 当前是否聚焦。 */
|
|
80
|
+
focused: boolean;
|
|
81
|
+
/** 当前字符数。 */
|
|
82
|
+
charCount: number;
|
|
83
|
+
/** 是否有字数限制。 */
|
|
84
|
+
hasMaxLength: boolean;
|
|
85
|
+
/** 是否显示清除按钮。 */
|
|
86
|
+
showClear: boolean;
|
|
87
|
+
/** 清空输入。 */
|
|
88
|
+
clear(): void;
|
|
89
|
+
/** 触发粘贴并识别回调。 */
|
|
90
|
+
pasteRecognize(): void;
|
|
91
|
+
/** 根容器 prop-getter。 */
|
|
92
|
+
getRootProps(): T["element"];
|
|
93
|
+
/** textarea 元素 prop-getter。 */
|
|
94
|
+
getTextareaProps(): T["textarea"];
|
|
95
|
+
/** 底部栏 prop-getter。 */
|
|
96
|
+
getBottomBarProps(): T["element"];
|
|
97
|
+
/** 字数统计 prop-getter。 */
|
|
98
|
+
getCounterProps(): T["element"];
|
|
99
|
+
/** 清除按钮 prop-getter。 */
|
|
100
|
+
getClearButtonProps(): T["button"];
|
|
101
|
+
/** 粘贴按钮 prop-getter。 */
|
|
102
|
+
getPasteButtonProps(): T["button"];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* connect:把 service 接成业务友好的 prop-getter。
|
|
106
|
+
* 适配层只需调用 connect(service, normalizeProps)。
|
|
107
|
+
*/
|
|
108
|
+
export declare function connect<T extends PropTypes>(service: Service<TextAreaSchema>, normalizeProps: NormalizeProps<T>): TextAreaApi<T>;
|
|
109
|
+
/**
|
|
110
|
+
* 把 QwTextAreaProps 转成 machine context(默认值收口)。
|
|
111
|
+
*/
|
|
112
|
+
export declare function createTextAreaProps(props?: QwTextAreaProps): Partial<QwTextAreaProps>;
|
|
113
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qw-ui/core",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "L2 · Headless Core — zag.js machine 薄包装,纯 TS / SSR-safe,零样式。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "qw-ui contributors",
|
|
@@ -79,6 +79,10 @@
|
|
|
79
79
|
"./image": {
|
|
80
80
|
"types": "./dist/image/index.d.ts",
|
|
81
81
|
"import": "./dist/image/index.js"
|
|
82
|
+
},
|
|
83
|
+
"./textarea": {
|
|
84
|
+
"types": "./dist/textarea/index.d.ts",
|
|
85
|
+
"import": "./dist/textarea/index.js"
|
|
82
86
|
}
|
|
83
87
|
},
|
|
84
88
|
"sideEffects": false,
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
"@zag-js/dialog": "^1.42.0",
|
|
88
92
|
"@zag-js/tabs": "^1.42.0",
|
|
89
93
|
"@zag-js/utils": "1.42.0",
|
|
90
|
-
"@qw-ui/icons": "^0.2.0
|
|
94
|
+
"@qw-ui/icons": "^0.2.0"
|
|
91
95
|
},
|
|
92
96
|
"devDependencies": {
|
|
93
97
|
"@microsoft/api-extractor": "^7.58.9",
|
|
@@ -101,8 +105,8 @@
|
|
|
101
105
|
"typescript": "^6.0.3",
|
|
102
106
|
"unrun": "^0.3.1",
|
|
103
107
|
"vitest": "^4.1.9",
|
|
104
|
-
"@qw-ui/eslint-config": "0.0.
|
|
105
|
-
"@qw-ui/tsconfig": "0.0.
|
|
108
|
+
"@qw-ui/eslint-config": "0.0.1",
|
|
109
|
+
"@qw-ui/tsconfig": "0.0.1"
|
|
106
110
|
},
|
|
107
111
|
"size-limit": [
|
|
108
112
|
{
|