Astryx TextArea 组件契约解析:多行输入域的解剖学所有权与主题化表面
【免费下载链接】astryxAn open source design system that's fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx
导读
本文基于 Astryx 设计系统仓库中的packages/core/src/TextArea/TextArea.spec.md组件契约文档,深入解析 TextArea(多行文本输入域)的消费者解剖结构、组件所有权边界,以及各部分与公共主题化 API 之间的映射关系。你将理解 Astryx 如何用「Theming anatomy」机制把"哪个可见部件归谁画"用机器可读的结构固化下来,学会如何依据 FR1–FR4 不变式、delegatesTo/inherits/none四种处置来审计一个组件的主题化表面,并掌握对应源码、文档与测试文件的验证路径。
一、组件契约(Component Contract)是什么
Astryx 的每个核心组件都配有.spec.md知识文档,它记录的不是消费者如何用组件(那属于TextArea.doc.mjs),而是组件在系统中的"解剖学契约":组件渲染了哪些可见部件、每个部件分别由谁拥有其外观语义、这些部件与公共主题化 target 的映射关系,以及维持这些关系的可验证不变式。
TextArea 契约当前处于draft(草案)状态,它记录的是现状事实:TextArea 通过 Field、FieldStatus、Icon、Spinner 等共享组件组合出带标签的多行输入域,并对text-area、text-area-control、text-area-counter三个主题 target 拥有所有权。该草案不改变任何运行时行为或公共 API——这一点在"兼容性与迁移"一节被明确声明:
- 发布默认保持:
yes - 兼容性类别:仅增量文档;运行时、DOM、样式、target、别名与公共 API 均不变
- 受控/非受控行为不变:TextArea 保持受控组件
- 迁移决策:无
消费者侧的迁移指引属于消费者文档与发布说明的职责,不写入本契约。
二、所有权边界:TextArea 画什么、不画什么
契约通过「Owns / Does not own」清单精确划定职责,这是整个主题化表面正确性的前提。
TextArea 拥有(Owns)
- 绘制出的输入容器(painted input container)
- 原生文本域(native text area)
- 字符计数器(character counter)
三者分别对应现有text-area、text-area-control、text-area-counter主题 target。从实现看,TextArea.tsx 中这三处themeProps()调用(themeProps('text-area', {...})、themeProps('text-area-control')、themeProps('text-area-counter'))正是契约所述结构的落地。
TextArea 不拥有(Does not own / non-goals)
| 部件 | 归属 |
|---|---|
| 标签、附加/分离式校验消息的呈现 | component:Field、component:FieldStatus |
| 工具提示变体的状态表面 | component:Tooltip |
| 标准起始图标与共享的场内状态图标 | component:Icon、component:Field |
| 加载指示器呈现 | component:Spinner |
| 调用方提供的自定义起始内容 | 不在 TextArea 公共主题化所有权内 |
已废弃的textarea别名 | 仅作为兼容性证据,不视为独立解剖部件 |
源码印证:TextArea 渲染时外层使用Field承载 label/status/width(TextArea.tsx),忙态时在 end slot 中渲染<Spinner size="sm" />与状态图标(TextArea.tsx),起始图标通过renderIconSlot(startIcon, {size: 'sm', color: 'secondary'})渲染(TextArea.tsx),状态图标经useInputStatusIcon取得——该 hook 也被 DateInput、DateRangeInput、DateTimeInput 等输入族成员复用。
三、行为与布局契约(FR1–FR4)
契约以"候选不变式"表格的形式固化了四条必须维持的行为事实:
| ID | 候选不变式 | 依据 | 草案评审状态 |
|---|---|---|---|
| FR1 | 当前渲染将原生文本域置于其绘制输入容器内部 | 当前源码、文档与聚焦测试 | 已验证当前行为;无新行为决策 |
| FR2 | 输入容器、原生控件与条件性字符计数器携带三个当前本地 target | 当前源码与公共文档 | 已验证当前行为;无 target 变更 |
| FR3 | 标签、标准起始图标、加载指示器、状态图标与状态表面继续使用其共享的 Field、Icon、Spinner、FieldStatus、Tooltip 所有者 | 当前源码 | 已验证条件组合边界 |
| FR4 | 占位文本仍属于原生控件;超限字形仍属于字符计数器 | 当前源码、文档与聚焦测试 | 已验证当前分组 |
这些不变式在 TextArea.test.tsx 中有对应断言:
- FR1/FR4 的 DOM 结构:
renders the counter inside the input container断言 counter 与 textarea 具有相同父级(同为容器内兄弟覆盖层),placeholder与rows系列用例验证占位与行数属于原生控件; - FR2 的 target 输出:
TextArea theme target names用例断言根元素同时携带当前类astryx-text-area与兼容类astryx-textarea(TextArea.test.tsx);disabled theme state与readonly theme state用例验证根 target 以data-disabled/data-readonly反映状态(TextArea.test.tsx); - FR3 的组合边界:
status prop系列与statusVariant forwarding系列验证附加/分离变体是否渲染场内状态图标、是否预留尾部空间(TextArea.test.tsx)。
此外themingTargets.test.ts(themingTargets.test.ts)作为源码/元数据守门人,核对themeProps()真实调用点与.doc.mjs中声明的theming.targets是否漂移。
允许变化(Allowed variation)
- value、placeholder、行数(
rows)、尺寸(size)、状态(status)、禁用/只读状态与可选插槽仍是现有能力,而非独立的 target 名称; - 起始图标内容可以是 Icon 支持的值,也可以是调用方提供的 ReactNode。
代表性状态
| 状态 | 必需不变式 | 允许变化 |
|---|---|---|
| 默认可编辑 | 标签、输入容器、原生文本域渲染 | value、placeholder、rows、size |
| 带字符限制 | 字符计数器渲染在输入容器内 | 计数与超限状态 |
| 忙态或状态 | 共享 Spinner 或状态呈现渲染在当前位置 | 状态变体与消息存在性 |
转换与优先级顺序
未引入新的 value、布局、状态或样式优先级规则。
性能与资源
未引入新的性能或资源规则。值得注意的是,源码中字符计数的分段(characterCount)仅在有maxLength时才执行,并通过useMemo缓存(TextArea.tsx),这印证了"不引入新规则"且对无计数器场景零额外开销的实现选择。
四、无障碍契约
该草案不改变或扩展 TextArea 现有的标签、描述、状态、计数器、忙态、禁用/只读状态或播报行为。但实现细节值得一提:当disabledMessage存在时,textarea 以aria-disabled+readOnly替代原生disabled,保持键盘可聚焦以便发现禁用原因(TextArea.tsx);字符计数区段(under/near/over)只在跨区时通过useAnnounce播报,超限用 assertive、接近上限用 polite(TextArea.tsx)。测试announces remaining characters politely与announces over-limit assertively直接验证了这一行为(TextArea.test.tsx)。
五、设计关系与 Theming Anatomy 映射
解剖学-需求对照
| 解剖部件或状态 | 设计要求 | 呈现权威 | 层级角色 | 组件契约 |
|---|---|---|---|---|
| 输入容器 | 呈现当前绘制字段边界 | 当前源码与公共文档 | 支撑 | FR1, FR2 |
| 文本域 | 呈现并编辑当前多行值 | 当前源码与公共文档 | 突出 | FR1, FR2 |
| 字符计数器 | 呈现当前与最大字符数 | 当前源码与公共文档 | 支撑 | FR2, FR4 |
| 共享字段反馈 | 呈现当前标签、标准图标、状态与加载内容 | 共享组件源码 | 支撑 | FR3 |
Theming anatomy 机器可读映射(原文档核心数据)
这是契约文档的核心资产——每个消费者可见解剖部件到主题化处置的精确映射。必须原样保留:
{ "Label": { "delegatesTo": {"owner": "component:Field", "target": "field-label"} }, "Description": { "none": { "reason": "unsettled: No current public target reaches the stable Description; future exposure still needs an owner decision" } }, "Input container": {"target": "text-area"}, "Text area": {"target": "text-area-control"}, "Placeholder": {"inherits": "text-area-control"}, "Start icon": { "delegatesTo": {"owner": "component:Icon", "target": "icon"} }, "Custom start content": { "none": { "reason": "intentional: Custom start content is caller-provided ReactNode content outside TextArea's public theming ownership" } }, "Spinner": { "delegatesTo": {"owner": "component:Spinner", "target": "spinner"} }, "Status icon": { "delegatesTo": { "owner": "component:Field", "target": "input-status-icon" } }, "Character counter": {"target": "text-area-counter"}, "Field status message": { "delegatesTo": { "owner": "component:FieldStatus", "target": "field-status" } }, "Tooltip status message": { "delegatesTo": {"owner": "component:Tooltip", "target": "tooltip"} } }如何读懂这张映射
依据 component-theming-surface.md 定义的系统模型,每条解剖条目对应四种处置之一:
target—— 本组件为可见部件承诺一个稳定公共 target,如text-area、text-area-control、text-area-counter;inherits—— 部件无独立 target,继承父 target 的样式,如 Placeholder 继承text-area-control;delegatesTo—— 由其他 Astryx 组件拥有该部件及其 target,如 Label 委托给Field/field-label、Status icon 委托给Field/input-status-icon、Field status message 委托给FieldStatus/field-status、Tooltip status message 委托给Tooltip/tooltip;none+ 分类原因—— 当前无公共 target 可达该部件,原因必须精确分类:intentional:(刻意边界)、reachability-gap:(应达未达)、unsettled:(待决策)。
TextArea 映射中的两处none正是分类用法的范例:Custom start content是调用方自带的 ReactNode,属于刻意排除(intentional);Description虽然语义稳定但尚无公共 target 可达,属待决策(unsettled)。契约明确指出:已废弃的textarea别名仅是兼容性证据,不进入映射——它与text-area的迁移关系见 component-theming-surface.md 的 0.7.0 废弃表面移除窗口表格(textarea → text-area)。
对应的.doc.mjs元数据(TextArea.doc.mjs)声明了四个 target:astryx-text-area(visualProps: size/status,states: disabled/readonly)、astryx-text-area-control、astryx-text-area-counter,以及保留的兼容类astryx-textarea(deprecatedFor: 'text-area');同时声明了私有变量--_textarea-inline-padding(默认var(--spacing-2),private: true)及其 derived 展开(paddingInline替换)。实现中该私有变量用于让文本内边距、起始图标、状态/Spinner 与字符计数器对齐(TextArea.tsx)。
六、家族与系统关系
architecture:component-theming-surface拥有解剖学资质、事实性none处置、保持组合的所有权与别名排除规则;- Field、FieldStatus、Icon、Spinner、Tooltip 在被 TextArea 组合时,保留各自现有的公共 target 契约。
TextArea 是family:input-fields(输入字段家族)的成员之一(input-fields.md)。家族契约记录:TextArea 采用 Field、FormLayout、size、width、isLoading、changeAction、status 与 disabled reason,并支持块轴增长;不兼容 InputGroup(input-fields.md)。家族不变式对 TextArea 的具体约束包括:
- FR1:普通状态切换(占位变值、忙态/状态出现)不得改变外部可用行内尺寸;
- FR2:已渲染的端部控件(Spinner、状态控件)必须拥有不重叠的空间,且不存在的控件不得留下陈旧预留;
- FR6:
changeAction路径必须先onChange、乐观呈现受控值、在 transition 中执行 Action 并共享同一忙态呈现——TextArea 中useOptimistic(value)+startTransition正是此规则的实现(TextArea.tsx)。
七、验证映射:契约如何被守住
契约的verified_by元数据声明了三类验证锚点:TextArea.test.tsx、themingTargets.test.ts、check-knowledge.mjs。逐条不变式的验证策略:
| 契约 | 验证方式 | 代表性状态 | 变异或失败预期 |
|---|---|---|---|
| FR1, FR4 | TextArea.test.tsx结构与计数器套件 | 默认、占位、计数、超限 | 移除或重组稳定部件会破坏现有 role、content 或 DOM 断言 |
| FR2 | TextArea.test.tsx根 target 套件与themingTargets.test.ts | 当前与废弃根名;当前 target | 移除根兼容类会失败聚焦覆盖;源码/文档漂移会触发 target 守门人 |
| FR3 | TextArea.test.tsx、useInputStatusIcon.test.tsx与renderIconSlot源码检查 | 标准/自定义起始内容;Spinner;附加/分离/工具提示状态 | 移除或改道组合内容会破坏现有 icon、spinner、tooltip、message 或关联断言 |
| Theming anatomy 映射 | scripts/check-knowledge.mjs | 规范解剖与当前本地 target | 规范键漂移、非法处置或 target 拼写、别名支撑的本地声明、未认领的当前本地 target 均验证失败 |
契约同时诚实记录了验证空白:TextArea 聚焦套件钉住了当前与废弃根类,但未单独断言text-area-control或text-area-counter的精确类放置——该覆盖由源码/元数据 target 守门人承担;且当前仓库没有检查会解析delegatesTo的 owner/target 配对,本草案中的配对是手工核验的,语义委托漂移仍是验证空白。
八、决策日志、开放问题与内容边界
- 决策日志:无。本草案仅记录现状事实,未引入组件本地设计或 API 决策。
- 开放问题:无。
- 内容边界:本文件不重复消费者 prop 表、示例、实现步骤或共享组件契约,而是链接到其所有者——prop 与 usage 详见 TextArea.doc.mjs,实现详见 TextArea.tsx,行为验证详见 TextArea.test.tsx。
结语
TextArea 组件契约是 Astryx"可主题化表面"治理的最小但完整范例:它用一张机器可读的解剖映射,把"输入容器归本组件、标签归 Field、消息归 FieldStatus、占位继承控件"这类所有权事实固化为可校验的规范,再以 FR1–FR4 不变式、themingTargets.test.ts与check-knowledge.mjs形成源码-元数据-测试的三方守门。理解这份契约,等于掌握了阅读 Astryx 任意核心组件 spec 的方法:先读解剖清单,再看处置映射,最后对照验证映射确认每一项都有可执行证据——这也是为组件编写新主题或扩展目标前必须完成的前置审计。
【免费下载链接】astryxAn open source design system that's fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考