@qw-ui/core 0.3.0-next.4 → 0.3.0-next.40

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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/badge/badge.d.ts +76 -0
  3. package/dist/badge/index.d.ts +1 -0
  4. package/dist/badge/index.js +75 -0
  5. package/dist/badge/index.js.map +1 -0
  6. package/dist/dialog/dialog.d.ts +31 -42
  7. package/dist/dialog/index.js +18 -19
  8. package/dist/dialog/index.js.map +1 -1
  9. package/dist/index.d.ts +12 -0
  10. package/dist/index.js +7 -1
  11. package/dist/overlay-panel/index.d.ts +2 -0
  12. package/dist/overlay-panel/index.js +2 -0
  13. package/dist/overlay-panel/overlay-panel.d.ts +20 -0
  14. package/dist/overlay-panel-Z_qdKeBj.js +26 -0
  15. package/dist/overlay-panel-Z_qdKeBj.js.map +1 -0
  16. package/dist/search-bar/index.js +1 -1
  17. package/dist/search-bar/index.js.map +1 -1
  18. package/dist/search-bar/search-bar.d.ts +2 -0
  19. package/dist/side-nav/index.d.ts +1 -0
  20. package/dist/side-nav/index.js +99 -0
  21. package/dist/side-nav/index.js.map +1 -0
  22. package/dist/side-nav/side-nav.d.ts +94 -0
  23. package/dist/stepper/index.d.ts +1 -0
  24. package/dist/stepper/index.js +113 -0
  25. package/dist/stepper/index.js.map +1 -0
  26. package/dist/stepper/stepper.d.ts +121 -0
  27. package/dist/switch/index.d.ts +1 -0
  28. package/dist/switch/index.js +43 -0
  29. package/dist/switch/index.js.map +1 -0
  30. package/dist/switch/switch.d.ts +55 -0
  31. package/dist/tabbar/index.d.ts +1 -0
  32. package/dist/tabbar/index.js +50 -0
  33. package/dist/tabbar/index.js.map +1 -0
  34. package/dist/tabbar/tabbar.d.ts +69 -0
  35. package/dist/textarea/index.js +2 -2
  36. package/dist/textarea/index.js.map +1 -1
  37. package/dist/textarea/textarea.d.ts +4 -0
  38. package/dist/visual-manifest.d.ts +21 -0
  39. package/package.json +28 -4
@@ -0,0 +1,69 @@
1
+ /**
2
+ * TabBar 的跨框架行为契约。
3
+ *
4
+ * TabBar 是无状态的 Prototype A 底部操作栏:白底容器 + 顶部 1px 分割线 +
5
+ * 1–N 个等宽 L 号按钮(组合已交付的 Button)。这里仅归一化公开 props 与
6
+ * item → Button 的档位映射,React/Vue 适配器不得复制可见性、默认档位或
7
+ * 映射规则。
8
+ */
9
+ export declare const TABBAR_ITEM_VARIANTS: readonly ["primary", "secondary"];
10
+ export declare const TABBAR_ITEM_TONES: readonly ["brand", "neutral"];
11
+ export declare const TABBAR_PARTS: readonly ["root"];
12
+ export type TabBarItemVariant = (typeof TABBAR_ITEM_VARIANTS)[number];
13
+ export type TabBarItemTone = (typeof TABBAR_ITEM_TONES)[number];
14
+ export type TabBarPart = (typeof TABBAR_PARTS)[number];
15
+ /** 单个操作项声明;双栈共享,框架特有的事件通道不进入 Core。 */
16
+ export interface QwTabBarItemProps {
17
+ /** 按钮文案,同时作为可访问名。 */
18
+ text: string;
19
+ /** 视觉档位,默认 `primary`。primary→Button solid;secondary→Button soft。 */
20
+ variant?: TabBarItemVariant;
21
+ /** 语气,默认 `brand`。 */
22
+ tone?: TabBarItemTone;
23
+ /** 原生禁用状态。 */
24
+ disabled?: boolean;
25
+ /** 忙碌态,沿用 Button loading 契约。 */
26
+ loading?: boolean;
27
+ /** 点击(含键盘激活)回调。 */
28
+ onClick?: () => void;
29
+ }
30
+ /** 双栈共享的标量 props。 */
31
+ export interface QwTabBarProps {
32
+ /** 操作项列表;缺省或空数组时组件不渲染。 */
33
+ items?: readonly QwTabBarItemProps[];
34
+ }
35
+ /** 逐项归一化结果。 */
36
+ export interface ResolvedTabBarItemState {
37
+ text: string;
38
+ variant: TabBarItemVariant;
39
+ tone: TabBarItemTone;
40
+ disabled: boolean;
41
+ loading: boolean;
42
+ onClick?: () => void;
43
+ }
44
+ /** 适配器唯一可消费的归一化结果。 */
45
+ export interface ResolvedTabBarState {
46
+ visible: boolean;
47
+ count: number;
48
+ items: ResolvedTabBarItemState[];
49
+ }
50
+ /** Core 生成、由适配器摊到根元素的属性。 */
51
+ export interface TabBarRootProps {
52
+ "data-scope": "tabbar";
53
+ "data-part": "root";
54
+ "data-count": string;
55
+ }
56
+ /** item → Button 的档位映射结果(size 固定 l)。 */
57
+ export interface TabBarItemButtonProps {
58
+ variant: "solid" | "soft";
59
+ tone: "brand" | "neutral";
60
+ size: "l";
61
+ disabled: boolean;
62
+ loading: boolean;
63
+ }
64
+ /** 将公开输入归一化为唯一的跨框架状态;items 缺省/空 → 不可见。 */
65
+ export declare function resolveTabBarState(props?: QwTabBarProps): ResolvedTabBarState;
66
+ /** 将归一化结果转成 root 属性。 */
67
+ export declare function getTabBarRootProps(state: ResolvedTabBarState): TabBarRootProps;
68
+ /** 将单个操作项映射为 Button props(档位收口,适配器不得覆盖)。 */
69
+ export declare function getTabBarItemButtonProps(item: ResolvedTabBarItemState): TabBarItemButtonProps;
@@ -202,7 +202,7 @@ function connect(service, normalizeProps) {
202
202
  "data-part": "clear-button",
203
203
  type: "button",
204
204
  disabled: !showClear || disabled || readOnly,
205
- "aria-label": "清除内容",
205
+ "aria-label": prop("clearLabel") ?? "清除内容",
206
206
  onClick() {
207
207
  send({ type: "VALUE.CLEAR" });
208
208
  }
@@ -214,7 +214,7 @@ function connect(service, normalizeProps) {
214
214
  "data-part": "paste-button",
215
215
  type: "button",
216
216
  disabled,
217
- "aria-label": "粘贴并识别",
217
+ "aria-label": prop("pasteLabel") ?? "粘贴并识别",
218
218
  onClick() {
219
219
  send({ type: "PASTE_RECOGNIZE" });
220
220
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/textarea/textarea.ts"],"sourcesContent":["import { createMachine } from \"@zag-js/core\";\nimport type { MachineSchema, Service } from \"@zag-js/core\";\nimport type { PropTypes, NormalizeProps } from \"@zag-js/types\";\nimport type { JSX } from \"@zag-js/types\";\nimport { compact } from \"@zag-js/utils\";\n\n/**\n * TextArea 公共 props 契约(双栈共享)。\n */\nexport interface QwTextAreaProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。 */\n defaultValue?: string;\n /** 占位符。 */\n placeholder?: string;\n /** 最大字符数。 */\n maxLength?: number;\n /** 禁用态。 */\n disabled?: boolean;\n /** 只读态。 */\n readOnly?: boolean;\n /** 可视行数(影响最小高度)。 */\n rows?: number;\n /** 自适应高度。 */\n autoSize?: boolean;\n /** 显示底部操作按钮。 */\n showActions?: boolean;\n /** name 属性。 */\n name?: string;\n /** 输入变化回调。 */\n onChange?: (value: string) => void;\n /** 清空回调。 */\n onClear?: () => void;\n /** 粘贴并识别回调,参数为粘贴的文本。 */\n onPasteRecognize?: (value: string) => void;\n /** 聚焦回调。 */\n onFocus?: () => void;\n /** 失焦回调。 */\n onBlur?: () => void;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface TextAreaSchema extends MachineSchema {\n props: QwTextAreaProps;\n context: {\n value: string;\n focused: boolean;\n };\n computed: {\n charCount: number;\n hasMaxLength: boolean;\n showClear: boolean;\n };\n state: \"idle\";\n event:\n | { type: \"VALUE.SET\"; value: string }\n | { type: \"VALUE.CLEAR\" }\n | { type: \"FOCUS.SET\"; focused: boolean }\n | { type: \"PASTE_RECOGNIZE\"; value?: string };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnClear\"\n | \"pasteValue\"\n | \"invokeOnPasteRecognize\"\n | \"invokeOnFocus\"\n | \"invokeOnBlur\";\n}\n\n/**\n * TextArea zag.js machine。\n * 所有交互逻辑唯一来源:value、focus、clear、pasteRecognize。\n */\nexport const machine = createMachine<TextAreaSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n disabled: false,\n readOnly: false,\n showActions: true,\n autoSize: false,\n rows: 4,\n ...props,\n };\n },\n\n initialState() {\n return \"idle\";\n },\n\n context({ bindable, prop }) {\n return {\n value: bindable<string>(() => {\n const controlledValue = prop(\"value\");\n return {\n defaultValue: prop(\"defaultValue\") ?? \"\",\n sync: true,\n ...(controlledValue !== undefined ? { value: controlledValue } : {}),\n onChange(value, prev) {\n if (value !== prev) {\n prop(\"onChange\")?.(value);\n }\n },\n };\n }),\n focused: bindable(() => ({\n defaultValue: false,\n })),\n };\n },\n\n computed: {\n charCount({ context }) {\n return context.get(\"value\").length;\n },\n hasMaxLength({ prop }) {\n const max = prop(\"maxLength\");\n return max != null && max > 0;\n },\n showClear({ context, prop }) {\n return context.get(\"value\").length > 0 && !prop(\"disabled\") && !prop(\"readOnly\");\n },\n },\n\n watch({ track, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n context.set(\"value\", external);\n }\n });\n },\n\n states: {\n idle: {\n on: {\n \"VALUE.SET\": {\n actions: [\"setValue\"],\n },\n \"VALUE.CLEAR\": {\n actions: [\"clearValue\", \"invokeOnClear\"],\n },\n \"FOCUS.SET\": {\n actions: [\"setFocused\"],\n },\n PASTE_RECOGNIZE: {\n actions: [\"pasteValue\", \"invokeOnPasteRecognize\"],\n },\n },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event, prop }) {\n if (event.type === \"VALUE.SET\") {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n clearValue({ context }) {\n context.set(\"value\", \"\");\n },\n setFocused({ context, event, prop }) {\n if (event.type === \"FOCUS.SET\") {\n const next = event.focused;\n const prev = context.get(\"focused\");\n context.set(\"focused\", next);\n if (next && !prev) prop(\"onFocus\")?.();\n if (!next && prev) prop(\"onBlur\")?.();\n }\n },\n invokeOnClear({ prop }) {\n prop(\"onClear\")?.();\n },\n pasteValue({ context, event, prop }) {\n if (event.type === \"PASTE_RECOGNIZE\" && event.value !== undefined) {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n invokeOnPasteRecognize({ prop, event }) {\n if (event.type === \"PASTE_RECOGNIZE\") {\n prop(\"onPasteRecognize\")?.(event.value ?? \"\");\n }\n },\n invokeOnFocus() {\n // focus 回调在 setFocused 中已触发\n },\n invokeOnBlur() {\n // blur 回调在 setFocused 中已触发\n },\n },\n },\n});\n\n/**\n * TextArea api 类型(由 connect 返回)。\n */\nexport interface TextAreaApi<T extends PropTypes = PropTypes> {\n /** 当前输入值。 */\n value: string;\n /** 当前是否聚焦。 */\n focused: boolean;\n /** 当前字符数。 */\n charCount: number;\n /** 是否有字数限制。 */\n hasMaxLength: boolean;\n /** 是否显示清除按钮。 */\n showClear: boolean;\n /** 清空输入。 */\n clear(): void;\n /** 触发粘贴并识别回调(可传入剪贴板文本,自动填入)。 */\n pasteRecognize(value?: string): void;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** textarea 元素 prop-getter。 */\n getTextareaProps(): T[\"textarea\"];\n /** 底部栏 prop-getter。 */\n getBottomBarProps(): T[\"element\"];\n /** 字数统计 prop-getter。 */\n getCounterProps(): T[\"element\"];\n /** 清除按钮 prop-getter。 */\n getClearButtonProps(): T[\"button\"];\n /** 粘贴按钮 prop-getter。 */\n getPasteButtonProps(): T[\"button\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps)。\n */\nexport function connect<T extends PropTypes>(service: Service<TextAreaSchema>, normalizeProps: NormalizeProps<T>): TextAreaApi<T> {\n const { context, computed, prop, send } = service;\n\n const value = context.get(\"value\");\n const focused = context.get(\"focused\");\n const charCount = computed(\"charCount\");\n const hasMaxLength = computed(\"hasMaxLength\");\n const showClear = computed(\"showClear\");\n const disabled = !!prop(\"disabled\");\n const readOnly = !!prop(\"readOnly\");\n const showActions = prop(\"showActions\") ?? true;\n const autoSize = !!prop(\"autoSize\");\n const maxLength = prop(\"maxLength\");\n const rows = prop(\"rows\") ?? 4;\n const isEmpty = value.length === 0;\n\n return {\n value,\n focused,\n charCount,\n hasMaxLength,\n showClear,\n\n clear() {\n send({ type: \"VALUE.CLEAR\" });\n },\n\n pasteRecognize(value?: string) {\n send({ type: \"PASTE_RECOGNIZE\", value });\n },\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"root\",\n \"data-focused\": focused ? \"true\" : undefined,\n \"data-disabled\": disabled ? \"true\" : undefined,\n \"data-readonly\": readOnly ? \"true\" : undefined,\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n \"data-auto-size\": autoSize ? \"true\" : undefined,\n \"data-empty\": isEmpty ? \"true\" : \"false\",\n }),\n );\n },\n\n getTextareaProps() {\n return normalizeProps.textarea(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"textarea\",\n value,\n placeholder: prop(\"placeholder\"),\n disabled,\n readOnly,\n maxLength: maxLength && maxLength > 0 ? maxLength : undefined,\n rows,\n name: prop(\"name\"),\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"aria-readonly\": readOnly ? (\"true\" as const) : undefined,\n onFocus() {\n send({ type: \"FOCUS.SET\", focused: true });\n },\n onBlur() {\n send({ type: \"FOCUS.SET\", focused: false });\n },\n onChange(event: JSX.ChangeEvent<HTMLTextAreaElement>) {\n const target = event.target as unknown as HTMLTextAreaElement;\n const nativeEvent = (event as { nativeEvent?: { isComposing?: boolean } }).nativeEvent;\n const isComposing = !!nativeEvent?.isComposing;\n let next = target.value;\n // IME composing 期间跳过 maxLength 截断,允许拼音中间态值流过\n // 防止 React 受控组件因值未更新而重置 DOM,打断输入法\n if (!isComposing) {\n const maxLen = prop(\"maxLength\") ?? 0;\n if (maxLen > 0 && next.length > maxLen) {\n next = next.slice(0, maxLen);\n }\n }\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", next);\n },\n }),\n );\n },\n\n getBottomBarProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"bottom-bar\",\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n }),\n );\n },\n\n getCounterProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"counter\",\n \"data-has-max-length\": hasMaxLength ? \"true\" : undefined,\n \"aria-live\": \"polite\" as const,\n }),\n );\n },\n\n getClearButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"clear-button\",\n type: \"button\" as const,\n disabled: !showClear || disabled || readOnly,\n \"aria-label\": \"清除内容\" as const,\n onClick() {\n send({ type: \"VALUE.CLEAR\" });\n },\n }),\n );\n },\n\n getPasteButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"paste-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": \"粘贴并识别\" as const,\n onClick() {\n send({ type: \"PASTE_RECOGNIZE\" });\n },\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwTextAreaProps 转成 machine context(默认值收口)。\n */\nexport function createTextAreaProps(props: QwTextAreaProps = {}): Partial<QwTextAreaProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n disabled: props.disabled ?? false,\n readOnly: props.readOnly ?? false,\n showActions: props.showActions ?? true,\n autoSize: props.autoSize ?? false,\n rows: props.rows ?? 4,\n ...props,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/textarea/textarea.ts"],"sourcesContent":["import { createMachine } from \"@zag-js/core\";\nimport type { MachineSchema, Service } from \"@zag-js/core\";\nimport type { PropTypes, NormalizeProps } from \"@zag-js/types\";\nimport type { JSX } from \"@zag-js/types\";\nimport { compact } from \"@zag-js/utils\";\n\n/**\n * TextArea 公共 props 契约(双栈共享)。\n */\nexport interface QwTextAreaProps {\n /** 唯一 id 前缀。 */\n id?: string;\n /** 受控值。 */\n value?: string;\n /** 非受控初始值。 */\n defaultValue?: string;\n /** 占位符。 */\n placeholder?: string;\n /** 最大字符数。 */\n maxLength?: number;\n /** 禁用态。 */\n disabled?: boolean;\n /** 只读态。 */\n readOnly?: boolean;\n /** 可视行数(影响最小高度)。 */\n rows?: number;\n /** 自适应高度。 */\n autoSize?: boolean;\n /** 显示底部操作按钮。 */\n showActions?: boolean;\n /** name 属性。 */\n name?: string;\n /** 清除按钮 aria-label,默认「清除内容」。 */\n clearLabel?: string;\n /** 粘贴按钮 aria-label,默认「粘贴并识别」。 */\n pasteLabel?: string;\n /** 输入变化回调。 */\n onChange?: (value: string) => void;\n /** 清空回调。 */\n onClear?: () => void;\n /** 粘贴并识别回调,参数为粘贴的文本。 */\n onPasteRecognize?: (value: string) => void;\n /** 聚焦回调。 */\n onFocus?: () => void;\n /** 失焦回调。 */\n onBlur?: () => void;\n}\n\n/**\n * 内部 schema,用于 createMachine 类型推导。\n */\ninterface TextAreaSchema extends MachineSchema {\n props: QwTextAreaProps;\n context: {\n value: string;\n focused: boolean;\n };\n computed: {\n charCount: number;\n hasMaxLength: boolean;\n showClear: boolean;\n };\n state: \"idle\";\n event:\n | { type: \"VALUE.SET\"; value: string }\n | { type: \"VALUE.CLEAR\" }\n | { type: \"FOCUS.SET\"; focused: boolean }\n | { type: \"PASTE_RECOGNIZE\"; value?: string };\n action:\n | \"setValue\"\n | \"clearValue\"\n | \"setFocused\"\n | \"invokeOnClear\"\n | \"pasteValue\"\n | \"invokeOnPasteRecognize\"\n | \"invokeOnFocus\"\n | \"invokeOnBlur\";\n}\n\n/**\n * TextArea zag.js machine。\n * 所有交互逻辑唯一来源:value、focus、clear、pasteRecognize。\n */\nexport const machine = createMachine<TextAreaSchema>({\n props({ props }) {\n return {\n defaultValue: \"\",\n disabled: false,\n readOnly: false,\n showActions: true,\n autoSize: false,\n rows: 4,\n ...props,\n };\n },\n\n initialState() {\n return \"idle\";\n },\n\n context({ bindable, prop }) {\n return {\n value: bindable<string>(() => {\n const controlledValue = prop(\"value\");\n return {\n defaultValue: prop(\"defaultValue\") ?? \"\",\n sync: true,\n ...(controlledValue !== undefined ? { value: controlledValue } : {}),\n onChange(value, prev) {\n if (value !== prev) {\n prop(\"onChange\")?.(value);\n }\n },\n };\n }),\n focused: bindable(() => ({\n defaultValue: false,\n })),\n };\n },\n\n computed: {\n charCount({ context }) {\n return context.get(\"value\").length;\n },\n hasMaxLength({ prop }) {\n const max = prop(\"maxLength\");\n return max != null && max > 0;\n },\n showClear({ context, prop }) {\n return context.get(\"value\").length > 0 && !prop(\"disabled\") && !prop(\"readOnly\");\n },\n },\n\n watch({ track, prop, context }) {\n track([() => prop(\"value\")], () => {\n const external = prop(\"value\");\n if (external !== undefined && external !== context.get(\"value\")) {\n context.set(\"value\", external);\n }\n });\n },\n\n states: {\n idle: {\n on: {\n \"VALUE.SET\": {\n actions: [\"setValue\"],\n },\n \"VALUE.CLEAR\": {\n actions: [\"clearValue\", \"invokeOnClear\"],\n },\n \"FOCUS.SET\": {\n actions: [\"setFocused\"],\n },\n PASTE_RECOGNIZE: {\n actions: [\"pasteValue\", \"invokeOnPasteRecognize\"],\n },\n },\n },\n },\n\n implementations: {\n actions: {\n setValue({ context, event, prop }) {\n if (event.type === \"VALUE.SET\") {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n clearValue({ context }) {\n context.set(\"value\", \"\");\n },\n setFocused({ context, event, prop }) {\n if (event.type === \"FOCUS.SET\") {\n const next = event.focused;\n const prev = context.get(\"focused\");\n context.set(\"focused\", next);\n if (next && !prev) prop(\"onFocus\")?.();\n if (!next && prev) prop(\"onBlur\")?.();\n }\n },\n invokeOnClear({ prop }) {\n prop(\"onClear\")?.();\n },\n pasteValue({ context, event, prop }) {\n if (event.type === \"PASTE_RECOGNIZE\" && event.value !== undefined) {\n const maxLength = prop(\"maxLength\") ?? 0;\n let next = event.value;\n if (maxLength > 0 && next.length > maxLength) {\n next = next.slice(0, maxLength);\n }\n context.set(\"value\", next);\n }\n },\n invokeOnPasteRecognize({ prop, event }) {\n if (event.type === \"PASTE_RECOGNIZE\") {\n prop(\"onPasteRecognize\")?.(event.value ?? \"\");\n }\n },\n invokeOnFocus() {\n // focus 回调在 setFocused 中已触发\n },\n invokeOnBlur() {\n // blur 回调在 setFocused 中已触发\n },\n },\n },\n});\n\n/**\n * TextArea api 类型(由 connect 返回)。\n */\nexport interface TextAreaApi<T extends PropTypes = PropTypes> {\n /** 当前输入值。 */\n value: string;\n /** 当前是否聚焦。 */\n focused: boolean;\n /** 当前字符数。 */\n charCount: number;\n /** 是否有字数限制。 */\n hasMaxLength: boolean;\n /** 是否显示清除按钮。 */\n showClear: boolean;\n /** 清空输入。 */\n clear(): void;\n /** 触发粘贴并识别回调(可传入剪贴板文本,自动填入)。 */\n pasteRecognize(value?: string): void;\n /** 根容器 prop-getter。 */\n getRootProps(): T[\"element\"];\n /** textarea 元素 prop-getter。 */\n getTextareaProps(): T[\"textarea\"];\n /** 底部栏 prop-getter。 */\n getBottomBarProps(): T[\"element\"];\n /** 字数统计 prop-getter。 */\n getCounterProps(): T[\"element\"];\n /** 清除按钮 prop-getter。 */\n getClearButtonProps(): T[\"button\"];\n /** 粘贴按钮 prop-getter。 */\n getPasteButtonProps(): T[\"button\"];\n}\n\n/**\n * connect:把 service 接成业务友好的 prop-getter。\n * 适配层只需调用 connect(service, normalizeProps)。\n */\nexport function connect<T extends PropTypes>(service: Service<TextAreaSchema>, normalizeProps: NormalizeProps<T>): TextAreaApi<T> {\n const { context, computed, prop, send } = service;\n\n const value = context.get(\"value\");\n const focused = context.get(\"focused\");\n const charCount = computed(\"charCount\");\n const hasMaxLength = computed(\"hasMaxLength\");\n const showClear = computed(\"showClear\");\n const disabled = !!prop(\"disabled\");\n const readOnly = !!prop(\"readOnly\");\n const showActions = prop(\"showActions\") ?? true;\n const autoSize = !!prop(\"autoSize\");\n const maxLength = prop(\"maxLength\");\n const rows = prop(\"rows\") ?? 4;\n const isEmpty = value.length === 0;\n\n return {\n value,\n focused,\n charCount,\n hasMaxLength,\n showClear,\n\n clear() {\n send({ type: \"VALUE.CLEAR\" });\n },\n\n pasteRecognize(value?: string) {\n send({ type: \"PASTE_RECOGNIZE\", value });\n },\n\n getRootProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"root\",\n \"data-focused\": focused ? \"true\" : undefined,\n \"data-disabled\": disabled ? \"true\" : undefined,\n \"data-readonly\": readOnly ? \"true\" : undefined,\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n \"data-auto-size\": autoSize ? \"true\" : undefined,\n \"data-empty\": isEmpty ? \"true\" : \"false\",\n }),\n );\n },\n\n getTextareaProps() {\n return normalizeProps.textarea(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"textarea\",\n value,\n placeholder: prop(\"placeholder\"),\n disabled,\n readOnly,\n maxLength: maxLength && maxLength > 0 ? maxLength : undefined,\n rows,\n name: prop(\"name\"),\n \"aria-disabled\": disabled ? (\"true\" as const) : undefined,\n \"aria-readonly\": readOnly ? (\"true\" as const) : undefined,\n onFocus() {\n send({ type: \"FOCUS.SET\", focused: true });\n },\n onBlur() {\n send({ type: \"FOCUS.SET\", focused: false });\n },\n onChange(event: JSX.ChangeEvent<HTMLTextAreaElement>) {\n const target = event.target as unknown as HTMLTextAreaElement;\n const nativeEvent = (event as { nativeEvent?: { isComposing?: boolean } }).nativeEvent;\n const isComposing = !!nativeEvent?.isComposing;\n let next = target.value;\n // IME composing 期间跳过 maxLength 截断,允许拼音中间态值流过\n // 防止 React 受控组件因值未更新而重置 DOM,打断输入法\n if (!isComposing) {\n const maxLen = prop(\"maxLength\") ?? 0;\n if (maxLen > 0 && next.length > maxLen) {\n next = next.slice(0, maxLen);\n }\n }\n // 直接同步更新,绕过 send() 的 queueMicrotask 延迟\n context.set(\"value\", next);\n },\n }),\n );\n },\n\n getBottomBarProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"bottom-bar\",\n \"data-show-actions\": showActions ? \"true\" : \"false\",\n }),\n );\n },\n\n getCounterProps() {\n return normalizeProps.element(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"counter\",\n \"data-has-max-length\": hasMaxLength ? \"true\" : undefined,\n \"aria-live\": \"polite\" as const,\n }),\n );\n },\n\n getClearButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"clear-button\",\n type: \"button\" as const,\n disabled: !showClear || disabled || readOnly,\n \"aria-label\": prop(\"clearLabel\") ?? \"清除内容\",\n onClick() {\n send({ type: \"VALUE.CLEAR\" });\n },\n }),\n );\n },\n\n getPasteButtonProps() {\n return normalizeProps.button(\n compact({\n \"data-scope\": \"textarea\",\n \"data-part\": \"paste-button\",\n type: \"button\" as const,\n disabled,\n \"aria-label\": prop(\"pasteLabel\") ?? \"粘贴并识别\",\n onClick() {\n send({ type: \"PASTE_RECOGNIZE\" });\n },\n }),\n );\n },\n };\n}\n\n/**\n * 把 QwTextAreaProps 转成 machine context(默认值收口)。\n */\nexport function createTextAreaProps(props: QwTextAreaProps = {}): Partial<QwTextAreaProps> {\n return {\n defaultValue: props.defaultValue ?? \"\",\n disabled: props.disabled ?? false,\n readOnly: props.readOnly ?? false,\n showActions: props.showActions ?? true,\n autoSize: props.autoSize ?? false,\n rows: props.rows ?? 4,\n ...props,\n };\n}\n"],"mappings":"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"}
@@ -26,6 +26,10 @@ export interface QwTextAreaProps {
26
26
  showActions?: boolean;
27
27
  /** name 属性。 */
28
28
  name?: string;
29
+ /** 清除按钮 aria-label,默认「清除内容」。 */
30
+ clearLabel?: string;
31
+ /** 粘贴按钮 aria-label,默认「粘贴并识别」。 */
32
+ pasteLabel?: string;
29
33
  /** 输入变化回调。 */
30
34
  onChange?: (value: string) => void;
31
35
  /** 清空回调。 */
@@ -16,6 +16,8 @@ export type QwVisualState = "default" | "hover" | "active" | "focus-visible" | "
16
16
  export type QwVisualTheme = "light" | "dark";
17
17
  /** 框架轴。 */
18
18
  export type QwVisualFramework = "react" | "vue";
19
+ /** visual target 的查找作用域;省略时先查 demo canvas,再回退到整页。 */
20
+ export type QwVisualTargetScope = "canvas" | "page";
19
21
  /** 视口配置(预留扩展;默认 mobile = 390×844 @2x,与 G3 一致)。 */
20
22
  export type QwVisualViewport = "mobile";
21
23
  /**
@@ -69,6 +71,15 @@ export type QwGeometryRelation = {
69
71
  /** T3 token 短名,如 "size-control-height-m";运行时从 tokens.json 解析 */
70
72
  token: string;
71
73
  };
74
+ /** 真实浏览器 computed style 的精确期望。 */
75
+ export interface QwVisualStyleExpectation {
76
+ target: string;
77
+ /** 期望生效的场景状态;省略时为 default。 */
78
+ state?: QwVisualState;
79
+ /** kebab-case CSS 属性名,如 `padding-inline-start`。 */
80
+ property: string;
81
+ value: string;
82
+ }
72
83
  /** 场景级可选强契约;省略时仍享有零声明基础保障。 */
73
84
  export interface QwVisualExpect {
74
85
  /** 覆盖库级默认通道(极少用,如某状态刻意不改 fill)。 */
@@ -78,6 +89,8 @@ export interface QwVisualExpect {
78
89
  * key = "state.property"(state 可省略,默认 default),value = token 短名(如 "border-focus")。
79
90
  */
80
91
  tokens?: Record<string, string>;
92
+ /** 精确 computed style;用于无法仅以 token 绑定表达的确定性尺寸契约。 */
93
+ styles?: QwVisualStyleExpectation[];
81
94
  /** 几何关系谓词。 */
82
95
  geometry?: QwGeometryRelation[];
83
96
  /** 该场景颜色随主题不变,显式豁免主题 delta 检查。 */
@@ -86,6 +99,12 @@ export interface QwVisualExpect {
86
99
  motion?: {
87
100
  /** data-visual-target 或 data-part 选择器别名;解析为动画宿主 */
88
101
  animatedPart: string;
102
+ /** animatedPart 内部的 data-part;省略时直接检查 animatedPart。 */
103
+ part?: string;
104
+ /** 浏览器解析后的精确 animation-duration。 */
105
+ duration?: string;
106
+ /** 浏览器解析后的精确 animation-timing-function。 */
107
+ timingFunction?: string;
89
108
  respectsReducedMotion?: boolean;
90
109
  };
91
110
  /** Icon 等:双栈 SVG 结构归一化哈希一致。 */
@@ -101,6 +120,8 @@ export interface QwVisualScenario {
101
120
  demo: string;
102
121
  /** 对应 demo 中 `data-visual-target` 的值。 */
103
122
  target: string;
123
+ /** Portal 等已知整页目标可声明 page,避免先等待 canvas 查找超时。 */
124
+ targetScope?: QwVisualTargetScope;
104
125
  /** 本场景要验证的视觉状态列表。 */
105
126
  states: QwVisualState[];
106
127
  /** 主题列表,默认 light + dark(主题 delta 使用;parity 仍跑双主题)。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qw-ui/core",
3
- "version": "0.3.0-next.4",
3
+ "version": "0.3.0-next.40",
4
4
  "description": "L2 · Headless Core — zag.js machine 薄包装,纯 TS / SSR-safe,零样式。",
5
5
  "license": "MIT",
6
6
  "author": "qw-ui contributors",
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/dialog/index.d.ts",
33
33
  "import": "./dist/dialog/index.js"
34
34
  },
35
+ "./overlay-panel": {
36
+ "types": "./dist/overlay-panel/index.d.ts",
37
+ "import": "./dist/overlay-panel/index.js"
38
+ },
35
39
  "./tabs": {
36
40
  "types": "./dist/tabs/index.d.ts",
37
41
  "import": "./dist/tabs/index.js"
@@ -91,6 +95,26 @@
91
95
  "./input": {
92
96
  "types": "./dist/input/index.d.ts",
93
97
  "import": "./dist/input/index.js"
98
+ },
99
+ "./switch": {
100
+ "types": "./dist/switch/index.d.ts",
101
+ "import": "./dist/switch/index.js"
102
+ },
103
+ "./stepper": {
104
+ "types": "./dist/stepper/index.d.ts",
105
+ "import": "./dist/stepper/index.js"
106
+ },
107
+ "./badge": {
108
+ "types": "./dist/badge/index.d.ts",
109
+ "import": "./dist/badge/index.js"
110
+ },
111
+ "./tabbar": {
112
+ "types": "./dist/tabbar/index.d.ts",
113
+ "import": "./dist/tabbar/index.js"
114
+ },
115
+ "./side-nav": {
116
+ "types": "./dist/side-nav/index.d.ts",
117
+ "import": "./dist/side-nav/index.js"
94
118
  }
95
119
  },
96
120
  "sideEffects": false,
@@ -99,7 +123,7 @@
99
123
  "@zag-js/dialog": "^1.42.0",
100
124
  "@zag-js/tabs": "^1.42.0",
101
125
  "@zag-js/utils": "1.42.0",
102
- "@qw-ui/icons": "^0.3.0-next.4"
126
+ "@qw-ui/icons": "^0.3.0-next.40"
103
127
  },
104
128
  "devDependencies": {
105
129
  "@microsoft/api-extractor": "^7.58.9",
@@ -113,8 +137,8 @@
113
137
  "typescript": "^6.0.3",
114
138
  "unrun": "^0.3.1",
115
139
  "vitest": "^4.1.9",
116
- "@qw-ui/eslint-config": "0.0.1",
117
- "@qw-ui/tsconfig": "0.0.1"
140
+ "@qw-ui/tsconfig": "0.0.1",
141
+ "@qw-ui/eslint-config": "0.0.1"
118
142
  },
119
143
  "size-limit": [
120
144
  {