@hjmds/design-contracts 1.41.1 → 1.42.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.
Files changed (138) hide show
  1. package/dist/affix.d.ts +18 -0
  2. package/dist/affix.d.ts.map +1 -1
  3. package/dist/affix.js +13 -1
  4. package/dist/affix.js.map +1 -1
  5. package/dist/alert-dialog.d.ts +32 -0
  6. package/dist/alert-dialog.d.ts.map +1 -1
  7. package/dist/alert-dialog.js +52 -0
  8. package/dist/alert-dialog.js.map +1 -1
  9. package/dist/behaviors.d.ts +6 -5
  10. package/dist/behaviors.d.ts.map +1 -1
  11. package/dist/behaviors.js +1 -1
  12. package/dist/behaviors.js.map +1 -1
  13. package/dist/catalog.d.ts +165 -15
  14. package/dist/catalog.d.ts.map +1 -1
  15. package/dist/catalog.js +4 -4
  16. package/dist/catalog.js.map +1 -1
  17. package/dist/character-count.d.ts +103 -0
  18. package/dist/character-count.d.ts.map +1 -0
  19. package/dist/character-count.js +68 -0
  20. package/dist/character-count.js.map +1 -0
  21. package/dist/chart.d.ts +30 -0
  22. package/dist/chart.d.ts.map +1 -1
  23. package/dist/chart.js +18 -0
  24. package/dist/chart.js.map +1 -1
  25. package/dist/collection-rail.d.ts +13 -1
  26. package/dist/collection-rail.d.ts.map +1 -1
  27. package/dist/collection-rail.js +9 -2
  28. package/dist/collection-rail.js.map +1 -1
  29. package/dist/component-recipes.d.ts +172 -2
  30. package/dist/component-recipes.d.ts.map +1 -1
  31. package/dist/component-recipes.js +122 -8
  32. package/dist/component-recipes.js.map +1 -1
  33. package/dist/design-system-provider.d.ts +13 -2
  34. package/dist/design-system-provider.d.ts.map +1 -1
  35. package/dist/design-system-provider.js +19 -3
  36. package/dist/design-system-provider.js.map +1 -1
  37. package/dist/foundations.d.ts +15 -0
  38. package/dist/foundations.d.ts.map +1 -1
  39. package/dist/foundations.js +7 -0
  40. package/dist/foundations.js.map +1 -1
  41. package/dist/icon-button-recipe.d.ts +33 -0
  42. package/dist/icon-button-recipe.d.ts.map +1 -1
  43. package/dist/icon-button-recipe.js +36 -1
  44. package/dist/icon-button-recipe.js.map +1 -1
  45. package/dist/image.d.ts +13 -0
  46. package/dist/image.d.ts.map +1 -1
  47. package/dist/image.js +17 -0
  48. package/dist/image.js.map +1 -1
  49. package/dist/index.d.ts +2 -2
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -2
  52. package/dist/index.js.map +1 -1
  53. package/dist/recipes.d.ts +1 -1
  54. package/dist/recipes.d.ts.map +1 -1
  55. package/dist/recipes.js +1 -1
  56. package/dist/recipes.js.map +1 -1
  57. package/dist/screen-patterns.d.ts +92 -0
  58. package/dist/screen-patterns.d.ts.map +1 -1
  59. package/dist/screen-patterns.js +79 -2
  60. package/dist/screen-patterns.js.map +1 -1
  61. package/dist/selection-helpers.d.ts +6 -0
  62. package/dist/selection-helpers.d.ts.map +1 -1
  63. package/dist/selection-helpers.js +12 -0
  64. package/dist/selection-helpers.js.map +1 -1
  65. package/dist/sidebar.d.ts +17 -2
  66. package/dist/sidebar.d.ts.map +1 -1
  67. package/dist/sidebar.js +8 -2
  68. package/dist/sidebar.js.map +1 -1
  69. package/dist/steps.d.ts +19 -19
  70. package/dist/steps.d.ts.map +1 -1
  71. package/dist/steps.js +21 -9
  72. package/dist/steps.js.map +1 -1
  73. package/dist/timeline.d.ts +41 -0
  74. package/dist/timeline.d.ts.map +1 -1
  75. package/dist/timeline.js +47 -1
  76. package/dist/timeline.js.map +1 -1
  77. package/dist/top.d.ts +21 -2
  78. package/dist/top.d.ts.map +1 -1
  79. package/dist/top.js +17 -1
  80. package/dist/top.js.map +1 -1
  81. package/dist/version.d.ts +1 -1
  82. package/dist/version.js +1 -1
  83. package/dist/version.js.map +1 -1
  84. package/docs/affix.md +7 -0
  85. package/docs/avatar-fallback.md +19 -0
  86. package/docs/brand-boundary.md +24 -2
  87. package/docs/chart.md +7 -0
  88. package/docs/collection-rail.md +4 -1
  89. package/docs/generated/component-maturity.md +1 -1
  90. package/docs/generated/renderer-evidence.json +10 -6
  91. package/docs/generated/renderer-evidence.md +1 -1
  92. package/docs/generated/showcase-manifest.json +12 -5
  93. package/docs/image.md +5 -0
  94. package/docs/list-row.md +8 -0
  95. package/docs/optional-adapters.md +3 -0
  96. package/docs/overlay-stack.md +27 -0
  97. package/docs/screen-patterns.md +39 -2
  98. package/docs/sidebar.md +15 -0
  99. package/docs/steps.md +23 -19
  100. package/docs/theme-palette.md +3 -3
  101. package/docs/theming.md +1 -0
  102. package/docs/timeline.md +18 -1
  103. package/docs/top.md +7 -0
  104. package/docs/usage/README.md +2 -2
  105. package/docs/usage/components/affix.md +18 -4
  106. package/docs/usage/components/alert-dialog.md +28 -0
  107. package/docs/usage/components/avatar.md +30 -9
  108. package/docs/usage/components/badge.md +20 -5
  109. package/docs/usage/components/chart.md +19 -1
  110. package/docs/usage/components/checkbox-group.md +24 -1
  111. package/docs/usage/components/choice-row.md +22 -5
  112. package/docs/usage/components/composition-layout.md +6 -2
  113. package/docs/usage/components/counter-badge.md +2 -2
  114. package/docs/usage/components/design-system-provider.md +2 -1
  115. package/docs/usage/components/divider.md +12 -1
  116. package/docs/usage/components/field.md +51 -0
  117. package/docs/usage/components/icon-button.md +48 -4
  118. package/docs/usage/components/icon-tile.md +13 -2
  119. package/docs/usage/components/image.md +25 -0
  120. package/docs/usage/components/list-row.md +8 -4
  121. package/docs/usage/components/list.md +1 -1
  122. package/docs/usage/components/notification-item.md +5 -1
  123. package/docs/usage/components/permission-screen.md +25 -4
  124. package/docs/usage/components/screen-layout.md +27 -1
  125. package/docs/usage/components/segmented-control.md +16 -3
  126. package/docs/usage/components/select.md +1 -0
  127. package/docs/usage/components/settings-screen.md +5 -4
  128. package/docs/usage/components/sheet.md +2 -1
  129. package/docs/usage/components/sidebar.md +34 -6
  130. package/docs/usage/components/steps.md +29 -8
  131. package/docs/usage/components/surface.md +2 -1
  132. package/docs/usage/components/text-area.md +2 -1
  133. package/docs/usage/components/timeline.md +25 -4
  134. package/docs/usage/components/top.md +15 -2
  135. package/docs/usage/screens/common-settings.md +3 -2
  136. package/docs/usage/screens/flow-permission.md +3 -3
  137. package/docs/usage/tokens/elevation-opacity.md +14 -2
  138. package/package.json +7 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport type { LinkDestination } from \"./link.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 데스크톱 웹의 세로 내비게이션. 관리자 화면·문서·작업 도구처럼 넓은 화면에서 좌우로\n * 나뉜 셸의 왼쪽이다.\n *\n * `BottomNavigation`과 같은 문제를 푸는 다른 표면이 아니다 — 두 축이 다르다.\n * BottomNavigation은 **3~5개 최상위 목적지**를 모바일 하단에 고정하고 그룹이 없다.\n * Sidebar는 **그룹이 있는 긴 목록**이고 접힌다. 한 컴포넌트의 `orientation` 축으로\n * 합치면 \"그룹이 있는 하단 탭바\"라는 존재하지 않는 조합이 타입에 생긴다.\n *\n * `Layout`의 sidebar 영역과도 다르다. Layout은 **자리**(폭·순서·반응형 규칙)를 갖고\n * 내용을 모른다. 이 계약은 그 자리에 들어가는 **내비게이션 자체**다.\n */\nexport type SidebarItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n destination?: LinkDestination;\n /** 접힌 상태에서도 보이는 상태 수치. Badge/CounterBadge가 그린다. */\n badgeCount?: number;\n disabled?: boolean;\n}>;\n\nexport type SidebarGroupDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n id: GroupId;\n /** 그룹 제목. 없으면 구분선만 있는 묶음이다. */\n label?: string;\n items: readonly SidebarItemDescriptor<Id>[];\n}>;\n\nexport type SidebarDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n accessibilityLabel: string;\n groups: readonly SidebarGroupDescriptor<Id, GroupId>[];\n /** 현재 위치. 링크 내비게이션의 `aria-current`가 된다. */\n currentId: Id | null;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Sidebar ${field} must not be empty`);\n }\n}\n\nexport function validateSidebarDescriptor<Id extends string, GroupId extends string>(\n descriptor: SidebarDescriptor<Id, GroupId>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.groups) || descriptor.groups.length === 0) {\n throw new RangeError(\"Sidebar must contain at least one group\");\n }\n const itemIds = new Set<Id>();\n const groupIds = new Set<GroupId>();\n for (const group of descriptor.groups) {\n assertNonEmpty(group.id, \"group id\");\n if (groupIds.has(group.id)) throw new TypeError(`Duplicate Sidebar group id: ${group.id}`);\n groupIds.add(group.id);\n if (group.label !== undefined) assertNonEmpty(group.label, `group ${group.id} label`);\n if (!Array.isArray(group.items) || group.items.length === 0) {\n throw new RangeError(`Sidebar group ${group.id} must contain at least one item`);\n }\n for (const item of group.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, `item ${item.id} label`);\n if (itemIds.has(item.id)) throw new TypeError(`Duplicate Sidebar item id: ${item.id}`);\n itemIds.add(item.id);\n if (item.badgeCount !== undefined && (!Number.isInteger(item.badgeCount) || item.badgeCount < 0)) {\n throw new RangeError(`Sidebar item ${item.id} badgeCount must be a non-negative integer`);\n }\n }\n }\n if (descriptor.currentId !== null && !itemIds.has(descriptor.currentId)) {\n throw new RangeError(`Sidebar currentId does not match any item: ${String(descriptor.currentId)}`);\n }\n}\n\n/**\n * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —\n * 접을 때 항목을 빼면 \"접었더니 메뉴가 사라졌다\"가 되고, 그건 다른 컴포넌트다.\n * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.\n */\nexport const sidebarDefaults = {\n collapsed: false,\n} as const satisfies Readonly<{ collapsed: boolean }>;\n\nexport const sidebarRecipe = {\n slots: [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"] as const,\n defaults: sidebarDefaults,\n widths: { expanded: 260, collapsed: 72 },\n background: semanticColors.canvas,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n paddingVertical: spacing.sm,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n groupGap: spacing.md,\n groupLabel: {\n color: semanticColors.content.secondary,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.sm,\n paddingVertical: spacing.xxs,\n },\n item: collectionItemContract,\n itemRadius: radius.md,\n itemMinHeight: control.minTouchTarget,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"];\n defaults: { collapsed: boolean };\n widths: { expanded: number; collapsed: number };\n background: ColorReference;\n border: ColorReference;\n borderWidth: number;\n paddingVertical: number;\n paddingHorizontal: number;\n gap: number;\n groupGap: number;\n groupLabel: { color: ColorReference; textVariant: \"label\"; paddingHorizontal: number; paddingVertical: number };\n item: typeof collectionItemContract;\n itemRadius: number;\n itemMinHeight: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const sidebarBehavior = {\n controlled: [\"collapsed\", \"defaultCollapsed\", \"onCollapsedChange\"],\n inputs: [\"groups\", \"currentId\", \"accessibilityLabel\"],\n events: [\"onNavigate\"],\n defaults: sidebarDefaults,\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"selected\"],\n interaction: [\"idle\", \"hover\", \"focusVisible\", \"pressed\"],\n },\n web: {\n roles: [\"navigation\", \"list\", \"listitem\", \"link\", \"button\"],\n keyboard: [\"Tab\", \"Enter\"],\n /** Each item is a link in document order; no roving focus and no arrow-key grid. */\n focus: \"native\",\n },\n /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-current-item-is-announced-with-aria-current-not-only-painted\",\n \"collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name\",\n \"groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only\",\n \"an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look\",\n \"bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it\",\n \"layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it\",\n ],\n} as const satisfies BehaviorContract;\n"]}
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport type { LinkDestination } from \"./link.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 데스크톱 웹의 세로 내비게이션. 관리자 화면·문서·작업 도구처럼 넓은 화면에서 좌우로\n * 나뉜 셸의 왼쪽이다.\n *\n * `BottomNavigation`과 같은 문제를 푸는 다른 표면이 아니다 — 두 축이 다르다.\n * BottomNavigation은 **3~5개 최상위 목적지**를 모바일 하단에 고정하고 그룹이 없다.\n * Sidebar는 **그룹이 있는 긴 목록**이고 접힌다. 한 컴포넌트의 `orientation` 축으로\n * 합치면 \"그룹이 있는 하단 탭바\"라는 존재하지 않는 조합이 타입에 생긴다.\n *\n * `Layout`의 sidebar 영역과도 다르다. Layout은 **자리**(폭·순서·반응형 규칙)를 갖고\n * 내용을 모른다. 이 계약은 그 자리에 들어가는 **내비게이션 자체**다.\n */\nexport type SidebarItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n destination?: LinkDestination;\n /** 접힌 상태에서도 보이는 상태 수치. Badge/CounterBadge가 그린다. */\n badgeCount?: number;\n disabled?: boolean;\n}>;\n\nexport type SidebarGroupDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n id: GroupId;\n /** 그룹 제목. 없으면 구분선만 있는 묶음이다. */\n label?: string;\n items: readonly SidebarItemDescriptor<Id>[];\n}>;\n\nexport type SidebarDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n accessibilityLabel: string;\n groups: readonly SidebarGroupDescriptor<Id, GroupId>[];\n /** 현재 위치. 링크 내비게이션의 `aria-current`가 된다. */\n currentId: Id | null;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Sidebar ${field} must not be empty`);\n }\n}\n\nexport function validateSidebarDescriptor<Id extends string, GroupId extends string>(\n descriptor: SidebarDescriptor<Id, GroupId>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.groups) || descriptor.groups.length === 0) {\n throw new RangeError(\"Sidebar must contain at least one group\");\n }\n const itemIds = new Set<Id>();\n const groupIds = new Set<GroupId>();\n for (const group of descriptor.groups) {\n assertNonEmpty(group.id, \"group id\");\n if (groupIds.has(group.id)) throw new TypeError(`Duplicate Sidebar group id: ${group.id}`);\n groupIds.add(group.id);\n if (group.label !== undefined) assertNonEmpty(group.label, `group ${group.id} label`);\n if (!Array.isArray(group.items) || group.items.length === 0) {\n throw new RangeError(`Sidebar group ${group.id} must contain at least one item`);\n }\n for (const item of group.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, `item ${item.id} label`);\n if (itemIds.has(item.id)) throw new TypeError(`Duplicate Sidebar item id: ${item.id}`);\n itemIds.add(item.id);\n if (item.badgeCount !== undefined && (!Number.isInteger(item.badgeCount) || item.badgeCount < 0)) {\n throw new RangeError(`Sidebar item ${item.id} badgeCount must be a non-negative integer`);\n }\n }\n }\n if (descriptor.currentId !== null && !itemIds.has(descriptor.currentId)) {\n throw new RangeError(`Sidebar currentId does not match any item: ${String(descriptor.currentId)}`);\n }\n}\n\n/**\n * 펼친 폭 단계. `regular` 260은 그룹 제목과 긴 라벨이 있는 관리·문서 목록, `compact` 220은 그룹 없이\n * 짧은 라벨 3~5개를 두는 제품 셸 rail이다(2026-10-23 다에리 웹 ProductNav가 220 rail이라 260 고정 Sidebar를\n * 채택하지 못했다). 임의 px 대신 두 단계만 둔다 — 폭은 라벨·배지 배치를 함께 정하는 recipe 값이다.\n */\nexport type SidebarSize = \"compact\" | \"regular\";\nexport const sidebarSizes = [\"compact\", \"regular\"] as const satisfies readonly SidebarSize[];\n\n/**\n * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —\n * 접을 때 항목을 빼면 \"접었더니 메뉴가 사라졌다\"가 되고, 그건 다른 컴포넌트다.\n * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.\n */\nexport const sidebarDefaults = {\n collapsed: false,\n size: \"regular\",\n} as const satisfies Readonly<{ collapsed: boolean; size: SidebarSize }>;\n\nconst sidebarSizeWidths = { compact: 220, regular: 260 } as const;\n\nexport const sidebarRecipe = {\n slots: [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"] as const,\n defaults: sidebarDefaults,\n sizes: sidebarSizeWidths,\n /** `expanded` is the regular size; kept because 1.12+ recipe readers use it for the default open width. */\n widths: { expanded: sidebarSizeWidths.regular, collapsed: 72 },\n background: semanticColors.canvas,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n paddingVertical: spacing.sm,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n groupGap: spacing.md,\n groupLabel: {\n color: semanticColors.content.secondary,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.sm,\n paddingVertical: spacing.xxs,\n },\n item: collectionItemContract,\n itemRadius: radius.md,\n itemMinHeight: control.minTouchTarget,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"];\n defaults: { collapsed: boolean; size: SidebarSize };\n sizes: Record<SidebarSize, number>;\n widths: { expanded: number; collapsed: number };\n background: ColorReference;\n border: ColorReference;\n borderWidth: number;\n paddingVertical: number;\n paddingHorizontal: number;\n gap: number;\n groupGap: number;\n groupLabel: { color: ColorReference; textVariant: \"label\"; paddingHorizontal: number; paddingVertical: number };\n item: typeof collectionItemContract;\n itemRadius: number;\n itemMinHeight: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const sidebarBehavior = {\n controlled: [\"collapsed\", \"defaultCollapsed\", \"onCollapsedChange\"],\n inputs: [\"groups\", \"currentId\", \"accessibilityLabel\", \"size\"],\n events: [\"onNavigate\"],\n defaults: sidebarDefaults,\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"selected\"],\n interaction: [\"idle\", \"hover\", \"focusVisible\", \"pressed\"],\n },\n web: {\n roles: [\"navigation\", \"list\", \"listitem\", \"link\", \"button\"],\n keyboard: [\"Tab\", \"Enter\"],\n /** Each item is a link in document order; no roving focus and no arrow-key grid. */\n focus: \"native\",\n },\n /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-current-item-is-announced-with-aria-current-not-only-painted\",\n \"collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name\",\n \"groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only\",\n \"an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look\",\n \"bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it\",\n \"layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it\",\n \"a-router-adapter-draws-the-item-anchor-while-hjm-keeps-current-disabled-and-collapsed-names\",\n ],\n} as const satisfies BehaviorContract;\n"]}
package/dist/steps.d.ts CHANGED
@@ -1,10 +1,4 @@
1
- /**
2
- * Ant Design Steps' full surface (navigation jump, dot type, custom per-item
3
- * icon, vertical orientation) is intentionally not reproduced. Steps stay a
4
- * single-cursor horizontal flow. Validated forms default to backward-only
5
- * navigation; reading flows explicitly opt into free navigation (2026-10-19 portfolio).
6
- * See docs/steps.md for the per-axis rationale.
7
- */
1
+ /** One cursor and navigation policy shared by every presentation. */
8
2
  export type StepStatus = "pending" | "current" | "complete" | "error";
9
3
  /** Only the cursor step can be "current" or "error"; every other step is derived. */
10
4
  export type StepCursorStatus = Extract<StepStatus, "current" | "error" | "complete">;
@@ -26,7 +20,10 @@ export type StepsDescriptor<Id extends string = string> = Readonly<{
26
20
  /** Free navigation is for reading flows; linear remains the default for validated forms. */
27
21
  navigationMode?: "linear" | "free";
28
22
  }>;
23
+ export type StepsPresentation = "focus" | "compact" | "vertical";
24
+ export declare function validateStepsPresentation(presentation: StepsPresentation, overviewLabel?: string): void;
29
25
  export declare const stepsDefaults: {
26
+ readonly presentation: "focus";
30
27
  readonly currentStepStatus: "current";
31
28
  };
32
29
  export type StepsStatusLabels = Readonly<{
@@ -82,7 +79,8 @@ export declare const stepsRecipe: {
82
79
  readonly gap: 8;
83
80
  readonly paddingInline: 16;
84
81
  readonly indicator: {
85
- readonly size: 44;
82
+ readonly size: 32;
83
+ readonly customSize: 44;
86
84
  readonly borderWidth: 1;
87
85
  readonly activeBorderWidth: 2;
88
86
  /** null means the renderer shows the step's own position number instead of a glyph. */
@@ -100,12 +98,12 @@ export declare const stepsRecipe: {
100
98
  }>;
101
99
  readonly current: Readonly<{
102
100
  source: "theme";
103
- key: "contentBrand";
101
+ key: "text";
104
102
  alpha?: number;
105
103
  }>;
106
104
  readonly complete: Readonly<{
107
- source: "accent";
108
- key: "success";
105
+ source: "theme";
106
+ key: "borderControl";
109
107
  alpha?: number;
110
108
  }>;
111
109
  readonly error: Readonly<{
@@ -116,12 +114,12 @@ export declare const stepsRecipe: {
116
114
  };
117
115
  readonly background: {
118
116
  readonly pending: null;
119
- readonly current: null;
120
- readonly complete: Readonly<{
121
- source: "accent";
122
- key: "success";
117
+ readonly current: Readonly<{
118
+ source: "theme";
119
+ key: "text";
123
120
  alpha?: number;
124
121
  }>;
122
+ readonly complete: null;
125
123
  readonly error: Readonly<{
126
124
  source: "theme";
127
125
  key: "danger";
@@ -136,12 +134,12 @@ export declare const stepsRecipe: {
136
134
  }>;
137
135
  readonly current: Readonly<{
138
136
  source: "theme";
139
- key: "contentBrand";
137
+ key: "bg";
140
138
  alpha?: number;
141
139
  }>;
142
140
  readonly complete: Readonly<{
143
- source: "accent";
144
- key: "success";
141
+ source: "theme";
142
+ key: "text";
145
143
  alpha?: number;
146
144
  }>;
147
145
  readonly error: Readonly<{
@@ -156,7 +154,7 @@ export declare const stepsRecipe: {
156
154
  readonly tone: {
157
155
  readonly reached: Readonly<{
158
156
  source: "theme";
159
- key: "contentBrand";
157
+ key: "text";
160
158
  alpha?: number;
161
159
  }>;
162
160
  readonly unreached: Readonly<{
@@ -204,5 +202,7 @@ export declare const stepsRecipe: {
204
202
  readonly press: {
205
203
  readonly minTouchTarget: 44;
206
204
  };
205
+ readonly transitionDuration: 200;
206
+ readonly segmentHeight: 4;
207
207
  };
208
208
  //# sourceMappingURL=steps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
package/dist/steps.js CHANGED
@@ -1,6 +1,13 @@
1
- import { control, fontWeight, glyph, spacing, stroke } from "./foundations.js";
1
+ import { control, fontWeight, glyph, motion, spacing, stroke } from "./foundations.js";
2
2
  import { semanticColors } from "./semantic-colors.js";
3
+ export function validateStepsPresentation(presentation, overviewLabel) {
4
+ if (!["focus", "compact", "vertical"].includes(presentation))
5
+ throw new TypeError(`Unsupported Steps presentation: ${presentation}`);
6
+ if (presentation === "compact" && !overviewLabel?.trim())
7
+ throw new TypeError("Compact Steps requires a localized overviewLabel");
8
+ }
3
9
  export const stepsDefaults = {
10
+ presentation: "focus",
4
11
  currentStepStatus: "current",
5
12
  };
6
13
  function assertNonEmpty(value, field) {
@@ -113,7 +120,9 @@ export const stepsRecipe = {
113
120
  // Spint review (2026-10-21): keep endpoint marks inset and give illustrated marks breathing room.
114
121
  paddingInline: spacing.md,
115
122
  indicator: {
116
- size: glyph.xxl,
123
+ size: glyph.xl,
124
+ // Illustrated product marks retain the space approved in the Spint review.
125
+ customSize: glyph.xxl,
117
126
  borderWidth: stroke.default,
118
127
  activeBorderWidth: stroke.strong,
119
128
  /** null means the renderer shows the step's own position number instead of a glyph. */
@@ -125,27 +134,27 @@ export const stepsRecipe = {
125
134
  },
126
135
  border: {
127
136
  pending: semanticColors.border.default,
128
- current: semanticColors.content.brand,
129
- complete: semanticColors.feedback.success.border,
137
+ current: semanticColors.content.primary,
138
+ complete: semanticColors.border.control,
130
139
  error: semanticColors.feedback.danger.border,
131
140
  },
132
141
  background: {
133
142
  pending: null,
134
- current: null,
135
- complete: semanticColors.feedback.success.badgeBackground,
143
+ current: semanticColors.content.primary,
144
+ complete: null,
136
145
  error: semanticColors.feedback.danger.badgeBackground,
137
146
  },
138
147
  content: {
139
148
  pending: semanticColors.content.secondary,
140
- current: semanticColors.content.brand,
141
- complete: semanticColors.feedback.success.foreground,
149
+ current: semanticColors.canvas,
150
+ complete: semanticColors.content.primary,
142
151
  error: semanticColors.feedback.danger.foreground,
143
152
  },
144
153
  },
145
154
  connector: {
146
155
  height: stroke.default,
147
156
  tone: {
148
- reached: semanticColors.content.brand,
157
+ reached: semanticColors.content.primary,
149
158
  unreached: semanticColors.border.default,
150
159
  },
151
160
  },
@@ -167,5 +176,8 @@ export const stepsRecipe = {
167
176
  press: {
168
177
  minTouchTarget: control.minTouchTarget,
169
178
  },
179
+ // User selected the focus proposal on 2026-10-23: neutral current fill, quieter completed rail.
180
+ transitionDuration: motion.normal,
181
+ segmentHeight: spacing.xxs,
170
182
  };
171
183
  //# sourceMappingURL=steps.js.map
package/dist/steps.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport type { SemanticIconName } from \"./icon.js\";\nimport { control, fontWeight, glyph, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * Ant Design Steps' full surface (navigation jump, dot type, custom per-item\n * icon, vertical orientation) is intentionally not reproduced. Steps stay a\n * single-cursor horizontal flow. Validated forms default to backward-only\n * navigation; reading flows explicitly opt into free navigation (2026-10-19 portfolio).\n * See docs/steps.md for the per-axis rationale.\n */\nexport type StepStatus = \"pending\" | \"current\" | \"complete\" | \"error\";\n\n/** Only the cursor step can be \"current\" or \"error\"; every other step is derived. */\n// \"complete\" marks a finished flow; see docs/steps.md.\nexport type StepCursorStatus = Extract<StepStatus, \"current\" | \"error\" | \"complete\">;\n\nexport type StepItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n description?: string;\n}>;\n\n/**\n * A single stable cursor (`currentStepId`) is the source of truth instead of a\n * per-item status array, so \"two current steps\" or \"a completed step past an\n * unresolved error\" cannot be represented. Linear navigation only returns before the cursor. Free reading navigation\n * can select any step without changing the single-cursor state model.\n */\nexport type StepsDescriptor<Id extends string = string> = Readonly<{\n steps: readonly StepItemDescriptor<Id>[];\n currentStepId: Id;\n currentStepStatus?: StepCursorStatus;\n /** Free navigation is for reading flows; linear remains the default for validated forms. */\n navigationMode?: \"linear\" | \"free\";\n}>;\n\nexport const stepsDefaults = {\n currentStepStatus: \"current\",\n} as const satisfies Readonly<{ currentStepStatus: StepCursorStatus }>;\n\nexport type StepsStatusLabels = Readonly<{\n pending: string;\n current: string;\n complete: string;\n error: string;\n}>;\n\nexport type StepsAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar (Korean\n * \"3단계 중 2단계\" does not translate structurally to English \"step 2 of 3\"),\n * so the resolver invokes this composer instead of assembling copy itself.\n */\nexport type ComposeStepsAccessibleName = (\n info: StepsAccessibleNameInfo,\n) => string;\n\nexport type ResolvedStepDescriptor<Id extends string = string> =\n StepItemDescriptor<Id> &\n Readonly<{\n status: StepStatus;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Primary accessible name: order + label. Never says status by itself. */\n accessibleName: string;\n /** Supplementary status announcement (Web aria-describedby text / RN accessibilityHint). */\n statusLabel: string;\n /**\n * Strictly before the cursor. Only these steps become buttons when the product supplies a step press handler:\n * jumping forward would skip validation, and the cursor step is where the user already is (2026-10-18).\n */\n revisitable: boolean;\n }>;\n\nexport type ResolveStepsOptions = Readonly<{\n statusLabels: StepsStatusLabels;\n composeAccessibleName: ComposeStepsAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Steps ${field} must not be empty`);\n }\n}\n\nexport function validateStepItemDescriptor<Id extends string>(\n step: StepItemDescriptor<Id>,\n): void {\n assertNonEmpty(step.id, \"item id\");\n if (step.id !== step.id.trim()) {\n throw new TypeError(\"Steps item id must not start or end with whitespace\");\n }\n assertNonEmpty(step.label, \"item label\");\n if (step.description !== undefined) {\n assertNonEmpty(step.description, \"item description\");\n }\n}\n\nexport function validateStepsDescriptor<Id extends string>(\n descriptor: StepsDescriptor<Id>,\n): void {\n if (descriptor.navigationMode !== undefined && descriptor.navigationMode !== \"linear\" && descriptor.navigationMode !== \"free\") {\n throw new TypeError(`Unsupported Steps navigationMode: ${String(descriptor.navigationMode)}`);\n }\n if (descriptor.steps.length < 2) {\n throw new RangeError(\"Steps must contain at least two steps\");\n }\n const ids = new Set<Id>();\n for (const step of descriptor.steps) {\n validateStepItemDescriptor(step);\n if (ids.has(step.id)) {\n throw new TypeError(`Duplicate Steps item id: ${step.id}`);\n }\n ids.add(step.id);\n }\n if (!ids.has(descriptor.currentStepId)) {\n throw new RangeError(\n `Steps currentStepId does not match any step id: ${String(descriptor.currentStepId)}`,\n );\n }\n if (\n descriptor.currentStepStatus !== undefined &&\n descriptor.currentStepStatus !== \"current\" &&\n descriptor.currentStepStatus !== \"error\" &&\n descriptor.currentStepStatus !== \"complete\"\n ) {\n throw new TypeError(\n `Unsupported Steps currentStepStatus: ${String(descriptor.currentStepStatus)}`,\n );\n }\n}\n\nexport function validateStepsStatusLabels(labels: StepsStatusLabels): void {\n if (labels === null || typeof labels !== \"object\") {\n throw new TypeError(\"Steps statusLabels must be an object\");\n }\n for (const key of [\"pending\", \"current\", \"complete\", \"error\"] as const) {\n const value = labels[key];\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Steps statusLabels.${key} must not be empty`);\n }\n }\n}\n\n/** A step is visually and semantically \"reached\" once it is no longer waiting. */\nexport function isStepReached(status: StepStatus): boolean {\n return status !== \"pending\";\n}\n\n/**\n * Derives every step's status from its position relative to the single\n * cursor, then attaches the order-preserving accessible name and the\n * supplementary status announcement. Throws on a malformed descriptor,\n * labels bag, or composer instead of silently rendering an inconsistent flow.\n */\nexport function resolveStepsDescriptor<Id extends string>(\n descriptor: StepsDescriptor<Id>,\n options: ResolveStepsOptions,\n): readonly ResolvedStepDescriptor<Id>[] {\n validateStepsDescriptor(descriptor);\n validateStepsStatusLabels(options.statusLabels);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Steps composeAccessibleName must be a function\");\n }\n\n const currentStepStatus =\n descriptor.currentStepStatus ?? stepsDefaults.currentStepStatus;\n const total = descriptor.steps.length;\n const cursorIndex = descriptor.steps.findIndex(\n (step) => step.id === descriptor.currentStepId,\n );\n\n return descriptor.steps.map((step, index) => {\n const status: StepStatus =\n index < cursorIndex\n ? \"complete\"\n : index > cursorIndex\n ? \"pending\"\n : currentStepStatus;\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: step.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Steps composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...step,\n status,\n position,\n total,\n accessibleName,\n statusLabel: options.statusLabels[status],\n revisitable: index < cursorIndex,\n };\n });\n}\n\nexport const stepsRecipe = {\n slots: [\n \"root\",\n \"step\",\n \"indicator\",\n \"marker\",\n \"connector\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.xs,\n // Spint review (2026-10-21): keep endpoint marks inset and give illustrated marks breathing room.\n paddingInline: spacing.md,\n indicator: {\n size: glyph.xxl,\n borderWidth: stroke.default,\n activeBorderWidth: stroke.strong,\n /** null means the renderer shows the step's own position number instead of a glyph. */\n marks: {\n pending: null,\n current: null,\n complete: \"check\",\n error: \"error\",\n },\n border: {\n pending: semanticColors.border.default,\n current: semanticColors.content.brand,\n complete: semanticColors.feedback.success.border,\n error: semanticColors.feedback.danger.border,\n },\n background: {\n pending: null,\n current: null,\n complete: semanticColors.feedback.success.badgeBackground,\n error: semanticColors.feedback.danger.badgeBackground,\n },\n content: {\n pending: semanticColors.content.secondary,\n current: semanticColors.content.brand,\n complete: semanticColors.feedback.success.foreground,\n error: semanticColors.feedback.danger.foreground,\n },\n },\n connector: {\n height: stroke.default,\n tone: {\n reached: semanticColors.content.brand,\n unreached: semanticColors.border.default,\n },\n },\n label: {\n textVariant: \"label\",\n fontWeight: fontWeight.medium,\n color: {\n pending: semanticColors.content.secondary,\n current: semanticColors.content.primary,\n complete: semanticColors.content.primary,\n error: semanticColors.feedback.danger.foreground,\n },\n },\n description: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n /** A revisitable step is one button over its marker and label; the shared touch minimum applies to the whole cell. */\n press: {\n minTouchTarget: control.minTouchTarget,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n paddingInline: number;\n indicator: {\n size: number;\n borderWidth: number;\n activeBorderWidth: number;\n marks: Record<StepStatus, SemanticIconName | null>;\n border: Record<StepStatus, ColorReference>;\n background: Record<StepStatus, ColorReference | null>;\n content: Record<StepStatus, ColorReference>;\n };\n connector: {\n height: number;\n tone: Record<\"reached\" | \"unreached\", ColorReference>;\n };\n label: {\n textVariant: TextVariant;\n fontWeight: FontWeightValue;\n color: Record<StepStatus, ColorReference>;\n };\n description: {\n textVariant: TextVariant;\n color: ColorReference;\n };\n press: {\n minTouchTarget: number;\n };\n};\n"]}
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AA+BtD,MAAM,UAAU,yBAAyB,CAAC,YAA+B,EAAE,aAAsB;IAC/F,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,mCAAmC,YAAY,EAAE,CAAC,CAAC;IACrI,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;AACpI,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,YAAY,EAAE,OAAO;IACrB,iBAAiB,EAAE,SAAS;CACyE,CAAC;AA+CxG,SAAS,cAAc,CAAC,KAAa,EAAE,KAAa;IAClD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,SAAS,CAAC,SAAS,KAAK,oBAAoB,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,IAA4B;IAE5B,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/B,MAAM,IAAI,SAAS,CAAC,qDAAqD,CAAC,CAAC;IAC7E,CAAC;IACD,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACzC,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACnC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,UAA+B;IAE/B,IAAI,UAAU,CAAC,cAAc,KAAK,SAAS,IAAI,UAAU,CAAC,cAAc,KAAK,QAAQ,IAAI,UAAU,CAAC,cAAc,KAAK,MAAM,EAAE,CAAC;QAC9H,MAAM,IAAI,SAAS,CAAC,qCAAqC,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,UAAU,CAAC,uCAAuC,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAM,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;QACpC,0BAA0B,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,SAAS,CAAC,4BAA4B,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,UAAU,CAClB,mDAAmD,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CACtF,CAAC;IACJ,CAAC;IACD,IACE,UAAU,CAAC,iBAAiB,KAAK,SAAS;QAC1C,UAAU,CAAC,iBAAiB,KAAK,SAAS;QAC1C,UAAU,CAAC,iBAAiB,KAAK,OAAO;QACxC,UAAU,CAAC,iBAAiB,KAAK,UAAU,EAC3C,CAAC;QACD,MAAM,IAAI,SAAS,CACjB,wCAAwC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAC/E,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,MAAyB;IACjE,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAClD,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAU,EAAE,CAAC;QACvE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,SAAS,CAAC,sBAAsB,GAAG,oBAAoB,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAAC,MAAkB;IAC9C,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAA+B,EAC/B,OAA4B;IAE5B,uBAAuB,CAAC,UAAU,CAAC,CAAC;IACpC,yBAAyB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAChD,IAAI,OAAO,OAAO,CAAC,qBAAqB,KAAK,UAAU,EAAE,CAAC;QACxD,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,iBAAiB,GACrB,UAAU,CAAC,iBAAiB,IAAI,aAAa,CAAC,iBAAiB,CAAC;IAClE,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;IACtC,MAAM,WAAW,GAAG,UAAU,CAAC,KAAK,CAAC,SAAS,CAC5C,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,CAAC,aAAa,CAC/C,CAAC;IAEF,OAAO,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC1C,MAAM,MAAM,GACV,KAAK,GAAG,WAAW;YACjB,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,KAAK,GAAG,WAAW;gBACnB,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,iBAAiB,CAAC;QAC1B,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC;QAC3B,MAAM,cAAc,GAAG,OAAO,CAAC,qBAAqB,CAAC;YACnD,QAAQ;YACR,KAAK;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7E,MAAM,IAAI,SAAS,CACjB,4DAA4D,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO;YACL,GAAG,IAAI;YACP,MAAM;YACN,QAAQ;YACR,KAAK;YACL,cAAc;YACd,WAAW,EAAE,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC;YACzC,WAAW,EAAE,KAAK,GAAG,WAAW;SACjC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,KAAK,EAAE;QACL,MAAM;QACN,MAAM;QACN,WAAW;QACX,QAAQ;QACR,WAAW;QACX,OAAO;QACP,aAAa;KACL;IACV,GAAG,EAAE,OAAO,CAAC,EAAE;IACf,kGAAkG;IAClG,aAAa,EAAE,OAAO,CAAC,EAAE;IACzB,SAAS,EAAE;QACT,IAAI,EAAE,KAAK,CAAC,EAAE;QACd,2EAA2E;QAC3E,UAAU,EAAE,KAAK,CAAC,GAAG;QACrB,WAAW,EAAE,MAAM,CAAC,OAAO;QAC3B,iBAAiB,EAAE,MAAM,CAAC,MAAM;QAChC,uFAAuF;QACvF,KAAK,EAAE;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,OAAO;SACf;QACD,MAAM,EAAE;YACN,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;YACtC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;YACvC,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;YACvC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM;SAC7C;QACD,UAAU,EAAE;YACV,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;YACvC,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,eAAe;SACtD;QACD,OAAO,EAAE;YACP,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,SAAS;YACzC,OAAO,EAAE,cAAc,CAAC,MAAM;YAC9B,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;YACxC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU;SACjD;KACF;IACD,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC,OAAO;QACtB,IAAI,EAAE;YACJ,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;YACvC,SAAS,EAAE,cAAc,CAAC,MAAM,CAAC,OAAO;SACzC;KACF;IACD,KAAK,EAAE;QACL,WAAW,EAAE,OAAO;QACpB,UAAU,EAAE,UAAU,CAAC,MAAM;QAC7B,KAAK,EAAE;YACL,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,SAAS;YACzC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;YACvC,QAAQ,EAAE,cAAc,CAAC,OAAO,CAAC,OAAO;YACxC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU;SACjD;KACF;IACD,WAAW,EAAE;QACX,WAAW,EAAE,SAAS;QACtB,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,SAAS;KACxC;IACD,sHAAsH;IACtH,KAAK,EAAE;QACL,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC;IACD,gGAAgG;IAChG,kBAAkB,EAAE,MAAM,CAAC,MAAM;IACjC,aAAa,EAAE,OAAO,CAAC,GAAG;CAiC3B,CAAC","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport type { SemanticIconName } from \"./icon.js\";\nimport { control, fontWeight, glyph, motion, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/** One cursor and navigation policy shared by every presentation. */\nexport type StepStatus = \"pending\" | \"current\" | \"complete\" | \"error\";\n\n/** Only the cursor step can be \"current\" or \"error\"; every other step is derived. */\n// \"complete\" marks a finished flow; see docs/steps.md.\nexport type StepCursorStatus = Extract<StepStatus, \"current\" | \"error\" | \"complete\">;\n\nexport type StepItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n description?: string;\n}>;\n\n/**\n * A single stable cursor (`currentStepId`) is the source of truth instead of a\n * per-item status array, so \"two current steps\" or \"a completed step past an\n * unresolved error\" cannot be represented. Linear navigation only returns before the cursor. Free reading navigation\n * can select any step without changing the single-cursor state model.\n */\nexport type StepsDescriptor<Id extends string = string> = Readonly<{\n steps: readonly StepItemDescriptor<Id>[];\n currentStepId: Id;\n currentStepStatus?: StepCursorStatus;\n /** Free navigation is for reading flows; linear remains the default for validated forms. */\n navigationMode?: \"linear\" | \"free\";\n}>;\n\nexport type StepsPresentation = \"focus\" | \"compact\" | \"vertical\";\n\nexport function validateStepsPresentation(presentation: StepsPresentation, overviewLabel?: string): void {\n if (![\"focus\", \"compact\", \"vertical\"].includes(presentation)) throw new TypeError(`Unsupported Steps presentation: ${presentation}`);\n if (presentation === \"compact\" && !overviewLabel?.trim()) throw new TypeError(\"Compact Steps requires a localized overviewLabel\");\n}\n\nexport const stepsDefaults = {\n presentation: \"focus\",\n currentStepStatus: \"current\",\n} as const satisfies Readonly<{ currentStepStatus: StepCursorStatus; presentation: StepsPresentation }>;\n\nexport type StepsStatusLabels = Readonly<{\n pending: string;\n current: string;\n complete: string;\n error: string;\n}>;\n\nexport type StepsAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar (Korean\n * \"3단계 중 2단계\" does not translate structurally to English \"step 2 of 3\"),\n * so the resolver invokes this composer instead of assembling copy itself.\n */\nexport type ComposeStepsAccessibleName = (\n info: StepsAccessibleNameInfo,\n) => string;\n\nexport type ResolvedStepDescriptor<Id extends string = string> =\n StepItemDescriptor<Id> &\n Readonly<{\n status: StepStatus;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Primary accessible name: order + label. Never says status by itself. */\n accessibleName: string;\n /** Supplementary status announcement (Web aria-describedby text / RN accessibilityHint). */\n statusLabel: string;\n /**\n * Strictly before the cursor. Only these steps become buttons when the product supplies a step press handler:\n * jumping forward would skip validation, and the cursor step is where the user already is (2026-10-18).\n */\n revisitable: boolean;\n }>;\n\nexport type ResolveStepsOptions = Readonly<{\n statusLabels: StepsStatusLabels;\n composeAccessibleName: ComposeStepsAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Steps ${field} must not be empty`);\n }\n}\n\nexport function validateStepItemDescriptor<Id extends string>(\n step: StepItemDescriptor<Id>,\n): void {\n assertNonEmpty(step.id, \"item id\");\n if (step.id !== step.id.trim()) {\n throw new TypeError(\"Steps item id must not start or end with whitespace\");\n }\n assertNonEmpty(step.label, \"item label\");\n if (step.description !== undefined) {\n assertNonEmpty(step.description, \"item description\");\n }\n}\n\nexport function validateStepsDescriptor<Id extends string>(\n descriptor: StepsDescriptor<Id>,\n): void {\n if (descriptor.navigationMode !== undefined && descriptor.navigationMode !== \"linear\" && descriptor.navigationMode !== \"free\") {\n throw new TypeError(`Unsupported Steps navigationMode: ${String(descriptor.navigationMode)}`);\n }\n if (descriptor.steps.length < 2) {\n throw new RangeError(\"Steps must contain at least two steps\");\n }\n const ids = new Set<Id>();\n for (const step of descriptor.steps) {\n validateStepItemDescriptor(step);\n if (ids.has(step.id)) {\n throw new TypeError(`Duplicate Steps item id: ${step.id}`);\n }\n ids.add(step.id);\n }\n if (!ids.has(descriptor.currentStepId)) {\n throw new RangeError(\n `Steps currentStepId does not match any step id: ${String(descriptor.currentStepId)}`,\n );\n }\n if (\n descriptor.currentStepStatus !== undefined &&\n descriptor.currentStepStatus !== \"current\" &&\n descriptor.currentStepStatus !== \"error\" &&\n descriptor.currentStepStatus !== \"complete\"\n ) {\n throw new TypeError(\n `Unsupported Steps currentStepStatus: ${String(descriptor.currentStepStatus)}`,\n );\n }\n}\n\nexport function validateStepsStatusLabels(labels: StepsStatusLabels): void {\n if (labels === null || typeof labels !== \"object\") {\n throw new TypeError(\"Steps statusLabels must be an object\");\n }\n for (const key of [\"pending\", \"current\", \"complete\", \"error\"] as const) {\n const value = labels[key];\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Steps statusLabels.${key} must not be empty`);\n }\n }\n}\n\n/** A step is visually and semantically \"reached\" once it is no longer waiting. */\nexport function isStepReached(status: StepStatus): boolean {\n return status !== \"pending\";\n}\n\n/**\n * Derives every step's status from its position relative to the single\n * cursor, then attaches the order-preserving accessible name and the\n * supplementary status announcement. Throws on a malformed descriptor,\n * labels bag, or composer instead of silently rendering an inconsistent flow.\n */\nexport function resolveStepsDescriptor<Id extends string>(\n descriptor: StepsDescriptor<Id>,\n options: ResolveStepsOptions,\n): readonly ResolvedStepDescriptor<Id>[] {\n validateStepsDescriptor(descriptor);\n validateStepsStatusLabels(options.statusLabels);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Steps composeAccessibleName must be a function\");\n }\n\n const currentStepStatus =\n descriptor.currentStepStatus ?? stepsDefaults.currentStepStatus;\n const total = descriptor.steps.length;\n const cursorIndex = descriptor.steps.findIndex(\n (step) => step.id === descriptor.currentStepId,\n );\n\n return descriptor.steps.map((step, index) => {\n const status: StepStatus =\n index < cursorIndex\n ? \"complete\"\n : index > cursorIndex\n ? \"pending\"\n : currentStepStatus;\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: step.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Steps composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...step,\n status,\n position,\n total,\n accessibleName,\n statusLabel: options.statusLabels[status],\n revisitable: index < cursorIndex,\n };\n });\n}\n\nexport const stepsRecipe = {\n slots: [\n \"root\",\n \"step\",\n \"indicator\",\n \"marker\",\n \"connector\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.xs,\n // Spint review (2026-10-21): keep endpoint marks inset and give illustrated marks breathing room.\n paddingInline: spacing.md,\n indicator: {\n size: glyph.xl,\n // Illustrated product marks retain the space approved in the Spint review.\n customSize: glyph.xxl,\n borderWidth: stroke.default,\n activeBorderWidth: stroke.strong,\n /** null means the renderer shows the step's own position number instead of a glyph. */\n marks: {\n pending: null,\n current: null,\n complete: \"check\",\n error: \"error\",\n },\n border: {\n pending: semanticColors.border.default,\n current: semanticColors.content.primary,\n complete: semanticColors.border.control,\n error: semanticColors.feedback.danger.border,\n },\n background: {\n pending: null,\n current: semanticColors.content.primary,\n complete: null,\n error: semanticColors.feedback.danger.badgeBackground,\n },\n content: {\n pending: semanticColors.content.secondary,\n current: semanticColors.canvas,\n complete: semanticColors.content.primary,\n error: semanticColors.feedback.danger.foreground,\n },\n },\n connector: {\n height: stroke.default,\n tone: {\n reached: semanticColors.content.primary,\n unreached: semanticColors.border.default,\n },\n },\n label: {\n textVariant: \"label\",\n fontWeight: fontWeight.medium,\n color: {\n pending: semanticColors.content.secondary,\n current: semanticColors.content.primary,\n complete: semanticColors.content.primary,\n error: semanticColors.feedback.danger.foreground,\n },\n },\n description: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n /** A revisitable step is one button over its marker and label; the shared touch minimum applies to the whole cell. */\n press: {\n minTouchTarget: control.minTouchTarget,\n },\n // User selected the focus proposal on 2026-10-23: neutral current fill, quieter completed rail.\n transitionDuration: motion.normal,\n segmentHeight: spacing.xxs,\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n paddingInline: number;\n indicator: {\n size: number;\n customSize: number;\n borderWidth: number;\n activeBorderWidth: number;\n marks: Record<StepStatus, SemanticIconName | null>;\n border: Record<StepStatus, ColorReference>;\n background: Record<StepStatus, ColorReference | null>;\n content: Record<StepStatus, ColorReference>;\n };\n connector: {\n height: number;\n tone: Record<\"reached\" | \"unreached\", ColorReference>;\n };\n label: {\n textVariant: TextVariant;\n fontWeight: FontWeightValue;\n color: Record<StepStatus, ColorReference>;\n };\n description: {\n textVariant: TextVariant;\n color: ColorReference;\n };\n press: {\n minTouchTarget: number;\n };\n transitionDuration: number;\n segmentHeight: number;\n};\n"]}
@@ -24,6 +24,12 @@ export type TimelineItemDescriptor<Id extends string = string> = Readonly<{
24
24
  }>;
25
25
  export type TimelineDescriptor<Id extends string = string> = Readonly<{
26
26
  items: readonly TimelineItemDescriptor<Id>[];
27
+ /**
28
+ * Single selection of one record (2026-10-23): `undefined` keeps the passive record, `null` makes the list selectable
29
+ * with nothing chosen yet. Selection is not a progress cursor — no status is derived from it. Diairy's journey log
30
+ * rebuilt this with a `Button` tone toggle + `aria-pressed` per step (SELECTION forbids button-as-selection).
31
+ */
32
+ selectedId?: Id | null;
27
33
  }>;
28
34
  export declare const timelineDefaults: {
29
35
  readonly itemTone: "neutral";
@@ -45,12 +51,30 @@ export type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<Ti
45
51
  total: number;
46
52
  /** Order + label, for platforms without built-in list-position semantics. */
47
53
  accessibleName: string;
54
+ /** True only for the record matching a selectable Timeline's `selectedId`. */
55
+ selected: boolean;
48
56
  }>;
49
57
  export type ResolveTimelineOptions = Readonly<{
50
58
  composeAccessibleName: ComposeTimelineAccessibleName;
51
59
  }>;
52
60
  export declare function validateTimelineItemDescriptor<Id extends string>(item: TimelineItemDescriptor<Id>): void;
53
61
  export declare function validateTimelineDescriptor<Id extends string>(descriptor: TimelineDescriptor<Id>): void;
62
+ /**
63
+ * Selection is a pair: a controlled `selectedId` (null for none) and the change callback, or neither. A selectable
64
+ * record is one target (Web option, Native selected button), so it cannot also host `renderActions` commands —
65
+ * nested controls inside an option are not operable by assistive tech. Products that need a per-record command keep
66
+ * the passive Timeline.
67
+ */
68
+ export declare function validateTimelineSelection(selectedId: unknown, onSelectedIdChange: unknown, options?: Readonly<{
69
+ hasActions: boolean;
70
+ }>): void;
71
+ /**
72
+ * Keyboard target for a selectable Timeline (vertical listbox): ArrowDown/ArrowUp step one record, Home/End jump to
73
+ * the ends, and the ends do not wrap — a history has a first and a last record. Selection follows focus, as in the
74
+ * single-select listbox pattern, because choosing a record only changes what the product shows next to it.
75
+ * Returns undefined for keys Timeline does not own. Reuses the shared collection navigation math.
76
+ */
77
+ export declare function getTimelineSelectionTarget<Id extends string>(items: readonly TimelineItemDescriptor<Id>[], currentId: Id | null, key: string): Id | undefined;
54
78
  /**
55
79
  * Attaches order-preserving accessible names to an already-ordered item list.
56
80
  * Unlike Steps, no status is derived from position — every item already
@@ -67,6 +91,23 @@ export declare const timelineRecipe: {
67
91
  key: "info";
68
92
  alpha?: number;
69
93
  }>;
94
+ /**
95
+ * Selected record (selectable Timeline): the neutral persistent-selection surface and ring shared with ListRow,
96
+ * not brand fill (theme-palette.md 선택 상태 색상 기준). Selection outranks passive emphasis on the same record.
97
+ */
98
+ readonly selected: {
99
+ readonly background: Readonly<{
100
+ source: "theme";
101
+ key: "surfaceSelected";
102
+ alpha?: number;
103
+ }>;
104
+ readonly border: Readonly<{
105
+ source: "theme";
106
+ key: "borderControl";
107
+ alpha?: number;
108
+ }>;
109
+ readonly borderWidth: 1;
110
+ };
70
111
  };
71
112
  readonly dot: {
72
113
  readonly diameter: 10;
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAKA;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;AAE5E,MAAM,MAAM,sBAAsB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACxE,EAAE,EAAE,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,CAAC;IACd,0HAA0H;IAC1H,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,iFAAiF;IACjF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,kBAAkB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACpE,KAAK,EAAE,SAAS,sBAAsB,CAAC,EAAE,CAAC,EAAE,CAAC;CAC9C,CAAC,CAAC;AAEH,eAAO,MAAM,gBAAgB;;CAEgC,CAAC;AAE9D,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG,CAC1C,IAAI,EAAE,0BAA0B,KAC7B,MAAM,CAAC;AAEZ,MAAM,MAAM,8BAA8B,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,IAAI,CAC3E,sBAAsB,CAAC,EAAE,CAAC,EAC1B,MAAM,CACP,GACC,QAAQ,CAAC;IACP,IAAI,EAAE,gBAAgB,CAAC;IACvB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAAC;AAEL,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,qBAAqB,EAAE,6BAA6B,CAAC;CACtD,CAAC,CAAC;AAQH,wBAAgB,8BAA8B,CAAC,EAAE,SAAS,MAAM,EAC9D,IAAI,EAAE,sBAAsB,CAAC,EAAE,CAAC,GAC/B,IAAI,CA4BN;AAED,wBAAgB,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAC1D,UAAU,EAAE,kBAAkB,CAAC,EAAE,CAAC,GACjC,IAAI,CAYN;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,SAAS,MAAM,EACzD,UAAU,EAAE,kBAAkB,CAAC,EAAE,CAAC,EAClC,OAAO,EAAE,sBAAsB,GAC9B,SAAS,8BAA8B,CAAC,EAAE,CAAC,EAAE,CA2B/C;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAiCzB;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCJ,CAAC"}
1
+ {"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
package/dist/timeline.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { fontWeight, spacing, stroke } from "./foundations.js";
2
2
  import { semanticColors } from "./semantic-colors.js";
3
+ import { getCollectionNavigationIntent, getCollectionNavigationTarget } from "./collection.js";
3
4
  export const timelineDefaults = {
4
5
  itemTone: "neutral",
5
6
  };
@@ -48,6 +49,38 @@ export function validateTimelineDescriptor(descriptor) {
48
49
  }
49
50
  ids.add(item.id);
50
51
  }
52
+ if (descriptor.selectedId !== undefined && descriptor.selectedId !== null && !ids.has(descriptor.selectedId)) {
53
+ throw new RangeError(`Timeline selectedId does not match any item id: ${String(descriptor.selectedId)}`);
54
+ }
55
+ }
56
+ /**
57
+ * Selection is a pair: a controlled `selectedId` (null for none) and the change callback, or neither. A selectable
58
+ * record is one target (Web option, Native selected button), so it cannot also host `renderActions` commands —
59
+ * nested controls inside an option are not operable by assistive tech. Products that need a per-record command keep
60
+ * the passive Timeline.
61
+ */
62
+ export function validateTimelineSelection(selectedId, onSelectedIdChange, options = { hasActions: false }) {
63
+ if ((selectedId === undefined) !== (onSelectedIdChange === undefined)) {
64
+ throw new TypeError("Timeline selectedId and onSelectedIdChange must be supplied together");
65
+ }
66
+ if (onSelectedIdChange !== undefined && typeof onSelectedIdChange !== "function") {
67
+ throw new TypeError("Timeline onSelectedIdChange must be a function");
68
+ }
69
+ if (onSelectedIdChange !== undefined && options.hasActions) {
70
+ throw new TypeError("A selectable Timeline cannot render per-record actions; the record itself is the selection target");
71
+ }
72
+ }
73
+ /**
74
+ * Keyboard target for a selectable Timeline (vertical listbox): ArrowDown/ArrowUp step one record, Home/End jump to
75
+ * the ends, and the ends do not wrap — a history has a first and a last record. Selection follows focus, as in the
76
+ * single-select listbox pattern, because choosing a record only changes what the product shows next to it.
77
+ * Returns undefined for keys Timeline does not own. Reuses the shared collection navigation math.
78
+ */
79
+ export function getTimelineSelectionTarget(items, currentId, key) {
80
+ const intent = getCollectionNavigationIntent(key);
81
+ if (!intent)
82
+ return undefined;
83
+ return getCollectionNavigationTarget({ items: items.map((item) => ({ id: item.id, label: item.label, textValue: item.label })) }, currentId, intent, false);
51
84
  }
52
85
  /**
53
86
  * Attaches order-preserving accessible names to an already-ordered item list.
@@ -76,6 +109,7 @@ export function resolveTimelineDescriptor(descriptor, options) {
76
109
  position,
77
110
  total,
78
111
  accessibleName,
112
+ selected: descriptor.selectedId !== undefined && descriptor.selectedId === item.id,
79
113
  };
80
114
  });
81
115
  }
@@ -92,7 +126,19 @@ export const timelineRecipe = {
92
126
  ],
93
127
  gap: spacing.lg,
94
128
  // A record remains passive: emphasis highlights its history without implying progress.
95
- item: { padding: spacing.sm, emphasisBackground: semanticColors.feedback.info.background },
129
+ item: {
130
+ padding: spacing.sm,
131
+ emphasisBackground: semanticColors.feedback.info.background,
132
+ /**
133
+ * Selected record (selectable Timeline): the neutral persistent-selection surface and ring shared with ListRow,
134
+ * not brand fill (theme-palette.md 선택 상태 색상 기준). Selection outranks passive emphasis on the same record.
135
+ */
136
+ selected: {
137
+ background: semanticColors.selection.background,
138
+ border: semanticColors.selection.border,
139
+ borderWidth: stroke.default,
140
+ },
141
+ },
96
142
  dot: {
97
143
  diameter: 10,
98
144
  borderWidth: stroke.default,
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * Timeline is a record of what already happened, not a position in a flow —\n * that is Steps' job (see docs/steps.md and the boundary note in\n * docs/timeline.md). Because there is no cursor, Timeline never derives a\n * status from position: every item is simply \"happened\", and the connector\n * between items carries no reached/unreached meaning. Ant Design's\n * alternating left/right layout is intentionally not reproduced — it is a\n * Web-only decoration with no equivalent in a single-direction native list;\n * see docs/timeline.md for the rationale.\n */\nexport type TimelineItemTone = \"neutral\" | \"info\" | \"success\" | \"attention\";\n\nexport type TimelineItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n /** Product-formatted \"when\" copy, e.g. \"3회 초\" or \"2024-01-15\". Timeline never formats a clock, inning, or date itself. */\n timestamp?: string;\n /** Machine-readable date for the formatted timestamp; products retain the original instant. */\n dateTime?: string;\n /** Product-formatted supplementary detail, e.g. \"안타로 1루 진출\". */\n description?: string;\n tone?: TimelineItemTone;\n /** Passive emphasis for a notable record, not a progress cursor or selection. */\n emphasized?: boolean;\n}>;\n\nexport type TimelineDescriptor<Id extends string = string> = Readonly<{\n items: readonly TimelineItemDescriptor<Id>[];\n}>;\n\nexport const timelineDefaults = {\n itemTone: \"neutral\",\n} as const satisfies Readonly<{ itemTone: TimelineItemTone }>;\n\nexport type TimelineAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar, same reason\n * Steps does not assemble its own accessible name (see docs/steps.md).\n */\nexport type ComposeTimelineAccessibleName = (\n info: TimelineAccessibleNameInfo,\n) => string;\n\nexport type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<\n TimelineItemDescriptor<Id>,\n \"tone\"\n> &\n Readonly<{\n tone: TimelineItemTone;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Order + label, for platforms without built-in list-position semantics. */\n accessibleName: string;\n }>;\n\nexport type ResolveTimelineOptions = Readonly<{\n composeAccessibleName: ComposeTimelineAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Timeline ${field} must not be empty`);\n }\n}\n\nexport function validateTimelineItemDescriptor<Id extends string>(\n item: TimelineItemDescriptor<Id>,\n): void {\n assertNonEmpty(item.id, \"item id\");\n if (item.emphasized !== undefined && typeof item.emphasized !== \"boolean\") {\n throw new TypeError(\"Timeline item emphasized must be a boolean\");\n }\n if (item.id !== item.id.trim()) {\n throw new TypeError(\"Timeline item id must not start or end with whitespace\");\n }\n assertNonEmpty(item.label, \"item label\");\n if (item.timestamp !== undefined) {\n assertNonEmpty(item.timestamp, \"item timestamp\");\n }\n if (item.dateTime !== undefined) {\n assertNonEmpty(item.dateTime, \"item dateTime\");\n if (item.timestamp === undefined) throw new TypeError(\"Timeline dateTime requires timestamp\");\n }\n if (item.description !== undefined) {\n assertNonEmpty(item.description, \"item description\");\n }\n if (\n item.tone !== undefined &&\n item.tone !== \"neutral\" &&\n item.tone !== \"info\" &&\n item.tone !== \"success\" &&\n item.tone !== \"attention\"\n ) {\n throw new TypeError(`Unsupported Timeline item tone: ${String(item.tone)}`);\n }\n}\n\nexport function validateTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Timeline must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n validateTimelineItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Timeline item id: ${item.id}`);\n }\n ids.add(item.id);\n }\n}\n\n/**\n * Attaches order-preserving accessible names to an already-ordered item list.\n * Unlike Steps, no status is derived from position — every item already\n * happened, so this only adds order and the resolved tone default.\n */\nexport function resolveTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n options: ResolveTimelineOptions,\n): readonly ResolvedTimelineItemDescriptor<Id>[] {\n validateTimelineDescriptor(descriptor);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Timeline composeAccessibleName must be a function\");\n }\n\n const total = descriptor.items.length;\n return descriptor.items.map((item, index) => {\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: item.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Timeline composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...item,\n tone: item.tone ?? timelineDefaults.itemTone,\n position,\n total,\n accessibleName,\n };\n });\n}\n\nexport const timelineRecipe = {\n slots: [\n \"root\",\n \"item\",\n \"dot\",\n \"connector\",\n \"content\",\n \"timestamp\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.lg,\n // A record remains passive: emphasis highlights its history without implying progress.\n item: { padding: spacing.sm, emphasisBackground: semanticColors.feedback.info.background },\n dot: {\n diameter: 10,\n borderWidth: stroke.default,\n tones: {\n neutral: { border: null, fill: semanticColors.content.secondary },\n info: {\n border: semanticColors.feedback.info.border,\n fill: semanticColors.feedback.info.foreground,\n },\n success: {\n border: semanticColors.feedback.success.border,\n fill: semanticColors.feedback.success.foreground,\n },\n attention: {\n border: semanticColors.feedback.attention.border,\n fill: semanticColors.feedback.attention.foreground,\n },\n },\n },\n /**\n * A single tone, unlike stepsRecipe.connector's reached/unreached pair —\n * Timeline has no cursor, so no segment is \"not yet reached\".\n */\n connector: {\n width: stroke.default,\n tone: semanticColors.border.default,\n },\n timestamp: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n label: {\n textVariant: \"body\",\n fontWeight: fontWeight.medium,\n color: semanticColors.content.primary,\n },\n description: {\n textVariant: \"body\",\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n item: { padding: number; emphasisBackground: ColorReference };\n dot: {\n diameter: number;\n borderWidth: number;\n tones: Record<\n TimelineItemTone,\n { border: ColorReference | null; fill: ColorReference }\n >;\n };\n connector: {\n width: number;\n tone: ColorReference;\n };\n timestamp: { textVariant: TextVariant; color: ColorReference };\n label: { textVariant: TextVariant; fontWeight: FontWeightValue; color: ColorReference };\n description: { textVariant: TextVariant; color: ColorReference };\n};\n"]}
1
+ {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport type { FontWeightValue, TextVariant } from \"./foundations.js\";\nimport { fontWeight, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport { getCollectionNavigationIntent, getCollectionNavigationTarget } from \"./collection.js\";\n\n/**\n * Timeline is a record of what already happened, not a position in a flow —\n * that is Steps' job (see docs/steps.md and the boundary note in\n * docs/timeline.md). Because there is no cursor, Timeline never derives a\n * status from position: every item is simply \"happened\", and the connector\n * between items carries no reached/unreached meaning. Ant Design's\n * alternating left/right layout is intentionally not reproduced — it is a\n * Web-only decoration with no equivalent in a single-direction native list;\n * see docs/timeline.md for the rationale.\n */\nexport type TimelineItemTone = \"neutral\" | \"info\" | \"success\" | \"attention\";\n\nexport type TimelineItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n /** Product-formatted \"when\" copy, e.g. \"3회 초\" or \"2024-01-15\". Timeline never formats a clock, inning, or date itself. */\n timestamp?: string;\n /** Machine-readable date for the formatted timestamp; products retain the original instant. */\n dateTime?: string;\n /** Product-formatted supplementary detail, e.g. \"안타로 1루 진출\". */\n description?: string;\n tone?: TimelineItemTone;\n /** Passive emphasis for a notable record, not a progress cursor or selection. */\n emphasized?: boolean;\n}>;\n\nexport type TimelineDescriptor<Id extends string = string> = Readonly<{\n items: readonly TimelineItemDescriptor<Id>[];\n /**\n * Single selection of one record (2026-10-23): `undefined` keeps the passive record, `null` makes the list selectable\n * with nothing chosen yet. Selection is not a progress cursor — no status is derived from it. Diairy's journey log\n * rebuilt this with a `Button` tone toggle + `aria-pressed` per step (SELECTION forbids button-as-selection).\n */\n selectedId?: Id | null;\n}>;\n\nexport const timelineDefaults = {\n itemTone: \"neutral\",\n} as const satisfies Readonly<{ itemTone: TimelineItemTone }>;\n\nexport type TimelineAccessibleNameInfo = Readonly<{\n position: number;\n total: number;\n label: string;\n}>;\n\n/**\n * Products own the exact phrase order and counter-word grammar, same reason\n * Steps does not assemble its own accessible name (see docs/steps.md).\n */\nexport type ComposeTimelineAccessibleName = (\n info: TimelineAccessibleNameInfo,\n) => string;\n\nexport type ResolvedTimelineItemDescriptor<Id extends string = string> = Omit<\n TimelineItemDescriptor<Id>,\n \"tone\"\n> &\n Readonly<{\n tone: TimelineItemTone;\n /** 1-indexed so position and label both preserve reading order for renderers. */\n position: number;\n total: number;\n /** Order + label, for platforms without built-in list-position semantics. */\n accessibleName: string;\n /** True only for the record matching a selectable Timeline's `selectedId`. */\n selected: boolean;\n }>;\n\nexport type ResolveTimelineOptions = Readonly<{\n composeAccessibleName: ComposeTimelineAccessibleName;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (value.trim().length === 0) {\n throw new TypeError(`Timeline ${field} must not be empty`);\n }\n}\n\nexport function validateTimelineItemDescriptor<Id extends string>(\n item: TimelineItemDescriptor<Id>,\n): void {\n assertNonEmpty(item.id, \"item id\");\n if (item.emphasized !== undefined && typeof item.emphasized !== \"boolean\") {\n throw new TypeError(\"Timeline item emphasized must be a boolean\");\n }\n if (item.id !== item.id.trim()) {\n throw new TypeError(\"Timeline item id must not start or end with whitespace\");\n }\n assertNonEmpty(item.label, \"item label\");\n if (item.timestamp !== undefined) {\n assertNonEmpty(item.timestamp, \"item timestamp\");\n }\n if (item.dateTime !== undefined) {\n assertNonEmpty(item.dateTime, \"item dateTime\");\n if (item.timestamp === undefined) throw new TypeError(\"Timeline dateTime requires timestamp\");\n }\n if (item.description !== undefined) {\n assertNonEmpty(item.description, \"item description\");\n }\n if (\n item.tone !== undefined &&\n item.tone !== \"neutral\" &&\n item.tone !== \"info\" &&\n item.tone !== \"success\" &&\n item.tone !== \"attention\"\n ) {\n throw new TypeError(`Unsupported Timeline item tone: ${String(item.tone)}`);\n }\n}\n\nexport function validateTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n): void {\n if (descriptor.items.length === 0) {\n throw new RangeError(\"Timeline must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n validateTimelineItemDescriptor(item);\n if (ids.has(item.id)) {\n throw new TypeError(`Duplicate Timeline item id: ${item.id}`);\n }\n ids.add(item.id);\n }\n if (descriptor.selectedId !== undefined && descriptor.selectedId !== null && !ids.has(descriptor.selectedId)) {\n throw new RangeError(`Timeline selectedId does not match any item id: ${String(descriptor.selectedId)}`);\n }\n}\n\n/**\n * Selection is a pair: a controlled `selectedId` (null for none) and the change callback, or neither. A selectable\n * record is one target (Web option, Native selected button), so it cannot also host `renderActions` commands —\n * nested controls inside an option are not operable by assistive tech. Products that need a per-record command keep\n * the passive Timeline.\n */\nexport function validateTimelineSelection(\n selectedId: unknown,\n onSelectedIdChange: unknown,\n options: Readonly<{ hasActions: boolean }> = { hasActions: false },\n): void {\n if ((selectedId === undefined) !== (onSelectedIdChange === undefined)) {\n throw new TypeError(\"Timeline selectedId and onSelectedIdChange must be supplied together\");\n }\n if (onSelectedIdChange !== undefined && typeof onSelectedIdChange !== \"function\") {\n throw new TypeError(\"Timeline onSelectedIdChange must be a function\");\n }\n if (onSelectedIdChange !== undefined && options.hasActions) {\n throw new TypeError(\"A selectable Timeline cannot render per-record actions; the record itself is the selection target\");\n }\n}\n\n/**\n * Keyboard target for a selectable Timeline (vertical listbox): ArrowDown/ArrowUp step one record, Home/End jump to\n * the ends, and the ends do not wrap — a history has a first and a last record. Selection follows focus, as in the\n * single-select listbox pattern, because choosing a record only changes what the product shows next to it.\n * Returns undefined for keys Timeline does not own. Reuses the shared collection navigation math.\n */\nexport function getTimelineSelectionTarget<Id extends string>(\n items: readonly TimelineItemDescriptor<Id>[],\n currentId: Id | null,\n key: string,\n): Id | undefined {\n const intent = getCollectionNavigationIntent(key as Parameters<typeof getCollectionNavigationIntent>[0]);\n if (!intent) return undefined;\n return getCollectionNavigationTarget(\n { items: items.map((item) => ({ id: item.id, label: item.label, textValue: item.label })) },\n currentId,\n intent,\n false,\n );\n}\n\n/**\n * Attaches order-preserving accessible names to an already-ordered item list.\n * Unlike Steps, no status is derived from position — every item already\n * happened, so this only adds order and the resolved tone default.\n */\nexport function resolveTimelineDescriptor<Id extends string>(\n descriptor: TimelineDescriptor<Id>,\n options: ResolveTimelineOptions,\n): readonly ResolvedTimelineItemDescriptor<Id>[] {\n validateTimelineDescriptor(descriptor);\n if (typeof options.composeAccessibleName !== \"function\") {\n throw new TypeError(\"Timeline composeAccessibleName must be a function\");\n }\n\n const total = descriptor.items.length;\n return descriptor.items.map((item, index) => {\n const position = index + 1;\n const accessibleName = options.composeAccessibleName({\n position,\n total,\n label: item.label,\n });\n if (typeof accessibleName !== \"string\" || accessibleName.trim().length === 0) {\n throw new TypeError(\n \"Timeline composeAccessibleName must return a non-empty string\",\n );\n }\n return {\n ...item,\n tone: item.tone ?? timelineDefaults.itemTone,\n position,\n total,\n accessibleName,\n selected: descriptor.selectedId !== undefined && descriptor.selectedId === item.id,\n };\n });\n}\n\nexport const timelineRecipe = {\n slots: [\n \"root\",\n \"item\",\n \"dot\",\n \"connector\",\n \"content\",\n \"timestamp\",\n \"label\",\n \"description\",\n ] as const,\n gap: spacing.lg,\n // A record remains passive: emphasis highlights its history without implying progress.\n item: {\n padding: spacing.sm,\n emphasisBackground: semanticColors.feedback.info.background,\n /**\n * Selected record (selectable Timeline): the neutral persistent-selection surface and ring shared with ListRow,\n * not brand fill (theme-palette.md 선택 상태 색상 기준). Selection outranks passive emphasis on the same record.\n */\n selected: {\n background: semanticColors.selection.background,\n border: semanticColors.selection.border,\n borderWidth: stroke.default,\n },\n },\n dot: {\n diameter: 10,\n borderWidth: stroke.default,\n tones: {\n neutral: { border: null, fill: semanticColors.content.secondary },\n info: {\n border: semanticColors.feedback.info.border,\n fill: semanticColors.feedback.info.foreground,\n },\n success: {\n border: semanticColors.feedback.success.border,\n fill: semanticColors.feedback.success.foreground,\n },\n attention: {\n border: semanticColors.feedback.attention.border,\n fill: semanticColors.feedback.attention.foreground,\n },\n },\n },\n /**\n * A single tone, unlike stepsRecipe.connector's reached/unreached pair —\n * Timeline has no cursor, so no segment is \"not yet reached\".\n */\n connector: {\n width: stroke.default,\n tone: semanticColors.border.default,\n },\n timestamp: {\n textVariant: \"caption\",\n color: semanticColors.content.secondary,\n },\n label: {\n textVariant: \"body\",\n fontWeight: fontWeight.medium,\n color: semanticColors.content.primary,\n },\n description: {\n textVariant: \"body\",\n color: semanticColors.content.body,\n },\n} as const satisfies {\n slots: readonly string[];\n gap: number;\n item: {\n padding: number;\n emphasisBackground: ColorReference;\n selected: { background: ColorReference; border: ColorReference; borderWidth: number };\n };\n dot: {\n diameter: number;\n borderWidth: number;\n tones: Record<\n TimelineItemTone,\n { border: ColorReference | null; fill: ColorReference }\n >;\n };\n connector: {\n width: number;\n tone: ColorReference;\n };\n timestamp: { textVariant: TextVariant; color: ColorReference };\n label: { textVariant: TextVariant; fontWeight: FontWeightValue; color: ColorReference };\n description: { textVariant: TextVariant; color: ColorReference };\n};\n"]}
package/dist/top.d.ts CHANGED
@@ -14,6 +14,13 @@
14
14
  * 계약을 합치지 않았다 — 합치면 "이 Section이 화면의 h1인가"를 매번 물어야 한다.
15
15
  */
16
16
  export type TopSize = "medium" | "large";
17
+ /**
18
+ * `\n` in the title (2026-10-23). `collapse`(기본): 줄바꿈 문자는 공백이 되고 폭에 따라 자연스럽게 감긴다 — Web이 원래
19
+ * 그렇게 그렸고, Native만 `\n`을 그대로 그려 두 플랫폼이 달랐다. `preserve`: 제품이 의도한 줄바꿈을 지킨다(히어로 문구).
20
+ * 어느 쪽이든 제목 이름은 한 문장으로 읽힌다 — 줄은 공백 하나로 이어진다. 포트폴리오 사이트 히어로가 `white-space: pre-line`
21
+ * span을 Heading 안에 넣느라 Top을 채택하지 못했다.
22
+ */
23
+ export type TopTitleLineBreaks = "collapse" | "preserve";
17
24
  export type TopDescriptor = Readonly<{
18
25
  title: string;
19
26
  /** 제목 위 한 줄. 카테고리·단계처럼 제목을 한정하는 짧은 말. */
@@ -21,6 +28,8 @@ export type TopDescriptor = Readonly<{
21
28
  /** 제목 아래 보조 문장. 길면 줄바꿈되며 잘리지 않는다. */
22
29
  description?: string;
23
30
  size?: TopSize;
31
+ /** 제목 안 `\n`의 처리. 기본 `collapse`. */
32
+ titleLineBreaks?: TopTitleLineBreaks;
24
33
  /**
25
34
  * 문서 구조상의 제목 단계. 화면당 하나인 Top은 `1`이 기본이지만, 한 라우트가
26
35
  * 여러 화면을 담는 경우(시트 안의 화면 등) 제품이 낮출 수 있다.
@@ -30,8 +39,18 @@ export type TopDescriptor = Readonly<{
30
39
  export declare const topDefaults: {
31
40
  readonly size: "large";
32
41
  readonly headingLevel: 1;
42
+ readonly titleLineBreaks: "collapse";
33
43
  };
34
44
  export declare function validateTopDescriptor(descriptor: TopDescriptor): void;
45
+ export type ResolvedTopTitle = Readonly<{
46
+ /** Text to draw. `preserve` keeps one `\n` between trimmed, non-empty lines; `collapse` has none. */
47
+ text: string;
48
+ /** The heading's spoken name: the same lines joined by one space, so an intended break never cuts the title. */
49
+ accessibleName: string;
50
+ /** Lines as drawn (one entry when collapsed). */
51
+ lines: readonly string[];
52
+ }>;
53
+ export declare function resolveTopTitle(descriptor: TopDescriptor): ResolvedTopTitle;
35
54
  /**
36
55
  * `large`는 화면의 첫 제목, `medium`은 시트·모달 안의 첫 제목이다. 두 단계뿐인 이유는
37
56
  * 세 번째가 필요해지는 자리가 곧 `Section`이기 때문이다 — 크기를 더 늘리는 대신 다른
@@ -96,7 +115,7 @@ export declare const topRecipe: {
96
115
  export declare const topBehavior: {
97
116
  /** 상태 축이 없다 — 화면이 제목을 바꾸면 그냥 다른 문자열을 넘긴다. */
98
117
  readonly controlled: readonly [];
99
- readonly inputs: readonly ["title", "eyebrow", "description", "size", "headingLevel"];
118
+ readonly inputs: readonly ["title", "eyebrow", "description", "size", "headingLevel", "titleLineBreaks"];
100
119
  readonly stateAxes: {};
101
120
  readonly web: {
102
121
  readonly roles: readonly ["heading"];
@@ -108,6 +127,6 @@ export declare const topBehavior: {
108
127
  readonly states: readonly [];
109
128
  readonly actions: readonly [];
110
129
  };
111
- readonly scenarios: readonly ["the-title-is-a-real-heading-element-at-the-declared-level-not-styled-text", "top-is-body-content-that-scrolls-away-while-topbar-is-fixed-chrome", "description-wraps-instead-of-truncating-so-large-text-never-hides-the-question", "a-trailing-action-shares-the-title-row-and-drops-below-it-when-space-runs-out", "eyebrow-never-carries-information-the-title-does-not-repeat-in-some-form"];
130
+ readonly scenarios: readonly ["the-title-is-a-real-heading-element-at-the-declared-level-not-styled-text", "top-is-body-content-that-scrolls-away-while-topbar-is-fixed-chrome", "description-wraps-instead-of-truncating-so-large-text-never-hides-the-question", "preserved-title-line-breaks-change-only-the-drawing-and-the-heading-name-stays-one-sentence", "a-trailing-action-shares-the-title-row-and-drops-below-it-when-space-runs-out", "eyebrow-never-carries-information-the-title-does-not-repeat-in-some-form"];
112
131
  };
113
132
  //# sourceMappingURL=top.d.ts.map
package/dist/top.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"top.d.ts","sourceRoot":"","sources":["../src/top.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEzC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH,eAAO,MAAM,WAAW;;;CAGiD,CAAC;AAQ1E,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAkBrE;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoBpB,uDAAuD;;;;;CAexD,CAAC;AAEF,eAAO,MAAM,WAAW;IACtB,6CAA6C;;;;;;;;;;;;;;;CAaV,CAAC"}
1
+ {"version":3,"file":"top.d.ts","sourceRoot":"","sources":["../src/top.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;AACzC;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEzD,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,oCAAoC;IACpC,eAAe,CAAC,EAAE,kBAAkB,CAAC;IACrC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC1B,CAAC,CAAC;AAEH,eAAO,MAAM,WAAW;;;;CAIsF,CAAC;AAQ/G,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAyBrE;AAED,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,qGAAqG;IACrG,IAAI,EAAE,MAAM,CAAC;IACb,gHAAgH;IAChH,cAAc,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B,CAAC,CAAC;AAEH,wBAAgB,eAAe,CAAC,UAAU,EAAE,aAAa,GAAG,gBAAgB,CAQ3E;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoBpB,uDAAuD;;;;;CAexD,CAAC;AAEF,eAAO,MAAM,WAAW;IACtB,6CAA6C;;;;;;;;;;;;;;;CAcV,CAAC"}