@q1travel/design-system 0.12.0 → 0.14.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/README.md CHANGED
@@ -12,10 +12,11 @@ q1travel 产品家族的设计权威上游仓,三件事集于一处:`@q1trav
12
12
  - Generated mirrors(生成镜像,勿手改):`public/design.md`、`public/design.dark.md` 由 `scripts/sync-design-contracts.mjs` 从契约源逐字节复制;`public/design.css` 由 `scripts/build-design-css.mjs` 把两份契约 frontmatter 的取值注入 `contracts/design.css` 的类词汇后生成,因而自足、可被独立 HTML 页面直接加载。
13
13
  - Runtime tokens:`src/styles/tokens.css`(canonical `--q1-*`,仅 `:root` 与 `:root[data-theme='dark']` 两个扁平 selector)。
14
14
  - 发布台账:`contracts/releases/`(仅存于仓库,不进 npm 包)。
15
+ - npm 发布与下游自动更新:[发布操作说明](docs/npm-release.md)。
15
16
 
16
17
  ## Getting Started
17
18
 
18
- 前置要求:Node.js 22+(CI 基线 22)与 npm(依赖锁定在 `package-lock.json`);运行 `check:browser` 需要本机 Chrome(可用 `CHROME_PATH` 指定)。
19
+ 前置要求:Node.js 22+(CI 基线 22)与 npm(依赖锁定在 `package-lock.json`);运行 `check:browser` 需要本机 Chrome(可用 `CHROME_PATH` 指定)。`check:browser` 在本地与 CI 都默认使用 `--headless=new`,不探测 TTY 或 `DISPLAY`;只有显式设置 `Q1_DESIGN_BROWSER_HEADLESS=false` 才切换到 headed 模式。
19
20
 
20
21
  ```bash
21
22
  npm install
package/bin/q1-design.mjs CHANGED
@@ -180,7 +180,7 @@ export function findsRawMotionLiteral(lineContent, commentState) {
180
180
  // 「用户动作到界面回应完毕」有多久;无限循环按定义不是对输入的响应,它的周期是该组件
181
181
  // 自己的节奏(spinner 1.2s、skeleton 1.6s),塞进 120–300ms 只会压坏它,或逼出假 token。
182
182
  if (/\binfinite\b/.test(segment)) return false;
183
- // 值来自 var() 时不判——`transition: var(--q1-navigation-chevron-transition)` 是合法组件 token。
183
+ // 值整体来自 var() 时不判——例如 `transition: var(--q1-duration-base)`,静态规则不能展开变量内容。
184
184
  const withoutVars = segment.replace(VAR_CALL_PATTERN, '');
185
185
  return BARE_DURATION_PATTERN.test(withoutVars) || BARE_EASING_PATTERN.test(withoutVars);
186
186
  });
@@ -378,7 +378,7 @@ These components ship in the package. Compose from this list before writing new
378
378
  - SplitButton is a primary action with an adjacent menu whose first entry mirrors it. Do not use it to hide unrelated actions behind a chevron.
379
379
  - IconButton requires an accessible name. An icon-only control without one is not deliverable.
380
380
 
381
- **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, Textarea, Select, Fieldset, ErrorMessage.
381
+ **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, VerificationCodeInput, Textarea, Select, Fieldset, ErrorMessage.
382
382
 
383
383
  - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
384
384
  - Select is for a short, fixed list. Anything searchable, or longer than roughly seven entries, is a Combobox.
@@ -535,3 +535,5 @@ Before delivery, confirm each of these. They are observable; none of them is a m
535
535
  - Nothing scrolls horizontally at the page level.
536
536
 
537
537
  In this repository, `npm run check` is the full gate; a partial run is not a pass. In consuming repositories, run their design and token gates before reporting completion.
538
+
539
+ 公开 Foundation 集成:`getOverlayCoordinator` 协调 package 与宿主 overlay 的栈序、inert 与滚动锁;契约见 [Overlay Foundation](./overlay-foundation.md#11-o3-公开-host-协调扩展2026-09-08)。
@@ -378,7 +378,7 @@ These components ship in the package. Compose from this list before writing new
378
378
  - SplitButton is a primary action with an adjacent menu whose first entry mirrors it. Do not use it to hide unrelated actions behind a chevron.
379
379
  - IconButton requires an accessible name. An icon-only control without one is not deliverable.
380
380
 
381
- **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, Textarea, Select, Fieldset, ErrorMessage.
381
+ **Text fields.** FormField, Label, Input, ClearableInput, SearchInput, VerificationCodeInput, Textarea, Select, Fieldset, ErrorMessage.
382
382
 
383
383
  - FormField owns the label, help text, and error slot. Do not assemble those three around a bare Input.
384
384
  - Select is for a short, fixed list. Anything searchable, or longer than roughly seven entries, is a Combobox.
@@ -535,3 +535,5 @@ Before delivery, confirm each of these. They are observable; none of them is a m
535
535
  - Nothing scrolls horizontally at the page level.
536
536
 
537
537
  In this repository, `npm run check` is the full gate; a partial run is not a pass. In consuming repositories, run their design and token gates before reporting completion.
538
+
539
+ 公开 Foundation 集成:`getOverlayCoordinator` 协调 package 与宿主 overlay 的栈序、inert 与滚动锁;契约见 [Overlay Foundation](./overlay-foundation.md#11-o3-公开-host-协调扩展2026-09-08)。
@@ -242,3 +242,20 @@ D7 q1travel-web adoption/migration
242
242
  - [q1travel Token Naming](./token-naming.md)
243
243
  - [WAI-ARIA APG: Dialog (Modal) Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
244
244
  - 历史范围证据:`docs/superpowers/specs/w2-overlays-familyization-scoping.md`(非当前决策源)
245
+
246
+
247
+ ## 11. O3 公开 host 协调扩展(2026-09-08)
248
+
249
+ 本节是 O3 要求的独立 public API contract,扩展 v1 的禁止 consumer 注入边界。
250
+ 授权范围为 package 实现与 Web 本地构建产物联调;不代表发布或生产采用。
251
+
252
+ - `getOverlayCoordinator(document?)` 从包主入口导出;同一 Document 返回同一协调器,SSR 无 document 时返回 null。
253
+ - `register({ id, root, blocking = true, getContainer })` 注册一个宿主实例,返回幂等释放函数。id 在整个 document 栈中必须唯一;root 必须属于该 document 且是 body 直接子节点,非法输入抛错。
254
+ - `getSnapshot()` 返回注册顺序 ids 与 blockingCount;`isTopMost(id)` 使用同一实时栈;`subscribe(listener)` 同步通知登记与释放,返回取消订阅函数。快照供即时读取,非 useSyncExternalStore 的缓存快照。
255
+ - 宿主保留渲染、事件、focus trap 与 focus return;Escape、外点、focus trap 必须先检查共享 top-most。Foundation 不替宿主调用关闭回调,也不绕过 dismissable/busy 策略。
256
+ - 所有 package 与宿主 blocking 实例共享唯一 body overflow/paddingRight 快照和计数。宿主必须移除独立 scroll lock、inert 和快捷键阻断计数;最后一个 blocker 释放才恢复原值,非 LIFO 卸载也适用。
257
+ - 活动 root 被显式登记后不作为背景 inert;原有 inert 状态保留。body 后续新增兄弟节点也纳入背景隔离,最终恢复原值。
258
+ - 多 root 临时使用同一 overlay semantic layer,并按各 root 最新登记实例排列 stacking context;宿主负责自身 root 内部顺序。最后一项释放时恢复 root 原有 position/isolation/zIndex,不删除宿主 root。该扩展不允许宿主覆盖 package root 的归属。
259
+ - 宿主每次 open 对应一次登记/释放;改变 blocking 策略应重新登记。idempotent disposer 支持 StrictMode 清理。不同 document 完全隔离。
260
+
261
+ 回归入口:`node --test scripts/check-overlay-coordination.test.mjs`;真实跨 root 的 Escape、焦点、快捷键与滚动锁由 Web consumer 浏览器测试验证。
@@ -775,6 +775,14 @@ export declare function ErrorMessage({ label, size, action, children, }: {
775
775
 
776
776
  export declare type ErrorMessageSize = 'sm' | 'md' | 'lg';
777
777
 
778
+ /** A host keeps its own rendering, focus and dismissal policy; Foundation owns shared effects. */
779
+ export declare type ExternalOverlayRegistration = {
780
+ id: string;
781
+ root: HTMLElement;
782
+ blocking?: boolean;
783
+ getContainer: () => HTMLElement | null;
784
+ };
785
+
778
786
  export declare function Feedback({ label, prompt, placeholder, inline, topics, }: {
779
787
  label?: string;
780
788
  prompt?: string;
@@ -845,6 +853,9 @@ export declare type GaugeSize = 'xs' | 'sm' | 'md' | 'lg';
845
853
 
846
854
  export declare type GaugeToneDirection = 'quota' | 'health';
847
855
 
856
+ /** One coordinator per document, shared with all package overlays. Null during SSR. */
857
+ export declare function getOverlayCoordinator(doc?: Document | null): OverlayCoordinator | null;
858
+
848
859
  export declare function IconButton({ 'aria-label': canonicalAriaLabel, ariaLabel: legacyAriaLabel, ...rest }: IconButtonProps): JSX.Element;
849
860
 
850
861
  declare type IconButtonAccessibleName = {
@@ -1169,6 +1180,18 @@ export declare type NoteSize = 'small' | 'default' | 'large';
1169
1180
 
1170
1181
  export declare type NoteTone = 'info' | 'success' | 'warning' | 'error' | 'secondary';
1171
1182
 
1183
+ export declare type OverlayCoordinator = {
1184
+ register: (entry: ExternalOverlayRegistration) => () => void;
1185
+ isTopMost: (id: string) => boolean;
1186
+ getSnapshot: () => OverlaySnapshot;
1187
+ subscribe: (listener: () => void) => () => void;
1188
+ };
1189
+
1190
+ export declare type OverlaySnapshot = {
1191
+ readonly ids: readonly string[];
1192
+ readonly blockingCount: number;
1193
+ };
1194
+
1172
1195
  export declare function Pagination({ page, pageSize: pageSizeProp, total, totalPages: totalPagesProp, hasNextPage, onChange, loading, showPageNumbers, summary, pageSizeSlot, prevLabel, nextLabel, compact, className, }: PaginationProps): JSX.Element;
1173
1196
 
1174
1197
  export declare type PaginationCommonProps = {
@@ -1396,7 +1419,7 @@ export declare const Sidebar: typeof SidebarRoot & {
1396
1419
 
1397
1420
  declare function SidebarContent({ className, ...props }: SidebarSlotProps): JSX.Element;
1398
1421
 
1399
- export declare function SidebarDrawer({ 'aria-label': ariaLabel, open, onOpenChange, trigger, header, footer, children, collapseBelow, drawerWidth, variant, }: SidebarDrawerProps): JSX.Element;
1422
+ export declare function SidebarDrawer({ 'aria-label': ariaLabel, open, onOpenChange, trigger, header, footer, children, collapseBelow, drawerWidth, variant, dismissOnBackdrop, }: SidebarDrawerProps): JSX.Element;
1400
1423
 
1401
1424
  export declare type SidebarDrawerProps = {
1402
1425
  'aria-label': string;
@@ -1409,6 +1432,7 @@ export declare type SidebarDrawerProps = {
1409
1432
  collapseBelow?: number;
1410
1433
  drawerWidth?: SidebarDrawerWidth;
1411
1434
  variant?: SidebarVariant;
1435
+ dismissOnBackdrop?: boolean;
1412
1436
  };
1413
1437
 
1414
1438
  export declare type SidebarDrawerTriggerProps = {
@@ -1771,6 +1795,33 @@ export declare type UseRadioOptions = {
1771
1795
 
1772
1796
  export declare function useToast(): ToastContextValue;
1773
1797
 
1798
+ export declare function VerificationCodeInput({ id, name, value, onValueChange, disabled, readOnly, required, invalid, errorId, size, ref, label, description, error, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': describedBy, }: VerificationCodeInputProps): JSX.Element;
1799
+
1800
+ export declare type VerificationCodeInputProps = {
1801
+ id?: string;
1802
+ name?: string;
1803
+ /** 受控的 0–6 位 ASCII 数字字符串;保留前导零。 */
1804
+ value: string;
1805
+ onValueChange: (value: string) => void;
1806
+ disabled?: boolean;
1807
+ readOnly?: boolean;
1808
+ required?: boolean;
1809
+ invalid?: boolean;
1810
+ errorId?: string;
1811
+ size?: 'md' | 'lg';
1812
+ ref?: Ref<HTMLInputElement>;
1813
+ 'aria-describedby'?: string;
1814
+ } & ((FieldShellProps & {
1815
+ 'aria-label'?: never;
1816
+ 'aria-labelledby'?: never;
1817
+ }) | (FieldBareProps & ({
1818
+ 'aria-label': string;
1819
+ 'aria-labelledby'?: never;
1820
+ } | {
1821
+ 'aria-labelledby': string;
1822
+ 'aria-label'?: never;
1823
+ })));
1824
+
1774
1825
  export declare function Video({ src, poster, tracks, label, controls, loop, }: VideoProps): JSX.Element;
1775
1826
 
1776
1827
  export declare type VideoProps = {