
astryx Grid 组件契约全解析二维轨道布局、GridSpan 跨越机制与主题化所有权边界【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryxGrid 是 astryx 设计系统中负责二维布局的基础组件它以 CSS Grid 为底层轨道模型把调用方提供的任意内容按行与列排列并借助可选的 GridSpan 包装器控制单个条目对行、列的跨越参与。本文以仓库中的组件规格文档 Grid.spec.md 为骨架结合 Grid.tsx、GridSpan.tsx、Grid.test.tsx 等源码与测试为你完整拆解 Grid/GridSpan 的公共契约、三条行为不变量、主题化 anatomy 映射以及整套验证机制。读完后你将清楚Grid 与 GridSpan 各自的职责边界是什么、columns轨道模型与grid-span主题目标的底层原理如何实现、以及“契约不变量—源码实现—机器校验”三者如何闭环。一、组件契约一份“只记录现状、不改变行为”的规格Grid.spec.md属于 astryx 知识库knowledge体系中的组件契约文档其 frontmatter 标注了kind: component、schema_version: 3、template_version: 3、authority: draft草案态并声明review_triggers: [layout, theming]——即布局与主题化变更会触发对该契约的复核。文档开头的 Intent 明确写道Grid arranges caller-supplied items in two-dimensional tracks. GridSpan optionally wraps one item to control its row or column participation. This draft records the current consumer anatomy and theming ownership without changing layout, targets, or public API.这句话定义了整份契约的基调它是当前消费者结构与主题化所有权的记录性文档不引入任何新的布局规则、主题目标或公共 API。契约的“兼容性与迁移”章节也为此提供了三个判定已发布默认行为是否保留yes兼容性分类纯增量文档additive documentation only运行时、DOM、样式、主题目标与公共 API 全部不变受控/非受控行为不适用not applicable迁移决策无因此阅读这份契约时应当把它当作一份“现状基线”而非“需求变更单”——这正是 astryx 用契约锁定稳定面、用architecture:public-component-api守住 prop 表面的工程方式。二、职责边界Owns 与 Non-goals契约用 Ownership boundary 一节精确划定了组件所有权的边界这是理解 Grid 架构语义的关键。Grid/GridSpan 拥有OwnsGrid 容器本身以及它当前的grid主题目标theming target可选的 GridSpan 包装器以及它当前的grid-span主题目标Grid 轨道track的构建以及 GridSpan 对行/列的跨越参与。Grid/GridSpan 不拥有Does not own / non-goals提供给 Grid 或 GridSpan 的内容——内容所有权属于调用方caller结构性的页面区域page-region或表面surface语义任何新的响应式行为、新的布局 props、新的主题目标或目标位置调整。这一边界在 Grid.doc.mjs 的 anatomy 定义中得到印证组件解剖结构只有两个元素——必需的Grid containerTwo-dimensional layout container that arranges caller-supplied items in rows and columns与可选的Spanning itemOptional GridSpan wrapper that changes one items column or row participation。契约还特别强调直接放入 Grid 的调用方内容不属于 Grid 拥有的解剖部件这保证了 Grid 只负责“轨道与参与方式”而内容与内容自身的样式永远由调用方掌控。三、行为与布局契约三条候选不变量契约的核心是 Behavioral and layout contract 表中的三条候选不变量candidate invariant。这三条是整份规格的事实锚点分别对应 Grid 容器、GridSpan 包装器与 GridSpan 的跨越多维度ID候选不变量依据草案评审状态FR1Grid 渲染一个携带当前grid目标的 Grid 容器并使用当前轨道模型排列调用方内容当前源码、文档、测试与 family 契约已验证为当前行为未决定新行为FR2GridSpan 渲染一个可选的 Spanning item 包装器携带当前grid-span目标当前源码、文档与测试已验证为当前行为无目标变更FR3GridSpan 当前的columns与rows输入只影响被包装条目在其父网格中的参与当前源码、测试与 family 契约已验证为当前行为无布局变更三条不变量与源码一一对应FR1 对应 Grid.tsx 中themeProps(grid, {...})与display: grid的容器渲染FR2 对应 GridSpan.tsx 中themeProps(grid-span)的包装器渲染FR3 对应 GridSpan 将columns/rows编译为gridColumn/gridRow内联样式的逻辑——这些样式只作用于被包装条目自身绝不外溢到父网格。3.1 允许的变异Allowed variation契约同时为不变量的实现保留了合理的自由度避免把实现细节误当成契约Grid 可以使用固定轨道也可以使用内在尺寸intrinsic响应式轨道而 Grid 容器所有权不变调用方内容可以直接渲染在 Grid 内也可以放在可选的 GridSpan 包装器内跨越条目Spanning item可以只跨列、只跨行、行列同跨、或都不跨同时始终保留同一个grid-span目标。3.2 代表性状态Representative states契约用四个代表性状态把“不变量 允许变异”落到可验证的维度上状态必要不变量允许的变异Fixed Grid固定网格Grid 容器拥有当前grid目标固定列数与间距可取不同正值Intrinsic Grid内在网格同一容器拥有响应式轨道构建填充/适应模式、最小宽度、列数上限可变Direct item直接条目调用方内容在无 Grid 包装器的情况下参与布局调用方拥有自己的元素与样式Spanning item跨越条目GridSpan 拥有一个包装器与grid-span目标列跨越与行跨越的取值可变3.3 变换与优先级、性能边界契约明确声明本草案不引入任何新的轨道构建、间距、对齐、尺寸或跨越优先级规则也不引入新的性能或资源规则。这意味着阅读者不应把契约当作算法规范——轨道构建的具体算法如buildCappedTemplate由实现层负责契约只锁定所有权与稳定面。四、从契约到源码Grid 的轨道模型实现契约 FR1 中提到的“当前轨道模型”在 Grid.tsx 中由GridColumns类型与运行时模板构建逻辑共同承载。4.1 columns 的两种形态GridColumns是一个联合类型Grid.tsx数字固定等宽列如columns{3}编译为repeat(3, 1fr)对象基于最小子项宽度的响应式列包含三个字段minWidth每个列轨道的最小宽度pxrepeatfill默认保留空轨道以获得一致的条目宽度fit折叠空轨道让条目拉伸填满剩余空间max限制最大列数。网格始终拉伸到父容器宽度的 100%已存在的列永远填满整行——因此移动端折叠为单列时会拉伸到全宽右侧不会出现死区。对应的构建逻辑Grid.tsx对象形态且max存在时调用buildCappedTemplate对象形态无max时输出repeat(auto-fill|auto-fit, minmax(minWidthpx, 1fr))数字形态且大于 0 时输出repeat(N, 1fr)其余情况未传、columns{0}、负数回退为单列1fr——这一点有专门的防御性测试覆盖见 Grid.test.tsx。4.2 轨道模板的 CSS 变量间接寻址实现中一个容易被忽略但至关重要的设计是动态轨道值不是以原始内联样式写入 DOM而是编译为“CSS 变量 类级别声明”grid-template-columns: var(--x)。Grid.tsx 的注释解释了动机原始内联grid-template-columns的优先级会压过任何类选择器导致调用方通过xstyle的覆盖包括media查询内的覆盖失效改用 CSS 变量间接寻址后消费者的xstyle覆盖依然可以胜出。Grid.test.tsx 为此专门写了一组回归测试断言grid.style.gridTemplateColumns 且grid.style.gridAutoRows 同时--x-gridTemplateColumns变量携带repeat(3, 1fr)、--x-gridAutoRows携带80px——从测试层面钉死了“声明必须活在类里绝不能写成内联属性”这一约束。4.3 列数上限的数学上限放在轨道 MIN而非 MAXbuildCappedTemplateGrid.tsx是响应式上限的核心算法。它把列数上限放在轨道的最小值上每个轨道至少为perColumn (100% - (max-1) * gap) / max因此超过max列永远装不下而轨道的最大值保持1fr所以当实际可容纳的列数少于max时尤其是移动端的单列这些列仍会拉伸填满整行不会留下右侧死区。轨道最小值被进一步写成min(100%, max(minWidthpx, perColumn))显式的minWidth仍然生效同时当视口窄于minWidth/perColumn时单列会收缩到容器宽度而不是溢出。测试给出了该算法的精确输出Grid.test.tsxrepeat(auto-fill, minmax(min(100%, max(250px, calc((100% - 2 * var(--spacing-4)) / 3))), 1fr))其中var(--spacing-4)表明 gap 参与了列数上限的数学运算而 Grid.test.tsx 记录的回归issue #3391说明旧实现把上限放在轨道 MAXminmax(minWidth, 100%/max)导致单列被钉在约100%/max的宽度上产生右侧死区新实现改为轨道 MIN 上限后问题消除。4.4 间距、尺寸与对齐Grid 的间距完全基于设计系统的 spacing token。gap、rowGap、columnGap的取值是离散的数值步长0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10每个取值都映射到packages/core/src/theme/tokens.stylex中的spacingVars如--spacing-4而不是任意的像素值——这保证了 Grid 与整个设计系统的节奏一致见 Grid.tsx。rowGap/columnGap会分别覆盖gap在对应轴上的取值。尺寸类 props 的语义是“数字按像素、字符串原样使用”width{600}编译为600pxwidth100%直接使用100%height50vh同理Grid.tsx 与 Grid.test.tsx。对齐方面align映射到align-items、justify映射到justify-items取值均为start | center | end | stretch默认stretchGrid.tsx。五、GridSpan跨越参与的可选包装器契约 FR2/FR3 的主角是 GridSpan。它作为 Grid 的直接子元素使用只负责改变被包装条目在父网格中的参与方式且参与值只在包装器自身的内联样式中体现绝不旁及兄弟条目或网格本身。GridSpan.tsx 暴露了两个布局 propscolumns?: number | fullnumber编译为grid-column: span Nfull编译为grid-column: 1 / -1跨越整行rows?: number编译为grid-row: span N。实现上跨越值直接以内联样式写入包装器元素GridSpan.tsx这与 Grid 的轨道模板“必须走 CSS 变量间接寻址”形成对比——因为跨越是条目级的一次性参与不涉及消费者在媒体查询中的覆盖场景。同时包装器基础样式包含minWidth: 0防止网格内溢出、display: grid与height: 100%让包装器填满网格单元并拉伸子内容GridSpan.tsx。Grid.test.tsx 对 GridSpan 的验证覆盖了全部代表性状态跨列gridColumn span 2跨整行gridColumn 1 / -1跨行gridRow span 2行列同跨两个内联属性同时存在不传跨越 props不产生任何gridColumn/gridRow内联样式neither 状态ref 转发与额外属性如aria-label透传。配合rowHeightGridSpan 还能构建瀑布流masonry式布局——Grid.tsx 的文档示例Grid columns{3} rowHeight{80} gap{3} GridSpan rows{4}Tall/GridSpan GridSpan rows{2}Short/GridSpan /GridrowHeight设置每个隐式行轨道的高度grid-auto-rows: 80px条目再通过rows跨越不同数量的行即可形成高矮错落的瀑布流效果。六、主题化契约Theming anatomy 与两个主题目标契约的 Design relationships 与 Theming anatomy 两节回答了“谁负责把 Grid 渲染成什么样子”的问题。设计关系表把两个解剖部件映射到不变量解剖部件或状态设计要求表征权威层级角色组件契约Grid container呈现当前二维布局容器当前源码、文档与 family 契约SupportingFR1Spanning item可选地改变某条目在 Grid 中的行/列参与当前源码、文档与 family 契约SupportingFR2, FR3Theming anatomy 的规范形态契约内嵌 JSON{ Grid container: {target: grid}, Spanning item: {target: grid-span} }在源码层面这两个目标由 themeProps.ts 统一生成Grid 渲染时调用themeProps(grid, {columns, gap, align, justify})Grid.tsx产出稳定的astryx-grid类以及data-columns、data-gap、data-align、data-justify等 data 属性GridSpan 调用themeProps(grid-span)GridSpan.tsx产出astryx-grid-span类。themeProps的机制是稳定目标类名来自集中式命名模块packages/core/src/naming.ts视觉 props 以 kebab-case 反射为data-*属性nullish 值一律省略。Grid.doc.mjs 的theming.targets与之一致并把可视 props 显式登记在案theming: { targets: [ {className: astryx-grid, visualProps: [align, columns, gap, justify]}, {className: astryx-grid-span}, ], },这套“解剖部件 → 主题目标”的映射正是 docs/architecture/component-theming-surface.md 中所述的主题化表面的具体实例主题作者只需要按astryx-grid/astryx-grid-span编写样式即可在不触碰 DOM 结构与公共 API 的前提下定制 Grid 的外观。七、验证地图不变量如何被机器校验契约的 Verification map 一节给出了每条不变量的验证责任全部落在仓库现有测试与脚本上形成“契约—测试—审计”闭环契约验证方式代表性状态变更/失败预期审计章节FR1Grid.test.tsx 的轨道、尺寸、对齐与内容套件fixed、default、fill、fit、capped、sized改变当前轨道输出或移除容器/内容会失败于精确的 style-variable、inline-style 或内容断言audit:Grid/anatomyFR2, FR3同一测试文件的 GridSpan 套件column、full-row、row、combined、no span改变包装器渲染或跨越值会失败于精确的内联样式与内容断言audit:GridSpan/anatomy目标清单target inventory源码检查 themingTargets.test.tsGrid 与 GridSpan 两个目标运行时与文档目标元数据漂移会导致目标校验失败audit:Grid/theming主题化 anatomy 映射scripts/check-knowledge.mjs规范 anatomy 与两个当前目标缺失、多余、带前缀或过期的映射会导致仓库校验失败audit:Grid/theming值得展开的是目标清单校验themingTargets.test.ts 的文件头注释说明它针对的是曾两次发生漂移issue #3652、#3680的theming.targets字段——该字段是手写的文档化 CSS 表面而真实情况存在于源码的themeProps()调用点中二者若不一致“未文档化的类就是不可主题化的元素”。测试通过 TypeScript AST 扫描所有themeProps()调用点执行SUBSET 策略源码渲染的每个类都必须被文档化且传给它的每个 prop key 都必须出现在对应目标的visualProps或states中文档允许列出比源码更多的项。对于 Grid即要求源码中的themeProps(grid, {columns, gap, align, justify})与themeProps(grid-span)被 Grid.doc.mjs 的 targets 完整覆盖。契约同时诚实标注了验证的边界组件级聚焦测试不会断言两个目标类target 类的实际落位由源码检查与全局清单负责因此“目标放置是否正确”属于仓库级校验的职责而非单组件测试的职责——这与 themingTargets.test.ts 的定位完全一致。八、家庭与系统关系谁在语义上约束 Grid契约的 Family and system relationships 一节把 Grid 置于三层关系网中docs/families/layout-primitives.mdfamily:layout-primitives拥有 Grid 与 GridSpan 共享的间距、尺寸、对齐与条目参与词汇——Grid 的gap/align/justify/GridSpan 的columns/rows语义均由此 family 契约统一定义docs/architecture/component-theming-surface.mdarchitecture:component-theming-surface拥有解剖部件资格认定与上述两个本地目标映射docs/architecture/public-component-api.mdarchitecture:public-component-api拥有稳定的 prop 表面本契约不新增任何 API。公开概念一节进一步强调契约没有引入任何新的公共概念消费者的 props 与用法以 Grid.doc.mjs 与其附属的 GridSpan.doc.mjs 为准。其中 GridSpan 被标记为subComponentOf: Grid且isHiddenFromOverview: true在总览中隐藏它的 playground 默认以Grid columns{3} gap{2}为包装器默认演示columns{2}的“跨越 2 列”效果——这些元数据进一步印证了“GridSpan 是 Grid 的附属能力而非独立顶层组件”的契约定位。九、兼容性、决策日志与内容边界契约的收尾三节同样值得关注决策日志Decision log为空。正如开头所述本草案只记录当前事实不产生任何组件局部的设计、布局、可访问性或 API 决策开放问题Open questions无内容边界Content boundary本文件不重复消费者的 prop 表格、示例、family 布局规则、轨道算法、实现步骤或系统规则而是链接到它们的所有者。这与 frontmatter 中的architecture: [architecture:component-theming-surface, architecture:public-component-api]和families: [family:layout-primitives]形成呼应——契约刻意保持“单一职责”把算法细节留给文档与 family 契约自己只锁定不变量与所有权。可访问性方面契约明确本草案不会给 Grid 或 GridSpan 增加语义角色也不改变它们当前的 DOM 转发与调用方自有内容的语义——即 Grid/GridSpan 是纯布局容器语义由调用方内容自行表达这与 Grid.test.tsx 中“额外属性如aria-label原样透传到根元素”的行为一致保证了调用方在不增加任何包装层的前提下就能为网格提供无障碍标注。十、实践小结选型需要多列布局卡片画廊、仪表盘、任意多列区块时使用 GridGrid.doc.mjs 的最佳实践提示——优先columns{{minWidth: 280}}的响应式列用max限制大屏上的列数上限用repeat: fill默认保持条目宽度一致、fit让条目拉伸填满剩余空间不要手动写 CSS grid也不要拿HStack换行来替代 Grid。跨越想让某个条目跨列/跨行/整行把它包进 GridSpan 即可其余条目保持直接子元素。覆盖轨道模板利用“CSS 变量间接寻址”的特性通过xstyle必须是stylex.create()的产物而非style{{}}内联对象覆盖gridTemplateColumns包括媒体查询内的覆盖。主题定制主题作者只需针对astryx-grid含data-columns/data-gap/data-align/data-justify与astryx-grid-span两个稳定目标编写样式即可。以上每一条结论都能在契约 Grid.spec.md、实现 Grid.tsx / GridSpan.tsx、文档 Grid.doc.mjs / GridSpan.doc.mjs 以及测试 Grid.test.tsx / themingTargets.test.ts 中找到直接依据——这正是 astryx 组件契约体系“可记录、可验证、可追溯”的工程价值所在。【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考