@hjmds/design-contracts 1.1.1 → 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 (194) 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/anchor.d.ts +62 -0
  6. package/dist/anchor.d.ts.map +1 -0
  7. package/dist/anchor.js +43 -0
  8. package/dist/anchor.js.map +1 -0
  9. package/dist/asset.d.ts +94 -0
  10. package/dist/asset.d.ts.map +1 -0
  11. package/dist/asset.js +60 -0
  12. package/dist/asset.js.map +1 -0
  13. package/dist/base-recipes.d.ts +19 -0
  14. package/dist/base-recipes.d.ts.map +1 -1
  15. package/dist/base-recipes.js +17 -0
  16. package/dist/base-recipes.js.map +1 -1
  17. package/dist/behaviors.d.ts +346 -3
  18. package/dist/behaviors.d.ts.map +1 -1
  19. package/dist/behaviors.js +35 -1
  20. package/dist/behaviors.js.map +1 -1
  21. package/dist/bottom-info.d.ts +58 -0
  22. package/dist/bottom-info.d.ts.map +1 -0
  23. package/dist/bottom-info.js +48 -0
  24. package/dist/bottom-info.js.map +1 -0
  25. package/dist/carousel.d.ts +3 -3
  26. package/dist/carousel.js +1 -1
  27. package/dist/carousel.js.map +1 -1
  28. package/dist/catalog.d.ts +1250 -239
  29. package/dist/catalog.d.ts.map +1 -1
  30. package/dist/catalog.js +65 -25
  31. package/dist/catalog.js.map +1 -1
  32. package/dist/collapsible.d.ts +56 -0
  33. package/dist/collapsible.d.ts.map +1 -0
  34. package/dist/collapsible.js +37 -0
  35. package/dist/collapsible.js.map +1 -0
  36. package/dist/component-definitions.d.ts +16 -0
  37. package/dist/component-definitions.d.ts.map +1 -1
  38. package/dist/component-definitions.js +16 -0
  39. package/dist/component-definitions.js.map +1 -1
  40. package/dist/component-recipes.d.ts +20 -3
  41. package/dist/component-recipes.d.ts.map +1 -1
  42. package/dist/component-recipes.js +24 -1
  43. package/dist/component-recipes.js.map +1 -1
  44. package/dist/context-menu.d.ts +53 -0
  45. package/dist/context-menu.d.ts.map +1 -0
  46. package/dist/context-menu.js +44 -0
  47. package/dist/context-menu.js.map +1 -0
  48. package/dist/counter-badge-recipe.d.ts +5 -0
  49. package/dist/counter-badge-recipe.d.ts.map +1 -1
  50. package/dist/counter-badge-recipe.js +5 -0
  51. package/dist/counter-badge-recipe.js.map +1 -1
  52. package/dist/dataviz.d.ts +76 -0
  53. package/dist/dataviz.d.ts.map +1 -0
  54. package/dist/dataviz.js +58 -0
  55. package/dist/dataviz.js.map +1 -0
  56. package/dist/date-range.d.ts +56 -0
  57. package/dist/date-range.d.ts.map +1 -0
  58. package/dist/date-range.js +79 -0
  59. package/dist/date-range.js.map +1 -0
  60. package/dist/design-system-provider.d.ts +24 -0
  61. package/dist/design-system-provider.d.ts.map +1 -1
  62. package/dist/design-system-provider.js +21 -0
  63. package/dist/design-system-provider.js.map +1 -1
  64. package/dist/floating-action-button.d.ts +1 -1
  65. package/dist/floating-action-button.d.ts.map +1 -1
  66. package/dist/floating-action-button.js +3 -1
  67. package/dist/floating-action-button.js.map +1 -1
  68. package/dist/formatters.d.ts +37 -0
  69. package/dist/formatters.d.ts.map +1 -0
  70. package/dist/formatters.js +69 -0
  71. package/dist/formatters.js.map +1 -0
  72. package/dist/heading.d.ts +82 -0
  73. package/dist/heading.d.ts.map +1 -0
  74. package/dist/heading.js +48 -0
  75. package/dist/heading.js.map +1 -0
  76. package/dist/index.d.ts +19 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +19 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/menubar.d.ts +133 -0
  81. package/dist/menubar.d.ts.map +1 -0
  82. package/dist/menubar.js +80 -0
  83. package/dist/menubar.js.map +1 -0
  84. package/dist/native-platform.d.ts +88 -0
  85. package/dist/native-platform.d.ts.map +1 -0
  86. package/dist/native-platform.js +70 -0
  87. package/dist/native-platform.js.map +1 -0
  88. package/dist/popover.d.ts +19 -4
  89. package/dist/popover.d.ts.map +1 -1
  90. package/dist/popover.js +3 -0
  91. package/dist/popover.js.map +1 -1
  92. package/dist/progress-recipe.d.ts +14 -0
  93. package/dist/progress-recipe.d.ts.map +1 -1
  94. package/dist/progress-recipe.js +14 -1
  95. package/dist/progress-recipe.js.map +1 -1
  96. package/dist/provider-button.d.ts +140 -0
  97. package/dist/provider-button.d.ts.map +1 -0
  98. package/dist/provider-button.js +83 -0
  99. package/dist/provider-button.js.map +1 -0
  100. package/dist/recipes.d.ts +15 -1
  101. package/dist/recipes.d.ts.map +1 -1
  102. package/dist/recipes.js +14 -0
  103. package/dist/recipes.js.map +1 -1
  104. package/dist/sheet.d.ts +16 -0
  105. package/dist/sheet.d.ts.map +1 -1
  106. package/dist/sheet.js +33 -0
  107. package/dist/sheet.js.map +1 -1
  108. package/dist/sidebar.d.ts +168 -0
  109. package/dist/sidebar.d.ts.map +1 -0
  110. package/dist/sidebar.js +98 -0
  111. package/dist/sidebar.js.map +1 -0
  112. package/dist/skip-nav.d.ts +67 -0
  113. package/dist/skip-nav.d.ts.map +1 -0
  114. package/dist/skip-nav.js +47 -0
  115. package/dist/skip-nav.js.map +1 -0
  116. package/dist/tags-input.d.ts +157 -0
  117. package/dist/tags-input.d.ts.map +1 -0
  118. package/dist/tags-input.js +108 -0
  119. package/dist/tags-input.js.map +1 -0
  120. package/dist/text-formats.d.ts +85 -0
  121. package/dist/text-formats.d.ts.map +1 -0
  122. package/dist/text-formats.js +43 -0
  123. package/dist/text-formats.js.map +1 -0
  124. package/dist/toggle-group.d.ts +111 -0
  125. package/dist/toggle-group.d.ts.map +1 -0
  126. package/dist/toggle-group.js +78 -0
  127. package/dist/toggle-group.js.map +1 -0
  128. package/dist/top.d.ts +113 -0
  129. package/dist/top.d.ts.map +1 -0
  130. package/dist/top.js +72 -0
  131. package/dist/top.js.map +1 -0
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/dist/version.js.map +1 -1
  135. package/docs/agreement.md +33 -0
  136. package/docs/anchor.md +59 -59
  137. package/docs/ant-design-coverage.md +5 -3
  138. package/docs/asset.md +26 -0
  139. package/docs/bottom-info.md +15 -0
  140. package/docs/breadcrumb.md +15 -15
  141. package/docs/button-label.md +20 -0
  142. package/docs/calendar.md +82 -154
  143. package/docs/carousel.md +27 -2
  144. package/docs/cascader.md +17 -0
  145. package/docs/chart.md +33 -0
  146. package/docs/clipboard.md +13 -0
  147. package/docs/collapsible.md +19 -0
  148. package/docs/command-palette.md +19 -0
  149. package/docs/confirm-popover.md +11 -1
  150. package/docs/context-menu.md +23 -0
  151. package/docs/data-table.md +22 -0
  152. package/docs/date-range.md +31 -0
  153. package/docs/density.md +23 -0
  154. package/docs/expansion-roadmap.md +16 -3
  155. package/docs/floating-action-button.md +35 -3
  156. package/docs/formatters.md +18 -0
  157. package/docs/generated/component-maturity.md +39 -23
  158. package/docs/generated/renderer-evidence.json +2921 -642
  159. package/docs/generated/renderer-evidence.md +53 -6
  160. package/docs/generated/showcase-manifest.json +1087 -64
  161. package/docs/heading.md +25 -0
  162. package/docs/identity.md +5 -0
  163. package/docs/layout.md +1 -1
  164. package/docs/library-gap-analysis.md +29 -9
  165. package/docs/list-row.md +14 -0
  166. package/docs/mentions.md +18 -0
  167. package/docs/menubar.md +22 -0
  168. package/docs/native-platform.md +36 -0
  169. package/docs/overlay-stack.md +24 -0
  170. package/docs/pagination.md +16 -8
  171. package/docs/popover.md +42 -13
  172. package/docs/product-audit-2026-09-15.md +88 -0
  173. package/docs/progress.md +16 -0
  174. package/docs/provider-button.md +33 -0
  175. package/docs/rating.md +19 -0
  176. package/docs/react-native-completion.md +347 -0
  177. package/docs/screen-chrome.md +52 -0
  178. package/docs/side-panel.md +23 -2
  179. package/docs/sidebar.md +30 -0
  180. package/docs/skip-nav.md +20 -0
  181. package/docs/splitter.md +22 -2
  182. package/docs/stable-promotion.md +51 -0
  183. package/docs/tags-input.md +25 -0
  184. package/docs/text-formats.md +15 -0
  185. package/docs/theming.md +84 -0
  186. package/docs/time-picker.md +22 -1
  187. package/docs/toast.md +18 -0
  188. package/docs/toggle-group.md +21 -0
  189. package/docs/top.md +32 -0
  190. package/docs/tour.md +23 -2
  191. package/docs/transfer-list.md +20 -0
  192. package/docs/tree-select.md +18 -0
  193. package/docs/tree.md +21 -0
  194. package/package.json +120 -6
package/dist/sheet.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sheet.js","sourceRoot":"","sources":["../src/sheet.ts"],"names":[],"mappings":"AAsCA,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,WAAW,EAAE,IAAI;IACjB,gBAAgB,EAAE,KAAK;IACvB,cAAc,EAAE,IAAI;IACpB,mBAAmB,EAAE,IAAI;IACzB,YAAY,EAAE,KAAK;CACkB,CAAC;AAExC;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,MAA0B,EAC1B,IAAa,EACb,SAA6B,qBAAqB;IAElD,IAAI,MAAM,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,gBAAgB;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,cAAc,CAAC;IACvD,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC7C,OAAO,MAAM,CAAC,mBAAmB,CAAC;IACpC,CAAC;IACD,IAAI,MAAM,KAAK,OAAO;QAAE,OAAO,MAAM,CAAC,YAAY,CAAC;IACnD,OAAO,IAAI,CAAC;AACd,CAAC;AAaD;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,gBAAgB,GAAG,KAAK;IAExB,IAAI,QAAQ,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,WAAW,GAAkB,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IACpE,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAE3C,OAAO;QACL,IAAI;YACF,IAAI,WAAW,KAAK,IAAI;gBAAE,OAAO,WAAW,CAAC;YAC7C,QAAQ,IAAI,CAAC,CAAC;YACd,WAAW,GAAG,QAAQ,CAAC;YACvB,cAAc,GAAG,KAAK,CAAC;YACvB,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,YAAY;YACV,IAAI,WAAW,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YACtC,MAAM,KAAK,GAAG,WAAW,CAAC;YAC1B,WAAW,GAAG,IAAI,CAAC;YACnB,cAAc,GAAG,KAAK,CAAC;YACvB,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC5B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,qBAAqB;YACvD,IAAI,WAAW,KAAK,IAAI,IAAI,cAAc;gBAAE,OAAO,KAAK,CAAC;YACzD,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;gBAAE,OAAO,KAAK,CAAC;YACzD,cAAc,GAAG,IAAI,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,eAAe,CAAC,KAAK;YACnB,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/** Why an open Sheet is being asked to close. */\nexport type SheetDismissReason =\n | \"close-action\"\n | \"escape\"\n | \"back\"\n | \"outside\"\n | \"swipe\"\n | \"programmatic\";\n\n/**\n * `trigger` only opens a Sheet. Every close reports the concrete dismiss reason\n * so renderers and product analytics do not have to infer it from platform events.\n */\nexport type SheetOpenChangeDetails = Readonly<{\n reason: \"trigger\" | SheetDismissReason;\n}>;\n\n/** User-dismiss policy. A controlled owner may always close programmatically. */\nexport type SheetDismissPolicy = Readonly<{\n dismissible: boolean;\n dismissWhileBusy: boolean;\n outsideDismiss: boolean;\n escapeOrBackDismiss: boolean;\n swipeDismiss: boolean;\n}>;\n\nexport type SheetOpenState =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange(open: boolean, detail: SheetOpenChangeDetails): void;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, detail: SheetOpenChangeDetails) => void;\n }>;\n\nexport const sheetBehaviorDefaults = {\n dismissible: true,\n dismissWhileBusy: false,\n outsideDismiss: true,\n escapeOrBackDismiss: true,\n swipeDismiss: false,\n} as const satisfies SheetDismissPolicy;\n\n/**\n * Resolves an attempted close without platform knowledge. Programmatic close is\n * intentionally an owner override: a controlled `open={false}` cannot be vetoed\n * by a renderer, even while the Sheet is busy or not user-dismissible.\n */\nexport function canDismissSheet(\n reason: SheetDismissReason,\n busy: boolean,\n policy: SheetDismissPolicy = sheetBehaviorDefaults,\n): boolean {\n if (reason === \"programmatic\") return true;\n if (!policy.dismissible) return false;\n if (busy && !policy.dismissWhileBusy) return false;\n if (reason === \"outside\") return policy.outsideDismiss;\n if (reason === \"escape\" || reason === \"back\") {\n return policy.escapeOrBackDismiss;\n }\n if (reason === \"swipe\") return policy.swipeDismiss;\n return true;\n}\n\nexport type SheetLifecycle = Readonly<{\n open(): number;\n beginDismiss(): number | null;\n requestClose(\n reason: SheetDismissReason,\n busy: boolean,\n policy?: SheetDismissPolicy,\n ): boolean;\n completeDismiss(cycle: number): boolean;\n}>;\n\n/**\n * Makes persistent native Modal renderers settle each visible cycle once.\n * Platform adapters still decide when native dismissal has actually completed.\n */\nexport function createSheetLifecycle(\n initiallyVisible = false,\n): SheetLifecycle {\n let sequence = initiallyVisible ? 1 : 0;\n let activeCycle: number | null = initiallyVisible ? sequence : null;\n let closeRequested = false;\n const dismissingCycles = new Set<number>();\n\n return {\n open() {\n if (activeCycle !== null) return activeCycle;\n sequence += 1;\n activeCycle = sequence;\n closeRequested = false;\n return activeCycle;\n },\n beginDismiss() {\n if (activeCycle === null) return null;\n const cycle = activeCycle;\n activeCycle = null;\n closeRequested = false;\n dismissingCycles.add(cycle);\n return cycle;\n },\n requestClose(reason, busy, policy = sheetBehaviorDefaults) {\n if (activeCycle === null || closeRequested) return false;\n if (!canDismissSheet(reason, busy, policy)) return false;\n closeRequested = true;\n return true;\n },\n completeDismiss(cycle) {\n return dismissingCycles.delete(cycle);\n },\n };\n}\n"]}
1
+ {"version":3,"file":"sheet.js","sourceRoot":"","sources":["../src/sheet.ts"],"names":[],"mappings":"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","sourcesContent":["/** Why an open Sheet is being asked to close. */\nexport type SheetDismissReason =\n | \"close-action\"\n | \"escape\"\n | \"back\"\n | \"outside\"\n | \"swipe\"\n | \"programmatic\";\n\n/**\n * `trigger` only opens a Sheet. Every close reports the concrete dismiss reason\n * so renderers and product analytics do not have to infer it from platform events.\n */\nexport type SheetOpenChangeDetails = Readonly<{\n reason: \"trigger\" | SheetDismissReason;\n}>;\n\n/** User-dismiss policy. A controlled owner may always close programmatically. */\nexport type SheetDismissPolicy = Readonly<{\n dismissible: boolean;\n dismissWhileBusy: boolean;\n outsideDismiss: boolean;\n escapeOrBackDismiss: boolean;\n swipeDismiss: boolean;\n}>;\n\nexport type SheetOpenState =\n | Readonly<{\n open: boolean;\n defaultOpen?: never;\n onOpenChange(open: boolean, detail: SheetOpenChangeDetails): void;\n }>\n | Readonly<{\n open?: never;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean, detail: SheetOpenChangeDetails) => void;\n }>;\n\nexport const sheetBehaviorDefaults = {\n dismissible: true,\n dismissWhileBusy: false,\n outsideDismiss: true,\n escapeOrBackDismiss: true,\n swipeDismiss: false,\n} as const satisfies SheetDismissPolicy;\n\n/**\n * Resolves an attempted close without platform knowledge. Programmatic close is\n * intentionally an owner override: a controlled `open={false}` cannot be vetoed\n * by a renderer, even while the Sheet is busy or not user-dismissible.\n */\nexport function canDismissSheet(\n reason: SheetDismissReason,\n busy: boolean,\n policy: SheetDismissPolicy = sheetBehaviorDefaults,\n): boolean {\n if (reason === \"programmatic\") return true;\n if (!policy.dismissible) return false;\n if (busy && !policy.dismissWhileBusy) return false;\n if (reason === \"outside\") return policy.outsideDismiss;\n if (reason === \"escape\" || reason === \"back\") {\n return policy.escapeOrBackDismiss;\n }\n if (reason === \"swipe\") return policy.swipeDismiss;\n return true;\n}\n\nexport type SheetLifecycle = Readonly<{\n open(): number;\n beginDismiss(): number | null;\n requestClose(\n reason: SheetDismissReason,\n busy: boolean,\n policy?: SheetDismissPolicy,\n ): boolean;\n completeDismiss(cycle: number): boolean;\n}>;\n\n/**\n * Makes persistent native Modal renderers settle each visible cycle once.\n * Platform adapters still decide when native dismissal has actually completed.\n */\nexport function createSheetLifecycle(\n initiallyVisible = false,\n): SheetLifecycle {\n let sequence = initiallyVisible ? 1 : 0;\n let activeCycle: number | null = initiallyVisible ? sequence : null;\n let closeRequested = false;\n const dismissingCycles = new Set<number>();\n\n return {\n open() {\n if (activeCycle !== null) return activeCycle;\n sequence += 1;\n activeCycle = sequence;\n closeRequested = false;\n return activeCycle;\n },\n beginDismiss() {\n if (activeCycle === null) return null;\n const cycle = activeCycle;\n activeCycle = null;\n closeRequested = false;\n dismissingCycles.add(cycle);\n return cycle;\n },\n requestClose(reason, busy, policy = sheetBehaviorDefaults) {\n if (activeCycle === null || closeRequested) return false;\n if (!canDismissSheet(reason, busy, policy)) return false;\n closeRequested = true;\n return true;\n },\n completeDismiss(cycle) {\n return dismissingCycles.delete(cycle);\n },\n };\n}\n\n/**\n * 사용자가 시트 높이를 바꾸는 축이다. `sheetRecipe.sizes`는 제품이 여는 높이를 정하고,\n * 이것은 열린 뒤 **사용자가** 단계를 옮기는 문제다 — 지도 위 목록처럼 \"조금 보기 ↔ 크게\n * 보기\"가 필요한 화면이 그 자리다.\n *\n * `auto`는 단계에 들어가지 않는다. 내용이 정하는 높이라 \"다음 단계\"가 정의되지 않는다.\n *\n * **드래그 물리는 여기 없다.** 이 계약은 단계 목록과 \"다음/이전 단계가 무엇인가\"만 갖고,\n * 손가락 속도·관성·고무줄은 넣지 않았다 — 실제 제품 화면 없이 그 값을 정하면 기기에서\n * 다시 다 고치게 된다. 대신 **핸들이 접근 가능한 컨트롤**이라 키보드·보조기기에서도\n * 단계를 옮길 수 있다. 제스처는 제품이 얹고, 결과를 같은 `onDetentChange`로 보낸다.\n */\nexport type SheetDetent = \"medium\" | \"large\" | \"full\";\n\nexport const sheetDetentOrder = [\"medium\", \"large\", \"full\"] as const satisfies readonly SheetDetent[];\n\nexport function validateSheetDetents(detents: readonly SheetDetent[]): void {\n if (detents.length === 0) {\n throw new RangeError(\"Sheet detents must not be empty\");\n }\n const seen = new Set<SheetDetent>();\n for (const detent of detents) {\n if (!sheetDetentOrder.includes(detent)) {\n throw new TypeError(`Unsupported Sheet detent: ${String(detent)}`);\n }\n if (seen.has(detent)) throw new TypeError(`Duplicate Sheet detent: ${detent}`);\n seen.add(detent);\n }\n const ordered = [...detents].sort(\n (left, right) => sheetDetentOrder.indexOf(left) - sheetDetentOrder.indexOf(right),\n );\n if (ordered.some((detent, index) => detent !== detents[index])) {\n /*\n 정렬을 대신 해 주지 않는 이유: 순서가 곧 \"다음 단계\"의 의미라서, 제품이 적은 순서와\n 실제 이동 순서가 다르면 디버깅할 수 없는 화면이 된다.\n */\n throw new TypeError(\"Sheet detents must be listed from smallest to largest\");\n }\n}\n\nexport function resolveNextSheetDetent(\n detents: readonly SheetDetent[],\n current: SheetDetent,\n direction: \"expand\" | \"collapse\",\n): SheetDetent | null {\n validateSheetDetents(detents);\n const index = detents.indexOf(current);\n if (index === -1) {\n throw new RangeError(`Sheet current detent is not in the list: ${current}`);\n }\n const next = direction === \"expand\" ? index + 1 : index - 1;\n /** 끝에서는 `null`이다 — renderer가 \"더 갈 수 없다\"를 컨트롤 상태로 표현한다. */\n return next < 0 || next >= detents.length ? null : detents[next]!;\n}\n\n"]}
@@ -0,0 +1,168 @@
1
+ import type { LinkDestination } from "./link.js";
2
+ /**
3
+ * 데스크톱 웹의 세로 내비게이션. 관리자 화면·문서·작업 도구처럼 넓은 화면에서 좌우로
4
+ * 나뉜 셸의 왼쪽이다.
5
+ *
6
+ * `BottomNavigation`과 같은 문제를 푸는 다른 표면이 아니다 — 두 축이 다르다.
7
+ * BottomNavigation은 **3~5개 최상위 목적지**를 모바일 하단에 고정하고 그룹이 없다.
8
+ * Sidebar는 **그룹이 있는 긴 목록**이고 접힌다. 한 컴포넌트의 `orientation` 축으로
9
+ * 합치면 "그룹이 있는 하단 탭바"라는 존재하지 않는 조합이 타입에 생긴다.
10
+ *
11
+ * `Layout`의 sidebar 영역과도 다르다. Layout은 **자리**(폭·순서·반응형 규칙)를 갖고
12
+ * 내용을 모른다. 이 계약은 그 자리에 들어가는 **내비게이션 자체**다.
13
+ */
14
+ export type SidebarItemDescriptor<Id extends string = string> = Readonly<{
15
+ id: Id;
16
+ label: string;
17
+ destination?: LinkDestination;
18
+ /** 접힌 상태에서도 보이는 상태 수치. Badge/CounterBadge가 그린다. */
19
+ badgeCount?: number;
20
+ disabled?: boolean;
21
+ }>;
22
+ export type SidebarGroupDescriptor<Id extends string = string, GroupId extends string = string> = Readonly<{
23
+ id: GroupId;
24
+ /** 그룹 제목. 없으면 구분선만 있는 묶음이다. */
25
+ label?: string;
26
+ items: readonly SidebarItemDescriptor<Id>[];
27
+ }>;
28
+ export type SidebarDescriptor<Id extends string = string, GroupId extends string = string> = Readonly<{
29
+ accessibilityLabel: string;
30
+ groups: readonly SidebarGroupDescriptor<Id, GroupId>[];
31
+ /** 현재 위치. 링크 내비게이션의 `aria-current`가 된다. */
32
+ currentId: Id | null;
33
+ }>;
34
+ export declare function validateSidebarDescriptor<Id extends string, GroupId extends string>(descriptor: SidebarDescriptor<Id, GroupId>): void;
35
+ /**
36
+ * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —
37
+ * 접을 때 항목을 빼면 "접었더니 메뉴가 사라졌다"가 되고, 그건 다른 컴포넌트다.
38
+ * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.
39
+ */
40
+ export declare const sidebarDefaults: {
41
+ readonly collapsed: false;
42
+ };
43
+ export declare const sidebarRecipe: {
44
+ readonly slots: readonly ["root", "group", "groupLabel", "item", "label", "badge", "toggle"];
45
+ readonly defaults: {
46
+ readonly collapsed: false;
47
+ };
48
+ readonly widths: {
49
+ readonly expanded: 260;
50
+ readonly collapsed: 72;
51
+ };
52
+ readonly background: Readonly<{
53
+ source: "theme";
54
+ key: "surfaceAlt";
55
+ alpha?: number;
56
+ }>;
57
+ readonly border: Readonly<{
58
+ source: "theme";
59
+ key: "border";
60
+ alpha?: number;
61
+ }>;
62
+ readonly borderWidth: 1;
63
+ readonly paddingVertical: 12;
64
+ readonly paddingHorizontal: 8;
65
+ readonly gap: 4;
66
+ readonly groupGap: 16;
67
+ readonly groupLabel: {
68
+ readonly color: Readonly<{
69
+ source: "theme";
70
+ key: "textMuted";
71
+ alpha?: number;
72
+ }>;
73
+ readonly textVariant: "label";
74
+ readonly paddingHorizontal: 12;
75
+ readonly paddingVertical: 4;
76
+ };
77
+ readonly item: {
78
+ readonly minHeight: 44;
79
+ readonly paddingHorizontal: 12;
80
+ readonly gap: 12;
81
+ readonly radius: "md";
82
+ readonly label: {
83
+ readonly color: Readonly<{
84
+ source: "theme";
85
+ key: "textBody";
86
+ alpha?: number;
87
+ }>;
88
+ readonly textVariant: "body";
89
+ };
90
+ readonly description: {
91
+ readonly color: Readonly<{
92
+ source: "theme";
93
+ key: "textMuted";
94
+ alpha?: number;
95
+ }>;
96
+ readonly textVariant: "label";
97
+ };
98
+ readonly highlightedBackground: Readonly<{
99
+ source: "theme";
100
+ key: "text";
101
+ alpha?: number;
102
+ }>;
103
+ readonly focus: {
104
+ readonly color: Readonly<{
105
+ source: "theme";
106
+ key: "contentBrand";
107
+ alpha?: number;
108
+ }>;
109
+ readonly width: 2;
110
+ readonly offset: 2;
111
+ };
112
+ readonly selectedBackground: Readonly<{
113
+ source: "theme";
114
+ key: "primary";
115
+ alpha?: number;
116
+ }>;
117
+ readonly selectedIndicator: Readonly<{
118
+ source: "theme";
119
+ key: "contentBrand";
120
+ alpha?: number;
121
+ }>;
122
+ readonly danger: Readonly<{
123
+ source: "theme";
124
+ key: "danger";
125
+ alpha?: number;
126
+ }>;
127
+ };
128
+ readonly itemRadius: 12;
129
+ readonly itemMinHeight: 44;
130
+ readonly states: {
131
+ readonly focus: {
132
+ readonly color: Readonly<{
133
+ source: "theme";
134
+ key: "contentBrand";
135
+ alpha?: number;
136
+ }>;
137
+ readonly width: 2;
138
+ readonly offset: 2;
139
+ };
140
+ };
141
+ };
142
+ export declare const sidebarBehavior: {
143
+ readonly controlled: readonly ["collapsed", "defaultCollapsed", "onCollapsedChange"];
144
+ readonly inputs: readonly ["groups", "currentId", "accessibilityLabel"];
145
+ readonly events: readonly ["onNavigate"];
146
+ readonly defaults: {
147
+ readonly collapsed: false;
148
+ };
149
+ readonly stateAxes: {
150
+ readonly availability: readonly ["enabled", "disabled"];
151
+ readonly value: readonly ["selected"];
152
+ readonly interaction: readonly ["idle", "hover", "focusVisible", "pressed"];
153
+ };
154
+ readonly web: {
155
+ readonly roles: readonly ["navigation", "list", "listitem", "link", "button"];
156
+ readonly keyboard: readonly ["Tab", "Enter"];
157
+ /** Each item is a link in document order; no roving focus and no arrow-key grid. */
158
+ readonly focus: "native";
159
+ };
160
+ /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */
161
+ readonly native: {
162
+ readonly roles: readonly [];
163
+ readonly states: readonly [];
164
+ readonly actions: readonly [];
165
+ };
166
+ readonly scenarios: readonly ["the-current-item-is-announced-with-aria-current-not-only-painted", "collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name", "groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only", "an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look", "bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it", "layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it"];
167
+ };
168
+ //# sourceMappingURL=sidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar.d.ts","sourceRoot":"","sources":["../src/sidebar.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAIjD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,CAAC,EAAE,SAAS,MAAM,GAAG,MAAM,IAAI,QAAQ,CAAC;IACvE,EAAE,EAAE,EAAE,CAAC;IACP,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,MAAM,sBAAsB,CAChC,EAAE,SAAS,MAAM,GAAG,MAAM,EAC1B,OAAO,SAAS,MAAM,GAAG,MAAM,IAC7B,QAAQ,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,SAAS,qBAAqB,CAAC,EAAE,CAAC,EAAE,CAAC;CAC7C,CAAC,CAAC;AAEH,MAAM,MAAM,iBAAiB,CAC3B,EAAE,SAAS,MAAM,GAAG,MAAM,EAC1B,OAAO,SAAS,MAAM,GAAG,MAAM,IAC7B,QAAQ,CAAC;IACX,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,SAAS,sBAAsB,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC;IACvD,2CAA2C;IAC3C,SAAS,EAAE,EAAE,GAAG,IAAI,CAAC;CACtB,CAAC,CAAC;AAQH,wBAAgB,yBAAyB,CAAC,EAAE,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,EACjF,UAAU,EAAE,iBAAiB,CAAC,EAAE,EAAE,OAAO,CAAC,GACzC,IAAI,CA4BN;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe;;CAEyB,CAAC;AAEtD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCzB,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;QAaxB,oFAAoF;;;IAGtF,sGAAsG;;;;;;;CAUnE,CAAC"}
@@ -0,0 +1,98 @@
1
+ import { collectionItemContract, focusIndicatorContract } from "./component-contracts.js";
2
+ import { control, radius, spacing, stroke } from "./foundations.js";
3
+ import { semanticColors } from "./semantic-colors.js";
4
+ function assertNonEmpty(value, field) {
5
+ if (typeof value !== "string" || value.trim().length === 0) {
6
+ throw new TypeError(`Sidebar ${field} must not be empty`);
7
+ }
8
+ }
9
+ export function validateSidebarDescriptor(descriptor) {
10
+ assertNonEmpty(descriptor.accessibilityLabel, "accessibilityLabel");
11
+ if (!Array.isArray(descriptor.groups) || descriptor.groups.length === 0) {
12
+ throw new RangeError("Sidebar must contain at least one group");
13
+ }
14
+ const itemIds = new Set();
15
+ const groupIds = new Set();
16
+ for (const group of descriptor.groups) {
17
+ assertNonEmpty(group.id, "group id");
18
+ if (groupIds.has(group.id))
19
+ throw new TypeError(`Duplicate Sidebar group id: ${group.id}`);
20
+ groupIds.add(group.id);
21
+ if (group.label !== undefined)
22
+ assertNonEmpty(group.label, `group ${group.id} label`);
23
+ if (!Array.isArray(group.items) || group.items.length === 0) {
24
+ throw new RangeError(`Sidebar group ${group.id} must contain at least one item`);
25
+ }
26
+ for (const item of group.items) {
27
+ assertNonEmpty(item.id, "item id");
28
+ assertNonEmpty(item.label, `item ${item.id} label`);
29
+ if (itemIds.has(item.id))
30
+ throw new TypeError(`Duplicate Sidebar item id: ${item.id}`);
31
+ itemIds.add(item.id);
32
+ if (item.badgeCount !== undefined && (!Number.isInteger(item.badgeCount) || item.badgeCount < 0)) {
33
+ throw new RangeError(`Sidebar item ${item.id} badgeCount must be a non-negative integer`);
34
+ }
35
+ }
36
+ }
37
+ if (descriptor.currentId !== null && !itemIds.has(descriptor.currentId)) {
38
+ throw new RangeError(`Sidebar currentId does not match any item: ${String(descriptor.currentId)}`);
39
+ }
40
+ }
41
+ /**
42
+ * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —
43
+ * 접을 때 항목을 빼면 "접었더니 메뉴가 사라졌다"가 되고, 그건 다른 컴포넌트다.
44
+ * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.
45
+ */
46
+ export const sidebarDefaults = {
47
+ collapsed: false,
48
+ };
49
+ export const sidebarRecipe = {
50
+ slots: ["root", "group", "groupLabel", "item", "label", "badge", "toggle"],
51
+ defaults: sidebarDefaults,
52
+ widths: { expanded: 260, collapsed: 72 },
53
+ background: semanticColors.surface.sunken,
54
+ border: semanticColors.border.default,
55
+ borderWidth: stroke.default,
56
+ paddingVertical: spacing.sm,
57
+ paddingHorizontal: spacing.xs,
58
+ gap: spacing.xxs,
59
+ groupGap: spacing.md,
60
+ groupLabel: {
61
+ color: semanticColors.content.secondary,
62
+ textVariant: "label",
63
+ paddingHorizontal: spacing.sm,
64
+ paddingVertical: spacing.xxs,
65
+ },
66
+ item: collectionItemContract,
67
+ itemRadius: radius.md,
68
+ itemMinHeight: control.minTouchTarget,
69
+ states: { focus: focusIndicatorContract },
70
+ };
71
+ export const sidebarBehavior = {
72
+ controlled: ["collapsed", "defaultCollapsed", "onCollapsedChange"],
73
+ inputs: ["groups", "currentId", "accessibilityLabel"],
74
+ events: ["onNavigate"],
75
+ defaults: sidebarDefaults,
76
+ stateAxes: {
77
+ availability: ["enabled", "disabled"],
78
+ value: ["selected"],
79
+ interaction: ["idle", "hover", "focusVisible", "pressed"],
80
+ },
81
+ web: {
82
+ roles: ["navigation", "list", "listitem", "link", "button"],
83
+ keyboard: ["Tab", "Enter"],
84
+ /** Each item is a link in document order; no roving focus and no arrow-key grid. */
85
+ focus: "native",
86
+ },
87
+ /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */
88
+ native: { roles: [], states: [], actions: [] },
89
+ scenarios: [
90
+ "the-current-item-is-announced-with-aria-current-not-only-painted",
91
+ "collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name",
92
+ "groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only",
93
+ "an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look",
94
+ "bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it",
95
+ "layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it",
96
+ ],
97
+ };
98
+ //# sourceMappingURL=sidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar.js","sourceRoot":"","sources":["../src/sidebar.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BehaviorContract } from \"./behaviors.js\";\nimport type { ColorReference } from \"./color-references.js\";\nimport { collectionItemContract, focusIndicatorContract } from \"./component-contracts.js\";\nimport type { LinkDestination } from \"./link.js\";\nimport { control, radius, spacing, stroke } from \"./foundations.js\";\nimport { semanticColors } from \"./semantic-colors.js\";\n\n/**\n * 데스크톱 웹의 세로 내비게이션. 관리자 화면·문서·작업 도구처럼 넓은 화면에서 좌우로\n * 나뉜 셸의 왼쪽이다.\n *\n * `BottomNavigation`과 같은 문제를 푸는 다른 표면이 아니다 — 두 축이 다르다.\n * BottomNavigation은 **3~5개 최상위 목적지**를 모바일 하단에 고정하고 그룹이 없다.\n * Sidebar는 **그룹이 있는 긴 목록**이고 접힌다. 한 컴포넌트의 `orientation` 축으로\n * 합치면 \"그룹이 있는 하단 탭바\"라는 존재하지 않는 조합이 타입에 생긴다.\n *\n * `Layout`의 sidebar 영역과도 다르다. Layout은 **자리**(폭·순서·반응형 규칙)를 갖고\n * 내용을 모른다. 이 계약은 그 자리에 들어가는 **내비게이션 자체**다.\n */\nexport type SidebarItemDescriptor<Id extends string = string> = Readonly<{\n id: Id;\n label: string;\n destination?: LinkDestination;\n /** 접힌 상태에서도 보이는 상태 수치. Badge/CounterBadge가 그린다. */\n badgeCount?: number;\n disabled?: boolean;\n}>;\n\nexport type SidebarGroupDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n id: GroupId;\n /** 그룹 제목. 없으면 구분선만 있는 묶음이다. */\n label?: string;\n items: readonly SidebarItemDescriptor<Id>[];\n}>;\n\nexport type SidebarDescriptor<\n Id extends string = string,\n GroupId extends string = string,\n> = Readonly<{\n accessibilityLabel: string;\n groups: readonly SidebarGroupDescriptor<Id, GroupId>[];\n /** 현재 위치. 링크 내비게이션의 `aria-current`가 된다. */\n currentId: Id | null;\n}>;\n\nfunction assertNonEmpty(value: string, field: string): void {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`Sidebar ${field} must not be empty`);\n }\n}\n\nexport function validateSidebarDescriptor<Id extends string, GroupId extends string>(\n descriptor: SidebarDescriptor<Id, GroupId>,\n): void {\n assertNonEmpty(descriptor.accessibilityLabel, \"accessibilityLabel\");\n if (!Array.isArray(descriptor.groups) || descriptor.groups.length === 0) {\n throw new RangeError(\"Sidebar must contain at least one group\");\n }\n const itemIds = new Set<Id>();\n const groupIds = new Set<GroupId>();\n for (const group of descriptor.groups) {\n assertNonEmpty(group.id, \"group id\");\n if (groupIds.has(group.id)) throw new TypeError(`Duplicate Sidebar group id: ${group.id}`);\n groupIds.add(group.id);\n if (group.label !== undefined) assertNonEmpty(group.label, `group ${group.id} label`);\n if (!Array.isArray(group.items) || group.items.length === 0) {\n throw new RangeError(`Sidebar group ${group.id} must contain at least one item`);\n }\n for (const item of group.items) {\n assertNonEmpty(item.id, \"item id\");\n assertNonEmpty(item.label, `item ${item.id} label`);\n if (itemIds.has(item.id)) throw new TypeError(`Duplicate Sidebar item id: ${item.id}`);\n itemIds.add(item.id);\n if (item.badgeCount !== undefined && (!Number.isInteger(item.badgeCount) || item.badgeCount < 0)) {\n throw new RangeError(`Sidebar item ${item.id} badgeCount must be a non-negative integer`);\n }\n }\n }\n if (descriptor.currentId !== null && !itemIds.has(descriptor.currentId)) {\n throw new RangeError(`Sidebar currentId does not match any item: ${String(descriptor.currentId)}`);\n }\n}\n\n/**\n * 접힘은 **표시 밀도**이지 내용이 아니다. 접혀도 항목은 모두 남고 라벨만 숨는다 —\n * 접을 때 항목을 빼면 \"접었더니 메뉴가 사라졌다\"가 되고, 그건 다른 컴포넌트다.\n * 라벨이 숨으면 아이콘만으로는 이름을 알 수 없으므로 renderer가 접근성 이름을 유지한다.\n */\nexport const sidebarDefaults = {\n collapsed: false,\n} as const satisfies Readonly<{ collapsed: boolean }>;\n\nexport const sidebarRecipe = {\n slots: [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"] as const,\n defaults: sidebarDefaults,\n widths: { expanded: 260, collapsed: 72 },\n background: semanticColors.surface.sunken,\n border: semanticColors.border.default,\n borderWidth: stroke.default,\n paddingVertical: spacing.sm,\n paddingHorizontal: spacing.xs,\n gap: spacing.xxs,\n groupGap: spacing.md,\n groupLabel: {\n color: semanticColors.content.secondary,\n textVariant: \"label\" as const,\n paddingHorizontal: spacing.sm,\n paddingVertical: spacing.xxs,\n },\n item: collectionItemContract,\n itemRadius: radius.md,\n itemMinHeight: control.minTouchTarget,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\", \"group\", \"groupLabel\", \"item\", \"label\", \"badge\", \"toggle\"];\n defaults: { collapsed: boolean };\n widths: { expanded: number; collapsed: number };\n background: ColorReference;\n border: ColorReference;\n borderWidth: number;\n paddingVertical: number;\n paddingHorizontal: number;\n gap: number;\n groupGap: number;\n groupLabel: { color: ColorReference; textVariant: \"label\"; paddingHorizontal: number; paddingVertical: number };\n item: typeof collectionItemContract;\n itemRadius: number;\n itemMinHeight: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const sidebarBehavior = {\n controlled: [\"collapsed\", \"defaultCollapsed\", \"onCollapsedChange\"],\n inputs: [\"groups\", \"currentId\", \"accessibilityLabel\"],\n events: [\"onNavigate\"],\n defaults: sidebarDefaults,\n stateAxes: {\n availability: [\"enabled\", \"disabled\"],\n value: [\"selected\"],\n interaction: [\"idle\", \"hover\", \"focusVisible\", \"pressed\"],\n },\n web: {\n roles: [\"navigation\", \"list\", \"listitem\", \"link\", \"button\"],\n keyboard: [\"Tab\", \"Enter\"],\n /** Each item is a link in document order; no roving focus and no arrow-key grid. */\n focus: \"native\",\n },\n /** Web-only: a phone uses BottomNavigation, and a native tablet split view is a navigator concern. */\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"the-current-item-is-announced-with-aria-current-not-only-painted\",\n \"collapsing-hides-labels-but-keeps-every-item-and-its-accessible-name\",\n \"groups-are-real-list-groupings-with-their-own-label-not-visual-dividers-only\",\n \"an-item-badge-stays-visible-while-collapsed-because-it-is-the-reason-to-look\",\n \"bottom-navigation-owns-the-three-to-five-destination-mobile-case-this-contract-never-adds-it\",\n \"layout-owns-where-the-sidebar-sits-this-contract-owns-what-is-inside-it\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * 반복되는 머리 부분을 건너뛰고 본문으로 바로 가는 링크. WCAG 2.4.1(Bypass Blocks)이
3
+ * 요구하는 것이고, 키보드·스위치 사용자가 매 화면에서 내비게이션 20개를 지나지 않게 한다.
4
+ *
5
+ * `VisuallyHidden`으로 직접 만들 수 있어 보이지만 그러면 **가장 중요한 절반이 빠진다**:
6
+ * 이 링크는 숨어 있다가 **포커스를 받으면 반드시 보여야** 한다. 숨긴 채로 두면 포커스가
7
+ * 화면 밖으로 사라지는 것과 같아서 WCAG 2.4.7(Focus Visible) 위반이 된다. 그 "숨었다가
8
+ * 포커스에서 나타난다"가 이 계약의 전부다.
9
+ *
10
+ * Web 전용이다. 네이티브에는 건너뛸 문서 흐름이 없고 화면 리더가 rotor로 이동한다.
11
+ */
12
+ export type SkipNavDescriptor = Readonly<{
13
+ /** 본문 컨테이너의 id. 링크는 `#id`로 이동하고 그 요소가 포커스를 받는다. */
14
+ targetId: string;
15
+ /** 현지화된 문구. renderer가 만들지 않는다. */
16
+ label: string;
17
+ }>;
18
+ export declare function validateSkipNavDescriptor(descriptor: SkipNavDescriptor): void;
19
+ export declare const skipNavRecipe: {
20
+ readonly slots: readonly ["root"];
21
+ readonly minHeight: 44;
22
+ readonly paddingHorizontal: 16;
23
+ readonly radius: 12;
24
+ readonly background: Readonly<{
25
+ source: "theme";
26
+ key: "primary";
27
+ alpha?: number;
28
+ }>;
29
+ readonly color: Readonly<{
30
+ source: "theme";
31
+ key: "onPrimary";
32
+ alpha?: number;
33
+ }>;
34
+ /** 포커스를 받으면 화면 좌상단에 겹쳐 나타난다. 레이아웃을 밀지 않는다. */
35
+ readonly offset: 12;
36
+ readonly states: {
37
+ readonly focus: {
38
+ readonly color: Readonly<{
39
+ source: "theme";
40
+ key: "contentBrand";
41
+ alpha?: number;
42
+ }>;
43
+ readonly width: 2;
44
+ readonly offset: 2;
45
+ };
46
+ };
47
+ };
48
+ export declare const skipNavBehavior: {
49
+ readonly controlled: readonly [];
50
+ readonly inputs: readonly ["targetId", "label"];
51
+ readonly stateAxes: {
52
+ readonly interaction: readonly ["idle", "focusVisible"];
53
+ };
54
+ readonly web: {
55
+ readonly roles: readonly ["link"];
56
+ readonly keyboard: readonly ["Tab", "Enter"];
57
+ readonly focus: "native";
58
+ };
59
+ /** No native surface: there is no document flow to bypass. */
60
+ readonly native: {
61
+ readonly roles: readonly [];
62
+ readonly states: readonly [];
63
+ readonly actions: readonly [];
64
+ };
65
+ readonly scenarios: readonly ["hidden-until-focused-and-always-visible-once-focused", "it-is-the-first-tab-stop-on-the-page-or-it-solves-nothing", "activating-it-moves-focus-into-the-target-not-only-the-scroll-position", "a-target-id-written-with-a-leading-hash-is-rejected-instead-of-silently-doubled"];
66
+ };
67
+ //# sourceMappingURL=skip-nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-nav.d.ts","sourceRoot":"","sources":["../src/skip-nav.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAiB7E;AAED,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;IAOxB,8CAA8C;;;;;;;;;;;;;CAY/C,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;IAK1B,8DAA8D;;;;;;;CAQ3B,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { focusIndicatorContract } from "./component-contracts.js";
2
+ import { control, radius, spacing } from "./foundations.js";
3
+ import { semanticColors } from "./semantic-colors.js";
4
+ export function validateSkipNavDescriptor(descriptor) {
5
+ if (descriptor === null || typeof descriptor !== "object") {
6
+ throw new TypeError("SkipNav descriptor must be an object");
7
+ }
8
+ if (typeof descriptor.targetId !== "string" || descriptor.targetId.trim().length === 0) {
9
+ throw new TypeError("SkipNav targetId must not be empty");
10
+ }
11
+ if (descriptor.targetId.startsWith("#")) {
12
+ /*
13
+ 제품이 `#main`을 넘기면 링크가 `##main`이 되어 조용히 깨진다. 잘라내 주는 대신
14
+ 거절하는 이유는 두 표기가 섞이면 어느 쪽이 맞는지 아무도 모르게 되기 때문이다.
15
+ */
16
+ throw new TypeError("SkipNav targetId must be a bare id without '#'");
17
+ }
18
+ if (typeof descriptor.label !== "string" || descriptor.label.trim().length === 0) {
19
+ throw new TypeError("SkipNav label must not be empty");
20
+ }
21
+ }
22
+ export const skipNavRecipe = {
23
+ slots: ["root"],
24
+ minHeight: control.minTouchTarget,
25
+ paddingHorizontal: spacing.md,
26
+ radius: radius.md,
27
+ background: semanticColors.action.brand.background,
28
+ color: semanticColors.action.brand.content,
29
+ /** 포커스를 받으면 화면 좌상단에 겹쳐 나타난다. 레이아웃을 밀지 않는다. */
30
+ offset: spacing.sm,
31
+ states: { focus: focusIndicatorContract },
32
+ };
33
+ export const skipNavBehavior = {
34
+ controlled: [],
35
+ inputs: ["targetId", "label"],
36
+ stateAxes: { interaction: ["idle", "focusVisible"] },
37
+ web: { roles: ["link"], keyboard: ["Tab", "Enter"], focus: "native" },
38
+ /** No native surface: there is no document flow to bypass. */
39
+ native: { roles: [], states: [], actions: [] },
40
+ scenarios: [
41
+ "hidden-until-focused-and-always-visible-once-focused",
42
+ "it-is-the-first-tab-stop-on-the-page-or-it-solves-nothing",
43
+ "activating-it-moves-focus-into-the-target-not-only-the-scroll-position",
44
+ "a-target-id-written-with-a-leading-hash-is-rejected-instead-of-silently-doubled",
45
+ ],
46
+ };
47
+ //# sourceMappingURL=skip-nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skip-nav.js","sourceRoot":"","sources":["../src/skip-nav.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAoBtD,MAAM,UAAU,yBAAyB,CAAC,UAA6B;IACrE,IAAI,UAAU,KAAK,IAAI,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,OAAO,UAAU,CAAC,QAAQ,KAAK,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvF,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxC;;;UAGE;QACF,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjF,MAAM,IAAI,SAAS,CAAC,iCAAiC,CAAC,CAAC;IACzD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,EAAE,CAAC,MAAM,CAAU;IACxB,SAAS,EAAE,OAAO,CAAC,cAAc;IACjC,iBAAiB,EAAE,OAAO,CAAC,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC,EAAE;IACjB,UAAU,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;IAClD,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO;IAC1C,8CAA8C;IAC9C,MAAM,EAAE,OAAO,CAAC,EAAE;IAClB,MAAM,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;CAU1C,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,UAAU,EAAE,EAAE;IACd,MAAM,EAAE,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,EAAE,WAAW,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,EAAE;IACpD,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;IACrE,8DAA8D;IAC9D,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;IAC9C,SAAS,EAAE;QACT,sDAAsD;QACtD,2DAA2D;QAC3D,wEAAwE;QACxE,iFAAiF;KAClF;CACkC,CAAC","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\";\n\n/**\n * 반복되는 머리 부분을 건너뛰고 본문으로 바로 가는 링크. WCAG 2.4.1(Bypass Blocks)이\n * 요구하는 것이고, 키보드·스위치 사용자가 매 화면에서 내비게이션 20개를 지나지 않게 한다.\n *\n * `VisuallyHidden`으로 직접 만들 수 있어 보이지만 그러면 **가장 중요한 절반이 빠진다**:\n * 이 링크는 숨어 있다가 **포커스를 받으면 반드시 보여야** 한다. 숨긴 채로 두면 포커스가\n * 화면 밖으로 사라지는 것과 같아서 WCAG 2.4.7(Focus Visible) 위반이 된다. 그 \"숨었다가\n * 포커스에서 나타난다\"가 이 계약의 전부다.\n *\n * Web 전용이다. 네이티브에는 건너뛸 문서 흐름이 없고 화면 리더가 rotor로 이동한다.\n */\nexport type SkipNavDescriptor = Readonly<{\n /** 본문 컨테이너의 id. 링크는 `#id`로 이동하고 그 요소가 포커스를 받는다. */\n targetId: string;\n /** 현지화된 문구. renderer가 만들지 않는다. */\n label: string;\n}>;\n\nexport function validateSkipNavDescriptor(descriptor: SkipNavDescriptor): void {\n if (descriptor === null || typeof descriptor !== \"object\") {\n throw new TypeError(\"SkipNav descriptor must be an object\");\n }\n if (typeof descriptor.targetId !== \"string\" || descriptor.targetId.trim().length === 0) {\n throw new TypeError(\"SkipNav targetId must not be empty\");\n }\n if (descriptor.targetId.startsWith(\"#\")) {\n /*\n 제품이 `#main`을 넘기면 링크가 `##main`이 되어 조용히 깨진다. 잘라내 주는 대신\n 거절하는 이유는 두 표기가 섞이면 어느 쪽이 맞는지 아무도 모르게 되기 때문이다.\n */\n throw new TypeError(\"SkipNav targetId must be a bare id without '#'\");\n }\n if (typeof descriptor.label !== \"string\" || descriptor.label.trim().length === 0) {\n throw new TypeError(\"SkipNav label must not be empty\");\n }\n}\n\nexport const skipNavRecipe = {\n slots: [\"root\"] as const,\n minHeight: control.minTouchTarget,\n paddingHorizontal: spacing.md,\n radius: radius.md,\n background: semanticColors.action.brand.background,\n color: semanticColors.action.brand.content,\n /** 포커스를 받으면 화면 좌상단에 겹쳐 나타난다. 레이아웃을 밀지 않는다. */\n offset: spacing.sm,\n states: { focus: focusIndicatorContract },\n} as const satisfies {\n slots: readonly [\"root\"];\n minHeight: number;\n paddingHorizontal: number;\n radius: number;\n background: ColorReference;\n color: ColorReference;\n offset: number;\n states: { focus: typeof focusIndicatorContract };\n};\n\nexport const skipNavBehavior = {\n controlled: [],\n inputs: [\"targetId\", \"label\"],\n stateAxes: { interaction: [\"idle\", \"focusVisible\"] },\n web: { roles: [\"link\"], keyboard: [\"Tab\", \"Enter\"], focus: \"native\" },\n /** No native surface: there is no document flow to bypass. */\n native: { roles: [], states: [], actions: [] },\n scenarios: [\n \"hidden-until-focused-and-always-visible-once-focused\",\n \"it-is-the-first-tab-stop-on-the-page-or-it-solves-nothing\",\n \"activating-it-moves-focus-into-the-target-not-only-the-scroll-position\",\n \"a-target-id-written-with-a-leading-hash-is-rejected-instead-of-silently-doubled\",\n ],\n} as const satisfies BehaviorContract;\n"]}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * 자유 입력으로 여러 값을 모으는 필드 — 해시태그, 초대할 사람, 검색 필터.
3
+ *
4
+ * Combobox와 다르다. Combobox는 **주어진 목록에서 고른다**. TagsInput은 목록이 없거나
5
+ * 있어도 목록 밖의 값을 만들 수 있다. 그래서 Combobox에 `multiple` 축을 붙이는 대신
6
+ * 별도로 둔다 — 합치면 "목록에 없는 값을 고를 수 있는 Select"라는 모순이 생긴다.
7
+ * (제품이 후보 목록을 함께 보여 주고 싶으면 이 필드와 Combobox의 listbox를 조합한다.)
8
+ *
9
+ * 이 계약이 소유하는 것은 **언제 태그가 확정되는가**와 **빈 입력에서 지우면 무엇이
10
+ * 지워지는가** 둘이다. 중복 허용 여부·최대 개수·글자 규칙은 제품 정책이라 판정 결과를
11
+ * 받기만 한다.
12
+ */
13
+ export type TagsInputCommitKey = "Enter" | "Comma" | "Space" | "Blur";
14
+ /**
15
+ * 후보 목록. Combobox의 listbox 계약을 **이 필드 안에서** 쓰는 형태다.
16
+ *
17
+ * "Combobox에 multiple을 붙이자"가 아니라 여기로 온 이유: Combobox의 값은 목록 안의
18
+ * 키 하나이고 이 필드의 값은 문자열 배열이다. 값 모양이 다른 둘을 한 컴포넌트에 넣으면
19
+ * 모든 소비자가 매번 좁히기를 해야 한다. 자유 입력이 이미 여기 있으므로 후보를 여기에
20
+ * 더하는 쪽이 값 하나로 끝난다.
21
+ *
22
+ * 필터링은 제품이 한다 — 이 계약은 "무엇을 보여줄지"가 아니라 "고른 것을 어떻게
23
+ * 확정할지"만 안다.
24
+ */
25
+ export type TagsInputSuggestion = Readonly<{
26
+ id: string;
27
+ label: string;
28
+ /** 확정될 문자열. 생략하면 `label`. */
29
+ value?: string;
30
+ disabled?: boolean;
31
+ }>;
32
+ /**
33
+ * 활성 후보를 방향키로 옮긴 결과. 목록 끝에서 순환한다 — 후보가 보통 짧아서 끝에
34
+ * 막히면 반대 끝까지 다시 내려가야 한다.
35
+ */
36
+ export declare function resolveTagsInputActiveSuggestion(suggestions: readonly TagsInputSuggestion[], currentId: string | null, direction: "next" | "previous"): string | null;
37
+ export declare const tagsInputBehaviorDefaults: {
38
+ /**
39
+ * Enter만 기본이다. 쉼표·공백은 언어에 따라 값의 일부다(한국어 태그에 공백이 흔하다).
40
+ * 제품이 자기 도메인을 알 때 켠다.
41
+ */
42
+ readonly commitKeys: readonly TagsInputCommitKey[];
43
+ /** 빈 입력에서 Backspace는 마지막 태그를 **선택**만 한다. 한 번 더 눌러야 지운다. */
44
+ readonly backspaceRemovesLastTag: false;
45
+ };
46
+ export type TagsInputRejectionReason = "empty" | "duplicate" | "limit" | "invalid";
47
+ export type TagsInputCommitResult = Readonly<{
48
+ accepted: boolean;
49
+ /** 정리된 값(앞뒤 공백 제거). 거절돼도 제품이 문구에 쓸 수 있게 돌려준다. */
50
+ value: string;
51
+ reason?: TagsInputRejectionReason;
52
+ }>;
53
+ export type TagsInputPolicy = Readonly<{
54
+ /** 같은 값을 다시 넣을 수 있는가. 기본은 막는다. */
55
+ allowDuplicates?: boolean;
56
+ maxTags?: number;
57
+ /** 제품 도메인 규칙. 실패 사유는 `invalid` 하나로 합쳐 돌려준다. */
58
+ isValid?: (value: string) => boolean;
59
+ }>;
60
+ /**
61
+ * 태그 하나를 확정할 수 있는지 판정한다. 값을 바꾸지 않고 결과만 돌려준다 —
62
+ * 실제 상태 갱신은 renderer가, 사용자에게 보여 줄 문장은 제품이 만든다.
63
+ */
64
+ export declare function resolveTagsInputCommit(current: readonly string[], rawValue: string, policy?: TagsInputPolicy): TagsInputCommitResult;
65
+ /** 태그 하나를 뺀 목록. 같은 값이 여러 개면 해당 위치 하나만 지운다. */
66
+ export declare function removeTagAt(current: readonly string[], index: number): readonly string[];
67
+ export declare const tagsInputRecipe: {
68
+ readonly slots: readonly ["root", "frame", "tag", "remove", "input"];
69
+ readonly defaults: {
70
+ readonly commitKeys: readonly TagsInputCommitKey[];
71
+ };
72
+ readonly frame: {
73
+ readonly minHeight: 44;
74
+ readonly paddingHorizontal: 16;
75
+ readonly paddingVertical: 4;
76
+ readonly gap: 8;
77
+ readonly border: Readonly<{
78
+ source: "theme";
79
+ key: "textMuted";
80
+ alpha?: number;
81
+ }>;
82
+ readonly background: Readonly<{
83
+ source: "theme";
84
+ key: "surface";
85
+ alpha?: number;
86
+ }>;
87
+ };
88
+ readonly tag: {
89
+ readonly minHeight: 28;
90
+ readonly paddingHorizontal: 8;
91
+ readonly gap: 4;
92
+ readonly background: Readonly<{
93
+ source: "theme";
94
+ key: "surfaceAlt";
95
+ alpha?: number;
96
+ }>;
97
+ readonly color: Readonly<{
98
+ source: "theme";
99
+ key: "textBody";
100
+ alpha?: number;
101
+ }>;
102
+ };
103
+ /** 삭제 버튼은 태그보다 작아도 되지만 포인터 타깃은 공용 최소값을 지킨다. */
104
+ readonly remove: {
105
+ readonly minTouchTarget: 44;
106
+ readonly color: Readonly<{
107
+ source: "theme";
108
+ key: "textMuted";
109
+ alpha?: number;
110
+ }>;
111
+ };
112
+ readonly selectedTagOutline: Readonly<{
113
+ source: "theme";
114
+ key: "contentBrand";
115
+ alpha?: number;
116
+ }>;
117
+ readonly states: {
118
+ readonly focus: {
119
+ readonly color: Readonly<{
120
+ source: "theme";
121
+ key: "contentBrand";
122
+ alpha?: number;
123
+ }>;
124
+ readonly width: 2;
125
+ readonly offset: 2;
126
+ };
127
+ };
128
+ };
129
+ export declare const tagsInputBehavior: {
130
+ readonly controlled: readonly ["tags", "defaultTags", "onTagsChange"];
131
+ readonly inputs: readonly ["policy", "commitKeys", "label", "removeLabel"];
132
+ /**
133
+ * `onDraftChange`가 없으면 제품이 후보를 거를 수 없다 — 무엇을 입력 중인지 모르기
134
+ * 때문이다. 후보 목록을 계약에 넣는 순간 이 이벤트도 함께 필요하다.
135
+ */
136
+ readonly events: readonly ["onReject", "onDraftChange"];
137
+ readonly defaults: {
138
+ readonly backspaceRemovesLastTag: false;
139
+ };
140
+ readonly stateAxes: {
141
+ readonly availability: readonly ["enabled", "disabled"];
142
+ readonly value: readonly ["empty", "filled"];
143
+ readonly validation: readonly ["valid", "invalid"];
144
+ };
145
+ readonly web: {
146
+ readonly roles: readonly ["combobox", "listbox", "option", "button", "list", "listitem"];
147
+ readonly keyboard: readonly ["Enter", "Backspace", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Tab"];
148
+ readonly focus: "native";
149
+ };
150
+ readonly native: {
151
+ readonly roles: readonly ["button"];
152
+ readonly states: readonly ["disabled"];
153
+ readonly actions: readonly ["commit", "remove"];
154
+ };
155
+ readonly scenarios: readonly ["enter-commits-the-trimmed-value-and-clears-the-input", "an-empty-or-whitespace-only-value-is-never-committed", "duplicates-are-rejected-by-default-and-the-reason-is-reported-not-swallowed", "backspace-in-an-empty-input-selects-the-last-tag-before-a-second-press-removes-it", "every-tag-carries-its-own-remove-control-with-a-localized-name", "the-policy-judges-and-the-product-writes-the-sentence-the-user-reads", "a-candidate-list-lives-in-this-field-because-the-value-shape-is-this-fields-not-comboboxs", "enter-commits-the-active-candidate-when-one-is-highlighted-and-the-typed-text-otherwise", "candidate-filtering-belongs-to-the-product-the-contract-only-commits-the-choice"];
156
+ };
157
+ //# sourceMappingURL=tags-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags-input.d.ts","sourceRoot":"","sources":["../src/tags-input.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,WAAW,EAAE,SAAS,mBAAmB,EAAE,EAC3C,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,SAAS,EAAE,MAAM,GAAG,UAAU,GAC7B,MAAM,GAAG,IAAI,CAOf;AAED,eAAO,MAAM,yBAAyB;IACpC;;;OAGG;yBACsB,SAAS,kBAAkB,EAAE;IACtD,2DAA2D;;CAEnD,CAAC;AAEX,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;AAEnF,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,wBAAwB,CAAC;CACnC,CAAC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,kCAAkC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,eAAoB,GAC3B,qBAAqB,CAavB;AAED,8CAA8C;AAC9C,wBAAgB,WAAW,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAKxF;AAED,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkB1B,+CAA+C;;;;;;;;;;;;;;;;;;;;;;;;;CAmBhD,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;IAG5B;;;OAGG;;;;;;;;;;;;;;;;;;;;;CAyBgC,CAAC"}