@hjmds/design-contracts 1.1.0 → 1.2.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 (198) hide show
  1. package/dist/agreement.d.ts +183 -0
  2. package/dist/agreement.d.ts.map +1 -0
  3. package/dist/agreement.js +137 -0
  4. package/dist/agreement.js.map +1 -0
  5. package/dist/alert-dialog.d.ts +1 -1
  6. package/dist/alert-dialog.d.ts.map +1 -1
  7. package/dist/alert-dialog.js.map +1 -1
  8. package/dist/anchor.d.ts +62 -0
  9. package/dist/anchor.d.ts.map +1 -0
  10. package/dist/anchor.js +43 -0
  11. package/dist/anchor.js.map +1 -0
  12. package/dist/asset.d.ts +94 -0
  13. package/dist/asset.d.ts.map +1 -0
  14. package/dist/asset.js +60 -0
  15. package/dist/asset.js.map +1 -0
  16. package/dist/base-recipes.d.ts +19 -0
  17. package/dist/base-recipes.d.ts.map +1 -1
  18. package/dist/base-recipes.js +17 -0
  19. package/dist/base-recipes.js.map +1 -1
  20. package/dist/behaviors.d.ts +346 -3
  21. package/dist/behaviors.d.ts.map +1 -1
  22. package/dist/behaviors.js +35 -1
  23. package/dist/behaviors.js.map +1 -1
  24. package/dist/bottom-info.d.ts +58 -0
  25. package/dist/bottom-info.d.ts.map +1 -0
  26. package/dist/bottom-info.js +48 -0
  27. package/dist/bottom-info.js.map +1 -0
  28. package/dist/carousel.d.ts +3 -3
  29. package/dist/carousel.js +1 -1
  30. package/dist/carousel.js.map +1 -1
  31. package/dist/catalog.d.ts +1294 -239
  32. package/dist/catalog.d.ts.map +1 -1
  33. package/dist/catalog.js +65 -25
  34. package/dist/catalog.js.map +1 -1
  35. package/dist/collapsible.d.ts +56 -0
  36. package/dist/collapsible.d.ts.map +1 -0
  37. package/dist/collapsible.js +37 -0
  38. package/dist/collapsible.js.map +1 -0
  39. package/dist/component-definitions.d.ts +16 -0
  40. package/dist/component-definitions.d.ts.map +1 -1
  41. package/dist/component-definitions.js +16 -0
  42. package/dist/component-definitions.js.map +1 -1
  43. package/dist/component-recipes.d.ts +76 -4
  44. package/dist/component-recipes.d.ts.map +1 -1
  45. package/dist/component-recipes.js +38 -1
  46. package/dist/component-recipes.js.map +1 -1
  47. package/dist/context-menu.d.ts +53 -0
  48. package/dist/context-menu.d.ts.map +1 -0
  49. package/dist/context-menu.js +44 -0
  50. package/dist/context-menu.js.map +1 -0
  51. package/dist/counter-badge-recipe.d.ts +5 -0
  52. package/dist/counter-badge-recipe.d.ts.map +1 -1
  53. package/dist/counter-badge-recipe.js +5 -0
  54. package/dist/counter-badge-recipe.js.map +1 -1
  55. package/dist/dataviz.d.ts +76 -0
  56. package/dist/dataviz.d.ts.map +1 -0
  57. package/dist/dataviz.js +58 -0
  58. package/dist/dataviz.js.map +1 -0
  59. package/dist/date-range.d.ts +56 -0
  60. package/dist/date-range.d.ts.map +1 -0
  61. package/dist/date-range.js +79 -0
  62. package/dist/date-range.js.map +1 -0
  63. package/dist/design-system-provider.d.ts +24 -0
  64. package/dist/design-system-provider.d.ts.map +1 -1
  65. package/dist/design-system-provider.js +21 -0
  66. package/dist/design-system-provider.js.map +1 -1
  67. package/dist/floating-action-button.d.ts +1 -1
  68. package/dist/floating-action-button.d.ts.map +1 -1
  69. package/dist/floating-action-button.js +3 -1
  70. package/dist/floating-action-button.js.map +1 -1
  71. package/dist/formatters.d.ts +37 -0
  72. package/dist/formatters.d.ts.map +1 -0
  73. package/dist/formatters.js +69 -0
  74. package/dist/formatters.js.map +1 -0
  75. package/dist/heading.d.ts +82 -0
  76. package/dist/heading.d.ts.map +1 -0
  77. package/dist/heading.js +48 -0
  78. package/dist/heading.js.map +1 -0
  79. package/dist/index.d.ts +19 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +19 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/menubar.d.ts +133 -0
  84. package/dist/menubar.d.ts.map +1 -0
  85. package/dist/menubar.js +80 -0
  86. package/dist/menubar.js.map +1 -0
  87. package/dist/native-platform.d.ts +88 -0
  88. package/dist/native-platform.d.ts.map +1 -0
  89. package/dist/native-platform.js +70 -0
  90. package/dist/native-platform.js.map +1 -0
  91. package/dist/popover.d.ts +19 -4
  92. package/dist/popover.d.ts.map +1 -1
  93. package/dist/popover.js +3 -0
  94. package/dist/popover.js.map +1 -1
  95. package/dist/progress-recipe.d.ts +14 -0
  96. package/dist/progress-recipe.d.ts.map +1 -1
  97. package/dist/progress-recipe.js +14 -1
  98. package/dist/progress-recipe.js.map +1 -1
  99. package/dist/provider-button.d.ts +140 -0
  100. package/dist/provider-button.d.ts.map +1 -0
  101. package/dist/provider-button.js +83 -0
  102. package/dist/provider-button.js.map +1 -0
  103. package/dist/recipes.d.ts +15 -1
  104. package/dist/recipes.d.ts.map +1 -1
  105. package/dist/recipes.js +14 -0
  106. package/dist/recipes.js.map +1 -1
  107. package/dist/sheet.d.ts +16 -0
  108. package/dist/sheet.d.ts.map +1 -1
  109. package/dist/sheet.js +33 -0
  110. package/dist/sheet.js.map +1 -1
  111. package/dist/sidebar.d.ts +168 -0
  112. package/dist/sidebar.d.ts.map +1 -0
  113. package/dist/sidebar.js +98 -0
  114. package/dist/sidebar.js.map +1 -0
  115. package/dist/skip-nav.d.ts +67 -0
  116. package/dist/skip-nav.d.ts.map +1 -0
  117. package/dist/skip-nav.js +47 -0
  118. package/dist/skip-nav.js.map +1 -0
  119. package/dist/tags-input.d.ts +157 -0
  120. package/dist/tags-input.d.ts.map +1 -0
  121. package/dist/tags-input.js +108 -0
  122. package/dist/tags-input.js.map +1 -0
  123. package/dist/text-formats.d.ts +85 -0
  124. package/dist/text-formats.d.ts.map +1 -0
  125. package/dist/text-formats.js +43 -0
  126. package/dist/text-formats.js.map +1 -0
  127. package/dist/toggle-group.d.ts +111 -0
  128. package/dist/toggle-group.d.ts.map +1 -0
  129. package/dist/toggle-group.js +78 -0
  130. package/dist/toggle-group.js.map +1 -0
  131. package/dist/top.d.ts +113 -0
  132. package/dist/top.d.ts.map +1 -0
  133. package/dist/top.js +72 -0
  134. package/dist/top.js.map +1 -0
  135. package/dist/version.d.ts +1 -1
  136. package/dist/version.js +1 -1
  137. package/dist/version.js.map +1 -1
  138. package/docs/agreement.md +33 -0
  139. package/docs/anchor.md +59 -59
  140. package/docs/ant-design-coverage.md +5 -3
  141. package/docs/architecture.md +10 -0
  142. package/docs/asset.md +26 -0
  143. package/docs/bottom-info.md +15 -0
  144. package/docs/breadcrumb.md +15 -15
  145. package/docs/button-label.md +20 -0
  146. package/docs/calendar.md +82 -154
  147. package/docs/carousel.md +27 -2
  148. package/docs/cascader.md +17 -0
  149. package/docs/chart.md +33 -0
  150. package/docs/clipboard.md +13 -0
  151. package/docs/collapsible.md +19 -0
  152. package/docs/command-palette.md +19 -0
  153. package/docs/confirm-popover.md +11 -1
  154. package/docs/context-menu.md +23 -0
  155. package/docs/data-table.md +22 -0
  156. package/docs/date-range.md +31 -0
  157. package/docs/density.md +23 -0
  158. package/docs/expansion-roadmap.md +16 -3
  159. package/docs/floating-action-button.md +35 -3
  160. package/docs/formatters.md +18 -0
  161. package/docs/generated/component-maturity.md +39 -23
  162. package/docs/generated/renderer-evidence.json +2921 -642
  163. package/docs/generated/renderer-evidence.md +53 -6
  164. package/docs/generated/showcase-manifest.json +1087 -64
  165. package/docs/heading.md +25 -0
  166. package/docs/identity.md +5 -0
  167. package/docs/layout.md +1 -1
  168. package/docs/library-gap-analysis.md +29 -9
  169. package/docs/list-row.md +14 -0
  170. package/docs/mentions.md +18 -0
  171. package/docs/menubar.md +22 -0
  172. package/docs/native-platform.md +36 -0
  173. package/docs/overlay-stack.md +24 -0
  174. package/docs/pagination.md +16 -8
  175. package/docs/popover.md +42 -13
  176. package/docs/product-audit-2026-09-15.md +88 -0
  177. package/docs/progress.md +16 -0
  178. package/docs/provider-button.md +33 -0
  179. package/docs/rating.md +19 -0
  180. package/docs/react-native-completion.md +347 -0
  181. package/docs/screen-chrome.md +52 -0
  182. package/docs/side-panel.md +23 -2
  183. package/docs/sidebar.md +30 -0
  184. package/docs/skip-nav.md +20 -0
  185. package/docs/splitter.md +22 -2
  186. package/docs/stable-promotion.md +51 -0
  187. package/docs/tags-input.md +25 -0
  188. package/docs/text-formats.md +15 -0
  189. package/docs/theming.md +84 -0
  190. package/docs/time-picker.md +22 -1
  191. package/docs/toast.md +18 -0
  192. package/docs/toggle-group.md +21 -0
  193. package/docs/top.md +32 -0
  194. package/docs/tour.md +23 -2
  195. package/docs/transfer-list.md +20 -0
  196. package/docs/tree-select.md +18 -0
  197. package/docs/tree.md +21 -0
  198. package/package.json +120 -6
@@ -0,0 +1,53 @@
1
+ import type { MenuItemDescriptor } from "./behaviors.js";
2
+ /**
3
+ * 포인터 위치에서 열리는 메뉴 — 우클릭, 그리고 터치의 길게 누르기.
4
+ *
5
+ * `Menu`와 **목록은 같고 앵커가 다르다.** Menu는 트리거 버튼에 붙고, ContextMenu는
6
+ * 이벤트가 일어난 **좌표**에 붙는다. 그 차이가 접근성까지 번진다: 트리거가 없으므로
7
+ * 키보드 사용자에게 열 방법을 따로 줘야 한다(Shift+F10 또는 메뉴 키). 그것을 빼면
8
+ * 마우스 없이는 존재하지 않는 기능이 된다 — 이 계약이 그 규칙을 갖는 이유다.
9
+ *
10
+ * 항목 모양은 `MenuItemDescriptor` 그대로다. 두 번째 항목 타입을 만들지 않는다.
11
+ */
12
+ export type ContextMenuItemDescriptor<Key extends string = string> = MenuItemDescriptor<Key>;
13
+ export type ContextMenuAnchor = Readonly<{
14
+ /** 뷰포트 기준 좌표. 렌더러가 화면 밖으로 나가지 않게 보정한다. */
15
+ x: number;
16
+ y: number;
17
+ }>;
18
+ export type ContextMenuOpenReason = "pointer" | "keyboard" | "longPress";
19
+ export type ContextMenuCloseReason = "selection" | "escape" | "outside" | "programmatic";
20
+ export declare function validateContextMenuAnchor(anchor: ContextMenuAnchor): void;
21
+ /**
22
+ * 키보드로 열 때는 좌표가 없다. 그때는 **초점이 있는 요소의 상자**를 앵커로 쓴다 —
23
+ * 화면 구석(0,0)에 띄우면 메뉴가 그 요소와 무관해 보인다.
24
+ */
25
+ export declare function resolveContextMenuAnchor(reason: ContextMenuOpenReason, pointer: ContextMenuAnchor | null, focusedRect: Readonly<{
26
+ left: number;
27
+ bottom: number;
28
+ }> | null): ContextMenuAnchor;
29
+ export declare const contextMenuBehavior: {
30
+ readonly controlled: readonly ["open", "defaultOpen", "onOpenChange"];
31
+ readonly inputs: readonly ["items", "sections", "accessibilityLabel"];
32
+ readonly events: readonly ["onAction"];
33
+ readonly configuration: {
34
+ readonly openReason: readonly ["pointer", "keyboard", "longPress"];
35
+ };
36
+ readonly stateAxes: {
37
+ readonly availability: readonly ["enabled", "disabled"];
38
+ readonly interaction: readonly ["idle", "hover", "focusVisible"];
39
+ };
40
+ readonly web: {
41
+ readonly roles: readonly ["menu", "menuitem"];
42
+ readonly keyboard: readonly ["ArrowUp", "ArrowDown", "Home", "End", "Enter", "Space", "Escape", "Typeahead"];
43
+ readonly focus: "activeDescendant";
44
+ readonly dismiss: readonly ["escape", "outside"];
45
+ };
46
+ readonly native: {
47
+ readonly roles: readonly [];
48
+ readonly states: readonly [];
49
+ readonly actions: readonly [];
50
+ };
51
+ readonly scenarios: readonly ["a-keyboard-user-can-open-it-or-the-feature-does-not-exist-without-a-mouse", "keyboard-opening-anchors-to-the-focused-elements-box-not-to-the-viewport-origin", "the-item-vocabulary-is-menus-own-there-is-no-second-item-type", "the-browser-context-menu-is-replaced-only-where-the-product-owns-the-surface", "closing-returns-focus-to-the-element-the-menu-was-opened-from"];
52
+ };
53
+ //# sourceMappingURL=context-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.d.ts","sourceRoot":"","sources":["../src/context-menu.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD;;;;;;;;;GASG;AACH,MAAM,MAAM,yBAAyB,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,IAAI,kBAAkB,CAAC,GAAG,CAAC,CAAC;AAE7F,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,0CAA0C;IAC1C,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;AACzE,MAAM,MAAM,sBAAsB,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,cAAc,CAAC;AAEzF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAIzE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,iBAAiB,GAAG,IAAI,EACjC,WAAW,EAAE,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,GAC7D,iBAAiB,CASnB;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;CAuBK,CAAC"}
@@ -0,0 +1,44 @@
1
+ export function validateContextMenuAnchor(anchor) {
2
+ if (!Number.isFinite(anchor.x) || !Number.isFinite(anchor.y)) {
3
+ throw new TypeError("ContextMenu anchor must be finite viewport coordinates");
4
+ }
5
+ }
6
+ /**
7
+ * 키보드로 열 때는 좌표가 없다. 그때는 **초점이 있는 요소의 상자**를 앵커로 쓴다 —
8
+ * 화면 구석(0,0)에 띄우면 메뉴가 그 요소와 무관해 보인다.
9
+ */
10
+ export function resolveContextMenuAnchor(reason, pointer, focusedRect) {
11
+ if (reason !== "keyboard" && pointer !== null) {
12
+ validateContextMenuAnchor(pointer);
13
+ return pointer;
14
+ }
15
+ if (focusedRect === null) {
16
+ throw new RangeError("ContextMenu opened by keyboard needs the focused element rect");
17
+ }
18
+ return { x: focusedRect.left, y: focusedRect.bottom };
19
+ }
20
+ export const contextMenuBehavior = {
21
+ controlled: ["open", "defaultOpen", "onOpenChange"],
22
+ inputs: ["items", "sections", "accessibilityLabel"],
23
+ events: ["onAction"],
24
+ configuration: { openReason: ["pointer", "keyboard", "longPress"] },
25
+ stateAxes: {
26
+ availability: ["enabled", "disabled"],
27
+ interaction: ["idle", "hover", "focusVisible"],
28
+ },
29
+ web: {
30
+ roles: ["menu", "menuitem"],
31
+ keyboard: ["ArrowUp", "ArrowDown", "Home", "End", "Enter", "Space", "Escape", "Typeahead"],
32
+ focus: "activeDescendant",
33
+ dismiss: ["escape", "outside"],
34
+ },
35
+ native: { roles: [], states: [], actions: [] },
36
+ scenarios: [
37
+ "a-keyboard-user-can-open-it-or-the-feature-does-not-exist-without-a-mouse",
38
+ "keyboard-opening-anchors-to-the-focused-elements-box-not-to-the-viewport-origin",
39
+ "the-item-vocabulary-is-menus-own-there-is-no-second-item-type",
40
+ "the-browser-context-menu-is-replaced-only-where-the-product-owns-the-surface",
41
+ "closing-returns-focus-to-the-element-the-menu-was-opened-from",
42
+ ],
43
+ };
44
+ //# sourceMappingURL=context-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.js","sourceRoot":"","sources":["../src/context-menu.ts"],"names":[],"mappings":"AAwBA,MAAM,UAAU,yBAAyB,CAAC,MAAyB;IACjE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,SAAS,CAAC,wDAAwD,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,MAA6B,EAC7B,OAAiC,EACjC,WAA8D;IAE9D,IAAI,MAAM,KAAK,UAAU,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QAC9C,yBAAyB,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,CAAC,+DAA+D,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,UAAU,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,cAAc,CAAC;IACnD,MAAM,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,oBAAoB,CAAC;IACnD,MAAM,EAAE,CAAC,UAAU,CAAC;IACpB,aAAa,EAAE,EAAE,UAAU,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE;IACnE,SAAS,EAAE;QACT,YAAY,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;QACrC,WAAW,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc,CAAC;KAC/C;IACD,GAAG,EAAE;QACH,KAAK,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC;QAC3B,QAAQ,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC;QAC1F,KAAK,EAAE,kBAAkB;QACzB,OAAO,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC;KAC/B;IACD,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC9C,SAAS,EAAE;QACT,2EAA2E;QAC3E,iFAAiF;QACjF,+DAA+D;QAC/D,8EAA8E;QAC9E,+DAA+D;KAChE;CACkC,CAAC","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { MenuItemDescriptor } from \"./behaviors.js\";\n\n/**\n * 포인터 위치에서 열리는 메뉴 — 우클릭, 그리고 터치의 길게 누르기.\n *\n * `Menu`와 **목록은 같고 앵커가 다르다.** Menu는 트리거 버튼에 붙고, ContextMenu는\n * 이벤트가 일어난 **좌표**에 붙는다. 그 차이가 접근성까지 번진다: 트리거가 없으므로\n * 키보드 사용자에게 열 방법을 따로 줘야 한다(Shift+F10 또는 메뉴 키). 그것을 빼면\n * 마우스 없이는 존재하지 않는 기능이 된다 — 이 계약이 그 규칙을 갖는 이유다.\n *\n * 항목 모양은 `MenuItemDescriptor` 그대로다. 두 번째 항목 타입을 만들지 않는다.\n */\nexport type ContextMenuItemDescriptor<Key extends string = string> = MenuItemDescriptor<Key>;\n\nexport type ContextMenuAnchor = Readonly<{\n /** 뷰포트 기준 좌표. 렌더러가 화면 밖으로 나가지 않게 보정한다. */\n x: number;\n y: number;\n}>;\n\nexport type ContextMenuOpenReason = \"pointer\" | \"keyboard\" | \"longPress\";\nexport type ContextMenuCloseReason = \"selection\" | \"escape\" | \"outside\" | \"programmatic\";\n\nexport function validateContextMenuAnchor(anchor: ContextMenuAnchor): void {\n if (!Number.isFinite(anchor.x) || !Number.isFinite(anchor.y)) {\n throw new TypeError(\"ContextMenu anchor must be finite viewport coordinates\");\n }\n}\n\n/**\n * 키보드로 열 때는 좌표가 없다. 그때는 **초점이 있는 요소의 상자**를 앵커로 쓴다 —\n * 화면 구석(0,0)에 띄우면 메뉴가 그 요소와 무관해 보인다.\n */\nexport function resolveContextMenuAnchor(\n reason: ContextMenuOpenReason,\n pointer: ContextMenuAnchor | null,\n focusedRect: Readonly<{ left: number; bottom: number }> | null,\n): ContextMenuAnchor {\n if (reason !== \"keyboard\" && pointer !== null) {\n validateContextMenuAnchor(pointer);\n return pointer;\n }\n if (focusedRect === null) {\n throw new RangeError(\"ContextMenu opened by keyboard needs the focused element rect\");\n }\n return { x: focusedRect.left, y: focusedRect.bottom };\n}\n\nexport const contextMenuBehavior = {\n controlled: [\"open\", \"defaultOpen\", \"onOpenChange\"],\n inputs: [\"items\", \"sections\", \"accessibilityLabel\"],\n events: [\"onAction\"],\n configuration: { openReason: [\"pointer\", \"keyboard\", \"longPress\"] },\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n interaction: [\"idle\", \"hover\", \"focusVisible\"],\n },\n web: {\n roles: [\"menu\", \"menuitem\"],\n keyboard: [\"ArrowUp\", \"ArrowDown\", \"Home\", \"End\", \"Enter\", \"Space\", \"Escape\", \"Typeahead\"],\n focus: \"activeDescendant\",\n dismiss: [\"escape\", \"outside\"],\n },\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"a-keyboard-user-can-open-it-or-the-feature-does-not-exist-without-a-mouse\",\n \"keyboard-opening-anchors-to-the-focused-elements-box-not-to-the-viewport-origin\",\n \"the-item-vocabulary-is-menus-own-there-is-no-second-item-type\",\n \"the-browser-context-menu-is-replaced-only-where-the-product-owns-the-surface\",\n \"closing-returns-focus-to-the-element-the-menu-was-opened-from\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -2,6 +2,11 @@ export { counterBadgeDefaults, formatCounterBadgeCount, type CounterBadgeSize, t
2
2
  /** Numeric counters use a compact solid plate, distinct from status badges. */
3
3
  export declare const counterBadgeRecipe: {
4
4
  readonly slots: readonly ["root", "label"];
5
+ /**
6
+ * 숫자 없이 "새것이 있다"만 말하는 점. 0이 아닌 개수를 모르거나 셀 필요가 없는
7
+ * 자리(탭 배지, 사이드바 항목)에서 숫자를 지어내지 않기 위해 별도 크기로 둔다.
8
+ */
9
+ readonly dotSize: 8;
5
10
  readonly defaults: {
6
11
  readonly tone: "danger";
7
12
  readonly size: "medium";
@@ -1 +1 @@
1
- {"version":3,"file":"counter-badge-recipe.d.ts","sourceRoot":"","sources":["../src/counter-badge-recipe.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgE9B,CAAC"}
1
+ {"version":3,"file":"counter-badge-recipe.d.ts","sourceRoot":"","sources":["../src/counter-badge-recipe.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;IAE7B;;;OAGG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEJ,CAAC"}
@@ -5,6 +5,11 @@ export { counterBadgeDefaults, formatCounterBadgeCount, } from "./counter-badge.
5
5
  /** Numeric counters use a compact solid plate, distinct from status badges. */
6
6
  export const counterBadgeRecipe = {
7
7
  slots: ["root", "label"],
8
+ /**
9
+ * 숫자 없이 "새것이 있다"만 말하는 점. 0이 아닌 개수를 모르거나 셀 필요가 없는
10
+ * 자리(탭 배지, 사이드바 항목)에서 숫자를 지어내지 않기 위해 별도 크기로 둔다.
11
+ */
12
+ dotSize: 8,
8
13
  defaults: counterBadgeDefaults,
9
14
  tones: {
10
15
  danger: {
@@ -1 +1 @@
1
- {"version":3,"file":"counter-badge-recipe.js","sourceRoot":"","sources":["../src/counter-badge-recipe.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EACV,MAAM,EACN,OAAO,EACP,MAAM,GAGP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,oBAAoB,GAIrB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GAIxB,MAAM,oBAAoB,CAAC;AAE5B,+EAA+E;AAC/E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,CAAU;IACjC,QAAQ,EAAE,oBAAoB;IAC9B,KAAK,EAAE;QACL,MAAM,EAAE;YACN,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU;YACnD,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO;SAC9C;QACD,KAAK,EAAE;YACL,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;YAClD,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;SAC7C;QACD,OAAO,EAAE;YACP,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;YACvC,OAAO,EAAE,cAAc,CAAC,MAAM;SAC/B;KACF;IACD,KAAK,EAAE;QACL,KAAK,EAAE;YACL,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;YACZ,iBAAiB,EAAE,OAAO,CAAC,GAAG;YAC9B,WAAW,EAAE,SAAS;SACvB;QACD,MAAM,EAAE;YACN,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;YACZ,iBAAiB,EAAE,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,SAAS;SACvB;KACF;IACD,QAAQ,EAAE;QACR,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE;QACxC,QAAQ,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;KACxE;IACD,MAAM,EAAE,MAAM;IACd,UAAU,EAAE,UAAU,CAAC,IAAI;CA4B5B,CAAC","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport {\n fontWeight,\n radius,\n spacing,\n stroke,\n type FontWeightValue,\n type TextVariant,\n} from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport {\n counterBadgeDefaults,\n type CounterBadgeSize,\n type CounterBadgeTone,\n type CounterBadgeVariant,\n} from \"./counter-badge.js\";\n\nexport {\n counterBadgeDefaults,\n formatCounterBadgeCount,\n type CounterBadgeSize,\n type CounterBadgeTone,\n type CounterBadgeVariant,\n} from \"./counter-badge.js\";\n\n/** Numeric counters use a compact solid plate, distinct from status badges. */\nexport const counterBadgeRecipe = {\n slots: [\"root\", \"label\"] as const,\n defaults: counterBadgeDefaults,\n tones: {\n danger: {\n background: semanticColors.action.danger.background,\n content: semanticColors.action.danger.content,\n },\n brand: {\n background: semanticColors.action.brand.background,\n content: semanticColors.action.brand.content,\n },\n neutral: {\n background: semanticColors.content.body,\n content: semanticColors.canvas,\n },\n },\n sizes: {\n small: {\n height: 16,\n minWidth: 16,\n paddingHorizontal: spacing.xxs,\n textVariant: \"caption\",\n },\n medium: {\n height: 20,\n minWidth: 20,\n paddingHorizontal: spacing.xs,\n textVariant: \"caption\",\n },\n },\n variants: {\n inline: { border: null, borderWidth: 0 },\n floating: { border: semanticColors.canvas, borderWidth: stroke.strong },\n },\n radius: \"full\",\n fontWeight: fontWeight.bold,\n} as const satisfies {\n slots: readonly [\"root\", \"label\"];\n defaults: {\n tone: CounterBadgeTone;\n size: CounterBadgeSize;\n variant: CounterBadgeVariant;\n max: number;\n };\n tones: Record<\n CounterBadgeTone,\n { background: ColorReference; content: ColorReference }\n >;\n sizes: Record<\n CounterBadgeSize,\n {\n height: number;\n minWidth: number;\n paddingHorizontal: number;\n textVariant: TextVariant;\n }\n >;\n variants: Record<\n CounterBadgeVariant,\n { border: ColorReference | null; borderWidth: number }\n >;\n radius: keyof typeof radius;\n fontWeight: FontWeightValue;\n};\n"]}
1
+ {"version":3,"file":"counter-badge-recipe.js","sourceRoot":"","sources":["../src/counter-badge-recipe.ts"],"names":[],"mappings":"AACA,OAAO,EACL,UAAU,EACV,MAAM,EACN,OAAO,EACP,MAAM,GAGP,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EACL,oBAAoB,GAIrB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GAIxB,MAAM,oBAAoB,CAAC;AAE5B,+EAA+E;AAC/E,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,CAAU;IACjC;;;OAGG;IACH,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,oBAAoB;IAC9B,KAAK,EAAE;QACL,MAAM,EAAE;YACN,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU;YACnD,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO;SAC9C;QACD,KAAK,EAAE;YACL,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;YAClD,OAAO,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;SAC7C;QACD,OAAO,EAAE;YACP,UAAU,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI;YACvC,OAAO,EAAE,cAAc,CAAC,MAAM;SAC/B;KACF;IACD,KAAK,EAAE;QACL,KAAK,EAAE;YACL,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;YACZ,iBAAiB,EAAE,OAAO,CAAC,GAAG;YAC9B,WAAW,EAAE,SAAS;SACvB;QACD,MAAM,EAAE;YACN,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;YACZ,iBAAiB,EAAE,OAAO,CAAC,EAAE;YAC7B,WAAW,EAAE,SAAS;SACvB;KACF;IACD,QAAQ,EAAE;QACR,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE;QACxC,QAAQ,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;KACxE;IACD,MAAM,EAAE,MAAM;IACd,UAAU,EAAE,UAAU,CAAC,IAAI;CA6B5B,CAAC","sourcesContent":["import type { ColorReference } from \"./color-references.js\";\nimport {\n fontWeight,\n radius,\n spacing,\n stroke,\n type FontWeightValue,\n type TextVariant,\n} from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\nimport {\n counterBadgeDefaults,\n type CounterBadgeSize,\n type CounterBadgeTone,\n type CounterBadgeVariant,\n} from \"./counter-badge.js\";\n\nexport {\n counterBadgeDefaults,\n formatCounterBadgeCount,\n type CounterBadgeSize,\n type CounterBadgeTone,\n type CounterBadgeVariant,\n} from \"./counter-badge.js\";\n\n/** Numeric counters use a compact solid plate, distinct from status badges. */\nexport const counterBadgeRecipe = {\n slots: [\"root\", \"label\"] as const,\n /**\n * 숫자 없이 \"새것이 있다\"만 말하는 점. 0이 아닌 개수를 모르거나 셀 필요가 없는\n * 자리(탭 배지, 사이드바 항목)에서 숫자를 지어내지 않기 위해 별도 크기로 둔다.\n */\n dotSize: 8,\n defaults: counterBadgeDefaults,\n tones: {\n danger: {\n background: semanticColors.action.danger.background,\n content: semanticColors.action.danger.content,\n },\n brand: {\n background: semanticColors.action.brand.background,\n content: semanticColors.action.brand.content,\n },\n neutral: {\n background: semanticColors.content.body,\n content: semanticColors.canvas,\n },\n },\n sizes: {\n small: {\n height: 16,\n minWidth: 16,\n paddingHorizontal: spacing.xxs,\n textVariant: \"caption\",\n },\n medium: {\n height: 20,\n minWidth: 20,\n paddingHorizontal: spacing.xs,\n textVariant: \"caption\",\n },\n },\n variants: {\n inline: { border: null, borderWidth: 0 },\n floating: { border: semanticColors.canvas, borderWidth: stroke.strong },\n },\n radius: \"full\",\n fontWeight: fontWeight.bold,\n} as const satisfies {\n slots: readonly [\"root\", \"label\"];\n dotSize: number;\n defaults: {\n tone: CounterBadgeTone;\n size: CounterBadgeSize;\n variant: CounterBadgeVariant;\n max: number;\n };\n tones: Record<\n CounterBadgeTone,\n { background: ColorReference; content: ColorReference }\n >;\n sizes: Record<\n CounterBadgeSize,\n {\n height: number;\n minWidth: number;\n paddingHorizontal: number;\n textVariant: TextVariant;\n }\n >;\n variants: Record<\n CounterBadgeVariant,\n { border: ColorReference | null; borderWidth: number }\n >;\n radius: keyof typeof radius;\n fontWeight: FontWeightValue;\n};\n"]}
@@ -0,0 +1,76 @@
1
+ import type { ResolvedTheme } from "./colors.js";
2
+ /**
3
+ * 차트의 **토큰 계약만** 둔다. 렌더러는 만들지 않는다.
4
+ *
5
+ * 이유: 막대 하나를 그리는 일은 제품 라이브러리(Recharts·victory-native·d3)가 이미
6
+ * 우리보다 잘한다. HJM이 대신 그리면 축·툴팁·애니메이션·접근성까지 전부 다시 만들어야
7
+ * 하고, 그 순간 제품은 우리 차트와 자기 차트 두 벌을 갖게 된다. 반대로 **색이 제각각인
8
+ * 것**은 실제로 일어난 문제다 — Spint 열지도와 Yajalal 통계가 각자 색을 골랐다.
9
+ * 그래서 색·축·격자·범례만 고정하고 그리기는 위임한다. 이 판단의 근거는 docs/chart.md다.
10
+ *
11
+ * 상태색(info/success/warning/attention)을 계열색으로 쓰지 않는다. 상태색은 뜻을 가진
12
+ * 색이라 "3번 계열"에 쓰면 사용자가 경고로 읽는다.
13
+ */
14
+ export declare const datavizSeriesPalette: {
15
+ readonly light: readonly ["#2563eb", "#db2777", "#0d9488", "#b45309", "#7c3aed", "#0369a1", "#9d174d", "#4d7c0f"];
16
+ readonly dark: readonly ["#60a5fa", "#f472b6", "#2dd4bf", "#fbbf24", "#a78bfa", "#38bdf8", "#fb7185", "#a3e635"];
17
+ };
18
+ export declare const datavizSeriesCount: 8;
19
+ /**
20
+ * 계열 수가 팔레트보다 많으면 **순환**한다. 색을 자동으로 더 만들지 않는다 — 생성한 색은
21
+ * 대비를 보장할 수 없고, 9번째 계열이 필요한 화면은 대개 차트가 아니라 표가 맞다.
22
+ */
23
+ export declare function resolveDatavizSeriesColor(theme: ResolvedTheme, index: number): string;
24
+ /**
25
+ * 축·격자·범례는 차트가 아니라 **화면의 부속**이라 본문 토큰을 그대로 쓴다. 차트 전용
26
+ * 회색을 따로 두면 같은 화면의 표·캡션과 어긋난다.
27
+ */
28
+ export declare const datavizChromeTokens: {
29
+ readonly axisLine: Readonly<{
30
+ source: "theme";
31
+ key: "border";
32
+ alpha?: number;
33
+ }>;
34
+ readonly axisLabel: Readonly<{
35
+ source: "theme";
36
+ key: "textMuted";
37
+ alpha?: number;
38
+ }>;
39
+ readonly gridLine: Readonly<{
40
+ source: "theme";
41
+ key: "border";
42
+ alpha?: number;
43
+ }>;
44
+ readonly legendLabel: Readonly<{
45
+ source: "theme";
46
+ key: "textBody";
47
+ alpha?: number;
48
+ }>;
49
+ readonly tooltipSurface: Readonly<{
50
+ source: "theme";
51
+ key: "surface";
52
+ alpha?: number;
53
+ }>;
54
+ readonly tooltipBorder: Readonly<{
55
+ source: "theme";
56
+ key: "border";
57
+ alpha?: number;
58
+ }>;
59
+ readonly tooltipContent: Readonly<{
60
+ source: "theme";
61
+ key: "textBody";
62
+ alpha?: number;
63
+ }>;
64
+ readonly lineWidth: 1;
65
+ readonly gridWidth: 1;
66
+ readonly labelVariant: "caption";
67
+ readonly legendVariant: "label";
68
+ };
69
+ /**
70
+ * 색만으로 계열을 구분하지 않는다(WCAG 1.4.1). 제품이 쓰는 라이브러리가 무엇이든 이
71
+ * 셋 중 하나를 색과 **함께** 붙여야 한다 — 색각 이상과 흑백 인쇄에서 차트가 남는 조건이다.
72
+ */
73
+ export declare const datavizRedundantEncodings: readonly ["directLabel", "pattern", "markerShape"];
74
+ export type DatavizRedundantEncoding = (typeof datavizRedundantEncodings)[number];
75
+ export declare function validateDatavizEncoding(encodings: readonly DatavizRedundantEncoding[]): void;
76
+ //# sourceMappingURL=dataviz.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataviz.d.ts","sourceRoot":"","sources":["../src/dataviz.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB;;;CAGsC,CAAC;AAExE,eAAO,MAAM,kBAAkB,GAAoC,CAAC;AAEpE;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMrF;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwB9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,yBAAyB,oDAAqD,CAAC;AAC5F,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,wBAAwB,EAAE,GAC7C,IAAI,CAMN"}
@@ -0,0 +1,58 @@
1
+ import { themeColor } from "./color-references.js";
2
+ import { stroke, typography } from "./foundations.js";
3
+ /**
4
+ * 차트의 **토큰 계약만** 둔다. 렌더러는 만들지 않는다.
5
+ *
6
+ * 이유: 막대 하나를 그리는 일은 제품 라이브러리(Recharts·victory-native·d3)가 이미
7
+ * 우리보다 잘한다. HJM이 대신 그리면 축·툴팁·애니메이션·접근성까지 전부 다시 만들어야
8
+ * 하고, 그 순간 제품은 우리 차트와 자기 차트 두 벌을 갖게 된다. 반대로 **색이 제각각인
9
+ * 것**은 실제로 일어난 문제다 — Spint 열지도와 Yajalal 통계가 각자 색을 골랐다.
10
+ * 그래서 색·축·격자·범례만 고정하고 그리기는 위임한다. 이 판단의 근거는 docs/chart.md다.
11
+ *
12
+ * 상태색(info/success/warning/attention)을 계열색으로 쓰지 않는다. 상태색은 뜻을 가진
13
+ * 색이라 "3번 계열"에 쓰면 사용자가 경고로 읽는다.
14
+ */
15
+ export const datavizSeriesPalette = {
16
+ light: ["#2563eb", "#db2777", "#0d9488", "#b45309", "#7c3aed", "#0369a1", "#9d174d", "#4d7c0f"],
17
+ dark: ["#60a5fa", "#f472b6", "#2dd4bf", "#fbbf24", "#a78bfa", "#38bdf8", "#fb7185", "#a3e635"],
18
+ };
19
+ export const datavizSeriesCount = datavizSeriesPalette.light.length;
20
+ /**
21
+ * 계열 수가 팔레트보다 많으면 **순환**한다. 색을 자동으로 더 만들지 않는다 — 생성한 색은
22
+ * 대비를 보장할 수 없고, 9번째 계열이 필요한 화면은 대개 차트가 아니라 표가 맞다.
23
+ */
24
+ export function resolveDatavizSeriesColor(theme, index) {
25
+ if (!Number.isInteger(index) || index < 0) {
26
+ throw new RangeError("Dataviz series index must be a non-negative integer");
27
+ }
28
+ const palette = datavizSeriesPalette[theme];
29
+ return palette[index % palette.length];
30
+ }
31
+ /**
32
+ * 축·격자·범례는 차트가 아니라 **화면의 부속**이라 본문 토큰을 그대로 쓴다. 차트 전용
33
+ * 회색을 따로 두면 같은 화면의 표·캡션과 어긋난다.
34
+ */
35
+ export const datavizChromeTokens = {
36
+ axisLine: themeColor("border"),
37
+ axisLabel: themeColor("textMuted"),
38
+ gridLine: themeColor("border", 0.6),
39
+ legendLabel: themeColor("textBody"),
40
+ tooltipSurface: themeColor("surface"),
41
+ tooltipBorder: themeColor("border"),
42
+ tooltipContent: themeColor("textBody"),
43
+ lineWidth: stroke.default,
44
+ gridWidth: stroke.subtle,
45
+ labelVariant: "caption",
46
+ legendVariant: "label",
47
+ };
48
+ /**
49
+ * 색만으로 계열을 구분하지 않는다(WCAG 1.4.1). 제품이 쓰는 라이브러리가 무엇이든 이
50
+ * 셋 중 하나를 색과 **함께** 붙여야 한다 — 색각 이상과 흑백 인쇄에서 차트가 남는 조건이다.
51
+ */
52
+ export const datavizRedundantEncodings = ["directLabel", "pattern", "markerShape"];
53
+ export function validateDatavizEncoding(encodings) {
54
+ if (encodings.length === 0) {
55
+ throw new RangeError("A dataviz series needs at least one non-color encoding: directLabel, pattern, or markerShape");
56
+ }
57
+ }
58
+ //# sourceMappingURL=dataviz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataviz.js","sourceRoot":"","sources":["../src/dataviz.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAuB,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,KAAK,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;IAC/F,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;CACzB,CAAC;AAExE,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAC,KAAK,CAAC,MAAM,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAoB,EAAE,KAAa;IAC3E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,UAAU,CAAC,qDAAqD,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC5C,OAAO,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,MAAM,CAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC;IAC9B,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC;IAClC,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC;IACnC,WAAW,EAAE,UAAU,CAAC,UAAU,CAAC;IACnC,cAAc,EAAE,UAAU,CAAC,SAAS,CAAC;IACrC,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC;IACnC,cAAc,EAAE,UAAU,CAAC,UAAU,CAAC;IACtC,SAAS,EAAE,MAAM,CAAC,OAAO;IACzB,SAAS,EAAE,MAAM,CAAC,MAAM;IACxB,YAAY,EAAE,SAAS;IACvB,aAAa,EAAE,OAAO;CAatB,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,aAAa,CAAU,CAAC;AAG5F,MAAM,UAAU,uBAAuB,CACrC,SAA8C;IAE9C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,UAAU,CAClB,8FAA8F,CAC/F,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["import type { ResolvedTheme } from \"./colors.js\";\nimport { themeColor, type ColorReference } from \"./color-references.js\";\nimport { stroke, typography } from \"./foundations.js\";\n\n/**\n * 차트의 **토큰 계약만** 둔다. 렌더러는 만들지 않는다.\n *\n * 이유: 막대 하나를 그리는 일은 제품 라이브러리(Recharts·victory-native·d3)가 이미\n * 우리보다 잘한다. HJM이 대신 그리면 축·툴팁·애니메이션·접근성까지 전부 다시 만들어야\n * 하고, 그 순간 제품은 우리 차트와 자기 차트 두 벌을 갖게 된다. 반대로 **색이 제각각인\n * 것**은 실제로 일어난 문제다 — Spint 열지도와 Yajalal 통계가 각자 색을 골랐다.\n * 그래서 색·축·격자·범례만 고정하고 그리기는 위임한다. 이 판단의 근거는 docs/chart.md다.\n *\n * 상태색(info/success/warning/attention)을 계열색으로 쓰지 않는다. 상태색은 뜻을 가진\n * 색이라 \"3번 계열\"에 쓰면 사용자가 경고로 읽는다.\n */\nexport const datavizSeriesPalette = {\n light: [\"#2563eb\", \"#db2777\", \"#0d9488\", \"#b45309\", \"#7c3aed\", \"#0369a1\", \"#9d174d\", \"#4d7c0f\"],\n dark: [\"#60a5fa\", \"#f472b6\", \"#2dd4bf\", \"#fbbf24\", \"#a78bfa\", \"#38bdf8\", \"#fb7185\", \"#a3e635\"],\n} as const satisfies Readonly<Record<ResolvedTheme, readonly string[]>>;\n\nexport const datavizSeriesCount = datavizSeriesPalette.light.length;\n\n/**\n * 계열 수가 팔레트보다 많으면 **순환**한다. 색을 자동으로 더 만들지 않는다 — 생성한 색은\n * 대비를 보장할 수 없고, 9번째 계열이 필요한 화면은 대개 차트가 아니라 표가 맞다.\n */\nexport function resolveDatavizSeriesColor(theme: ResolvedTheme, index: number): string {\n if (!Number.isInteger(index) || index < 0) {\n throw new RangeError(\"Dataviz series index must be a non-negative integer\");\n }\n const palette = datavizSeriesPalette[theme];\n return palette[index % palette.length]!;\n}\n\n/**\n * 축·격자·범례는 차트가 아니라 **화면의 부속**이라 본문 토큰을 그대로 쓴다. 차트 전용\n * 회색을 따로 두면 같은 화면의 표·캡션과 어긋난다.\n */\nexport const datavizChromeTokens = {\n axisLine: themeColor(\"border\"),\n axisLabel: themeColor(\"textMuted\"),\n gridLine: themeColor(\"border\", 0.6),\n legendLabel: themeColor(\"textBody\"),\n tooltipSurface: themeColor(\"surface\"),\n tooltipBorder: themeColor(\"border\"),\n tooltipContent: themeColor(\"textBody\"),\n lineWidth: stroke.default,\n gridWidth: stroke.subtle,\n labelVariant: \"caption\",\n legendVariant: \"label\",\n} as const satisfies Readonly<{\n axisLine: ColorReference;\n axisLabel: ColorReference;\n gridLine: ColorReference;\n legendLabel: ColorReference;\n tooltipSurface: ColorReference;\n tooltipBorder: ColorReference;\n tooltipContent: ColorReference;\n lineWidth: number;\n gridWidth: number;\n labelVariant: keyof typeof typography;\n legendVariant: keyof typeof typography;\n}>;\n\n/**\n * 색만으로 계열을 구분하지 않는다(WCAG 1.4.1). 제품이 쓰는 라이브러리가 무엇이든 이\n * 셋 중 하나를 색과 **함께** 붙여야 한다 — 색각 이상과 흑백 인쇄에서 차트가 남는 조건이다.\n */\nexport const datavizRedundantEncodings = [\"directLabel\", \"pattern\", \"markerShape\"] as const;\nexport type DatavizRedundantEncoding = (typeof datavizRedundantEncodings)[number];\n\nexport function validateDatavizEncoding(\n encodings: readonly DatavizRedundantEncoding[],\n): void {\n if (encodings.length === 0) {\n throw new RangeError(\n \"A dataviz series needs at least one non-color encoding: directLabel, pattern, or markerShape\",\n );\n }\n}\n"]}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 날짜 **구간** 선택. Calendar의 단일 선택과 같은 격자를 쓰되, "지금 무엇을 고르는
3
+ * 중인가"라는 축이 하나 더 있다.
4
+ *
5
+ * Calendar에 `mode: "single" | "range"`를 넣지 않은 이유: 단일 선택의 값은
6
+ * `string | null`이고 구간의 값은 `{start, end}`다. 한 컴포넌트가 두 값 모양을 가지면
7
+ * 모든 소비자가 매번 좁히기(narrowing)를 해야 하고, "range 모드인데 selectedDate를 준"
8
+ * 조합이 타입에 남는다. 대신 이 모듈이 **구간 판정만** 갖고 격자는 그대로 재사용한다.
9
+ *
10
+ * 값은 제품이 저장한다 — 이 모듈은 다음 클릭이 시작을 바꾸는지 끝을 정하는지만 답한다.
11
+ */
12
+ export type DateRangeValue = Readonly<{
13
+ start: string | null;
14
+ /** `start`만 있고 `end`가 없는 상태가 "고르는 중"이다. 별도 플래그를 두지 않는다. */
15
+ end: string | null;
16
+ }>;
17
+ export declare const emptyDateRange: DateRangeValue;
18
+ export declare function validateDateRange(value: DateRangeValue): void;
19
+ /**
20
+ * 날짜 하나를 눌렀을 때의 다음 값.
21
+ *
22
+ * - 비어 있거나 이미 완성된 구간이면 새 구간을 **시작**한다. (완성된 구간을 다시 눌렀을
23
+ * 때 끝을 옮기는 동작은 "어느 쪽 끝인가"를 추측해야 해서 넣지 않았다 — 다시 시작하는
24
+ * 쪽이 예측 가능하다.)
25
+ * - 고르는 중이면 **끝**을 정한다. 시작보다 앞선 날짜를 누르면 두 값을 바꿔 담는다 —
26
+ * 거절하면 사용자가 왜 안 되는지 알 수 없다.
27
+ */
28
+ export declare function resolveDateRangeSelection(current: DateRangeValue, date: string): DateRangeValue;
29
+ export type DateRangeCellState = "start" | "end" | "between" | "none";
30
+ /**
31
+ * 한 칸이 구간 안에서 어떤 자리인지. 미리보기(`hoveredDate`)는 아직 확정되지 않은 끝을
32
+ * 가리키므로 같은 함수가 처리한다 — renderer가 두 벌의 칠하기 규칙을 만들지 않게 한다.
33
+ */
34
+ export declare function resolveDateRangeCellState(value: DateRangeValue, date: string, hoveredDate?: string | null): DateRangeCellState;
35
+ /** 구간이 실제로 쓸 수 있는 값인지. 제품의 제출 버튼이 읽는다. */
36
+ export declare function isCompleteDateRange(value: DateRangeValue): boolean;
37
+ export declare const dateRangeBehavior: {
38
+ readonly controlled: readonly ["value", "defaultValue", "onValueChange"];
39
+ readonly inputs: readonly ["grid", "monthLabel", "hoveredDate"];
40
+ readonly stateAxes: {
41
+ readonly value: readonly ["empty", "selecting", "complete"];
42
+ readonly availability: readonly ["enabled", "disabled"];
43
+ };
44
+ readonly web: {
45
+ readonly roles: readonly ["grid", "gridcell"];
46
+ readonly keyboard: readonly ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "Enter", "Space"];
47
+ readonly focus: "roving";
48
+ };
49
+ readonly native: {
50
+ readonly roles: readonly [];
51
+ readonly states: readonly ["selected", "disabled"];
52
+ readonly actions: readonly ["select"];
53
+ };
54
+ readonly scenarios: readonly ["a-click-on-a-complete-range-starts-a-new-one-instead-of-guessing-which-end-to-move", "picking-an-earlier-date-while-selecting-swaps-the-two-instead-of-rejecting-it", "the-selecting-state-is-start-without-end-not-a-separate-flag", "a-hovered-date-previews-the-end-through-the-same-cell-state-function", "an-end-without-a-start-is-rejected-by-validation-not-rendered-as-something", "calendar-keeps-single-selection-this-module-never-adds-a-mode-axis-to-it"];
55
+ };
56
+ //# sourceMappingURL=date-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-range.d.ts","sourceRoot":"","sources":["../src/date-range.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,2DAA2D;IAC3D,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB,CAAC,CAAC;AAEH,eAAO,MAAM,cAAc,EAAE,cAA2C,CAAC;AAEzE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAS7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,GACX,cAAc,CAShB;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,kBAAkB,CASpB;AAED,0CAA0C;AAC1C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAGlE;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;CAqBO,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { assertIsoCalendarDate } from "./calendar.js";
2
+ export const emptyDateRange = { start: null, end: null };
3
+ export function validateDateRange(value) {
4
+ if (value.start !== null)
5
+ assertIsoCalendarDate(value.start, "range start");
6
+ if (value.end !== null)
7
+ assertIsoCalendarDate(value.end, "range end");
8
+ if (value.start === null && value.end !== null) {
9
+ throw new TypeError("Date range cannot have an end without a start");
10
+ }
11
+ if (value.start !== null && value.end !== null && value.end < value.start) {
12
+ throw new RangeError("Date range end must not precede start");
13
+ }
14
+ }
15
+ /**
16
+ * 날짜 하나를 눌렀을 때의 다음 값.
17
+ *
18
+ * - 비어 있거나 이미 완성된 구간이면 새 구간을 **시작**한다. (완성된 구간을 다시 눌렀을
19
+ * 때 끝을 옮기는 동작은 "어느 쪽 끝인가"를 추측해야 해서 넣지 않았다 — 다시 시작하는
20
+ * 쪽이 예측 가능하다.)
21
+ * - 고르는 중이면 **끝**을 정한다. 시작보다 앞선 날짜를 누르면 두 값을 바꿔 담는다 —
22
+ * 거절하면 사용자가 왜 안 되는지 알 수 없다.
23
+ */
24
+ export function resolveDateRangeSelection(current, date) {
25
+ assertIsoCalendarDate(date, "selected date");
26
+ validateDateRange(current);
27
+ if (current.start === null || current.end !== null) {
28
+ return { start: date, end: null };
29
+ }
30
+ return date < current.start
31
+ ? { start: date, end: current.start }
32
+ : { start: current.start, end: date };
33
+ }
34
+ /**
35
+ * 한 칸이 구간 안에서 어떤 자리인지. 미리보기(`hoveredDate`)는 아직 확정되지 않은 끝을
36
+ * 가리키므로 같은 함수가 처리한다 — renderer가 두 벌의 칠하기 규칙을 만들지 않게 한다.
37
+ */
38
+ export function resolveDateRangeCellState(value, date, hoveredDate) {
39
+ validateDateRange(value);
40
+ if (value.start === null)
41
+ return "none";
42
+ const provisionalEnd = value.end
43
+ ?? (hoveredDate != null && hoveredDate >= value.start ? hoveredDate : null);
44
+ if (date === value.start && (provisionalEnd === null || date !== provisionalEnd))
45
+ return "start";
46
+ if (provisionalEnd !== null && date === provisionalEnd)
47
+ return "end";
48
+ if (provisionalEnd !== null && date > value.start && date < provisionalEnd)
49
+ return "between";
50
+ return "none";
51
+ }
52
+ /** 구간이 실제로 쓸 수 있는 값인지. 제품의 제출 버튼이 읽는다. */
53
+ export function isCompleteDateRange(value) {
54
+ validateDateRange(value);
55
+ return value.start !== null && value.end !== null;
56
+ }
57
+ export const dateRangeBehavior = {
58
+ controlled: ["value", "defaultValue", "onValueChange"],
59
+ inputs: ["grid", "monthLabel", "hoveredDate"],
60
+ stateAxes: {
61
+ value: ["empty", "selecting", "complete"],
62
+ availability: ["enabled", "disabled"],
63
+ },
64
+ web: {
65
+ roles: ["grid", "gridcell"],
66
+ keyboard: ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "Enter", "Space"],
67
+ focus: "roving",
68
+ },
69
+ native: { roles: [], states: ["selected", "disabled"], actions: ["select"] },
70
+ scenarios: [
71
+ "a-click-on-a-complete-range-starts-a-new-one-instead-of-guessing-which-end-to-move",
72
+ "picking-an-earlier-date-while-selecting-swaps-the-two-instead-of-rejecting-it",
73
+ "the-selecting-state-is-start-without-end-not-a-separate-flag",
74
+ "a-hovered-date-previews-the-end-through-the-same-cell-state-function",
75
+ "an-end-without-a-start-is-rejected-by-validation-not-rendered-as-something",
76
+ "calendar-keeps-single-selection-this-module-never-adds-a-mode-axis-to-it",
77
+ ],
78
+ };
79
+ //# sourceMappingURL=date-range.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date-range.js","sourceRoot":"","sources":["../src/date-range.ts"],"names":[],"mappings":"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","sourcesContent":["import { assertIsoCalendarDate } from \"./calendar.js\";\nimport type { BehaviorContract } from \"./behaviors.js\";\n\n/**\n * 날짜 **구간** 선택. Calendar의 단일 선택과 같은 격자를 쓰되, \"지금 무엇을 고르는\n * 중인가\"라는 축이 하나 더 있다.\n *\n * Calendar에 `mode: \"single\" | \"range\"`를 넣지 않은 이유: 단일 선택의 값은\n * `string | null`이고 구간의 값은 `{start, end}`다. 한 컴포넌트가 두 값 모양을 가지면\n * 모든 소비자가 매번 좁히기(narrowing)를 해야 하고, \"range 모드인데 selectedDate를 준\"\n * 조합이 타입에 남는다. 대신 이 모듈이 **구간 판정만** 갖고 격자는 그대로 재사용한다.\n *\n * 값은 제품이 저장한다 — 이 모듈은 다음 클릭이 시작을 바꾸는지 끝을 정하는지만 답한다.\n */\nexport type DateRangeValue = Readonly<{\n start: string | null;\n /** `start`만 있고 `end`가 없는 상태가 \"고르는 중\"이다. 별도 플래그를 두지 않는다. */\n end: string | null;\n}>;\n\nexport const emptyDateRange: DateRangeValue = { start: null, end: null };\n\nexport function validateDateRange(value: DateRangeValue): void {\n if (value.start !== null) assertIsoCalendarDate(value.start, \"range start\");\n if (value.end !== null) assertIsoCalendarDate(value.end, \"range end\");\n if (value.start === null && value.end !== null) {\n throw new TypeError(\"Date range cannot have an end without a start\");\n }\n if (value.start !== null && value.end !== null && value.end < value.start) {\n throw new RangeError(\"Date range end must not precede start\");\n }\n}\n\n/**\n * 날짜 하나를 눌렀을 때의 다음 값.\n *\n * - 비어 있거나 이미 완성된 구간이면 새 구간을 **시작**한다. (완성된 구간을 다시 눌렀을\n * 때 끝을 옮기는 동작은 \"어느 쪽 끝인가\"를 추측해야 해서 넣지 않았다 — 다시 시작하는\n * 쪽이 예측 가능하다.)\n * - 고르는 중이면 **끝**을 정한다. 시작보다 앞선 날짜를 누르면 두 값을 바꿔 담는다 —\n * 거절하면 사용자가 왜 안 되는지 알 수 없다.\n */\nexport function resolveDateRangeSelection(\n current: DateRangeValue,\n date: string,\n): DateRangeValue {\n assertIsoCalendarDate(date, \"selected date\");\n validateDateRange(current);\n if (current.start === null || current.end !== null) {\n return { start: date, end: null };\n }\n return date < current.start\n ? { start: date, end: current.start }\n : { start: current.start, end: date };\n}\n\nexport type DateRangeCellState = \"start\" | \"end\" | \"between\" | \"none\";\n\n/**\n * 한 칸이 구간 안에서 어떤 자리인지. 미리보기(`hoveredDate`)는 아직 확정되지 않은 끝을\n * 가리키므로 같은 함수가 처리한다 — renderer가 두 벌의 칠하기 규칙을 만들지 않게 한다.\n */\nexport function resolveDateRangeCellState(\n value: DateRangeValue,\n date: string,\n hoveredDate?: string | null,\n): DateRangeCellState {\n validateDateRange(value);\n if (value.start === null) return \"none\";\n const provisionalEnd = value.end\n ?? (hoveredDate != null && hoveredDate >= value.start ? hoveredDate : null);\n if (date === value.start && (provisionalEnd === null || date !== provisionalEnd)) return \"start\";\n if (provisionalEnd !== null && date === provisionalEnd) return \"end\";\n if (provisionalEnd !== null && date > value.start && date < provisionalEnd) return \"between\";\n return \"none\";\n}\n\n/** 구간이 실제로 쓸 수 있는 값인지. 제품의 제출 버튼이 읽는다. */\nexport function isCompleteDateRange(value: DateRangeValue): boolean {\n validateDateRange(value);\n return value.start !== null && value.end !== null;\n}\n\nexport const dateRangeBehavior = {\n controlled: [\"value\", \"defaultValue\", \"onValueChange\"],\n inputs: [\"grid\", \"monthLabel\", \"hoveredDate\"],\n stateAxes: {\n value: [\"empty\", \"selecting\", \"complete\"],\n availability: [\"enabled\", \"disabled\"],\n },\n web: {\n roles: [\"grid\", \"gridcell\"],\n keyboard: [\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\", \"Home\", \"End\", \"Enter\", \"Space\"],\n focus: \"roving\",\n },\n native: { roles: [], states: [\"selected\", \"disabled\"], actions: [\"select\"] },\n scenarios: [\n \"a-click-on-a-complete-range-starts-a-new-one-instead-of-guessing-which-end-to-move\",\n \"picking-an-earlier-date-while-selecting-swaps-the-two-instead-of-rejecting-it\",\n \"the-selecting-state-is-start-without-end-not-a-separate-flag\",\n \"a-hovered-date-previews-the-end-through-the-same-cell-state-function\",\n \"an-end-without-a-start-is-rejected-by-validation-not-rendered-as-something\",\n \"calendar-keeps-single-selection-this-module-never-adds-a-mode-axis-to-it\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -29,6 +29,26 @@ export declare function isLargeTextScale(textScale: DesignSystemTextScale): bool
29
29
  * rather than in `foundations` because it is a policy over a token, not a token.
30
30
  */
31
31
  export declare function visibleControlHeight(recipeHeight: number, minimumVisualTarget: boolean): number;
32
+ /**
33
+ * 한 화면 안에서 목록·메뉴·표가 각각 다른 밀도로 그려지던 문제를 하나의 축으로 모은다.
34
+ * 지금까지는 `ListRow density`, `Menu density`, DataTable의 행 높이가 서로 모르는 값이라
35
+ * 제품이 세 군데를 따로 맞췄다. 컴포넌트의 명시적 prop이 언제나 이 축을 이긴다 —
36
+ * 전역값은 기본이지 강제가 아니다.
37
+ *
38
+ * OS 신호가 없다. 밀도는 제품의 입장(같은 화면에 얼마나 담을 것인가)이지 사용자 설정이
39
+ * 아니라서 `system*` 짝을 두지 않았다. 반대로 큰 글자 설정은 사용자 설정이므로
40
+ * `textScale`이 큰 상태에서 `compact`를 쓰는 것은 제품이 스스로 막아야 한다.
41
+ */
42
+ export type DesignSystemDensity = "comfortable" | "compact";
43
+ /**
44
+ * 전역 밀도를 컴포넌트마다 다른 밀도 어휘로 옮긴다. 컴포넌트의 명시적 prop이 언제나
45
+ * 이긴다 — 전역값은 기본이지 강제가 아니다. 어휘를 하나로 통일하지 않은 이유: 목록의
46
+ * `relaxed`와 표의 `regular`는 같은 말이 아니고, 억지로 합치면 둘 중 하나가 거짓말을 한다.
47
+ */
48
+ export declare function resolveDensityDefault<Comfortable extends string, Compact extends string>(density: DesignSystemDensity, vocabulary: Readonly<{
49
+ comfortable: Comfortable;
50
+ compact: Compact;
51
+ }>): Comfortable | Compact;
32
52
  export type DesignSystemEnvironmentInput = Readonly<{
33
53
  theme?: ThemePreference;
34
54
  direction?: DesignSystemDirection;
@@ -40,6 +60,8 @@ export type DesignSystemEnvironmentInput = Readonly<{
40
60
  * OS signal, so it has no `system*` counterpart.
41
61
  */
42
62
  minimumVisualTarget?: boolean;
63
+ /** Default row/menu/table density for everything below this provider. */
64
+ density?: DesignSystemDensity;
43
65
  }>;
44
66
  export declare const designSystemEnvironmentDefaults: {
45
67
  readonly theme: "system";
@@ -47,6 +69,7 @@ export declare const designSystemEnvironmentDefaults: {
47
69
  readonly textScale: 1;
48
70
  readonly reducedMotion: false;
49
71
  readonly minimumVisualTarget: false;
72
+ readonly density: "comfortable";
50
73
  };
51
74
  export type ResolvedDesignSystemEnvironment = Readonly<{
52
75
  theme: ResolvedTheme;
@@ -54,6 +77,7 @@ export type ResolvedDesignSystemEnvironment = Readonly<{
54
77
  textScale: DesignSystemTextScale;
55
78
  reducedMotion: boolean;
56
79
  minimumVisualTarget: boolean;
80
+ density: DesignSystemDensity;
57
81
  }>;
58
82
  export type ResolveDesignSystemEnvironmentOptions = Readonly<{
59
83
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"design-system-provider.d.ts","sourceRoot":"","sources":["../src/design-system-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,KAAK,CAAC;AAElD;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE3C;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,qBAAqB,GAAG,OAAO,CAE1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,OAAO,GAC3B,MAAM,CAER;AAED,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC,CAAC;AAEH,eAAO,MAAM,+BAA+B;;;;;;CAY1C,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG,QAAQ,CAAC;IACrD,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,qBAAqB,CAAC;IACjC,SAAS,EAAE,qBAAqB,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,EAAE,OAAO,CAAC;CAC9B,CAAC,CAAC;AAEH,MAAM,MAAM,qCAAqC,GAAG,QAAQ,CAAC;IAC3D;;;;;OAKG;IACH,WAAW,EAAE,aAAa,CAAC;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxF,4FAA4F;IAC5F,MAAM,CAAC,EAAE,+BAA+B,CAAC;CAC1C,CAAC,CAAC;AAEH,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,WAAW,EAAE,+BAA+B,CAAC;IAC7C,4DAA4D;IAC5D,OAAO,EAAE,qBAAqB,CAAC;CAChC,CAAC,CAAC;AAyBH;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,yBAAyB,GAC/B,IAAI,CAuBN;AAsBD,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,4BAA4B,GAClC,IAAI,CAsBN;AAED;;;;GAIG;AACH,wBAAgB,uCAAuC,CACrD,WAAW,EAAE,+BAA+B,GAC3C,IAAI,CAUN;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,qCAAqC,GAC7C,+BAA+B,CA0CjC;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,qCAAqC,GAC7C,yBAAyB,CAe3B"}
1
+ {"version":3,"file":"design-system-provider.d.ts","sourceRoot":"","sources":["../src/design-system-provider.ts"],"names":[],"mappings":"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"}
@@ -21,12 +21,21 @@ export function isLargeTextScale(textScale) {
21
21
  export function visibleControlHeight(recipeHeight, minimumVisualTarget) {
22
22
  return minimumVisualTarget ? Math.max(recipeHeight, control.minTouchTarget) : recipeHeight;
23
23
  }
24
+ /**
25
+ * 전역 밀도를 컴포넌트마다 다른 밀도 어휘로 옮긴다. 컴포넌트의 명시적 prop이 언제나
26
+ * 이긴다 — 전역값은 기본이지 강제가 아니다. 어휘를 하나로 통일하지 않은 이유: 목록의
27
+ * `relaxed`와 표의 `regular`는 같은 말이 아니고, 억지로 합치면 둘 중 하나가 거짓말을 한다.
28
+ */
29
+ export function resolveDensityDefault(density, vocabulary) {
30
+ return density === "compact" ? vocabulary.compact : vocabulary.comfortable;
31
+ }
24
32
  export const designSystemEnvironmentDefaults = {
25
33
  theme: "system",
26
34
  direction: "ltr",
27
35
  textScale: 1,
28
36
  reducedMotion: false,
29
37
  minimumVisualTarget: false,
38
+ density: "comfortable",
30
39
  };
31
40
  const themeColorKeys = Object.keys(THEMES.light);
32
41
  const accentColorKeys = Object.keys(ACCENTS.light);
@@ -79,6 +88,11 @@ function assertTextScale(value, field) {
79
88
  throw new RangeError(`DesignSystemEnvironment ${field} must be a finite number greater than 0`);
80
89
  }
81
90
  }
91
+ function assertDensity(value, field) {
92
+ if (value !== "comfortable" && value !== "compact") {
93
+ throw new TypeError(`Unsupported DesignSystemEnvironment ${field}: ${String(value)}`);
94
+ }
95
+ }
82
96
  export function validateDesignSystemEnvironmentInput(input) {
83
97
  if (input.theme !== undefined && !isThemePreference(input.theme)) {
84
98
  throw new TypeError(`Unsupported DesignSystemEnvironment theme: ${String(input.theme)}`);
@@ -97,6 +111,9 @@ export function validateDesignSystemEnvironmentInput(input) {
97
111
  if (input.minimumVisualTarget !== undefined) {
98
112
  assertBoolean(input.minimumVisualTarget, "minimumVisualTarget");
99
113
  }
114
+ if (input.density !== undefined) {
115
+ assertDensity(input.density, "density");
116
+ }
100
117
  }
101
118
  /**
102
119
  * A parent has already crossed the system-preference boundary. Unlike the
@@ -111,6 +128,7 @@ export function validateResolvedDesignSystemEnvironment(environment) {
111
128
  assertTextScale(environment.textScale, "parent textScale");
112
129
  assertBoolean(environment.reducedMotion, "parent reducedMotion");
113
130
  assertBoolean(environment.minimumVisualTarget, "parent minimumVisualTarget");
131
+ assertDensity(environment.density, "parent density");
114
132
  }
115
133
  /**
116
134
  * Merges partial signals with safe defaults and resolves `"system"` against
@@ -154,6 +172,9 @@ export function resolveDesignSystemEnvironment(input, options) {
154
172
  minimumVisualTarget: input.minimumVisualTarget ??
155
173
  options.parent?.minimumVisualTarget ??
156
174
  designSystemEnvironmentDefaults.minimumVisualTarget,
175
+ density: input.density ??
176
+ options.parent?.density ??
177
+ designSystemEnvironmentDefaults.density,
157
178
  };
158
179
  }
159
180
  /**