@hjmds/design-contracts 1.41.2 → 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.
- package/dist/affix.d.ts +18 -0
- package/dist/affix.d.ts.map +1 -1
- package/dist/affix.js +13 -1
- package/dist/affix.js.map +1 -1
- package/dist/alert-dialog.d.ts +32 -0
- package/dist/alert-dialog.d.ts.map +1 -1
- package/dist/alert-dialog.js +52 -0
- package/dist/alert-dialog.js.map +1 -1
- package/dist/behaviors.d.ts +6 -5
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +1 -1
- package/dist/behaviors.js.map +1 -1
- package/dist/catalog.d.ts +165 -15
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +4 -4
- package/dist/catalog.js.map +1 -1
- package/dist/character-count.d.ts +103 -0
- package/dist/character-count.d.ts.map +1 -0
- package/dist/character-count.js +68 -0
- package/dist/character-count.js.map +1 -0
- package/dist/chart.d.ts +30 -0
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +18 -0
- package/dist/chart.js.map +1 -1
- package/dist/collection-rail.d.ts +13 -1
- package/dist/collection-rail.d.ts.map +1 -1
- package/dist/collection-rail.js +9 -2
- package/dist/collection-rail.js.map +1 -1
- package/dist/component-recipes.d.ts +172 -2
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +122 -8
- package/dist/component-recipes.js.map +1 -1
- package/dist/design-system-provider.d.ts +13 -2
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +19 -3
- package/dist/design-system-provider.js.map +1 -1
- package/dist/foundations.d.ts +15 -0
- package/dist/foundations.d.ts.map +1 -1
- package/dist/foundations.js +7 -0
- package/dist/foundations.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +33 -0
- package/dist/icon-button-recipe.d.ts.map +1 -1
- package/dist/icon-button-recipe.js +36 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/image.d.ts +13 -0
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js +17 -0
- package/dist/image.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/recipes.d.ts +1 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1 -1
- package/dist/recipes.js.map +1 -1
- package/dist/screen-patterns.d.ts +92 -0
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +79 -2
- package/dist/screen-patterns.js.map +1 -1
- package/dist/selection-helpers.d.ts +6 -0
- package/dist/selection-helpers.d.ts.map +1 -1
- package/dist/selection-helpers.js +12 -0
- package/dist/selection-helpers.js.map +1 -1
- package/dist/sidebar.d.ts +17 -2
- package/dist/sidebar.d.ts.map +1 -1
- package/dist/sidebar.js +8 -2
- package/dist/sidebar.js.map +1 -1
- package/dist/steps.d.ts +19 -19
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +21 -9
- package/dist/steps.js.map +1 -1
- package/dist/timeline.d.ts +41 -0
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +47 -1
- package/dist/timeline.js.map +1 -1
- package/dist/top.d.ts +21 -2
- package/dist/top.d.ts.map +1 -1
- package/dist/top.js +17 -1
- package/dist/top.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/affix.md +7 -0
- package/docs/avatar-fallback.md +19 -0
- package/docs/brand-boundary.md +24 -2
- package/docs/chart.md +7 -0
- package/docs/collection-rail.md +4 -1
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +10 -6
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +12 -5
- package/docs/image.md +5 -0
- package/docs/list-row.md +8 -0
- package/docs/optional-adapters.md +3 -0
- package/docs/overlay-stack.md +27 -0
- package/docs/screen-patterns.md +39 -2
- package/docs/sidebar.md +15 -0
- package/docs/steps.md +23 -19
- package/docs/theme-palette.md +3 -3
- package/docs/theming.md +1 -0
- package/docs/timeline.md +18 -1
- package/docs/top.md +7 -0
- package/docs/usage/README.md +2 -2
- package/docs/usage/components/affix.md +18 -4
- package/docs/usage/components/alert-dialog.md +28 -0
- package/docs/usage/components/avatar.md +30 -9
- package/docs/usage/components/badge.md +20 -5
- package/docs/usage/components/chart.md +19 -1
- package/docs/usage/components/checkbox-group.md +24 -1
- package/docs/usage/components/choice-row.md +19 -4
- package/docs/usage/components/composition-layout.md +6 -2
- package/docs/usage/components/counter-badge.md +2 -2
- package/docs/usage/components/design-system-provider.md +2 -1
- package/docs/usage/components/divider.md +12 -1
- package/docs/usage/components/field.md +51 -0
- package/docs/usage/components/icon-button.md +48 -4
- package/docs/usage/components/icon-tile.md +13 -2
- package/docs/usage/components/image.md +25 -0
- package/docs/usage/components/list-row.md +8 -4
- package/docs/usage/components/list.md +1 -1
- package/docs/usage/components/notification-item.md +5 -1
- package/docs/usage/components/permission-screen.md +25 -4
- package/docs/usage/components/screen-layout.md +27 -1
- package/docs/usage/components/segmented-control.md +16 -3
- package/docs/usage/components/select.md +1 -0
- package/docs/usage/components/settings-screen.md +5 -4
- package/docs/usage/components/sheet.md +2 -1
- package/docs/usage/components/sidebar.md +34 -6
- package/docs/usage/components/steps.md +29 -8
- package/docs/usage/components/surface.md +2 -1
- package/docs/usage/components/text-area.md +2 -1
- package/docs/usage/components/timeline.md +25 -4
- package/docs/usage/components/top.md +15 -2
- package/docs/usage/screens/common-settings.md +3 -2
- package/docs/usage/screens/flow-permission.md +3 -3
- package/docs/usage/tokens/elevation-opacity.md +14 -2
- package/package.json +7 -1
package/dist/sidebar.js.map
CHANGED
|
@@ -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:
|
|
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: "
|
|
101
|
+
key: "text";
|
|
104
102
|
alpha?: number;
|
|
105
103
|
}>;
|
|
106
104
|
readonly complete: Readonly<{
|
|
107
|
-
source: "
|
|
108
|
-
key: "
|
|
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:
|
|
120
|
-
|
|
121
|
-
|
|
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: "
|
|
137
|
+
key: "bg";
|
|
140
138
|
alpha?: number;
|
|
141
139
|
}>;
|
|
142
140
|
readonly complete: Readonly<{
|
|
143
|
-
source: "
|
|
144
|
-
key: "
|
|
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: "
|
|
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
|
package/dist/steps.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAMA
|
|
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.
|
|
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.
|
|
129
|
-
complete: semanticColors.
|
|
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:
|
|
135
|
-
complete:
|
|
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.
|
|
141
|
-
complete: semanticColors.
|
|
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.
|
|
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":"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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, 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"]}
|
package/dist/timeline.d.ts
CHANGED
|
@@ -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;
|
package/dist/timeline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"timeline.d.ts","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAMA;;;;;;;;;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;IAC7C;;;;OAIG;IACH,UAAU,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC;CACxB,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;IACvB,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,CAAC;CACnB,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,CAeN;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,OAAO,EACnB,kBAAkB,EAAE,OAAO,EAC3B,OAAO,GAAE,QAAQ,CAAC;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,CAAyB,GACjE,IAAI,CAUN;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,EAAE,SAAS,MAAM,EAC1D,KAAK,EAAE,SAAS,sBAAsB,CAAC,EAAE,CAAC,EAAE,EAC5C,SAAS,EAAE,EAAE,GAAG,IAAI,EACpB,GAAG,EAAE,MAAM,GACV,EAAE,GAAG,SAAS,CAShB;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,CA4B/C;AAED,eAAO,MAAM,cAAc;;;;;;;;;;QAgBvB;;;WAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0BL;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCJ,CAAC"}
|
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: {
|
|
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,
|
package/dist/timeline.js.map
CHANGED
|
@@ -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;
|
|
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"}
|