Astryx Resizable 结构化百分比配置系统规范解读:`useResizable` 的像素基础与有界百分比设计

Astryx Resizable 结构化百分比配置系统规范解读:`useResizable` 的像素基础与有界百分比设计 Astryx Resizable 结构化百分比配置系统规范解读useResizable的像素基础与有界百分比设计【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx导读本文以 Astryx 设计系统仓库中的系统规范文档 docs/specs/AST-010/spec.md 为主体完整剖析 Resizable 组件的百分比尺寸配置体系如何通过percent(value, {min: pixel(...)})/percent(value, {max: pixel(...)})表达带一个像素下限或上限的百分比如何用containerRef把百分比解析基准从视口切换到容器内容盒以及为什么所有交互、持久化、回调与 ARIA 值始终停留在已解析的像素层面。读完后你将掌握useResizable0.6 版本中defaultSize、minSize、maxSize的完整配置语法、非法配置的确定性回退规则以及该 API 在指针拖拽、键盘、折叠/展开、SSR 与持久化场景下的行为边界。1. 规范背景为什么需要有结构的百分比在 Astryx 中可调整大小的面板panel由 packages/core/src/Resizable/useResizable.ts 提供的useResizableHook 与 packages/core/src/Resizable/ResizeHandle.tsx 渲染的焦点化分隔条共同驱动而布局结构由 LayoutPanel、SideNav 等调用方持有。在统一边界unified bounds发布之前像素版 API 只能表达像素约束如果开发者想给百分比尺寸附加一个像素下限或上限只能单独用 CSS 约束结果会造成绘制尺寸paint与 Hook 状态、ResizeHandle 的 ARIA 值三者不一致——CSS 裁剪了渲染结果但 Hook 内部的size状态与aria-valuenow仍停留在裁剪前的数值。AST-010 的目标正是消灭这种分歧把像素与有界百分比收进同一个配置词汇表让有效几何effective geometry在任何时刻都同步——useResizable返回的选中像素尺寸、LayoutPanel或调用方渲染的尺寸、持久化存储的展开值以及 ResizeHandle 的aria-valuenow/aria-valuemin/aria-valuemax描述的是同一个已解析几何。规范明确列出了大量非目标Non-goals它们是理解设计边界的关键不在本规范中实现运行时行为本文件是系统规范实现与验证另行落地不保留拖拽、键盘或编程调整后的百分比比例没有相对尺寸模式不自动分配剩余空间也不要求独立尺寸的区域总和为 100%不支持任意 CSS 单位、函数、表达式或递归组合——包括rem、vw、calc()、CSSmin()/max()不增加百分比吸附点或百分比折叠阈值不引入受控尺寸状态现有受控折叠行为不变不把resize()的参数放宽到字符串始终是像素数字不改变持久化状态结构不增加相对尺寸字段不扩大 SideNav 简化的defaultWidth/minWidth/maxWidth配置该集成需要单独的组件决策。2. 核心 APIpercent()、ResizableSize与ResizablePercentSize2.1 类型与帮助函数定义AST-010 为 Resizable 引入一套服务端安全server-safe的纯函数与类型。规范给出的权威定义如下type ResizablePercentSize { type: percent; value: number; } ({min: PixelWidth; max?: never} | {min?: never; max: PixelWidth}); type ResizableSize number | ${number}px | ${number}% | PixelWidth | ResizablePercentSize; function percent( value: number, options: {min: PixelWidth; max?: never} | {min?: never; max: PixelWidth}, ): ResizablePercentSize;这段定义在仓库源码中得到完整落地。在 packages/core/src/Resizable/utils.ts 中ResizablePercentSize是带判别字段type: percent的互斥联合XOR bound要么有min且max标记为never要么有max且min标记为never从类型层面杜绝两个边界同时存在或两个边界都不存在ResizableSize是number像素、精确Npx模板字符串、精确N%字符串、PixelWidthTable 的结构化像素值或ResizablePercentSize的联合percent()的options参数必填且恰好携带一个min或max其值必须是合法的PixelWidth。实现极简return {type: percent, value, ...options}。2.2 一个词汇表多种拼写defaultSize保留已发布的宽泛SizeValuenumber | string类型并追加PixelWidth | ResizablePercentSize保证既有number | string调用方继续编译字符串的运行时校验仍是权威。minSize与maxSize则直接使用ResizableSize。配置映射规则原文表格完整保留输入语义333或333px不变像素值pixel(333)是新组合场景下的规范结构化静态形式40%不变规范的无界百分比拼写max(40%, 333px)的意图写为percent(40, {min: pixel(333)})min(10%, 400px)的意图写为percent(10, {max: pixel(400)})关键设计决策percent(40)刻意不是40%的第二种拼写。无界百分比保持唯一的既有写法N%字符串percent()只能表达带一个像素边界的有界百分比。DEC-3 明确说明不要复用proportional()——它是 Table 中表达兄弟列之间相对权重relative weight的概念而不是某个实测基准的百分比字面量。2.3 复用 Table 的pixel()与PixelWidth规范强调不要为固定尺寸创建第二个帮助函数。pixel()与PixelWidth直接复用 Table 的实现packages/core/src/Table/utils.ts 从columnUtils与types中导出pixel、PixelWidth而 packages/core/src/Resizable/utils.ts 将其原样再导出。因此Resizable/utils子路径与根包解析出同一个pixel符号不会产生冲突。Resizable根入口 packages/core/src/Resizable/index.ts 统一导出percent、useResizable、ResizeHandle以及ResizablePercentSize、ResizableSize、ResizableRegion等全部公开类型。3. 百分比解析的基准basis视口与容器内容盒3.1 无containerRef保留视口兼容路径AST-010 首先保住已发布语义当不提供containerRef时defaultSize: N%仍然只解析一次解析基准是window.innerWidth客户端若window不可用则用1200px 服务端回退值解析完成后就作为像素继续使用。在源码中这个常量被明确定义为 SERVER_BASIS 1200并通过useSyncExternalStore的getServerSnapshot返回。无容器的百分比边界minSize/maxSize则不同它们要跟随视口变化持续重解析FR6所以仅在有百分比边界时订阅window的resize事件。3.2 有containerRef内容盒的活动轴提供containerRef?: React.RefObjectHTMLElement | null时百分比配置的基准从视口切换为该容器内容盒content-box的活动轴水平horizontal方向使用容器的内容盒内联尺寸inline size垂直vertical方向使用内容盒块尺寸block sizeRTL 可以反转指针增量但不得改变百分比基准无containerRef时无论方向如何兼容基准始终是window.innerWidth。注意containerRef只改变百分比解析的基准不创建持续的相对尺寸模式——选中尺寸在解析为像素后不会随视口或容器变化而缩放。源码中 measureContentBox 的实现细节很有价值它优先读取ResizeObserverEntry.contentBoxSize[0]该字段本身已感知书写模式 writing-mode返回inlineSize/blockSize仅在observeResize注册时合成的伪 entry 没有内容盒数据时才退回用clientWidth/clientHeight减去 padding 计算。注释明确解释了原因如果误用边框盒border box带边框或内边距的容器会让基准比百分比实际占有的空间大几个像素而这个误差最终会进入aria-valuemax。容器基准通过 Astryx 的共享 ResizeObserver 单例packages/core/src/utils/sharedResizeObserver.tsobserveResize/unobserveResize订阅返回的退订函数只移除当前回调不干扰同一元素上的其他订阅者——这是多区域共享一个容器时的关键保证。3.3 方向显式化与一致性校验direction?: horizontal | vertical默认horizontal只影响有containerRef时的容器基准轴。多区域配置复用顶层的direction为所有区域提供轴。useResizable通过ResizableProps._direction把 Hook 的轴传给 HandleResizeHandle.tsx 在开发环境检测到 Handle 的direction与 Hook 区域轴不一致时会发出警告使不匹配可被检测。3.4 1200px 临时基准与首次实测在提供的容器尚未产生正测量值之前首帧、服务端、display:none、脱离文档流等时刻依赖基准的配置允许先用确定的 1200px 作为临时回退。第一次有效的正测量会把依赖基准的默认值解析为最终的初始像素尺寸并解析百分比边界——但不持久化、不宣告这次临时回退。源码中0 不是测量值的处理值得注意隐藏或脱离的容器会报告 0若把50%解析在 0 之上会得到真实的最大值 0导致面板折叠并覆盖掉用户已保存的宽度因此代码把 0 视为未测量保留临时基准直到容器真正布局完成。4. 默认值解析一次边界保持活跃FR1/FR4/FR6AST-010 最核心的运行时区分是百分比默认值default解析一次结构化的percent()或原子N%默认值在初始化时解析为像素选择随后不再跟随基准变化百分比边界bounds保持活跃minSize/maxSize中的百分比在其视口或容器基准变化时重新解析套用各自的像素下限/上限后对既有像素选择执行钳制clamp而不是按比例缩放。源码通过 初始默认值 ref 的惰性锁存latching机制 实现只解析一次initialDefaultRef.current在首次渲染时写入解析结果并标记isFinal当容器尚未测量时该结果不视为最终首次真实测量到来后以新基准重新解析一次并置为 final。随后的视口或容器尺寸变化不会再重解析默认值——否则就等于制造了一个规范刻意禁止的第二种响应式尺寸模式。钳制顺序保持已发布的Math.min(max, Math.max(min, size))clampSize 先按该顺序钳制再在有吸附点snaps时吸附到最近的吸附点并再次钳制。当解析后的最小值超过最大值时开发环境警告、最大值胜出maximum wins保证几何永远有限且非NaN。一个值得展开的细节重新解析的边界钳制的是选择本身而不是只钳制绘制。源码注释记录了一个真实回归——如果只用派生值钳制绘制存储的选择原封不动容器缩小再放大后被钳制前的 320px 会复活成 560px。因此 提交边界committed bounds 在渲染阶段React 文档化的prop 变化后 state 必须跟随形态把新边界提交进状态并对chosenSize执行钳制。渲染阶段调整是刻意的React 在绘制前重跑该渲染所有观察者看到同一像素值且不会触发交互回调FR10。5. 非法配置的确定性回退FR12规范给出了一张完整的非法输入行为表原文完整继承如下输入可接受值非法回退开发环境行为生产环境行为defaultSize已发布SizeValue加PixelWidth或ResizablePercentSize结构化默认值解析一次250px再做常规边界钳制警告并使用回退使用相同回退不警告minSizeResizableSize全匹配原子字符串合法的pixel(value)或percent(value, {min XOR max: PixelWidth})50px警告并使用回退使用相同回退不警告maxSizeResizableSize全匹配原子字符串合法结构化值显式Infinity保持无界无界Infinity警告并使用回退使用相同回退不警告解析后的最小值高于最大值两个值各自合法最大值胜出警告并使用已发布钳制顺序使用相同顺序不警告可接受的配置必须是非负有限数字、精确的非负有限Npx字符串、0%–100% 的精确N%字符串或percent()结构化值百分比有限且在 0–100 内且恰好一个min或max是有限非负的PixelWidth。缺少边界、双边界、其他判别字段或任何非法数字都是非法配置。回退修复的是配置不是一次新的用户选择。初始化之后非法原始值永远不直接替换已持久化或合法的选中像素状态回退归一化后的边界只能通过常规钳制影响该状态。源码中 resolveBound 通过devWarn保证警告仅出现在开发环境而所有构建采用相同回退同时 显式maxSize: Infinity被保留为合法无界输入。字符串解析使用两个精确锚定正则PX_PATTERN与PERCENT_PATTERNparseSizeString结构化值校验则由 parsePercentSize 检查判别类型、0–100 范围与hasMin hasMax的互斥约束——类型系统之外的 JavaScript 与any调用方也由此获得同等保护。6. 交互、持久化与回调像素是唯一的语言FR3/FR5/FR8-FR106.1 指针与键盘指针增量从选中像素尺寸出发dragStartSizeRef delta方向键步进、Shift方向键、Home、End、折叠阈值与吸附点全部保持像素语义。规范要求FR7一次手势中使用一个稳定基准——手势期间容器若变化新基准要等手势结束后才生效否则 Handle 会脱离指针。源码用 gestureBasisRef 冻结基准并在onResizeEnd与onResizeCancelpointercancel、丢失捕获、拖拽中 Handle 卸载时通过releaseGestureBasis释放onResizeCancel不触发 resize 结束信号对应 #5297 的契约。折叠保留展开时的像素尺寸展开时恢复该像素尺寸并受当前已解析边界的钳制。折叠态下_size与返回的size为 0而 ResizeHandle 在渲染aria-valuenow时将其钳制到aria-valuemin见 ResizeHandle.tsx以满足 WCAG 4.1.2 对分隔条 role 值域的约束。6.2 持久化持久化结构不变规范给出的接口为interface PersistedResizableState { size: number | null; isCollapsed: boolean | null; }原子或结构化百分比默认值在持久化之前就已变成像素选择。恢复时已持久化的像素选择优先于defaultSize并被当前解析边界钳制。遗留的普通0保持原义折叠启用时它是已折叠且无保存的展开尺寸标记因此展开回退到配置的默认值折叠禁用时它不提供任何可用的已保存尺寸。源码 loadPersistedState 逐条注释了存储中的三种历史格式当前{size, isCollapsed}、遗留非零数字、遗留0写入时使用STORAGE_PREFIX astryx-resizable:前缀的 localStorage 键。规范特别强调基准变化从不写入百分比描述符或相对意图当基准仍是 1200px 临时占位时isBasisMeasured为假持久化 effect 直接跳过写入避免用从未真实存在过的基准派生的占位值覆盖用户的真实保存值。6.3 回调onSizeChange始终接收已解析的像素数字。初始化、水合修正、视口/容器尺寸变化、仅边界重钳制都不会作为用户交互上报。resize(size: number)保留已发布签名百分比字符串在类型上必须报错运行时非法的负值或非有限值在开发环境警告且不替换最后合法的像素选择源码在 resize 回调 中先校验Number.isFinite与非负再钳制提交。受控折叠回调保持已发布的意图上报语义当拥有者拒绝请求的状态变化时渲染尺寸仍由拥有者控制即使回调已经上报了被请求的转换。7. 单区域与多区域共享同一个契约FR11多区域调用regions对象必须为所有区域使用同一个可选容器与同一个轴。每个区域仍独立进行像素选择——本次改动不引入区域间的平衡、比例保留或总和 100% 的不变量。源码 useMultiResizable 把containerRef与direction统一注入每个区域并为每个区域派生持久化键autoSaveId:key区域的 Hook 调用顺序保持稳定调用方需提供静态regions对象。共享容器通过observeResize的按回调退订机制共存任何数量的无关 Hook 都可以观察同一个节点各自的退订互不干扰。8. 0.6 迁移minSizePx/maxSizePx退役API4 / DEC-4minSize与maxSize是 0.6 版本中唯一的公开边界拼写。已弃用的minSizePx和maxSizePx别名在 0.5 及以前被支持0.6 移除它们不再通过类型检查也不再有运行时解析、冲突警告或源码类型。数字型minSize/maxSize保留完全相同的像素语义包括显式maxSize: Infinity。静态内联配置由 0.6 升级 codemodastryx upgrade重写动态组装的配置必须手动重命名键。类型层面useResizable.ts 底部的RejectRemovedPixelBounds通过{minSizePx?: never; maxSizePx?: never}让已移除的键即使在绕过过剩属性检查的变量上也无法通过类型检查。DEC-4 的取舍记录说明保留两套词汇会让类型联合、运行时解析、冲突警告、测试与文档长期存在而不增加任何能力因此在两个稳定发布周期后移除。9. SSR 与水合无containerRef原子与结构化百分比默认值保留已发布的 1200px 服务端基准与客户端初始化行为有containerRef服务端同样使用临时 1200px 基准直到客户端首次测量水合允许做一次文档化的修正但不得把临时回退写入存储也不得为该修正触发onSizeChange。Resizable/utils子路径astryxdesign/core/Resizable/utils必须保持服务端安全——它不包含任何use client指令utils.ts 头部只有版权与文档注释只承载percent、Table 的pixel绑定、PixelWidth与 Resizable 类型而 useResizable.ts 与 index.ts 都带use client指令。useSyncExternalStore的getServerSnapshot对容器返回null服务端无测量对视口返回SERVER_BASIS从而保证 SSR 输出确定。10. 平台支持与验证要求规范的平台支持条款包括特性/引擎下限Astryx Core 支持且带ResizeObserver的每个浏览器共享 observer 是提供容器时的唯一运行时所有者浏览器证据真实 Chromium 必须验证三个规范结构化调用、宽/窄初始容器、后续基准变化、存储与 ARIA以及不变的视口兼容与像素路径仅靠 jsdom 几何桩stub不够。验证矩阵覆盖 12 类契约FR1-FR12、API3/API4/API6、Platform/SSR、Compatibility每一项都列明代表性状态与失败预期。例如 FR1/API1 的失败预期是默认值用错基准、初始化后重新缩放、提供的 ref 测量了错误元素FR12 的失败预期包括回退因构建而异、生产环境出现警告、非法输入替换合法状态、任何路径产生 NaN。对应到仓库行为测试集中在 useResizable.test.ts如defaultSize: percent(50, {min: pixel(1)})的结构化默认、maxSize: 50%的容器基准钳制等用例与 ResizeHandle.test.tsx、shippedDirections.test.ts组件契约记录在 component:Resizable 草稿。完成标准Completion criteria要点可概括为无 ref 的原子/结构化百分比默认保留一次性window.innerWidth解析与 1200px SSR 回退且不跟踪视口变化有容器时结构化默认在首次正测量后解析一次并移除默认专用观察百分比边界在两种基准模式下重解析、套用像素下限/上限并钳制既有像素状态percent()在类型系统与运行时都要求必填且恰一个min/maxminSizePx/maxSizePx不再通过类型检查精确解析只接受非负有限数字、完整Npx字符串与 0–100 的N%字符串反转边界在开发环境警告、确定性地选择最大值且绝不产生 NaN默认专用订阅即使在测量前调用过resize()也会终止。11. 决策记录小结DEC-1 至 DEC-4DEC-1百分比配置像素状态而非创建相对状态。保留无 ref 的一次性window.innerWidth解析与 1200px 回退containerRef仅改变百分比默认与边界的解析基准不使选中尺寸响应式。已拒绝拖拽后保留百分比比例、把resize()放宽到百分比字符串。DEC-2初始尺寸与边界共享一个配置词汇表。minSize/maxSize采用与defaultSize相同的原子值加ResizablePercentSize结构化描述符混用单位合法。回退为defaultSize250px、minSize50px、maxSize无界Infinity。DEC-3有界百分比使用一个显式结构化帮助函数。用 Table 风格的判别值percent()不用 JavaScript 解析 CSS 函数options 必填不复用proportional()。DEC-40.6 移除已弃用的像素边界别名。两个稳定发布后移除astryx upgrade重写内联配置。12. 当前状态与所有权边界当前main分支已完整落地 AST-010 模型defaultSize、minSize、maxSize接受像素与百分比形式containerRef提供可选内容盒基准百分比默认解析一次百分比边界保持活跃而选中状态、绘制、持久化、回调与分隔条 ARIA 全部保持在已解析像素中。无 ref 路径保留已发布的window.innerWidth基准与确定的 1200px 服务端回退。该契约分布在其现有所有者之间architecture:public-component-api拥有稳定导出、破坏性变更分类与迁移边界family:layout-regions把尺寸状态与交互委托给 useResizable 与 ResizeHandle而 LayoutPanel 消费解析后的尺寸spec:AST-002管辖调用方拥有的容器基准与可预测状态已发布的 Resizable 源码、测试、浏览器证据与消费者文档验证指针、键盘、折叠、吸附、持久化、Handle、SSR 与 ARIA 行为component:ResizableResizable.spec.md记录组件级实现契约。规范最终以Open questions: None收尾——修改已发布的 1200px 服务端回退或新增持久化相对尺寸模式需要另行决策。结语AST-010 的核心设计哲学可以概括为一句话百分比只是配置期的表述像素才是运行期的唯一语言。通过percent(value, {min|max: pixel(value)})这一单一结构化帮助函数Astryx 在类型系统与运行时双重层面表达了带一个像素地板或天花板的百分比把 CSS 约束与 Hook 状态、ARIA 值的分歧消灭在源头。理解默认值解析一次与边界持续重解析的区分、1200px 临时基准的语义、以及非法输入的确定性回退是正确使用useResizable0.6 版配置系统的关键。【免费下载链接】astryxAn open source design system thats fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考