@hjmds/design-contracts 1.40.0 → 1.41.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 (76) hide show
  1. package/dist/agreement.d.ts +2 -2
  2. package/dist/base-recipes.d.ts +11 -3
  3. package/dist/base-recipes.d.ts.map +1 -1
  4. package/dist/base-recipes.js +9 -4
  5. package/dist/base-recipes.js.map +1 -1
  6. package/dist/behaviors.d.ts +4 -4
  7. package/dist/card.d.ts +1 -1
  8. package/dist/card.js +1 -1
  9. package/dist/card.js.map +1 -1
  10. package/dist/catalog.d.ts +107 -85
  11. package/dist/catalog.d.ts.map +1 -1
  12. package/dist/choice-row.d.ts +26 -0
  13. package/dist/choice-row.d.ts.map +1 -0
  14. package/dist/choice-row.js +24 -0
  15. package/dist/choice-row.js.map +1 -0
  16. package/dist/colors.d.ts +2 -0
  17. package/dist/colors.d.ts.map +1 -1
  18. package/dist/colors.js +3 -1
  19. package/dist/colors.js.map +1 -1
  20. package/dist/command-palette.d.ts +2 -2
  21. package/dist/component-contracts.d.ts +2 -2
  22. package/dist/component-contracts.js +2 -2
  23. package/dist/component-contracts.js.map +1 -1
  24. package/dist/component-recipes.d.ts +76 -64
  25. package/dist/component-recipes.d.ts.map +1 -1
  26. package/dist/component-recipes.js +18 -16
  27. package/dist/component-recipes.js.map +1 -1
  28. package/dist/data-table.d.ts +1 -1
  29. package/dist/icon-button-recipe.d.ts +2 -2
  30. package/dist/icon-button-recipe.js +3 -3
  31. package/dist/icon-button-recipe.js.map +1 -1
  32. package/dist/menubar.d.ts +2 -2
  33. package/dist/palette-contrast.d.ts.map +1 -1
  34. package/dist/palette-contrast.js +2 -0
  35. package/dist/palette-contrast.js.map +1 -1
  36. package/dist/semantic-colors.d.ts +18 -1
  37. package/dist/semantic-colors.d.ts.map +1 -1
  38. package/dist/semantic-colors.js +7 -1
  39. package/dist/semantic-colors.js.map +1 -1
  40. package/dist/sidebar.d.ts +2 -2
  41. package/dist/time-picker.d.ts +16 -4
  42. package/dist/time-picker.d.ts.map +1 -1
  43. package/dist/time-picker.js +26 -5
  44. package/dist/time-picker.js.map +1 -1
  45. package/dist/toggle-group.d.ts +3 -3
  46. package/dist/toggle-group.js +3 -3
  47. package/dist/toggle-group.js.map +1 -1
  48. package/dist/transfer-list.d.ts +2 -2
  49. package/dist/tree.d.ts +2 -2
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.js +1 -1
  52. package/dist/version.js.map +1 -1
  53. package/docs/ant-design-coverage.md +6 -1
  54. package/docs/brand-boundary.md +1 -0
  55. package/docs/generated/component-maturity.md +1 -1
  56. package/docs/generated/renderer-evidence.json +3 -3
  57. package/docs/generated/renderer-evidence.md +1 -1
  58. package/docs/generated/showcase-manifest.json +1 -1
  59. package/docs/theme-palette.md +60 -0
  60. package/docs/time-picker.md +21 -0
  61. package/docs/usage/FINDING.md +71 -0
  62. package/docs/usage/README.md +5 -3
  63. package/docs/usage/SELECTION.md +8 -2
  64. package/docs/usage/components/bottom-navigation.md +5 -0
  65. package/docs/usage/components/card.md +2 -2
  66. package/docs/usage/components/chip.md +1 -1
  67. package/docs/usage/components/choice-row.md +77 -0
  68. package/docs/usage/components/keyboard-avoidance-context.md +3 -3
  69. package/docs/usage/components/segmented-control.md +1 -1
  70. package/docs/usage/components/surface.md +7 -1
  71. package/docs/usage/components/tabs.md +5 -0
  72. package/docs/usage/components/time-picker.md +20 -2
  73. package/docs/usage/components/toggle-group.md +5 -0
  74. package/docs/usage/compositions/save-feedback.md +43 -21
  75. package/docs/usage/tokens/color.md +17 -3
  76. package/package.json +7 -1
@@ -152,7 +152,7 @@ export declare const dataTableRecipe: {
152
152
  }>;
153
153
  readonly selectedBackground: Readonly<{
154
154
  source: "theme";
155
- key: "bg";
155
+ key: "surfaceSelected";
156
156
  alpha?: number;
157
157
  }>;
158
158
  };
@@ -105,12 +105,12 @@ export declare const iconButtonRecipe: {
105
105
  readonly selected: {
106
106
  readonly background: Readonly<{
107
107
  source: "theme";
108
- key: "bg";
108
+ key: "surfaceSelected";
109
109
  alpha?: number;
110
110
  }>;
111
111
  readonly content: Readonly<{
112
112
  source: "theme";
113
- key: "contentBrand";
113
+ key: "text";
114
114
  alpha?: number;
115
115
  }>;
116
116
  readonly border: null;
@@ -46,9 +46,9 @@ export const iconButtonRecipe = {
46
46
  // state; without a paired visual every consumer painted the pressed icon
47
47
  // button in product styles. Mirrors `buttonRecipe.states.selected`.
48
48
  selected: {
49
- background: semanticColors.canvas,
50
- content: semanticColors.content.brand,
51
- // Match Button: selection changes color without introducing an outline.
49
+ background: semanticColors.selection.background,
50
+ content: semanticColors.selection.content,
51
+ // Match Button: neutral selection does not introduce an outline.
52
52
  border: null,
53
53
  },
54
54
  pressedOpacity: opacity.pressed,
@@ -1 +1 @@
1
- {"version":3,"file":"icon-button-recipe.js","sourceRoot":"","sources":["../src/icon-button-recipe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveColorReference,\n type ColorReference,\n type ColorReferencePalette,\n} from \"./color-references.js\";\nimport {\n glyph,\n opacity,\n radius,\n type GlyphSize,\n} from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\nexport type IconButtonSize = \"small\" | \"medium\" | \"large\";\nexport type IconButtonShape = \"rounded\" | \"circle\";\nexport type IconButtonTone = \"primary\" | \"secondary\" | \"ghost\" | \"danger\";\n/**\n * Which container edge a ghost IconButton sits on (logical: `start`/`end` follow the reading direction). The\n * button keeps its full touch target but overhangs that edge so its glyph slot, not the transparent hit area,\n * sets the visible inset (2026-10-17 measured audit: glyphs sat 10–15px inside the row/header inset).\n */\nexport type IconButtonEdge = \"start\" | \"end\" | \"both\";\nexport const iconButtonEdges = [\"start\", \"end\", \"both\"] as const satisfies readonly IconButtonEdge[];\n\nexport const iconButtonRecipe = {\n slots: [\"root\", \"icon\", \"spinner\"] as const,\n defaults: { tone: \"ghost\", size: \"medium\", shape: \"rounded\" },\n tones: {\n primary: {\n background: semanticColors.action.brand.background,\n content: semanticColors.action.brand.content,\n border: null,\n },\n secondary: {\n background: semanticColors.action.neutral.background,\n content: semanticColors.action.neutral.content,\n border: semanticColors.action.neutral.border,\n },\n ghost: {\n background: null,\n content: semanticColors.content.secondary,\n border: null,\n },\n danger: {\n background: semanticColors.action.danger.background,\n content: semanticColors.action.danger.content,\n border: null,\n },\n },\n sizes: {\n small: { diameter: 36, hitSlop: 4, glyph: \"sm\" },\n medium: { diameter: 44, hitSlop: 0, glyph: \"md\" },\n large: { diameter: 52, hitSlop: 0, glyph: \"lg\" },\n },\n shapes: { rounded: \"md\", circle: \"full\" },\n /**\n * Edge alignment (2026-10-17 measured audit): a ghost IconButton at a row/header edge overhangs that edge by\n * (visible diameter − glyph slot) / 2, so the glyph slot lines up with the container inset like text does while\n * the hit area stays whole. Only `ghost` overhangs — filled and outlined tones show their frame, and the frame is\n * what aligns. Renderers read `resolveIconButtonEdgeOverhang` so a raised visible diameter (minimumVisualTarget)\n * moves the glyph slot by the same amount on Web and Native.\n */\n edge: { tones: [\"ghost\"] as const },\n states: {\n // `aria-pressed` / `accessibilityState.selected` already carried the toggle\n // state; without a paired visual every consumer painted the pressed icon\n // button in product styles. Mirrors `buttonRecipe.states.selected`.\n selected: {\n background: semanticColors.canvas,\n content: semanticColors.content.brand,\n // Match Button: selection changes color without introducing an outline.\n border: null,\n },\n pressedOpacity: opacity.pressed,\n disabledOpacity: opacity.disabled,\n },\n} as const satisfies {\n slots: readonly string[];\n defaults: {\n tone: IconButtonTone;\n size: IconButtonSize;\n shape: IconButtonShape;\n };\n tones: Record<\n IconButtonTone,\n { background: ColorReference | null; content: ColorReference; border: ColorReference | null }\n >;\n sizes: Record<IconButtonSize, { diameter: number; hitSlop: number; glyph: GlyphSize }>;\n shapes: Record<IconButtonShape, keyof typeof radius>;\n edge: { tones: readonly IconButtonTone[] };\n states: {\n selected: { background: ColorReference; content: ColorReference; border: ColorReference | null };\n pressedOpacity: number;\n disabledOpacity: number;\n };\n};\n\nexport type ResolvedIconButtonPresentation = Readonly<{\n background: string | null;\n content: string;\n border: string | null;\n}>;\n\n/** Resolve one recipe tone for non-CSS renderers without a second tone table. */\nexport function resolveIconButtonPresentation(\n tone: IconButtonTone,\n palette: ColorReferencePalette,\n selected = false,\n): ResolvedIconButtonPresentation {\n const contract: {\n background: ColorReference | null;\n content: ColorReference;\n border: ColorReference | null;\n } = selected ? iconButtonRecipe.states.selected : iconButtonRecipe.tones[tone];\n return {\n background:\n contract.background === null\n ? null\n : resolveColorReference(contract.background, palette),\n content: resolveColorReference(contract.content, palette),\n border:\n contract.border === null\n ? null\n : resolveColorReference(contract.border, palette),\n };\n}\n\n/**\n * Negative margin a ghost IconButton applies on its `edge` side(s): (visible diameter − glyph slot) / 2, e.g. 10 for\n * medium (44 − 24). 0 for tones that draw a frame. `visibleDiameter` is the rendered size after the Provider's\n * minimumVisualTarget axis (`visibleControlHeight`), so the overhang tracks what is actually drawn.\n */\nexport function resolveIconButtonEdgeOverhang(\n tone: IconButtonTone,\n size: IconButtonSize,\n visibleDiameter: number = iconButtonRecipe.sizes[size].diameter,\n): number {\n if (!(iconButtonRecipe.edge.tones as readonly IconButtonTone[]).includes(tone)) return 0;\n return Math.max(0, (visibleDiameter - glyph[iconButtonRecipe.sizes[size].glyph]) / 2);\n}\n"]}
1
+ {"version":3,"file":"icon-button-recipe.js","sourceRoot":"","sources":["../src/icon-button-recipe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveColorReference,\n type ColorReference,\n type ColorReferencePalette,\n} from \"./color-references.js\";\nimport {\n glyph,\n opacity,\n radius,\n type GlyphSize,\n} from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\nexport type IconButtonSize = \"small\" | \"medium\" | \"large\";\nexport type IconButtonShape = \"rounded\" | \"circle\";\nexport type IconButtonTone = \"primary\" | \"secondary\" | \"ghost\" | \"danger\";\n/**\n * Which container edge a ghost IconButton sits on (logical: `start`/`end` follow the reading direction). The\n * button keeps its full touch target but overhangs that edge so its glyph slot, not the transparent hit area,\n * sets the visible inset (2026-10-17 measured audit: glyphs sat 10–15px inside the row/header inset).\n */\nexport type IconButtonEdge = \"start\" | \"end\" | \"both\";\nexport const iconButtonEdges = [\"start\", \"end\", \"both\"] as const satisfies readonly IconButtonEdge[];\n\nexport const iconButtonRecipe = {\n slots: [\"root\", \"icon\", \"spinner\"] as const,\n defaults: { tone: \"ghost\", size: \"medium\", shape: \"rounded\" },\n tones: {\n primary: {\n background: semanticColors.action.brand.background,\n content: semanticColors.action.brand.content,\n border: null,\n },\n secondary: {\n background: semanticColors.action.neutral.background,\n content: semanticColors.action.neutral.content,\n border: semanticColors.action.neutral.border,\n },\n ghost: {\n background: null,\n content: semanticColors.content.secondary,\n border: null,\n },\n danger: {\n background: semanticColors.action.danger.background,\n content: semanticColors.action.danger.content,\n border: null,\n },\n },\n sizes: {\n small: { diameter: 36, hitSlop: 4, glyph: \"sm\" },\n medium: { diameter: 44, hitSlop: 0, glyph: \"md\" },\n large: { diameter: 52, hitSlop: 0, glyph: \"lg\" },\n },\n shapes: { rounded: \"md\", circle: \"full\" },\n /**\n * Edge alignment (2026-10-17 measured audit): a ghost IconButton at a row/header edge overhangs that edge by\n * (visible diameter − glyph slot) / 2, so the glyph slot lines up with the container inset like text does while\n * the hit area stays whole. Only `ghost` overhangs — filled and outlined tones show their frame, and the frame is\n * what aligns. Renderers read `resolveIconButtonEdgeOverhang` so a raised visible diameter (minimumVisualTarget)\n * moves the glyph slot by the same amount on Web and Native.\n */\n edge: { tones: [\"ghost\"] as const },\n states: {\n // `aria-pressed` / `accessibilityState.selected` already carried the toggle\n // state; without a paired visual every consumer painted the pressed icon\n // button in product styles. Mirrors `buttonRecipe.states.selected`.\n selected: {\n background: semanticColors.selection.background,\n content: semanticColors.selection.content,\n // Match Button: neutral selection does not introduce an outline.\n border: null,\n },\n pressedOpacity: opacity.pressed,\n disabledOpacity: opacity.disabled,\n },\n} as const satisfies {\n slots: readonly string[];\n defaults: {\n tone: IconButtonTone;\n size: IconButtonSize;\n shape: IconButtonShape;\n };\n tones: Record<\n IconButtonTone,\n { background: ColorReference | null; content: ColorReference; border: ColorReference | null }\n >;\n sizes: Record<IconButtonSize, { diameter: number; hitSlop: number; glyph: GlyphSize }>;\n shapes: Record<IconButtonShape, keyof typeof radius>;\n edge: { tones: readonly IconButtonTone[] };\n states: {\n selected: { background: ColorReference; content: ColorReference; border: ColorReference | null };\n pressedOpacity: number;\n disabledOpacity: number;\n };\n};\n\nexport type ResolvedIconButtonPresentation = Readonly<{\n background: string | null;\n content: string;\n border: string | null;\n}>;\n\n/** Resolve one recipe tone for non-CSS renderers without a second tone table. */\nexport function resolveIconButtonPresentation(\n tone: IconButtonTone,\n palette: ColorReferencePalette,\n selected = false,\n): ResolvedIconButtonPresentation {\n const contract: {\n background: ColorReference | null;\n content: ColorReference;\n border: ColorReference | null;\n } = selected ? iconButtonRecipe.states.selected : iconButtonRecipe.tones[tone];\n return {\n background:\n contract.background === null\n ? null\n : resolveColorReference(contract.background, palette),\n content: resolveColorReference(contract.content, palette),\n border:\n contract.border === null\n ? null\n : resolveColorReference(contract.border, palette),\n };\n}\n\n/**\n * Negative margin a ghost IconButton applies on its `edge` side(s): (visible diameter − glyph slot) / 2, e.g. 10 for\n * medium (44 − 24). 0 for tones that draw a frame. `visibleDiameter` is the rendered size after the Provider's\n * minimumVisualTarget axis (`visibleControlHeight`), so the overhang tracks what is actually drawn.\n */\nexport function resolveIconButtonEdgeOverhang(\n tone: IconButtonTone,\n size: IconButtonSize,\n visibleDiameter: number = iconButtonRecipe.sizes[size].diameter,\n): number {\n if (!(iconButtonRecipe.edge.tones as readonly IconButtonTone[]).includes(tone)) return 0;\n return Math.max(0, (visibleDiameter - glyph[iconButtonRecipe.sizes[size].glyph]) / 2);\n}\n"]}
package/dist/menubar.d.ts CHANGED
@@ -81,12 +81,12 @@ export declare const menubarRecipe: {
81
81
  };
82
82
  readonly selectedBackground: Readonly<{
83
83
  source: "theme";
84
- key: "bg";
84
+ key: "surfaceSelected";
85
85
  alpha?: number;
86
86
  }>;
87
87
  readonly selectedIndicator: Readonly<{
88
88
  source: "theme";
89
- key: "contentBrand";
89
+ key: "text";
90
90
  alpha?: number;
91
91
  }>;
92
92
  readonly danger: Readonly<{
@@ -1 +1 @@
1
- {"version":3,"file":"palette-contrast.d.ts","sourceRoot":"","sources":["../src/palette-contrast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAExG,mEAAmE;AACnE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,UAAU,EAAE,MAAM,WAAW,CAAC;IAC9B,UAAU,EAAE,MAAM,WAAW,GAAG,cAAc,UAAU,EAAE,CAAC;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;CAC3B,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAKvF,eAAO,MAAM,oBAAoB,EAAE,SAAS,mBAAmB,EAe9D,CAAC;AAaF,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAI5E;AAED,iGAAiG;AACjG,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,GAAG,EAAE,mBAAmB,CAAC,YAAY,CAAC,GAAG,MAAM,CAExH;AAED,gGAAgG;AAChG,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC7B,SAAS,sBAAsB,EAAE,CAOnC;AAED,wGAAwG;AACxG,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,GACrF,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,sBAAsB,EAAE,CAAC,CAAC,CAKpE;AAED;;;;oFAIoF;AACpF,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,MAAM,CASnG"}
1
+ {"version":3,"file":"palette-contrast.d.ts","sourceRoot":"","sources":["../src/palette-contrast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAExG,mEAAmE;AACnE,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,UAAU,EAAE,MAAM,WAAW,CAAC;IAC9B,UAAU,EAAE,MAAM,WAAW,GAAG,cAAc,UAAU,EAAE,CAAC;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;CAC3B,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAKvF,eAAO,MAAM,oBAAoB,EAAE,SAAS,mBAAmB,EAiB9D,CAAC;AAaF,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAI5E;AAED,iGAAiG;AACjG,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,GAAG,EAAE,mBAAmB,CAAC,YAAY,CAAC,GAAG,MAAM,CAExH;AAED,gGAAgG;AAChG,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC7B,SAAS,sBAAsB,EAAE,CAOnC;AAED,wGAAwG;AACxG,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,GACrF,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,sBAAsB,EAAE,CAAC,CAAC,CAKpE;AAED;;;;oFAIoF;AACpF,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,MAAM,CASnG"}
@@ -3,6 +3,8 @@ const readable = ["text", "textBody", "textMuted", "textSub", "contentBrand", "d
3
3
  const surfaces = ["bg", "surface"];
4
4
  export const paletteContrastRules = [
5
5
  ...readable.flatMap((foreground) => surfaces.map((background) => ({ foreground, background, minimum: 4.5, kind: "text" }))),
6
+ // Selected text must remain readable when a product overrides the shared neutral fill.
7
+ ...readable.map(foreground => ({ foreground, background: "surfaceSelected", minimum: 4.5, kind: "text" })),
6
8
  { foreground: "onPrimary", background: "primary", minimum: 4.5, kind: "text" },
7
9
  { foreground: "onDanger", background: "dangerFill", minimum: 4.5, kind: "text" },
8
10
  // Filled badges use a separate ink/fill pair, independent of primary buttons.
@@ -1 +1 @@
1
- {"version":3,"file":"palette-contrast.js","sourceRoot":"","sources":["../src/palette-contrast.ts"],"names":[],"mappings":"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","sourcesContent":["import { THEMES, accentFill, type AccentTone, type ResolvedTheme, type ThemeColors } from \"./colors.js\";\n\n/** Pair thresholds and their rationale: docs/brand-boundary.md. */\nexport type PaletteContrastRule = Readonly<{\n foreground: keyof ThemeColors;\n background: keyof ThemeColors | `accentFill.${AccentTone}`;\n minimum: number;\n kind: \"text\" | \"non-text\";\n}>;\n\nexport type PaletteContrastFinding = PaletteContrastRule & Readonly<{ ratio: number }>;\n\nconst readable = [\"text\", \"textBody\", \"textMuted\", \"textSub\", \"contentBrand\", \"danger\"] as const;\nconst surfaces = [\"bg\", \"surface\"] as const;\n\nexport const paletteContrastRules: readonly PaletteContrastRule[] = [\n ...readable.flatMap((foreground) =>\n surfaces.map((background) => ({ foreground, background, minimum: 4.5, kind: \"text\" as const })),\n ),\n { foreground: \"onPrimary\", background: \"primary\", minimum: 4.5, kind: \"text\" },\n { foreground: \"onDanger\", background: \"dangerFill\", minimum: 4.5, kind: \"text\" },\n // Filled badges use a separate ink/fill pair, independent of primary buttons.\n ...([\"neutralFill\", \"strongFill\"] as const).map(background => ({ foreground: \"onStatusFill\" as const, background, minimum: 4.5, kind: \"text\" as const })),\n ...(Object.keys(accentFill) as AccentTone[]).map(tone => ({ foreground: \"onStatusFill\" as const, background: `accentFill.${tone}` as const, minimum: 4.5, kind: \"text\" as const })),\n ...([\"primary\", \"borderControl\"] as const).flatMap((foreground) =>\n surfaces.map((background) => ({ foreground, background, minimum: 3, kind: \"non-text\" as const })),\n ),\n // textWeak is the disabled/decorative/placeholder tier, exempt from text AA;\n // it also paints border.strong on canvas, which must stay a visible boundary.\n { foreground: \"textWeak\", background: \"bg\", minimum: 3, kind: \"non-text\" },\n];\n\nconst sixDigitHex = /^#[0-9a-f]{6}$/i;\n\nfunction luminance(hex: string): number {\n if (!sixDigitHex.test(hex)) throw new TypeError(`Expected a six-digit hex color, got ${hex}`);\n const [r, g, b] = [1, 3, 5].map((offset) => {\n const channel = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;\n return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG 2.x contrast ratio between two six-digit hex colors. */\nexport function contrastRatio(foreground: string, background: string): number {\n const a = luminance(foreground);\n const b = luminance(background);\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);\n}\n\n/** Fixed status fills remain outside the product palette, but participate in the same report. */\nexport function resolveContrastBackground(palette: Readonly<ThemeColors>, key: PaletteContrastRule[\"background\"]): string {\n return key.startsWith(\"accentFill.\") ? accentFill[key.slice(\"accentFill.\".length) as AccentTone] : palette[key as keyof ThemeColors];\n}\n\n/** Pairs of a resolved theme palette that fall below their threshold; empty means it passes. */\nexport function checkPaletteContrast(\n palette: Readonly<ThemeColors>,\n): readonly PaletteContrastFinding[] {\n const findings: PaletteContrastFinding[] = [];\n for (const rule of paletteContrastRules) {\n const ratio = contrastRatio(palette[rule.foreground], resolveContrastBackground(palette, rule.background));\n if (ratio < rule.minimum) findings.push({ ...rule, ratio: Math.round(ratio * 100) / 100 });\n }\n return findings;\n}\n\n/** Checks a `brandPalette` the way the Provider applies it: each theme merged over the HJM defaults. */\nexport function checkBrandPaletteContrast(\n brandPalette: Readonly<Partial<Record<ResolvedTheme, Readonly<Partial<ThemeColors>>>>>,\n): Readonly<Record<ResolvedTheme, readonly PaletteContrastFinding[]>> {\n return {\n light: checkPaletteContrast({ ...THEMES.light, ...brandPalette.light }),\n dark: checkPaletteContrast({ ...THEMES.dark, ...brandPalette.dark }),\n };\n}\n\n/** Shared whole-surface fill adaptation. This palette-only entry keeps ordinary\n * renderer imports independent of the optional preset registry/effect descriptors.\n * The supported 0.85..1 interval keeps readable foregrounds outside the backdrop\n * luminance interval; testing its black/white endpoints bounds any opaque backdrop.\n * See docs/design-profile.md for why a reference card's low alpha is not copied. */\nexport function resolveSurfaceFillOpacity(requested: number, palette: Readonly<ThemeColors>): number {\n if (!Number.isFinite(requested) || requested < 0.85 || requested > 1) throw new RangeError(\"Surface fill opacity must be between 0.85 and 1\");\n const channels = [1, 3, 5].map(offset => Number.parseInt(palette.bg.slice(offset, offset + 2), 16));\n const rules = paletteContrastRules.filter(rule => rule.background === \"bg\");\n for (let opacity = requested; opacity < 1; opacity = Math.min(1, opacity + 0.01)) {\n const endpoints = [0, 255].map(backdrop => \"#\" + channels.map(channel => Math.round(channel * opacity + backdrop * (1 - opacity)).toString(16).padStart(2, \"0\")).join(\"\"));\n if (endpoints.every(color => rules.every(rule => contrastRatio(palette[rule.foreground], color) >= rule.minimum))) return opacity;\n }\n return 1;\n}\n"]}
1
+ {"version":3,"file":"palette-contrast.js","sourceRoot":"","sources":["../src/palette-contrast.ts"],"names":[],"mappings":"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","sourcesContent":["import { THEMES, accentFill, type AccentTone, type ResolvedTheme, type ThemeColors } from \"./colors.js\";\n\n/** Pair thresholds and their rationale: docs/brand-boundary.md. */\nexport type PaletteContrastRule = Readonly<{\n foreground: keyof ThemeColors;\n background: keyof ThemeColors | `accentFill.${AccentTone}`;\n minimum: number;\n kind: \"text\" | \"non-text\";\n}>;\n\nexport type PaletteContrastFinding = PaletteContrastRule & Readonly<{ ratio: number }>;\n\nconst readable = [\"text\", \"textBody\", \"textMuted\", \"textSub\", \"contentBrand\", \"danger\"] as const;\nconst surfaces = [\"bg\", \"surface\"] as const;\n\nexport const paletteContrastRules: readonly PaletteContrastRule[] = [\n ...readable.flatMap((foreground) =>\n surfaces.map((background) => ({ foreground, background, minimum: 4.5, kind: \"text\" as const })),\n ),\n // Selected text must remain readable when a product overrides the shared neutral fill.\n ...readable.map(foreground => ({ foreground, background: \"surfaceSelected\" as const, minimum: 4.5, kind: \"text\" as const })),\n { foreground: \"onPrimary\", background: \"primary\", minimum: 4.5, kind: \"text\" },\n { foreground: \"onDanger\", background: \"dangerFill\", minimum: 4.5, kind: \"text\" },\n // Filled badges use a separate ink/fill pair, independent of primary buttons.\n ...([\"neutralFill\", \"strongFill\"] as const).map(background => ({ foreground: \"onStatusFill\" as const, background, minimum: 4.5, kind: \"text\" as const })),\n ...(Object.keys(accentFill) as AccentTone[]).map(tone => ({ foreground: \"onStatusFill\" as const, background: `accentFill.${tone}` as const, minimum: 4.5, kind: \"text\" as const })),\n ...([\"primary\", \"borderControl\"] as const).flatMap((foreground) =>\n surfaces.map((background) => ({ foreground, background, minimum: 3, kind: \"non-text\" as const })),\n ),\n // textWeak is the disabled/decorative/placeholder tier, exempt from text AA;\n // it also paints border.strong on canvas, which must stay a visible boundary.\n { foreground: \"textWeak\", background: \"bg\", minimum: 3, kind: \"non-text\" },\n];\n\nconst sixDigitHex = /^#[0-9a-f]{6}$/i;\n\nfunction luminance(hex: string): number {\n if (!sixDigitHex.test(hex)) throw new TypeError(`Expected a six-digit hex color, got ${hex}`);\n const [r, g, b] = [1, 3, 5].map((offset) => {\n const channel = Number.parseInt(hex.slice(offset, offset + 2), 16) / 255;\n return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/** WCAG 2.x contrast ratio between two six-digit hex colors. */\nexport function contrastRatio(foreground: string, background: string): number {\n const a = luminance(foreground);\n const b = luminance(background);\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);\n}\n\n/** Fixed status fills remain outside the product palette, but participate in the same report. */\nexport function resolveContrastBackground(palette: Readonly<ThemeColors>, key: PaletteContrastRule[\"background\"]): string {\n return key.startsWith(\"accentFill.\") ? accentFill[key.slice(\"accentFill.\".length) as AccentTone] : palette[key as keyof ThemeColors];\n}\n\n/** Pairs of a resolved theme palette that fall below their threshold; empty means it passes. */\nexport function checkPaletteContrast(\n palette: Readonly<ThemeColors>,\n): readonly PaletteContrastFinding[] {\n const findings: PaletteContrastFinding[] = [];\n for (const rule of paletteContrastRules) {\n const ratio = contrastRatio(palette[rule.foreground], resolveContrastBackground(palette, rule.background));\n if (ratio < rule.minimum) findings.push({ ...rule, ratio: Math.round(ratio * 100) / 100 });\n }\n return findings;\n}\n\n/** Checks a `brandPalette` the way the Provider applies it: each theme merged over the HJM defaults. */\nexport function checkBrandPaletteContrast(\n brandPalette: Readonly<Partial<Record<ResolvedTheme, Readonly<Partial<ThemeColors>>>>>,\n): Readonly<Record<ResolvedTheme, readonly PaletteContrastFinding[]>> {\n return {\n light: checkPaletteContrast({ ...THEMES.light, ...brandPalette.light }),\n dark: checkPaletteContrast({ ...THEMES.dark, ...brandPalette.dark }),\n };\n}\n\n/** Shared whole-surface fill adaptation. This palette-only entry keeps ordinary\n * renderer imports independent of the optional preset registry/effect descriptors.\n * The supported 0.85..1 interval keeps readable foregrounds outside the backdrop\n * luminance interval; testing its black/white endpoints bounds any opaque backdrop.\n * See docs/design-profile.md for why a reference card's low alpha is not copied. */\nexport function resolveSurfaceFillOpacity(requested: number, palette: Readonly<ThemeColors>): number {\n if (!Number.isFinite(requested) || requested < 0.85 || requested > 1) throw new RangeError(\"Surface fill opacity must be between 0.85 and 1\");\n const channels = [1, 3, 5].map(offset => Number.parseInt(palette.bg.slice(offset, offset + 2), 16));\n const rules = paletteContrastRules.filter(rule => rule.background === \"bg\");\n for (let opacity = requested; opacity < 1; opacity = Math.min(1, opacity + 0.01)) {\n const endpoints = [0, 255].map(backdrop => \"#\" + channels.map(channel => Math.round(channel * opacity + backdrop * (1 - opacity)).toString(16).padStart(2, \"0\")).join(\"\"));\n if (endpoints.every(color => rules.every(rule => contrastRatio(palette[rule.foreground], color) >= rule.minimum))) return opacity;\n }\n return 1;\n}\n"]}
@@ -50,6 +50,23 @@ export declare const semanticColors: {
50
50
  alpha?: number;
51
51
  }>;
52
52
  };
53
+ readonly selection: {
54
+ readonly background: Readonly<{
55
+ source: "theme";
56
+ key: "surfaceSelected";
57
+ alpha?: number;
58
+ }>;
59
+ readonly content: Readonly<{
60
+ source: "theme";
61
+ key: "text";
62
+ alpha?: number;
63
+ }>;
64
+ readonly border: Readonly<{
65
+ source: "theme";
66
+ key: "borderControl";
67
+ alpha?: number;
68
+ }>;
69
+ };
53
70
  readonly content: {
54
71
  readonly primary: Readonly<{
55
72
  source: "theme";
@@ -303,7 +320,7 @@ export declare const semanticColors: {
303
320
  }>;
304
321
  readonly selected: Readonly<{
305
322
  source: "theme";
306
- key: "primary";
323
+ key: "surfaceSelected";
307
324
  alpha?: number;
308
325
  }>;
309
326
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"semantic-colors.d.ts","sourceRoot":"","sources":["../src/semantic-colors.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAUvB;;;;WAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAkBH,iDAAiD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4DjD;;;WAGG;;;;;;;CAGG,CAAC"}
1
+ {"version":3,"file":"semantic-colors.d.ts","sourceRoot":"","sources":["../src/semantic-colors.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAUvB;;;;WAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwBH,iDAAiD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA4DjD;;;WAGG;;;;;;;CAGG,CAAC"}
@@ -22,6 +22,12 @@ export const semanticColors = {
22
22
  */
23
23
  placeholder: themeColor("text", 0.12),
24
24
  },
25
+ // 2026-10-23: a neutral selection must not recolor brand surfaces or primary actions.
26
+ selection: {
27
+ background: themeColor("surfaceSelected"),
28
+ content: themeColor("text"),
29
+ border: themeColor("borderControl"),
30
+ },
25
31
  content: {
26
32
  primary: themeColor("text"),
27
33
  body: themeColor("textBody"),
@@ -96,7 +102,7 @@ export const semanticColors = {
96
102
  hover: themeColor("text", stateLayer.hover),
97
103
  focus: themeColor("contentBrand", stateLayer.focus),
98
104
  pressed: themeColor("text", stateLayer.pressed),
99
- selected: themeColor("primary", stateLayer.selected),
105
+ selected: themeColor("surfaceSelected"),
100
106
  /**
101
107
  * Lower edge of a raised object, composited over its own fill so one shade
102
108
  * darkens any brand color (BottomNavigation raised tile, Spint 2026-10-13).
@@ -1 +1 @@
1
- {"version":3,"file":"semantic-colors.js","sourceRoot":"","sources":["../src/semantic-colors.ts"],"names":[],"mappings":"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","sourcesContent":["import { stateLayer } from \"./foundations.js\";\nimport { accentTint } from \"./colors.js\";\nimport { accentColor, themeColor } from \"./color-references.js\";\n\n/**\n * Stable role aliases above the legacy ThemeColors key set. New recipes should\n * prefer these roles so the underlying theme shape can evolve independently.\n */\nexport const semanticColors = {\n canvas: themeColor(\"bg\"),\n surface: {\n default: themeColor(\"surface\"),\n sunken: themeColor(\"surfaceAlt\"),\n raised: themeColor(\"bg\"),\n brand: themeColor(\"surfaceAccent\"),\n // Opaque neutral status plates are surfaces, independent of notice severity.\n statusNeutral: themeColor(\"neutralFill\"),\n statusStrong: themeColor(\"strongFill\"),\n /**\n * Loading placeholder fill (Skeleton, ListRow `loading`). A translucent text tint instead of an opaque\n * neutral: it darkens/lightens whatever it sits on, so it stays visible on canvas, Card surfaces and\n * profile palettes whose `surfaceAlt` is as light as `bg` (2026-10-17 Skeleton review, skeleton.md 근거).\n */\n placeholder: themeColor(\"text\", 0.12),\n },\n content: {\n primary: themeColor(\"text\"),\n body: themeColor(\"textBody\"),\n secondary: themeColor(\"textMuted\"),\n tertiary: themeColor(\"textSub\"),\n decorative: themeColor(\"textWeak\"),\n brand: themeColor(\"contentBrand\"),\n danger: themeColor(\"danger\"),\n inverse: themeColor(\"onPrimary\"),\n // Status fills are independent of a product's primary button contrast pair.\n statusInverse: themeColor(\"onStatusFill\"),\n },\n border: {\n subtle: themeColor(\"border\", 0.7),\n default: themeColor(\"border\"),\n /** Resting outline of an interactive control. */\n control: themeColor(\"borderControl\"),\n strong: themeColor(\"textWeak\"),\n focus: themeColor(\"contentBrand\"),\n danger: themeColor(\"danger\"),\n },\n action: {\n brand: {\n background: themeColor(\"primary\"),\n content: themeColor(\"onPrimary\"),\n },\n neutral: {\n background: themeColor(\"bg\"),\n content: themeColor(\"text\"),\n // A control outline is a border role. `textSub` made a resting neutral\n // action read heavier than the selected control beside it.\n border: themeColor(\"borderControl\"),\n },\n danger: {\n background: themeColor(\"dangerFill\"),\n content: themeColor(\"onDanger\"),\n },\n },\n feedback: {\n info: {\n foreground: accentColor(\"info\"),\n background: accentColor(\"info\", accentTint.weak),\n badgeBackground: accentColor(\"info\", accentTint.weak),\n border: accentColor(\"info\", accentTint.border),\n },\n success: {\n foreground: accentColor(\"success\"),\n background: accentColor(\"success\", accentTint.weak),\n badgeBackground: accentColor(\"success\", accentTint.weak),\n border: accentColor(\"success\", accentTint.border),\n },\n warning: {\n foreground: accentColor(\"warning\"),\n background: accentColor(\"warning\", accentTint.weak),\n badgeBackground: accentColor(\"warning\", accentTint.weak),\n border: accentColor(\"warning\", accentTint.border),\n },\n attention: {\n foreground: accentColor(\"attention\"),\n background: accentColor(\"attention\", accentTint.weak),\n badgeBackground: accentColor(\"attention\", accentTint.weak),\n border: accentColor(\"attention\", accentTint.border),\n },\n danger: {\n foreground: themeColor(\"danger\"),\n background: themeColor(\"danger\", accentTint.weak),\n badgeBackground: themeColor(\"danger\", accentTint.weak),\n border: themeColor(\"danger\", accentTint.border),\n },\n },\n interaction: {\n hover: themeColor(\"text\", stateLayer.hover),\n focus: themeColor(\"contentBrand\", stateLayer.focus),\n pressed: themeColor(\"text\", stateLayer.pressed),\n selected: themeColor(\"primary\", stateLayer.selected),\n /**\n * Lower edge of a raised object, composited over its own fill so one shade\n * darkens any brand color (BottomNavigation raised tile, Spint 2026-10-13).\n */\n depth: themeColor(\"text\", 0.45),\n },\n} as const;\n"]}
1
+ {"version":3,"file":"semantic-colors.js","sourceRoot":"","sources":["../src/semantic-colors.ts"],"names":[],"mappings":"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","sourcesContent":["import { stateLayer } from \"./foundations.js\";\nimport { accentTint } from \"./colors.js\";\nimport { accentColor, themeColor } from \"./color-references.js\";\n\n/**\n * Stable role aliases above the legacy ThemeColors key set. New recipes should\n * prefer these roles so the underlying theme shape can evolve independently.\n */\nexport const semanticColors = {\n canvas: themeColor(\"bg\"),\n surface: {\n default: themeColor(\"surface\"),\n sunken: themeColor(\"surfaceAlt\"),\n raised: themeColor(\"bg\"),\n brand: themeColor(\"surfaceAccent\"),\n // Opaque neutral status plates are surfaces, independent of notice severity.\n statusNeutral: themeColor(\"neutralFill\"),\n statusStrong: themeColor(\"strongFill\"),\n /**\n * Loading placeholder fill (Skeleton, ListRow `loading`). A translucent text tint instead of an opaque\n * neutral: it darkens/lightens whatever it sits on, so it stays visible on canvas, Card surfaces and\n * profile palettes whose `surfaceAlt` is as light as `bg` (2026-10-17 Skeleton review, skeleton.md 근거).\n */\n placeholder: themeColor(\"text\", 0.12),\n },\n // 2026-10-23: a neutral selection must not recolor brand surfaces or primary actions.\n selection: {\n background: themeColor(\"surfaceSelected\"),\n content: themeColor(\"text\"),\n border: themeColor(\"borderControl\"),\n },\n content: {\n primary: themeColor(\"text\"),\n body: themeColor(\"textBody\"),\n secondary: themeColor(\"textMuted\"),\n tertiary: themeColor(\"textSub\"),\n decorative: themeColor(\"textWeak\"),\n brand: themeColor(\"contentBrand\"),\n danger: themeColor(\"danger\"),\n inverse: themeColor(\"onPrimary\"),\n // Status fills are independent of a product's primary button contrast pair.\n statusInverse: themeColor(\"onStatusFill\"),\n },\n border: {\n subtle: themeColor(\"border\", 0.7),\n default: themeColor(\"border\"),\n /** Resting outline of an interactive control. */\n control: themeColor(\"borderControl\"),\n strong: themeColor(\"textWeak\"),\n focus: themeColor(\"contentBrand\"),\n danger: themeColor(\"danger\"),\n },\n action: {\n brand: {\n background: themeColor(\"primary\"),\n content: themeColor(\"onPrimary\"),\n },\n neutral: {\n background: themeColor(\"bg\"),\n content: themeColor(\"text\"),\n // A control outline is a border role. `textSub` made a resting neutral\n // action read heavier than the selected control beside it.\n border: themeColor(\"borderControl\"),\n },\n danger: {\n background: themeColor(\"dangerFill\"),\n content: themeColor(\"onDanger\"),\n },\n },\n feedback: {\n info: {\n foreground: accentColor(\"info\"),\n background: accentColor(\"info\", accentTint.weak),\n badgeBackground: accentColor(\"info\", accentTint.weak),\n border: accentColor(\"info\", accentTint.border),\n },\n success: {\n foreground: accentColor(\"success\"),\n background: accentColor(\"success\", accentTint.weak),\n badgeBackground: accentColor(\"success\", accentTint.weak),\n border: accentColor(\"success\", accentTint.border),\n },\n warning: {\n foreground: accentColor(\"warning\"),\n background: accentColor(\"warning\", accentTint.weak),\n badgeBackground: accentColor(\"warning\", accentTint.weak),\n border: accentColor(\"warning\", accentTint.border),\n },\n attention: {\n foreground: accentColor(\"attention\"),\n background: accentColor(\"attention\", accentTint.weak),\n badgeBackground: accentColor(\"attention\", accentTint.weak),\n border: accentColor(\"attention\", accentTint.border),\n },\n danger: {\n foreground: themeColor(\"danger\"),\n background: themeColor(\"danger\", accentTint.weak),\n badgeBackground: themeColor(\"danger\", accentTint.weak),\n border: themeColor(\"danger\", accentTint.border),\n },\n },\n interaction: {\n hover: themeColor(\"text\", stateLayer.hover),\n focus: themeColor(\"contentBrand\", stateLayer.focus),\n pressed: themeColor(\"text\", stateLayer.pressed),\n selected: themeColor(\"surfaceSelected\"),\n /**\n * Lower edge of a raised object, composited over its own fill so one shade\n * darkens any brand color (BottomNavigation raised tile, Spint 2026-10-13).\n */\n depth: themeColor(\"text\", 0.45),\n },\n} as const;\n"]}
package/dist/sidebar.d.ts CHANGED
@@ -111,12 +111,12 @@ export declare const sidebarRecipe: {
111
111
  };
112
112
  readonly selectedBackground: Readonly<{
113
113
  source: "theme";
114
- key: "bg";
114
+ key: "surfaceSelected";
115
115
  alpha?: number;
116
116
  }>;
117
117
  readonly selectedIndicator: Readonly<{
118
118
  source: "theme";
119
- key: "contentBrand";
119
+ key: "text";
120
120
  alpha?: number;
121
121
  }>;
122
122
  readonly danger: Readonly<{
@@ -17,6 +17,8 @@ export type TimePickerOptions = Readonly<{
17
17
  labels: TimePickerLabels;
18
18
  /** Inline changes update the parent's draft; its enclosing form owns confirmation. */
19
19
  presentation?: "field" | "inline";
20
+ /** Keep tap-list behavior by default; wheel selection shares the same draft and confirmation. */
21
+ selectionStyle?: "list" | "wheel";
20
22
  minuteStep?: number;
21
23
  isTimeUnavailable?: (value: string) => boolean;
22
24
  disabled?: boolean;
@@ -33,12 +35,22 @@ export declare const timePickerRecipe: {
33
35
  readonly readoutSize: 20;
34
36
  readonly readoutLineHeight: 28;
35
37
  readonly panelWidth: 304;
38
+ readonly selectionStroke: 1;
36
39
  readonly defaults: {
37
40
  readonly presentation: "field";
41
+ readonly selectionStyle: "list";
38
42
  readonly minuteStep: 1;
39
43
  };
40
44
  };
41
45
  export declare function validateTimePicker(options: TimePickerOptions): void;
46
+ /** Offset 0 centers the first row because both renderers add half a viewport of end padding. */
47
+ export declare function resolveTimePickerWheelIndex(items: readonly Readonly<{
48
+ disabled: boolean;
49
+ }>[], offset: number): number | null;
50
+ /** Keyboard and native adjustable actions use the same available slots, without wrapping. */
51
+ export declare function moveTimePickerWheelIndex(items: readonly Readonly<{
52
+ disabled: boolean;
53
+ }>[], from: number, direction: 1 | -1): number | null;
42
54
  export declare function timePickerDraft(value: string | null): TimePickerDraft;
43
55
  export declare function resolveTimePickerValue(draft: TimePickerDraft, options: Pick<TimePickerOptions, "minuteStep" | "isTimeUnavailable">): string | null;
44
56
  export declare function timePickerItems(part: TimePickerPart, draft: TimePickerDraft, options: Pick<TimePickerOptions, "minuteStep" | "isTimeUnavailable">): {
@@ -47,7 +59,7 @@ export declare function timePickerItems(part: TimePickerPart, draft: TimePickerD
47
59
  }[];
48
60
  export declare const timePickerBehavior: {
49
61
  readonly controlled: readonly ["value", "onValueChange"];
50
- readonly inputs: readonly ["labels", "minuteStep", "isTimeUnavailable", "presentation"];
62
+ readonly inputs: readonly ["labels", "minuteStep", "isTimeUnavailable", "presentation", "selectionStyle"];
51
63
  readonly stateAxes: {
52
64
  readonly availability: readonly ["enabled", "disabled", "readOnly"];
53
65
  readonly value: readonly ["empty", "partial", "selected"];
@@ -60,11 +72,11 @@ export declare const timePickerBehavior: {
60
72
  readonly dismiss: readonly ["action", "escape", "outside", "blur"];
61
73
  };
62
74
  readonly native: {
63
- readonly roles: readonly ["button", "dialog", "radio"];
75
+ readonly roles: readonly ["button", "dialog", "radio", "adjustable"];
64
76
  readonly states: readonly ["disabled", "expanded", "checked"];
65
- readonly actions: readonly ["activate", "dismiss"];
77
+ readonly actions: readonly ["activate", "dismiss", "increment", "decrement"];
66
78
  readonly dismiss: readonly ["action", "back", "outside"];
67
79
  };
68
- readonly scenarios: readonly ["two-columns-share-one-draft-and-overlay", "column-selection-never-dismisses", "confirm-commits-a-complete-available-HH:mm", "cancel-discards-edits", "external-value-reconciles-draft", "inline-parent-owns-confirmation", "unavailable-options-never-activate", "midnight-is-a-valid-value"];
80
+ readonly scenarios: readonly ["two-columns-share-one-draft-and-overlay", "column-selection-never-dismisses", "confirm-commits-a-complete-available-HH:mm", "cancel-discards-edits", "external-value-reconciles-draft", "inline-parent-owns-confirmation", "unavailable-options-never-activate", "midnight-is-a-valid-value", "wheel-centers-available-slots-without-initial-commit", "wheel-adjustment-preserves-confirm-and-cancel"];
69
81
  };
70
82
  //# sourceMappingURL=time-picker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"time-picker.d.ts","sourceRoot":"","sources":["../src/time-picker.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;AAC/C,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AACvF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CACnG,CAAC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,YAAY,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAKH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;CAInB,CAAC;AAEX,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAMnE;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,eAAe,CAErE;AACD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,mBAAmB,CAAC,GAAG,MAAM,GAAG,IAAI,CAKlJ;AACD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,mBAAmB,CAAC;;;IAUjJ;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;CAMM,CAAC"}
1
+ {"version":3,"file":"time-picker.d.ts","sourceRoot":"","sources":["../src/time-picker.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;AAC/C,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AACvF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CACnG,CAAC,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1C,MAAM,EAAE,gBAAgB,CAAC;IACzB,sFAAsF;IACtF,YAAY,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAClC,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC,CAAC;AAKH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;CAKnB,CAAC;AAEX,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAOnE;AAED,gGAAgG;AAChG,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,SAAS,QAAQ,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5H;AAED,6FAA6F;AAC7F,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,SAAS,QAAQ,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAK1I;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,eAAe,CAErE;AACD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,mBAAmB,CAAC,GAAG,MAAM,GAAG,IAAI,CAKlJ;AACD,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,mBAAmB,CAAC;;;IAUjJ;AAED,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;CAMM,CAAC"}
@@ -1,11 +1,12 @@
1
- import { control, spacing, typography } from "./foundations.js";
1
+ import { control, spacing, stroke, typography } from "./foundations.js";
2
2
  // A single two-column session replaces independently closing Select overlays (2026-10-19).
3
3
  // Keep the five 44px selection rows, but use a compact readout: the 40px summary exceeded the shared
4
4
  // 24px cap and dominated the mobile departure Sheet (2026-10-20 review; APP_VISUAL_QUALITY_STANDARD §4).
5
5
  export const timePickerRecipe = {
6
6
  rowHeight: control.minTouchTarget, visibleRows: 5, gap: spacing.sm, sectionGap: spacing.md,
7
7
  radius: "md", readoutSize: typography.titleLarge.fontSize, readoutLineHeight: typography.titleLarge.lineHeight, panelWidth: 304,
8
- defaults: { presentation: "field", minuteStep: 1 },
8
+ selectionStroke: stroke.default,
9
+ defaults: { presentation: "field", selectionStyle: "list", minuteStep: 1 },
9
10
  };
10
11
  export function validateTimePicker(options) {
11
12
  if (options.value !== null && !/^([01]\d|2[0-3]):[0-5]\d$/.test(options.value))
@@ -18,6 +19,26 @@ export function validateTimePicker(options) {
18
19
  throw new TypeError("TimePicker labels must not be empty");
19
20
  if (options.presentation !== undefined && options.presentation !== "field" && options.presentation !== "inline")
20
21
  throw new TypeError("Invalid TimePicker presentation");
22
+ if (options.selectionStyle !== undefined && options.selectionStyle !== "list" && options.selectionStyle !== "wheel")
23
+ throw new TypeError("Invalid TimePicker selectionStyle");
24
+ }
25
+ /** Offset 0 centers the first row because both renderers add half a viewport of end padding. */
26
+ export function resolveTimePickerWheelIndex(items, offset) {
27
+ const position = Math.max(0, offset) / timePickerRecipe.rowHeight;
28
+ let nearest = null;
29
+ for (let index = 0; index < items.length; index++) {
30
+ if (!items[index].disabled && (nearest === null || Math.abs(index - position) < Math.abs(nearest - position)))
31
+ nearest = index;
32
+ }
33
+ return nearest;
34
+ }
35
+ /** Keyboard and native adjustable actions use the same available slots, without wrapping. */
36
+ export function moveTimePickerWheelIndex(items, from, direction) {
37
+ for (let index = from < 0 ? (direction === 1 ? 0 : items.length - 1) : from + direction; index >= 0 && index < items.length; index += direction) {
38
+ if (!items[index].disabled)
39
+ return index;
40
+ }
41
+ return null;
21
42
  }
22
43
  export function timePickerDraft(value) {
23
44
  return { hour: value?.slice(0, 2) ?? null, minute: value?.slice(3, 5) ?? null };
@@ -42,10 +63,10 @@ export function timePickerItems(part, draft, options) {
42
63
  });
43
64
  }
44
65
  export const timePickerBehavior = {
45
- controlled: ["value", "onValueChange"], inputs: ["labels", "minuteStep", "isTimeUnavailable", "presentation"],
66
+ controlled: ["value", "onValueChange"], inputs: ["labels", "minuteStep", "isTimeUnavailable", "presentation", "selectionStyle"],
46
67
  stateAxes: { availability: ["enabled", "disabled", "readOnly"], value: ["empty", "partial", "selected"], interaction: ["closed", "editing"] },
47
68
  web: { roles: ["button", "dialog", "listbox", "option"], keyboard: ["Tab", "Enter", "Space", "Escape", "ArrowUp", "ArrowDown", "Home", "End"], focus: "roving", dismiss: ["action", "escape", "outside", "blur"] },
48
- native: { roles: ["button", "dialog", "radio"], states: ["disabled", "expanded", "checked"], actions: ["activate", "dismiss"], dismiss: ["action", "back", "outside"] },
49
- scenarios: ["two-columns-share-one-draft-and-overlay", "column-selection-never-dismisses", "confirm-commits-a-complete-available-HH:mm", "cancel-discards-edits", "external-value-reconciles-draft", "inline-parent-owns-confirmation", "unavailable-options-never-activate", "midnight-is-a-valid-value"],
69
+ native: { roles: ["button", "dialog", "radio", "adjustable"], states: ["disabled", "expanded", "checked"], actions: ["activate", "dismiss", "increment", "decrement"], dismiss: ["action", "back", "outside"] },
70
+ scenarios: ["two-columns-share-one-draft-and-overlay", "column-selection-never-dismisses", "confirm-commits-a-complete-available-HH:mm", "cancel-discards-edits", "external-value-reconciles-draft", "inline-parent-owns-confirmation", "unavailable-options-never-activate", "midnight-is-a-valid-value", "wheel-centers-available-slots-without-initial-commit", "wheel-adjustment-preserves-confirm-and-cancel"],
50
71
  };
51
72
  //# sourceMappingURL=time-picker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"time-picker.js","sourceRoot":"","sources":["../src/time-picker.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport { control, spacing, typography } from \"./foundations.js\";\n\nexport type TimePickerPart = \"hour\" | \"minute\";\nexport type TimePickerDraft = Readonly<{ hour: string | null; minute: string | null }>;\nexport type TimePickerLabels = Readonly<{\n label: string; hour: string; minute: string; placeholder: string; confirm: string; cancel: string;\n}>;\nexport type TimePickerOptions = Readonly<{\n value: string | null;\n onValueChange(value: string | null): void;\n labels: TimePickerLabels;\n /** Inline changes update the parent's draft; its enclosing form owns confirmation. */\n presentation?: \"field\" | \"inline\";\n minuteStep?: number;\n isTimeUnavailable?: (value: string) => boolean;\n disabled?: boolean;\n readOnly?: boolean;\n description?: string;\n error?: string;\n}>;\n\n// A single two-column session replaces independently closing Select overlays (2026-10-19).\n// Keep the five 44px selection rows, but use a compact readout: the 40px summary exceeded the shared\n// 24px cap and dominated the mobile departure Sheet (2026-10-20 review; APP_VISUAL_QUALITY_STANDARD §4).\nexport const timePickerRecipe = {\n rowHeight: control.minTouchTarget, visibleRows: 5, gap: spacing.sm, sectionGap: spacing.md,\n radius: \"md\", readoutSize: typography.titleLarge.fontSize, readoutLineHeight: typography.titleLarge.lineHeight, panelWidth: 304,\n defaults: { presentation: \"field\", minuteStep: 1 },\n} as const;\n\nexport function validateTimePicker(options: TimePickerOptions): void {\n if (options.value !== null && !/^([01]\\d|2[0-3]):[0-5]\\d$/.test(options.value)) throw new TypeError(\"TimePicker value must be HH:mm or null\");\n const step = options.minuteStep ?? 1;\n if (!Number.isInteger(step) || step < 1 || step > 60 || 60 % step !== 0) throw new RangeError(\"TimePicker minuteStep must divide 60\");\n for (const label of Object.values(options.labels)) if (!label.trim()) throw new TypeError(\"TimePicker labels must not be empty\");\n if (options.presentation !== undefined && options.presentation !== \"field\" && options.presentation !== \"inline\") throw new TypeError(\"Invalid TimePicker presentation\");\n}\nexport function timePickerDraft(value: string | null): TimePickerDraft {\n return { hour: value?.slice(0, 2) ?? null, minute: value?.slice(3, 5) ?? null };\n}\nexport function resolveTimePickerValue(draft: TimePickerDraft, options: Pick<TimePickerOptions, \"minuteStep\" | \"isTimeUnavailable\">): string | null {\n if (draft.hour === null || draft.minute === null) return null;\n const value = `${draft.hour}:${draft.minute}`;\n if (!/^([01]\\d|2[0-3]):[0-5]\\d$/.test(value) || Number(draft.minute) % (options.minuteStep ?? 1) !== 0 || options.isTimeUnavailable?.(value)) return null;\n return value;\n}\nexport function timePickerItems(part: TimePickerPart, draft: TimePickerDraft, options: Pick<TimePickerOptions, \"minuteStep\" | \"isTimeUnavailable\">) {\n const step = options.minuteStep ?? 1;\n return Array.from({ length: part === \"hour\" ? 24 : 60 / step }, (_, index) => {\n const value = String(part === \"hour\" ? index : index * step).padStart(2, \"0\");\n // An hour remains available if any of its minute slots is valid. Never silently replace the minute.\n const disabled = part === \"hour\"\n ? !Array.from({ length: 60 / step }, (_, i) => `${value}:${String(i * step).padStart(2, \"0\")}`).some(time => !options.isTimeUnavailable?.(time))\n : draft.hour !== null && !!options.isTimeUnavailable?.(`${draft.hour}:${value}`);\n return { value, disabled };\n });\n}\n\nexport const timePickerBehavior = {\n controlled: [\"value\", \"onValueChange\"], inputs: [\"labels\", \"minuteStep\", \"isTimeUnavailable\", \"presentation\"],\n stateAxes: { availability: [\"enabled\", \"disabled\", \"readOnly\"], value: [\"empty\", \"partial\", \"selected\"], interaction: [\"closed\", \"editing\"] },\n web: { roles: [\"button\", \"dialog\", \"listbox\", \"option\"], keyboard: [\"Tab\", \"Enter\", \"Space\", \"Escape\", \"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"], focus: \"roving\", dismiss: [\"action\", \"escape\", \"outside\", \"blur\"] },\n native: { roles: [\"button\", \"dialog\", \"radio\"], states: [\"disabled\", \"expanded\", \"checked\"], actions: [\"activate\", \"dismiss\"], dismiss: [\"action\", \"back\", \"outside\"] },\n scenarios: [\"two-columns-share-one-draft-and-overlay\", \"column-selection-never-dismisses\", \"confirm-commits-a-complete-available-HH:mm\", \"cancel-discards-edits\", \"external-value-reconciles-draft\", \"inline-parent-owns-confirmation\", \"unavailable-options-never-activate\", \"midnight-is-a-valid-value\"],\n} as const satisfies BehaviorContract;\n"]}
1
+ {"version":3,"file":"time-picker.js","sourceRoot":"","sources":["../src/time-picker.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport { control, spacing, stroke, typography } from \"./foundations.js\";\n\nexport type TimePickerPart = \"hour\" | \"minute\";\nexport type TimePickerDraft = Readonly<{ hour: string | null; minute: string | null }>;\nexport type TimePickerLabels = Readonly<{\n label: string; hour: string; minute: string; placeholder: string; confirm: string; cancel: string;\n}>;\nexport type TimePickerOptions = Readonly<{\n value: string | null;\n onValueChange(value: string | null): void;\n labels: TimePickerLabels;\n /** Inline changes update the parent's draft; its enclosing form owns confirmation. */\n presentation?: \"field\" | \"inline\";\n /** Keep tap-list behavior by default; wheel selection shares the same draft and confirmation. */\n selectionStyle?: \"list\" | \"wheel\";\n minuteStep?: number;\n isTimeUnavailable?: (value: string) => boolean;\n disabled?: boolean;\n readOnly?: boolean;\n description?: string;\n error?: string;\n}>;\n\n// A single two-column session replaces independently closing Select overlays (2026-10-19).\n// Keep the five 44px selection rows, but use a compact readout: the 40px summary exceeded the shared\n// 24px cap and dominated the mobile departure Sheet (2026-10-20 review; APP_VISUAL_QUALITY_STANDARD §4).\nexport const timePickerRecipe = {\n rowHeight: control.minTouchTarget, visibleRows: 5, gap: spacing.sm, sectionGap: spacing.md,\n radius: \"md\", readoutSize: typography.titleLarge.fontSize, readoutLineHeight: typography.titleLarge.lineHeight, panelWidth: 304,\n selectionStroke: stroke.default,\n defaults: { presentation: \"field\", selectionStyle: \"list\", minuteStep: 1 },\n} as const;\n\nexport function validateTimePicker(options: TimePickerOptions): void {\n if (options.value !== null && !/^([01]\\d|2[0-3]):[0-5]\\d$/.test(options.value)) throw new TypeError(\"TimePicker value must be HH:mm or null\");\n const step = options.minuteStep ?? 1;\n if (!Number.isInteger(step) || step < 1 || step > 60 || 60 % step !== 0) throw new RangeError(\"TimePicker minuteStep must divide 60\");\n for (const label of Object.values(options.labels)) if (!label.trim()) throw new TypeError(\"TimePicker labels must not be empty\");\n if (options.presentation !== undefined && options.presentation !== \"field\" && options.presentation !== \"inline\") throw new TypeError(\"Invalid TimePicker presentation\");\n if (options.selectionStyle !== undefined && options.selectionStyle !== \"list\" && options.selectionStyle !== \"wheel\") throw new TypeError(\"Invalid TimePicker selectionStyle\");\n}\n\n/** Offset 0 centers the first row because both renderers add half a viewport of end padding. */\nexport function resolveTimePickerWheelIndex(items: readonly Readonly<{ disabled: boolean }>[], offset: number): number | null {\n const position = Math.max(0, offset) / timePickerRecipe.rowHeight;\n let nearest: number | null = null;\n for (let index = 0; index < items.length; index++) {\n if (!items[index]!.disabled && (nearest === null || Math.abs(index - position) < Math.abs(nearest - position))) nearest = index;\n }\n return nearest;\n}\n\n/** Keyboard and native adjustable actions use the same available slots, without wrapping. */\nexport function moveTimePickerWheelIndex(items: readonly Readonly<{ disabled: boolean }>[], from: number, direction: 1 | -1): number | null {\n for (let index = from < 0 ? (direction === 1 ? 0 : items.length - 1) : from + direction; index >= 0 && index < items.length; index += direction) {\n if (!items[index]!.disabled) return index;\n }\n return null;\n}\nexport function timePickerDraft(value: string | null): TimePickerDraft {\n return { hour: value?.slice(0, 2) ?? null, minute: value?.slice(3, 5) ?? null };\n}\nexport function resolveTimePickerValue(draft: TimePickerDraft, options: Pick<TimePickerOptions, \"minuteStep\" | \"isTimeUnavailable\">): string | null {\n if (draft.hour === null || draft.minute === null) return null;\n const value = `${draft.hour}:${draft.minute}`;\n if (!/^([01]\\d|2[0-3]):[0-5]\\d$/.test(value) || Number(draft.minute) % (options.minuteStep ?? 1) !== 0 || options.isTimeUnavailable?.(value)) return null;\n return value;\n}\nexport function timePickerItems(part: TimePickerPart, draft: TimePickerDraft, options: Pick<TimePickerOptions, \"minuteStep\" | \"isTimeUnavailable\">) {\n const step = options.minuteStep ?? 1;\n return Array.from({ length: part === \"hour\" ? 24 : 60 / step }, (_, index) => {\n const value = String(part === \"hour\" ? index : index * step).padStart(2, \"0\");\n // An hour remains available if any of its minute slots is valid. Never silently replace the minute.\n const disabled = part === \"hour\"\n ? !Array.from({ length: 60 / step }, (_, i) => `${value}:${String(i * step).padStart(2, \"0\")}`).some(time => !options.isTimeUnavailable?.(time))\n : draft.hour !== null && !!options.isTimeUnavailable?.(`${draft.hour}:${value}`);\n return { value, disabled };\n });\n}\n\nexport const timePickerBehavior = {\n controlled: [\"value\", \"onValueChange\"], inputs: [\"labels\", \"minuteStep\", \"isTimeUnavailable\", \"presentation\", \"selectionStyle\"],\n stateAxes: { availability: [\"enabled\", \"disabled\", \"readOnly\"], value: [\"empty\", \"partial\", \"selected\"], interaction: [\"closed\", \"editing\"] },\n web: { roles: [\"button\", \"dialog\", \"listbox\", \"option\"], keyboard: [\"Tab\", \"Enter\", \"Space\", \"Escape\", \"ArrowUp\", \"ArrowDown\", \"Home\", \"End\"], focus: \"roving\", dismiss: [\"action\", \"escape\", \"outside\", \"blur\"] },\n native: { roles: [\"button\", \"dialog\", \"radio\", \"adjustable\"], states: [\"disabled\", \"expanded\", \"checked\"], actions: [\"activate\", \"dismiss\", \"increment\", \"decrement\"], dismiss: [\"action\", \"back\", \"outside\"] },\n scenarios: [\"two-columns-share-one-draft-and-overlay\", \"column-selection-never-dismisses\", \"confirm-commits-a-complete-available-HH:mm\", \"cancel-discards-edits\", \"external-value-reconciles-draft\", \"inline-parent-owns-confirmation\", \"unavailable-options-never-activate\", \"midnight-is-a-valid-value\", \"wheel-centers-available-slots-without-initial-commit\", \"wheel-adjustment-preserves-confirm-and-cancel\"],\n} as const satisfies BehaviorContract;\n"]}
@@ -62,17 +62,17 @@ export declare const toggleGroupRecipe: {
62
62
  readonly pressed: {
63
63
  readonly background: Readonly<{
64
64
  source: "theme";
65
- key: "bg";
65
+ key: "surfaceSelected";
66
66
  alpha?: number;
67
67
  }>;
68
68
  readonly color: Readonly<{
69
69
  source: "theme";
70
- key: "contentBrand";
70
+ key: "text";
71
71
  alpha?: number;
72
72
  }>;
73
73
  readonly border: Readonly<{
74
74
  source: "theme";
75
- key: "contentBrand";
75
+ key: "borderControl";
76
76
  alpha?: number;
77
77
  }>;
78
78
  };
@@ -46,9 +46,9 @@ export const toggleGroupRecipe = {
46
46
  border: semanticColors.border.control,
47
47
  },
48
48
  pressed: {
49
- background: semanticColors.canvas,
50
- color: semanticColors.content.brand,
51
- border: semanticColors.border.focus,
49
+ background: semanticColors.selection.background,
50
+ color: semanticColors.selection.content,
51
+ border: semanticColors.selection.border,
52
52
  },
53
53
  states: { focus: focusIndicatorContract },
54
54
  };
@@ -1 +1 @@
1
- {"version":3,"file":"toggle-group.js","sourceRoot":"","sources":["../src/toggle-group.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, radius, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport {\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n type CheckboxSelectionItem,\n} from \"./selection-helpers.js\";\n\n/**\n * 여러 개를 동시에 켜고 끄는 버튼 묶음이다 — 굵게/기울임/밑줄, 필터 칩 여러 개처럼.\n *\n * `SegmentedControl`과 경계가 분명하다. Segmented는 **하나를 고른다**(어떤 화면을 볼지),\n * ToggleGroup은 **여러 개를 켠다**(무엇을 적용할지). 그래서 Segmented에는 \"선택 없음\"이\n * 없고 ToggleGroup에는 있다. 둘을 한 컴포넌트의 `mode` 축으로 합치면 그 차이가 사라지고,\n * \"아무것도 선택되지 않은 Segmented\"라는 표현 불가능한 상태가 타입에 생긴다.\n *\n * `CheckboxGroup`과도 다르다. CheckboxGroup은 목록 안의 동의/선택이라 각 항목이 자기\n * 줄과 설명을 갖는다. ToggleGroup은 도구 모음이라 한 줄에 붙어 있고 라벨이 짧다.\n * 판정(무엇을 켤 수 있고 비활성은 어떻게 되는가)은 같은 helper를 공유한다.\n */\nexport type ToggleGroupItemDescriptor<Id extends string = string> =\n CheckboxSelectionItem<Id>;\n\nexport type ToggleGroupDescriptor<Id extends string = string> = Readonly<{\n /** 묶음 전체의 접근성 이름. 개별 버튼 이름으로 대체할 수 없다. */\n accessibilityLabel: string;\n items: readonly ToggleGroupItemDescriptor<Id>[];\n}>;\n\nexport type ToggleGroupSize = \"small\" | \"medium\";\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`ToggleGroup ${field} must not be empty`);\n }\n}\n\nexport function validateToggleGroupDescriptor<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.items) || descriptor.items.length === 0) {\n throw new RangeError(\"ToggleGroup must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, \"item label\");\n if (ids.has(item.id)) throw new TypeError(`Duplicate ToggleGroup item id: ${item.id}`);\n ids.add(item.id);\n }\n}\n\n/** 하나를 켜고 끈다. 비활성 보호는 공용 helper가 이미 갖고 있다. */\nexport function toggleGroupSelection<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n pressedIds: ReadonlySet<Id>,\n id: Id,\n): ReadonlySet<Id> {\n validateToggleGroupDescriptor(descriptor);\n return toggleCheckboxSelection(descriptor.items, pressedIds, id);\n}\n\n/** 목록에서 사라진 항목의 눌림 상태는 남기지 않는다. */\nexport function reconcileToggleGroupSelection<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n pressedIds: ReadonlySet<Id>,\n): ReadonlySet<Id> {\n validateToggleGroupDescriptor(descriptor);\n return reconcileCheckboxSelection(descriptor.items, pressedIds);\n}\n\nexport const toggleGroupRecipe = {\n slots: [\"root\", \"item\", \"label\"] as const,\n defaults: { size: \"medium\" as ToggleGroupSize },\n sizes: {\n small: { minHeight: control.minTouchTarget, paddingHorizontal: spacing.sm, textVariant: \"label\" as const },\n medium: { minHeight: 44, paddingHorizontal: spacing.md, textVariant: \"body\" as const },\n },\n radius: radius.md,\n gap: spacing.xxs,\n idle: {\n background: semanticColors.canvas,\n color: semanticColors.content.body,\n border: semanticColors.border.control,\n },\n pressed: {\n background: semanticColors.canvas,\n color: semanticColors.content.brand,\n border: semanticColors.border.focus,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"item\", \"label\"];\n defaults: { size: ToggleGroupSize };\n sizes: Record<ToggleGroupSize, { minHeight: number; paddingHorizontal: number; textVariant: \"label\" | \"body\" }>;\n radius: number;\n gap: number;\n idle: { background: ColorReference; color: ColorReference; border: ColorReference };\n pressed: { background: ColorReference; color: ColorReference; border: ColorReference };\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const toggleGroupBehavior = {\n controlled: [\"pressedIds\", \"defaultPressedIds\", \"onPressedIdsChange\"],\n inputs: [\"items\", \"accessibilityLabel\", \"size\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"pressed\", \"unpressed\"],\n },\n web: {\n roles: [\"group\", \"button\"],\n keyboard: [\"Tab\", \"Enter\", \"Space\"],\n /** Toolbar-style roving focus is not used: each toggle is its own tab stop, like a row of checkboxes. */\n focus: \"native\",\n },\n native: { roles: [\"button\"], states: [\"selected\", \"disabled\"], actions: [\"toggle\"] },\n scenarios: [\n \"several-items-can-be-pressed-at-once-and-none-is-a-valid-state\",\n \"pressed-state-rides-on-aria-pressed-not-on-color-alone\",\n \"a-disabled-item-never-toggles-and-never-enters-the-pressed-set\",\n \"removing-an-item-drops-its-pressed-state-instead-of-keeping-an-orphan-id\",\n \"single-choice-belongs-to-segmented-control-this-contract-never-adds-a-single-mode\",\n \"the-group-carries-its-own-accessible-name-separate-from-each-buttons-name\",\n ],\n} as const satisfies BehaviorContract;\n"]}
1
+ {"version":3,"file":"toggle-group.js","sourceRoot":"","sources":["../src/toggle-group.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { focusIndicatorContract } from \"./component-contracts.js\";\nimport { control, radius, spacing } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport {\n reconcileCheckboxSelection,\n toggleCheckboxSelection,\n type CheckboxSelectionItem,\n} from \"./selection-helpers.js\";\n\n/**\n * 여러 개를 동시에 켜고 끄는 버튼 묶음이다 — 굵게/기울임/밑줄, 필터 칩 여러 개처럼.\n *\n * `SegmentedControl`과 경계가 분명하다. Segmented는 **하나를 고른다**(어떤 화면을 볼지),\n * ToggleGroup은 **여러 개를 켠다**(무엇을 적용할지). 그래서 Segmented에는 \"선택 없음\"이\n * 없고 ToggleGroup에는 있다. 둘을 한 컴포넌트의 `mode` 축으로 합치면 그 차이가 사라지고,\n * \"아무것도 선택되지 않은 Segmented\"라는 표현 불가능한 상태가 타입에 생긴다.\n *\n * `CheckboxGroup`과도 다르다. CheckboxGroup은 목록 안의 동의/선택이라 각 항목이 자기\n * 줄과 설명을 갖는다. ToggleGroup은 도구 모음이라 한 줄에 붙어 있고 라벨이 짧다.\n * 판정(무엇을 켤 수 있고 비활성은 어떻게 되는가)은 같은 helper를 공유한다.\n */\nexport type ToggleGroupItemDescriptor<Id extends string = string> =\n CheckboxSelectionItem<Id>;\n\nexport type ToggleGroupDescriptor<Id extends string = string> = Readonly<{\n /** 묶음 전체의 접근성 이름. 개별 버튼 이름으로 대체할 수 없다. */\n accessibilityLabel: string;\n items: readonly ToggleGroupItemDescriptor<Id>[];\n}>;\n\nexport type ToggleGroupSize = \"small\" | \"medium\";\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`ToggleGroup ${field} must not be empty`);\n }\n}\n\nexport function validateToggleGroupDescriptor<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.items) || descriptor.items.length === 0) {\n throw new RangeError(\"ToggleGroup must contain at least one item\");\n }\n const ids = new Set<Id>();\n for (const item of descriptor.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, \"item label\");\n if (ids.has(item.id)) throw new TypeError(`Duplicate ToggleGroup item id: ${item.id}`);\n ids.add(item.id);\n }\n}\n\n/** 하나를 켜고 끈다. 비활성 보호는 공용 helper가 이미 갖고 있다. */\nexport function toggleGroupSelection<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n pressedIds: ReadonlySet<Id>,\n id: Id,\n): ReadonlySet<Id> {\n validateToggleGroupDescriptor(descriptor);\n return toggleCheckboxSelection(descriptor.items, pressedIds, id);\n}\n\n/** 목록에서 사라진 항목의 눌림 상태는 남기지 않는다. */\nexport function reconcileToggleGroupSelection<Id extends string>(\n descriptor: ToggleGroupDescriptor<Id>,\n pressedIds: ReadonlySet<Id>,\n): ReadonlySet<Id> {\n validateToggleGroupDescriptor(descriptor);\n return reconcileCheckboxSelection(descriptor.items, pressedIds);\n}\n\nexport const toggleGroupRecipe = {\n slots: [\"root\", \"item\", \"label\"] as const,\n defaults: { size: \"medium\" as ToggleGroupSize },\n sizes: {\n small: { minHeight: control.minTouchTarget, paddingHorizontal: spacing.sm, textVariant: \"label\" as const },\n medium: { minHeight: 44, paddingHorizontal: spacing.md, textVariant: \"body\" as const },\n },\n radius: radius.md,\n gap: spacing.xxs,\n idle: {\n background: semanticColors.canvas,\n color: semanticColors.content.body,\n border: semanticColors.border.control,\n },\n pressed: {\n background: semanticColors.selection.background,\n color: semanticColors.selection.content,\n border: semanticColors.selection.border,\n },\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"item\", \"label\"];\n defaults: { size: ToggleGroupSize };\n sizes: Record<ToggleGroupSize, { minHeight: number; paddingHorizontal: number; textVariant: \"label\" | \"body\" }>;\n radius: number;\n gap: number;\n idle: { background: ColorReference; color: ColorReference; border: ColorReference };\n pressed: { background: ColorReference; color: ColorReference; border: ColorReference };\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const toggleGroupBehavior = {\n controlled: [\"pressedIds\", \"defaultPressedIds\", \"onPressedIdsChange\"],\n inputs: [\"items\", \"accessibilityLabel\", \"size\"],\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"pressed\", \"unpressed\"],\n },\n web: {\n roles: [\"group\", \"button\"],\n keyboard: [\"Tab\", \"Enter\", \"Space\"],\n /** Toolbar-style roving focus is not used: each toggle is its own tab stop, like a row of checkboxes. */\n focus: \"native\",\n },\n native: { roles: [\"button\"], states: [\"selected\", \"disabled\"], actions: [\"toggle\"] },\n scenarios: [\n \"several-items-can-be-pressed-at-once-and-none-is-a-valid-state\",\n \"pressed-state-rides-on-aria-pressed-not-on-color-alone\",\n \"a-disabled-item-never-toggles-and-never-enters-the-pressed-set\",\n \"removing-an-item-drops-its-pressed-state-instead-of-keeping-an-orphan-id\",\n \"single-choice-belongs-to-segmented-control-this-contract-never-adds-a-single-mode\",\n \"the-group-carries-its-own-accessible-name-separate-from-each-buttons-name\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -138,12 +138,12 @@ export declare const transferListRecipe: {
138
138
  };
139
139
  readonly selectedBackground: Readonly<{
140
140
  source: "theme";
141
- key: "bg";
141
+ key: "surfaceSelected";
142
142
  alpha?: number;
143
143
  }>;
144
144
  readonly selectedIndicator: Readonly<{
145
145
  source: "theme";
146
- key: "contentBrand";
146
+ key: "text";
147
147
  alpha?: number;
148
148
  }>;
149
149
  readonly danger: Readonly<{
package/dist/tree.d.ts CHANGED
@@ -175,12 +175,12 @@ export declare const treeRecipe: {
175
175
  };
176
176
  readonly selectedBackground: Readonly<{
177
177
  source: "theme";
178
- key: "bg";
178
+ key: "surfaceSelected";
179
179
  alpha?: number;
180
180
  }>;
181
181
  readonly selectedIndicator: Readonly<{
182
182
  source: "theme";
183
- key: "contentBrand";
183
+ key: "text";
184
184
  alpha?: number;
185
185
  }>;
186
186
  readonly danger: Readonly<{
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Package release shown by documentation surfaces. Kept in sync by a test. */
2
- export declare const designSystemVersion: "1.40.0";
2
+ export declare const designSystemVersion: "1.41.0";
3
3
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Package release shown by documentation surfaces. Kept in sync by a test. */
2
- export const designSystemVersion = "1.40.0";
2
+ export const designSystemVersion = "1.41.0";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.40.0\" as const;\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAiB,CAAC","sourcesContent":["/** Package release shown by documentation surfaces. Kept in sync by a test. */\nexport const designSystemVersion = \"1.41.0\" as const;\n"]}