@k2b/ui 0.49.0 → 0.50.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 (265) hide show
  1. package/dist/base.css +40 -2
  2. package/dist/browser/actions/ContextMenu.js +3 -0
  3. package/dist/browser/actions/ContextMenu.js.map +1 -1
  4. package/dist/browser/charts/core/area.js +190 -0
  5. package/dist/browser/charts/core/area.js.map +1 -0
  6. package/dist/browser/charts/core/calendar.js +186 -0
  7. package/dist/browser/charts/core/calendar.js.map +1 -0
  8. package/dist/browser/charts/core/chart-map-data.js +9 -0
  9. package/dist/browser/charts/core/chart-map-data.js.map +1 -0
  10. package/dist/browser/charts/core/index.js +12 -0
  11. package/dist/browser/charts/core/index.js.map +1 -0
  12. package/dist/browser/charts/core/inspection.js +9 -0
  13. package/dist/browser/charts/core/inspection.js.map +1 -0
  14. package/dist/browser/charts/core/registry.js +28 -0
  15. package/dist/browser/charts/core/registry.js.map +1 -0
  16. package/dist/browser/charts/core/render.js +103 -0
  17. package/dist/browser/charts/core/render.js.map +1 -0
  18. package/dist/browser/charts/core/snapshot.js +65 -0
  19. package/dist/browser/charts/core/snapshot.js.map +1 -0
  20. package/dist/browser/charts/core/svg.js +2542 -0
  21. package/dist/browser/charts/core/svg.js.map +1 -0
  22. package/dist/browser/charts/core/termination.test-fixture.js +162 -0
  23. package/dist/browser/charts/core/termination.test-fixture.js.map +1 -0
  24. package/dist/browser/charts/core/text.js +13 -0
  25. package/dist/browser/charts/core/text.js.map +1 -0
  26. package/dist/browser/charts/core/types.js +8 -0
  27. package/dist/browser/charts/core/types.js.map +1 -0
  28. package/dist/browser/charts.js +4 -2
  29. package/dist/browser/charts.js.map +1 -1
  30. package/dist/browser/content/BalanceSummary.js +106 -0
  31. package/dist/browser/content/BalanceSummary.js.map +1 -0
  32. package/dist/browser/content/Chart.js +134 -115
  33. package/dist/browser/content/Chart.js.map +1 -1
  34. package/dist/browser/content/ChartExplorer.js +366 -122
  35. package/dist/browser/content/ChartExplorer.js.map +1 -1
  36. package/dist/browser/content/ChartSnapshotView.js +16 -9
  37. package/dist/browser/content/ChartSnapshotView.js.map +1 -1
  38. package/dist/browser/content/DataTable.js +676 -293
  39. package/dist/browser/content/DataTable.js.map +1 -1
  40. package/dist/browser/content/DataTableTools.js +119 -0
  41. package/dist/browser/content/DataTableTools.js.map +1 -0
  42. package/dist/browser/content/FlowAmount.js +82 -0
  43. package/dist/browser/content/FlowAmount.js.map +1 -0
  44. package/dist/browser/content/balance-summary-messages.js +26 -0
  45. package/dist/browser/content/balance-summary-messages.js.map +1 -0
  46. package/dist/browser/content/chart-bar.js +6 -6
  47. package/dist/browser/content/chart-bar.js.map +1 -1
  48. package/dist/browser/content/chart-calendar.js +43 -0
  49. package/dist/browser/content/chart-calendar.js.map +1 -0
  50. package/dist/browser/content/chart-inspection-core.js +164 -37
  51. package/dist/browser/content/chart-inspection-core.js.map +1 -1
  52. package/dist/browser/content/chart-inspection.js.map +1 -1
  53. package/dist/browser/content/chart-line-gaps.js +2 -2
  54. package/dist/browser/content/chart-line-gaps.js.map +1 -1
  55. package/dist/browser/content/chart-map-viewport.js.map +1 -1
  56. package/dist/browser/content/chart-message-catalog.js +164 -0
  57. package/dist/browser/content/chart-message-catalog.js.map +1 -0
  58. package/dist/browser/content/chart-messages.js +2 -138
  59. package/dist/browser/content/chart-messages.js.map +1 -1
  60. package/dist/browser/content/chart-snapshot.js +1 -62
  61. package/dist/browser/content/chart-snapshot.js.map +1 -1
  62. package/dist/browser/content/chart-state-timeline.js +1 -1
  63. package/dist/browser/content/chart-state-timeline.js.map +1 -1
  64. package/dist/browser/content/chart-svg.js +2 -1
  65. package/dist/browser/content/chart-svg.js.map +1 -1
  66. package/dist/browser/content/chart-time-axis.js +139 -0
  67. package/dist/browser/content/chart-time-axis.js.map +1 -0
  68. package/dist/browser/content/data-table-columns.js +160 -0
  69. package/dist/browser/content/data-table-columns.js.map +1 -0
  70. package/dist/browser/content/data-table-messages.js +38 -2
  71. package/dist/browser/content/data-table-messages.js.map +1 -1
  72. package/dist/browser/content/flow-amount-messages.js +26 -0
  73. package/dist/browser/content/flow-amount-messages.js.map +1 -0
  74. package/dist/browser/content/index.js +4 -0
  75. package/dist/browser/content/index.js.map +1 -1
  76. package/dist/browser/inputs/DatePicker.js +5 -4
  77. package/dist/browser/inputs/DatePicker.js.map +1 -1
  78. package/dist/browser/inputs/SelectChip.js +50 -17
  79. package/dist/browser/inputs/SelectChip.js.map +1 -1
  80. package/dist/browser/inputs/date-picker.js +13 -0
  81. package/dist/browser/inputs/date-picker.js.map +1 -1
  82. package/dist/browser/intl/Format.js +31 -8
  83. package/dist/browser/intl/Format.js.map +1 -1
  84. package/dist/browser/layout/AppWorkspace.js +7 -1
  85. package/dist/browser/layout/AppWorkspace.js.map +1 -1
  86. package/dist/browser/surfaces/BudgetList.js +281 -0
  87. package/dist/browser/surfaces/BudgetList.js.map +1 -0
  88. package/dist/browser/surfaces/ProgressBar.js +134 -21
  89. package/dist/browser/surfaces/ProgressBar.js.map +1 -1
  90. package/dist/browser/surfaces/StatCell.js +320 -144
  91. package/dist/browser/surfaces/StatCell.js.map +1 -1
  92. package/dist/browser/surfaces/StatGrid.js +75 -57
  93. package/dist/browser/surfaces/StatGrid.js.map +1 -1
  94. package/dist/browser/surfaces/budget-messages.js +64 -0
  95. package/dist/browser/surfaces/budget-messages.js.map +1 -0
  96. package/dist/browser/surfaces/delta-badge.js +65 -0
  97. package/dist/browser/surfaces/delta-badge.js.map +1 -0
  98. package/dist/browser/surfaces/index.js +2 -1
  99. package/dist/browser/surfaces/index.js.map +1 -1
  100. package/dist/browser/surfaces/person-color.js +2 -2
  101. package/dist/browser/surfaces/person-color.js.map +1 -1
  102. package/dist/browser/surfaces/stat-cell-messages.js +41 -0
  103. package/dist/browser/surfaces/stat-cell-messages.js.map +1 -0
  104. package/dist/browser/widgets/Dashboard.js +137 -0
  105. package/dist/browser/widgets/Dashboard.js.map +1 -0
  106. package/dist/browser/widgets/index.js +1 -0
  107. package/dist/browser/widgets/index.js.map +1 -1
  108. package/dist/ssr/actions/ContextMenu.js +3 -0
  109. package/dist/ssr/actions/ContextMenu.js.map +1 -1
  110. package/dist/ssr/charts/core/area.js +190 -0
  111. package/dist/ssr/charts/core/area.js.map +1 -0
  112. package/dist/ssr/charts/core/calendar.js +186 -0
  113. package/dist/ssr/charts/core/calendar.js.map +1 -0
  114. package/dist/ssr/charts/core/chart-map-data.js +9 -0
  115. package/dist/ssr/charts/core/chart-map-data.js.map +1 -0
  116. package/dist/ssr/charts/core/index.js +12 -0
  117. package/dist/ssr/charts/core/index.js.map +1 -0
  118. package/dist/ssr/charts/core/inspection.js +9 -0
  119. package/dist/ssr/charts/core/inspection.js.map +1 -0
  120. package/dist/ssr/charts/core/registry.js +28 -0
  121. package/dist/ssr/charts/core/registry.js.map +1 -0
  122. package/dist/ssr/charts/core/render.js +103 -0
  123. package/dist/ssr/charts/core/render.js.map +1 -0
  124. package/dist/ssr/charts/core/snapshot.js +65 -0
  125. package/dist/ssr/charts/core/snapshot.js.map +1 -0
  126. package/dist/ssr/charts/core/svg.js +2542 -0
  127. package/dist/ssr/charts/core/svg.js.map +1 -0
  128. package/dist/ssr/charts/core/termination.test-fixture.js +162 -0
  129. package/dist/ssr/charts/core/termination.test-fixture.js.map +1 -0
  130. package/dist/ssr/charts/core/text.js +13 -0
  131. package/dist/ssr/charts/core/text.js.map +1 -0
  132. package/dist/ssr/charts/core/types.js +8 -0
  133. package/dist/ssr/charts/core/types.js.map +1 -0
  134. package/dist/ssr/charts.js +4 -2
  135. package/dist/ssr/charts.js.map +1 -1
  136. package/dist/ssr/content/BalanceSummary.js +75 -0
  137. package/dist/ssr/content/BalanceSummary.js.map +1 -0
  138. package/dist/ssr/content/Chart.js +90 -95
  139. package/dist/ssr/content/Chart.js.map +1 -1
  140. package/dist/ssr/content/ChartExplorer.js +190 -46
  141. package/dist/ssr/content/ChartExplorer.js.map +1 -1
  142. package/dist/ssr/content/ChartSnapshotView.js +5 -1
  143. package/dist/ssr/content/ChartSnapshotView.js.map +1 -1
  144. package/dist/ssr/content/DataTable.js +287 -78
  145. package/dist/ssr/content/DataTable.js.map +1 -1
  146. package/dist/ssr/content/DataTableTools.js +106 -0
  147. package/dist/ssr/content/DataTableTools.js.map +1 -0
  148. package/dist/ssr/content/FlowAmount.js +58 -0
  149. package/dist/ssr/content/FlowAmount.js.map +1 -0
  150. package/dist/ssr/content/balance-summary-messages.js +26 -0
  151. package/dist/ssr/content/balance-summary-messages.js.map +1 -0
  152. package/dist/ssr/content/chart-bar.js +6 -6
  153. package/dist/ssr/content/chart-bar.js.map +1 -1
  154. package/dist/ssr/content/chart-calendar.js +43 -0
  155. package/dist/ssr/content/chart-calendar.js.map +1 -0
  156. package/dist/ssr/content/chart-inspection-core.js +164 -37
  157. package/dist/ssr/content/chart-inspection-core.js.map +1 -1
  158. package/dist/ssr/content/chart-inspection.js.map +1 -1
  159. package/dist/ssr/content/chart-line-gaps.js +2 -2
  160. package/dist/ssr/content/chart-line-gaps.js.map +1 -1
  161. package/dist/ssr/content/chart-map-viewport.js.map +1 -1
  162. package/dist/ssr/content/chart-message-catalog.js +164 -0
  163. package/dist/ssr/content/chart-message-catalog.js.map +1 -0
  164. package/dist/ssr/content/chart-messages.js +2 -138
  165. package/dist/ssr/content/chart-messages.js.map +1 -1
  166. package/dist/ssr/content/chart-snapshot.js +1 -62
  167. package/dist/ssr/content/chart-snapshot.js.map +1 -1
  168. package/dist/ssr/content/chart-state-timeline.js +1 -1
  169. package/dist/ssr/content/chart-state-timeline.js.map +1 -1
  170. package/dist/ssr/content/chart-svg.js +2 -1
  171. package/dist/ssr/content/chart-svg.js.map +1 -1
  172. package/dist/ssr/content/chart-time-axis.js +139 -0
  173. package/dist/ssr/content/chart-time-axis.js.map +1 -0
  174. package/dist/ssr/content/data-table-columns.js +160 -0
  175. package/dist/ssr/content/data-table-columns.js.map +1 -0
  176. package/dist/ssr/content/data-table-messages.js +38 -2
  177. package/dist/ssr/content/data-table-messages.js.map +1 -1
  178. package/dist/ssr/content/flow-amount-messages.js +26 -0
  179. package/dist/ssr/content/flow-amount-messages.js.map +1 -0
  180. package/dist/ssr/content/index.js +4 -0
  181. package/dist/ssr/content/index.js.map +1 -1
  182. package/dist/ssr/inputs/DatePicker.js +5 -4
  183. package/dist/ssr/inputs/DatePicker.js.map +1 -1
  184. package/dist/ssr/inputs/SelectChip.js +26 -9
  185. package/dist/ssr/inputs/SelectChip.js.map +1 -1
  186. package/dist/ssr/inputs/date-picker.js +13 -0
  187. package/dist/ssr/inputs/date-picker.js.map +1 -1
  188. package/dist/ssr/intl/Format.js +22 -2
  189. package/dist/ssr/intl/Format.js.map +1 -1
  190. package/dist/ssr/layout/AppWorkspace.js +7 -1
  191. package/dist/ssr/layout/AppWorkspace.js.map +1 -1
  192. package/dist/ssr/surfaces/BudgetList.js +170 -0
  193. package/dist/ssr/surfaces/BudgetList.js.map +1 -0
  194. package/dist/ssr/surfaces/ProgressBar.js +96 -5
  195. package/dist/ssr/surfaces/ProgressBar.js.map +1 -1
  196. package/dist/ssr/surfaces/StatCell.js +157 -51
  197. package/dist/ssr/surfaces/StatCell.js.map +1 -1
  198. package/dist/ssr/surfaces/StatGrid.js +24 -35
  199. package/dist/ssr/surfaces/StatGrid.js.map +1 -1
  200. package/dist/ssr/surfaces/budget-messages.js +64 -0
  201. package/dist/ssr/surfaces/budget-messages.js.map +1 -0
  202. package/dist/ssr/surfaces/delta-badge.js +41 -0
  203. package/dist/ssr/surfaces/delta-badge.js.map +1 -0
  204. package/dist/ssr/surfaces/index.js +2 -1
  205. package/dist/ssr/surfaces/index.js.map +1 -1
  206. package/dist/ssr/surfaces/person-color.js +2 -2
  207. package/dist/ssr/surfaces/person-color.js.map +1 -1
  208. package/dist/ssr/surfaces/stat-cell-messages.js +41 -0
  209. package/dist/ssr/surfaces/stat-cell-messages.js.map +1 -0
  210. package/dist/ssr/widgets/Dashboard.js +84 -0
  211. package/dist/ssr/widgets/Dashboard.js.map +1 -0
  212. package/dist/ssr/widgets/index.js +1 -0
  213. package/dist/ssr/widgets/index.js.map +1 -1
  214. package/dist/styles.css +1 -1
  215. package/dist/types/charts/core/area.d.ts +62 -0
  216. package/dist/types/charts/core/calendar.d.ts +57 -0
  217. package/dist/types/charts/core/chart-map-data.d.ts +3 -0
  218. package/dist/types/charts/core/index.d.ts +11 -0
  219. package/dist/types/charts/core/inspection.d.ts +16 -0
  220. package/dist/types/charts/core/registry.d.ts +27 -0
  221. package/dist/types/charts/core/render.d.ts +40 -0
  222. package/dist/types/charts/core/snapshot.d.ts +27 -0
  223. package/dist/types/charts/core/svg.d.ts +714 -0
  224. package/dist/types/charts/core/termination.test-fixture.d.ts +1 -0
  225. package/dist/types/charts/core/text.d.ts +5 -0
  226. package/dist/types/charts/core/types.d.ts +31 -0
  227. package/dist/types/charts.d.ts +4 -2
  228. package/dist/types/content/BalanceSummary.d.ts +29 -0
  229. package/dist/types/content/Chart.d.ts +26 -36
  230. package/dist/types/content/ChartExplorer.d.ts +43 -2
  231. package/dist/types/content/ChartSnapshotView.d.ts +1 -0
  232. package/dist/types/content/DataTable.d.ts +28 -0
  233. package/dist/types/content/DataTableTools.d.ts +44 -0
  234. package/dist/types/content/FlowAmount.d.ts +28 -0
  235. package/dist/types/content/balance-summary-messages.d.ts +11 -0
  236. package/dist/types/content/chart-bar.d.ts +1 -1
  237. package/dist/types/content/chart-calendar.d.ts +4 -0
  238. package/dist/types/content/chart-inspection-core.d.ts +21 -16
  239. package/dist/types/content/chart-inspection.d.ts +5 -1
  240. package/dist/types/content/chart-map-viewport.d.ts +1 -1
  241. package/dist/types/content/chart-message-catalog.d.ts +84 -0
  242. package/dist/types/content/chart-messages.d.ts +2 -33
  243. package/dist/types/content/chart-snapshot.d.ts +1 -26
  244. package/dist/types/content/chart-svg.d.ts +1 -1
  245. package/dist/types/content/chart-time-axis.d.ts +26 -0
  246. package/dist/types/content/data-table-columns.d.ts +72 -0
  247. package/dist/types/content/data-table-messages.d.ts +18 -0
  248. package/dist/types/content/flow-amount-messages.d.ts +11 -0
  249. package/dist/types/content/index.d.ts +10 -2
  250. package/dist/types/inputs/SelectChip.d.ts +2 -0
  251. package/dist/types/inputs/date-picker.d.ts +6 -0
  252. package/dist/types/intl/Format.d.ts +5 -0
  253. package/dist/types/layout/AppWorkspace.d.ts +5 -0
  254. package/dist/types/surfaces/BudgetList.d.ts +42 -0
  255. package/dist/types/surfaces/ProgressBar.d.ts +38 -2
  256. package/dist/types/surfaces/StatCell.d.ts +35 -2
  257. package/dist/types/surfaces/StatGrid.d.ts +3 -1
  258. package/dist/types/surfaces/budget-messages.d.ts +30 -0
  259. package/dist/types/surfaces/delta-badge.d.ts +10 -0
  260. package/dist/types/surfaces/index.d.ts +4 -3
  261. package/dist/types/surfaces/person-color.d.ts +2 -2
  262. package/dist/types/surfaces/stat-cell-messages.d.ts +19 -0
  263. package/dist/types/widgets/Dashboard.d.ts +42 -0
  264. package/dist/types/widgets/index.d.ts +1 -0
  265. package/package.json +7 -1
package/dist/base.css CHANGED
@@ -35,6 +35,14 @@
35
35
  --stdlib-chart-c6: #06b6d4;
36
36
  --stdlib-chart-c7: #84cc16;
37
37
  --stdlib-chart-c8: #f97316;
38
+ /* Money with a direction, as in the full stylesheet: turquoise comes in, violet goes out. */
39
+ --k2b-flow-in-text: light-dark(#0e7490, #67e8f9);
40
+ --k2b-flow-in-surface: light-dark(#ecfeff, rgb(8 51 68 / 0.55));
41
+ --k2b-flow-in-ring: light-dark(#a5f3fc, #155e75);
42
+ --k2b-flow-in-mark: light-dark(#0891b2, #22d3ee);
43
+ --k2b-flow-out-text: light-dark(#6d28d9, #c4b5fd);
44
+ --k2b-flow-out-surface: light-dark(#f5f3ff, rgb(46 16 101 / 0.55));
45
+ --k2b-flow-out-mark: light-dark(#8b5cf6, #a78bfa);
38
46
 
39
47
  --k2b-surface: light-dark(#ffffff, #11151b);
40
48
  --k2b-surface-canvas: light-dark(#fafafa, #090d12);
@@ -1070,11 +1078,37 @@
1070
1078
  pointer-events: none;
1071
1079
  }
1072
1080
 
1073
- .k2b-chart [data-inspected]:not([data-chart-role="value"]) > :is(rect, path, circle) {
1081
+ /* Calm hover: nothing else dims. A single mark gets a thin outline; a column or row of a category chart gets a
1082
+ light band behind it instead (data-inspecting="category"). */
1083
+ .k2b-chart:not([data-chart-kind="bar"], [data-chart-kind="histogram"], [data-chart-kind="boxplot"]) [data-inspected]:not([data-chart-role="value"]) > :is(rect, path, circle) {
1074
1084
  stroke: var(--k2b-chart-inspection-color);
1075
- stroke-width: 2px;
1076
1085
  vector-effect: non-scaling-stroke;
1077
1086
  }
1087
+ .k2b-chart:not([data-chart-kind="bar"], [data-chart-kind="histogram"], [data-chart-kind="boxplot"]) [data-inspected]:not([data-chart-role="value"]) > :is(rect, path) {
1088
+ stroke-width: 1.5px;
1089
+ }
1090
+ /* Points keep a 2 px ring: a tiny point is hit through its stroke, so a thinner ring would let the drawing take the pointer. */
1091
+ .k2b-chart:not([data-chart-kind="bar"], [data-chart-kind="histogram"], [data-chart-kind="boxplot"]) [data-inspected]:not([data-chart-role="value"]) > circle {
1092
+ stroke-width: 2px;
1093
+ }
1094
+
1095
+ /* The band sits behind the marks without positioning the drawing, which would change what lies on top. */
1096
+ .k2b-chart[data-inspecting="category"] {
1097
+ isolation: isolate;
1098
+ }
1099
+
1100
+ .k2b-chart[data-inspecting="category"]::before {
1101
+ content: "";
1102
+ position: absolute;
1103
+ left: var(--k2b-chart-band-left);
1104
+ top: var(--k2b-chart-band-top);
1105
+ width: var(--k2b-chart-band-width);
1106
+ height: var(--k2b-chart-band-height);
1107
+ z-index: -1;
1108
+ border-radius: 3px;
1109
+ background: color-mix(in srgb, currentColor 7%, transparent);
1110
+ pointer-events: none;
1111
+ }
1078
1112
 
1079
1113
  .k2b-chart [data-inspected] > .stdlib-chart-inspection-point {
1080
1114
  fill: var(--k2b-chart-inspection-color);
@@ -1277,3 +1311,7 @@
1277
1311
  }
1278
1312
  }
1279
1313
  }
1314
+
1315
+ @layer k2b-base {
1316
+ .k2b-chart[data-chart-kind="area"] [data-selected] > .k2b-area-inspection-point { opacity: 1; stroke-width: 1.5px; }
1317
+ }
@@ -110,6 +110,9 @@ export function ContextMenuBase(props) {
110
110
  const close = () => {
111
111
  detachOpenListeners();
112
112
  if (!isOpen()) return;
113
+ // An activated entry closes the menu before its action runs: focus goes back to where the menu opened from, as
114
+ // Escape returns it, so the keyboard never lands on the page body.
115
+ if (menu?.contains(document.activeElement)) returnFocus(opener?.isConnected ? opener : host, hostRing);
113
116
  setPosition();
114
117
  if (closeActiveContextMenu === close) closeActiveContextMenu = undefined;
115
118
  props.onClose?.();
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["actions/ContextMenu.tsx"],"sourcesContent":["import { createMemo, createSignal, createUniqueId, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { Portal } from \"solid-js/web\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { type DropdownItem, DropdownItems, type DropdownPosition, dropdownPosition } from \"./Dropdown\";\nimport { focusRowSegment } from \"./menu-rows\";\n\nexport type ContextMenuProps = {\n items: readonly DropdownItem[];\n children: JSX.Element;\n class?: string | ((isOpen: boolean) => string);\n /** Tab order of the context-menu host. Descendant-focused composite widgets use -1. */\n tabIndex?: number;\n disabled?: boolean;\n onClose?: () => void;\n onOpen?: () => void;\n id?: string;\n label?: string;\n /**\n * Links, form fields, editable text, and selected text inside the host keep the browser's own menu, with its Copy,\n * Paste, and Open link. Use it where the host wraps readable content rather than one row.\n */\n keepNativeMenu?: boolean;\n};\n\nconst NATIVE_MENU_TARGETS = \"a[href], input, textarea, select, [contenteditable]:not([contenteditable='false'])\";\n\nlet closeActiveContextMenu: (() => void) | undefined;\n\nconst focusableItems = (menu: HTMLElement | undefined): HTMLElement[] =>\n menu\n ? Array.from(\n menu.querySelectorAll<HTMLElement>(\n [\n \"[role='menuitem']:not([aria-disabled='true'])\",\n \"[role='menuitemcheckbox']:not([aria-disabled='true'])\",\n \"[role='menuitemradio']:not([aria-disabled='true'])\",\n ].join(\", \"),\n ),\n )\n : [];\n\n/** Right-click menu with keyboard invocation, one-open-at-a-time behavior, and viewport clamping. */\nexport function ContextMenu(props: ContextMenuProps): JSX.Element {\n return ContextMenuBase(props);\n}\n\n/**\n * Package-internal, not exported from the package index: `openAt` lets GestureMenu open the menu at a held finger, and\n * `title` names what was pressed above the entries, since the finger covers it.\n */\nexport function ContextMenuBase(\n props: ContextMenuProps & {\n openAt?: (open: (x: number, y: number) => void) => void;\n title?: string;\n closeWith?: (close: () => void) => void;\n },\n): JSX.Element {\n const messages = useUiMessages();\n const generatedId = `k2b-context-${createUniqueId()}`;\n const menuId = `${generatedId}-menu`;\n const [position, setPosition] = createSignal<{ x: number; y: number }>();\n let host: HTMLDivElement | undefined;\n let menu: HTMLDivElement | undefined;\n let listenersAttached = false;\n let hostRing = true;\n /** The element inside the host that had focus as the menu opened, such as a row or a day of a composite widget. */\n let opener: HTMLElement | undefined;\n let anchor = { x: 0, y: 0 };\n let viewport = { width: 0, height: 0 };\n let resizeObserver: ResizeObserver | undefined;\n\n const isOpen = () => position() !== undefined;\n const keepsNativeMenu = (target: EventTarget | null): boolean => {\n if (!props.keepNativeMenu || !(target instanceof Node)) return false;\n const element = target instanceof Element ? target : target.parentElement;\n const native = element?.closest(NATIVE_MENU_TARGETS);\n if (native && host?.contains(native)) return true;\n const selection = window.getSelection();\n return Boolean(selection && !selection.isCollapsed && selection.toString().trim() && selection.containsNode(target, true));\n };\n const hostClass = createMemo(() => (typeof props.class === \"function\" ? props.class(isOpen()) : props.class));\n const items = createMemo<readonly DropdownItem[]>(() => props.items);\n\n const dismiss = (event: PointerEvent) => {\n if (menu?.contains(event.target as Node)) return;\n close();\n };\n const closeOnScroll = (event: Event) => {\n const target = event.target;\n if (target instanceof Node && menu?.contains(target)) return;\n close();\n };\n // A resize event may leave the viewport's size unchanged; headless Chromium\n // was seen to fire one on a fresh page. The clamped menu is still in place.\n const closeOnResize = () => {\n if (window.innerWidth === viewport.width && window.innerHeight === viewport.height) return;\n close();\n };\n const attachOpenListeners = () => {\n if (listenersAttached) return;\n listenersAttached = true;\n document.addEventListener(\"pointerdown\", dismiss);\n document.addEventListener(\"keydown\", keyDown);\n window.addEventListener(\"resize\", closeOnResize);\n window.addEventListener(\"scroll\", closeOnScroll, true);\n };\n const detachOpenListeners = () => {\n if (!listenersAttached) return;\n listenersAttached = false;\n document.removeEventListener(\"pointerdown\", dismiss);\n document.removeEventListener(\"keydown\", keyDown);\n window.removeEventListener(\"resize\", closeOnResize);\n window.removeEventListener(\"scroll\", closeOnScroll, true);\n resizeObserver?.disconnect();\n };\n /** Clamps the rendered menu into the viewport beside the point that opened it. */\n const place = () => {\n if (!isOpen() || !menu?.isConnected) return;\n const point = dropdownPosition(\n new DOMRect(anchor.x, anchor.y, 0, 0),\n menu.getBoundingClientRect(),\n \"bottom-right\" satisfies DropdownPosition,\n { width: window.innerWidth, height: window.innerHeight },\n 0,\n );\n setPosition({ x: point.left, y: point.top });\n };\n const close = () => {\n detachOpenListeners();\n if (!isOpen()) return;\n setPosition();\n if (closeActiveContextMenu === close) closeActiveContextMenu = undefined;\n props.onClose?.();\n };\n const focusItem = (index: number) => {\n const items = focusableItems(menu);\n if (items.length === 0) return;\n items[(index + items.length) % items.length]?.focus();\n };\n const open = (x: number, y: number) => {\n if (props.disabled) return;\n closeActiveContextMenu?.();\n // Focus may sit on a descendant of the host, as in composite widgets; Escape returns it there.\n const focused = document.activeElement;\n opener = focused instanceof HTMLElement && focused !== host && host?.contains(focused) ? focused : undefined;\n hostRing = ringOnReturn(focused);\n anchor = { x, y };\n viewport = { width: window.innerWidth, height: window.innerHeight };\n setPosition({ x, y });\n closeActiveContextMenu = close;\n attachOpenListeners();\n props.onOpen?.();\n // The menu sizes to its entries, so it is measured once rendered and\n // clamped into the viewport before the next paint, and again whenever its\n // entries change its size while it is open.\n queueMicrotask(() => {\n if (!isOpen() || !menu?.isConnected) return;\n place();\n if (typeof ResizeObserver !== \"undefined\") {\n resizeObserver ??= new ResizeObserver(place);\n resizeObserver.observe(menu);\n }\n focusItem(0);\n });\n };\n const keyDown = (event: KeyboardEvent) => {\n if (!isOpen()) return;\n const items = focusableItems(menu);\n const current = items.indexOf(document.activeElement as HTMLElement);\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n focusItem(current + (event.key === \"ArrowDown\" ? 1 : -1));\n } else if (focusRowSegment(items, event.key)) {\n event.preventDefault();\n } else if (event.key === \"Home\" || event.key === \"End\") {\n event.preventDefault();\n focusItem(event.key === \"Home\" ? 0 : -1);\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n close();\n returnFocus(opener?.isConnected ? opener : host, hostRing);\n } else if (event.key === \"Tab\") {\n close();\n }\n };\n\n props.openAt?.(open);\n // A close from outside, such as a drag taking over the press, returns focus that sits in the menu to where it came from.\n props.closeWith?.(() => {\n if (isOpen() && menu?.contains(document.activeElement)) returnFocus(opener?.isConnected ? opener : host, hostRing);\n close();\n });\n\n onMount(() => {\n onCleanup(() => {\n detachOpenListeners();\n if (closeActiveContextMenu === close) closeActiveContextMenu = undefined;\n });\n });\n\n return (\n <>\n {/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: the named group owns the context-menu relationship without pretending arbitrary children form one button. */}\n <div\n ref={host}\n id={props.id ?? generatedId}\n class={hostClass()}\n role=\"group\"\n tabIndex={props.disabled ? undefined : (props.tabIndex ?? 0)}\n aria-label={props.label ?? messages().contextMenu}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen()}\n aria-controls={isOpen() ? menuId : undefined}\n aria-disabled={props.disabled ? \"true\" : undefined}\n onContextMenu={(event) => {\n if (props.disabled) return;\n // An enclosing context menu must not take over the browser's own either.\n if (keepsNativeMenu(event.target)) return void event.stopPropagation();\n event.preventDefault();\n event.stopPropagation();\n open(event.clientX, event.clientY);\n }}\n onKeyDown={(event) => {\n if (props.disabled || (event.key !== \"ContextMenu\" && !(event.shiftKey && event.key === \"F10\"))) return;\n // A nested context menu already opened its own; a field keeps the browser's.\n if (\n event.defaultPrevented ||\n (props.keepNativeMenu && event.target instanceof Element && event.target.closest(NATIVE_MENU_TARGETS))\n )\n return;\n event.preventDefault();\n // Beside the focused element, such as a row of a composite widget, rather than the corner of the whole host.\n const focused = document.activeElement;\n const rect = (focused instanceof HTMLElement && host?.contains(focused) ? focused : host)?.getBoundingClientRect();\n if (rect) open(rect.left + 12, rect.top + 12);\n }}\n >\n {props.children}\n </div>\n <Show when={position()}>\n {(point) => (\n <Portal>\n <div\n ref={menu}\n id={menuId}\n class=\"k2b-ui k2b-context-menu\"\n role=\"menu\"\n aria-label={props.label ?? messages().contextMenu}\n style={{ left: `${point().x}px`, top: `${point().y}px` }}\n onPointerDown={(event) => event.stopPropagation()}\n >\n {/* The menu carries the same name for assistive technology. */}\n <Show when={props.title}>\n <div class=\"k2b-context-menu__title\" aria-hidden=\"true\">\n {props.title}\n </div>\n </Show>\n <DropdownItems items={items()} close={close} />\n </div>\n </Portal>\n )}\n </Show>\n </>\n );\n}\n\nexport default ContextMenu;\n"],"mappings":";;;;;;;;;;;;;;;AAAA,SAAS,UAAU,EAAE,YAAY,EAAE,cAAc,EAAY,SAAS,EAAE,OAAO,EAAE,IAAI,QAAQ,UAAU;AACvG,SAAS,MAAM,QAAQ,cAAc;AACrC,SAAS,WAAW,EAAE,YAAY,QAAQ,0BAA0B;AACpE,SAAS,aAAa,QAAQ,kBAAkB;AAChD,SAA4B,aAAa,EAAyB,gBAAgB,QAAQ,YAAY;AACtG,SAAS,eAAe,QAAQ,aAAa;AAoB7C,MAAM,mBAAmB,GAAG,oFAAoF;AAEhH,IAAI,sBAAgD;AAEpD,MAAM,cAAc,GAAI,IAA6B,IACnD,IAAI,GACA,KAAK,CAAC,IAAI,CACR,IAAI,CAAC,gBAAgB,CACnB,CACE,+CAA+C,EAC/C,uDAAuD,EACvD,oDAAoD,CACrD,CAAC,IAAI,CAAC,IAAI,CACb,CACF,CAAC,GACD,EAAE;;AAER;AACA,OAAO,SAAS,WAAW,CAAC,KAAuB,EAAe;EAChE,OAAO,eAAe,CAAC,KAAK,CAAC;AAC/B;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAAS,eAAe,CAC7B,KAIC,EACY;EACb,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC;EAChC,MAAM,WAAW,GAAG,eAAe,cAAc,CAAC,CAAC,EAAE;EACrD,MAAM,MAAM,GAAG,GAAG,WAAW,OAAO;EACpC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,YAAY,CAA2B,CAAC;EACxE,IAAI,IAAgC;EACpC,IAAI,IAAgC;EACpC,IAAI,iBAAiB,GAAG,KAAK;EAC7B,IAAI,QAAQ,GAAG,IAAI;EACnB;EACA,IAAI,MAA+B;EACnC,IAAI,MAAM,GAAG;IAAE,CAAC,EAAE,CAAC;IAAE,CAAC,EAAE;EAAE,CAAC;EAC3B,IAAI,QAAQ,GAAG;IAAE,KAAK,EAAE,CAAC;IAAE,MAAM,EAAE;EAAE,CAAC;EACtC,IAAI,cAA0C;EAE9C,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,CAAC,KAAK,SAAS;EAC7C,MAAM,eAAe,GAAI,MAA0B,IAAc;IAC/D,IAAI,CAAC,KAAK,CAAC,cAAc,IAAI,EAAE,MAAM,YAAY,IAAI,CAAC,EAAE,OAAO,KAAK;IACpE,MAAM,OAAO,GAAG,MAAM,YAAY,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,aAAa;IACzE,MAAM,MAAM,GAAG,OAAO,EAAE,OAAO,CAAC,mBAAmB,CAAC;IACpD,IAAI,MAAM,IAAI,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,IAAI;IACjD,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,WAAW,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;EAC5H,CAAC;EACD,MAAM,SAAS,GAAG,UAAU,CAAC,MAAO,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAM,CAAC;EAC7G,MAAM,KAAK,GAAG,UAAU,CAA0B,MAAM,KAAK,CAAC,KAAK,CAAC;EAEpE,MAAM,OAAO,GAAI,KAAmB,IAAK;IACvC,IAAI,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAc,CAAC,EAAE;IAC1C,KAAK,CAAC,CAAC;EACT,CAAC;EACD,MAAM,aAAa,GAAI,KAAY,IAAK;IACtC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;IAC3B,IAAI,MAAM,YAAY,IAAI,IAAI,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE;IACtD,KAAK,CAAC,CAAC;EACT,CAAC;EACD;EACA;EACA,MAAM,aAAa,GAAG,MAAM;IAC1B,IAAI,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,KAAK,IAAI,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC,MAAM,EAAE;IACpF,KAAK,CAAC,CAAC;EACT,CAAC;EACD,MAAM,mBAAmB,GAAG,MAAM;IAChC,IAAI,iBAAiB,EAAE;IACvB,iBAAiB,GAAG,IAAI;IACxB,QAAQ,CAAC,gBAAgB,CAAC,aAAa,EAAE,OAAO,CAAC;IACjD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC;IAC7C,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC;IAChD,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC;EACxD,CAAC;EACD,MAAM,mBAAmB,GAAG,MAAM;IAChC,IAAI,CAAC,iBAAiB,EAAE;IACxB,iBAAiB,GAAG,KAAK;IACzB,QAAQ,CAAC,mBAAmB,CAAC,aAAa,EAAE,OAAO,CAAC;IACpD,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC;IAChD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,aAAa,CAAC;IACnD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,CAAC;IACzD,cAAc,EAAE,UAAU,CAAC,CAAC;EAC9B,CAAC;EACD;EACA,MAAM,KAAK,GAAG,MAAM;IAClB,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE;IACrC,MAAM,KAAK,GAAG,gBAAgB,CAC5B,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACrC,IAAI,CAAC,qBAAqB,CAAC,CAAC,EAC5B,cAAc,EACd;MAAE,KAAK,EAAE,MAAM,CAAC,UAAU;MAAE,MAAM,EAAE,MAAM,CAAC;IAAY,CAAC,EACxD,CACF,CAAC;IACD,WAAW,CAAC;MAAE,CAAC,EAAE,KAAK,CAAC,IAAI;MAAE,CAAC,EAAE,KAAK,CAAC;IAAI,CAAC,CAAC;EAC9C,CAAC;EACD,MAAM,KAAK,GAAG,MAAM;IAClB,mBAAmB,CAAC,CAAC;IACrB,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE;IACf,WAAW,CAAC,CAAC;IACb,IAAI,sBAAsB,KAAK,KAAK,EAAE,sBAAsB,GAAG,SAAS;IACxE,KAAK,CAAC,OAAO,GAAG,CAAC;EACnB,CAAC;EACD,MAAM,SAAS,GAAI,KAAa,IAAK;IACnC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC;IAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;IACxB,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;EACvD,CAAC;EACD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,KAAK;IACrC,IAAI,KAAK,CAAC,QAAQ,EAAE;IACpB,sBAAsB,GAAG,CAAC;IAC1B;IACA,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa;IACtC,MAAM,GAAG,OAAO,YAAY,WAAW,IAAI,OAAO,KAAK,IAAI,IAAI,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,SAAS;IAC5G,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC;IAChC,MAAM,GAAG;MAAE,CAAC;MAAE;IAAE,CAAC;IACjB,QAAQ,GAAG;MAAE,KAAK,EAAE,MAAM,CAAC,UAAU;MAAE,MAAM,EAAE,MAAM,CAAC;IAAY,CAAC;IACnE,WAAW,CAAC;MAAE,CAAC;MAAE;IAAE,CAAC,CAAC;IACrB,sBAAsB,GAAG,KAAK;IAC9B,mBAAmB,CAAC,CAAC;IACrB,KAAK,CAAC,MAAM,GAAG,CAAC;IAChB;IACA;IACA;IACA,cAAc,CAAC,MAAM;MACnB,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE;MACrC,KAAK,CAAC,CAAC;MACP,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE;QACzC,cAAc,KAAK,IAAI,cAAc,CAAC,KAAK,CAAC;QAC5C,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC;MAC9B;MACA,SAAS,CAAC,CAAC,CAAC;IACd,CAAC,CAAC;EACJ,CAAC;EACD,MAAM,OAAO,GAAI,KAAoB,IAAK;IACxC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE;IACf,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,aAA4B,CAAC;IACpE,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE;MACxD,KAAK,CAAC,cAAc,CAAC,CAAC;MACtB,SAAS,CAAC,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC,MAAM,IAAI,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE;MAC5C,KAAK,CAAC,cAAc,CAAC,CAAC;IACxB,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE;MACtD,KAAK,CAAC,cAAc,CAAC,CAAC;MACtB,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE;MACjC,KAAK,CAAC,cAAc,CAAC,CAAC;MACtB,KAAK,CAAC,CAAC;MACP,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,EAAE,QAAQ,CAAC;IAC5D,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE;MAC9B,KAAK,CAAC,CAAC;IACT;EACF,CAAC;EAED,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;EACpB;EACA,KAAK,CAAC,SAAS,GAAG,MAAM;IACtB,IAAI,MAAM,CAAC,CAAC,IAAI,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,EAAE,QAAQ,CAAC;IAClH,KAAK,CAAC,CAAC;EACT,CAAC,CAAC;EAEF,OAAO,CAAC,MAAM;IACZ,SAAS,CAAC,MAAM;MACd,mBAAmB,CAAC,CAAC;MACrB,IAAI,sBAAsB,KAAK,KAAK,EAAE,sBAAsB,GAAG,SAAS;IAC1E,CAAC,CAAC;EACJ,CAAC,CAAC;EAEF;IAAA;IAAA,iBAsBkB,KAAK,IAAK;MACpB,IAAI,KAAK,CAAC,QAAQ,IAAK,KAAK,CAAC,GAAG,KAAK,aAAa,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,CAAE,EAAE;MACjG;MACA,IACE,KAAK,CAAC,gBAAgB,IACrB,KAAK,CAAC,cAAc,IAAI,KAAK,CAAC,MAAM,YAAY,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAE,EAEtG;MACF,KAAK,CAAC,cAAc,CAAC,CAAC;MACtB;MACA,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa;MACtC,MAAM,IAAI,GAAG,CAAC,OAAO,YAAY,WAAW,IAAI,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,IAAI,GAAG,qBAAqB,CAAC,CAAC;MAClH,IAAI,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,EAAE,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;IAC/C,CAAC;IAAA,qBArBe,KAAK,IAAK;MACxB,IAAI,KAAK,CAAC,QAAQ,EAAE;MACpB;MACA,IAAI,eAAe,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,KAAK,CAAC,eAAe,CAAC,CAAC;MACtE,KAAK,CAAC,cAAc,CAAC,CAAC;MACtB,KAAK,CAAC,eAAe,CAAC,CAAC;MACvB,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;IACpC,CAAC;IAAA,YAjBI,IAAI;IAAA,mDAAJ,IAAI;IAAA,qBAiCR,KAAK,CAAC,QAAQ;IAAA;MAAA,UAhCX,KAAK,CAAC,EAAE,IAAI,WAAW;QAAA,OACpB,SAAS,CAAC,CAAC;QAAA,OAER,KAAK,CAAC,QAAQ,GAAG,SAAS,GAAI,KAAK,CAAC,QAAQ,IAAI,CAAE;QAAA,OAChD,KAAK,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW;QAAA,OAElC,MAAM,CAAC,CAAC;QAAA,OACR,MAAM,CAAC,CAAC,GAAG,MAAM,GAAG,SAAS;QAAA,OAC7B,KAAK,CAAC,QAAQ,GAAG,MAAM,GAAG,SAAS;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;MAAA;IAAA;IAAA;IAAA;EAAA,wBA0BnD,IAAI;IAAA,IAAC,IAAI;MAAA,OAAE,QAAQ,CAAC,CAAC;IAAA;IAAA,UAClB,KAAK,sBACJ,MAAM;MAAA;QAAA;UAAA;UAAA;UAAA;UAAA;QAAA,sBAQa,KAAK,IAAK,KAAK,CAAC,eAAe,CAAC,CAAC;QAAA,aAN5C,IAAI;QAAA,sDAAJ,IAAI;QAAA,4BACL,MAAM;QAAA,kCAQT,IAAI;UAAA,IAAC,IAAI;YAAA,OAAE,KAAK,CAAC,KAAK;UAAA;UAAA;YAAA;YAAA,sBAElB,KAAK,CAAC,KAAK;YAAA;UAAA;QAAA;QAAA,kCAGf,aAAa;UAAA,IAAC,KAAK;YAAA,OAAE,KAAK,CAAC,CAAC;UAAA;UAAE,KAAK,EAAE;QAAK;QAAA;UAAA,WAV/B,KAAK,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW;YAAA,OAClC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;YAAA,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI;UAAA;UAAA;UAAA;UAAA;QAAA;UAAA;UAAA;UAAA;QAAA;QAAA;QAAA;MAAA;IAAA;EAY3D;AAIT;AAEA,eAAe,WAAW;AAAC","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["actions/ContextMenu.tsx"],"sourcesContent":["import { createMemo, createSignal, createUniqueId, type JSX, onCleanup, onMount, Show } from \"solid-js\";\nimport { Portal } from \"solid-js/web\";\nimport { returnFocus, ringOnReturn } from \"../internal/focus-return\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { type DropdownItem, DropdownItems, type DropdownPosition, dropdownPosition } from \"./Dropdown\";\nimport { focusRowSegment } from \"./menu-rows\";\n\nexport type ContextMenuProps = {\n items: readonly DropdownItem[];\n children: JSX.Element;\n class?: string | ((isOpen: boolean) => string);\n /** Tab order of the context-menu host. Descendant-focused composite widgets use -1. */\n tabIndex?: number;\n disabled?: boolean;\n onClose?: () => void;\n onOpen?: () => void;\n id?: string;\n label?: string;\n /**\n * Links, form fields, editable text, and selected text inside the host keep the browser's own menu, with its Copy,\n * Paste, and Open link. Use it where the host wraps readable content rather than one row.\n */\n keepNativeMenu?: boolean;\n};\n\nconst NATIVE_MENU_TARGETS = \"a[href], input, textarea, select, [contenteditable]:not([contenteditable='false'])\";\n\nlet closeActiveContextMenu: (() => void) | undefined;\n\nconst focusableItems = (menu: HTMLElement | undefined): HTMLElement[] =>\n menu\n ? Array.from(\n menu.querySelectorAll<HTMLElement>(\n [\n \"[role='menuitem']:not([aria-disabled='true'])\",\n \"[role='menuitemcheckbox']:not([aria-disabled='true'])\",\n \"[role='menuitemradio']:not([aria-disabled='true'])\",\n ].join(\", \"),\n ),\n )\n : [];\n\n/** Right-click menu with keyboard invocation, one-open-at-a-time behavior, and viewport clamping. */\nexport function ContextMenu(props: ContextMenuProps): JSX.Element {\n return ContextMenuBase(props);\n}\n\n/**\n * Package-internal, not exported from the package index: `openAt` lets GestureMenu open the menu at a held finger, and\n * `title` names what was pressed above the entries, since the finger covers it.\n */\nexport function ContextMenuBase(\n props: ContextMenuProps & {\n openAt?: (open: (x: number, y: number) => void) => void;\n title?: string;\n closeWith?: (close: () => void) => void;\n },\n): JSX.Element {\n const messages = useUiMessages();\n const generatedId = `k2b-context-${createUniqueId()}`;\n const menuId = `${generatedId}-menu`;\n const [position, setPosition] = createSignal<{ x: number; y: number }>();\n let host: HTMLDivElement | undefined;\n let menu: HTMLDivElement | undefined;\n let listenersAttached = false;\n let hostRing = true;\n /** The element inside the host that had focus as the menu opened, such as a row or a day of a composite widget. */\n let opener: HTMLElement | undefined;\n let anchor = { x: 0, y: 0 };\n let viewport = { width: 0, height: 0 };\n let resizeObserver: ResizeObserver | undefined;\n\n const isOpen = () => position() !== undefined;\n const keepsNativeMenu = (target: EventTarget | null): boolean => {\n if (!props.keepNativeMenu || !(target instanceof Node)) return false;\n const element = target instanceof Element ? target : target.parentElement;\n const native = element?.closest(NATIVE_MENU_TARGETS);\n if (native && host?.contains(native)) return true;\n const selection = window.getSelection();\n return Boolean(selection && !selection.isCollapsed && selection.toString().trim() && selection.containsNode(target, true));\n };\n const hostClass = createMemo(() => (typeof props.class === \"function\" ? props.class(isOpen()) : props.class));\n const items = createMemo<readonly DropdownItem[]>(() => props.items);\n\n const dismiss = (event: PointerEvent) => {\n if (menu?.contains(event.target as Node)) return;\n close();\n };\n const closeOnScroll = (event: Event) => {\n const target = event.target;\n if (target instanceof Node && menu?.contains(target)) return;\n close();\n };\n // A resize event may leave the viewport's size unchanged; headless Chromium\n // was seen to fire one on a fresh page. The clamped menu is still in place.\n const closeOnResize = () => {\n if (window.innerWidth === viewport.width && window.innerHeight === viewport.height) return;\n close();\n };\n const attachOpenListeners = () => {\n if (listenersAttached) return;\n listenersAttached = true;\n document.addEventListener(\"pointerdown\", dismiss);\n document.addEventListener(\"keydown\", keyDown);\n window.addEventListener(\"resize\", closeOnResize);\n window.addEventListener(\"scroll\", closeOnScroll, true);\n };\n const detachOpenListeners = () => {\n if (!listenersAttached) return;\n listenersAttached = false;\n document.removeEventListener(\"pointerdown\", dismiss);\n document.removeEventListener(\"keydown\", keyDown);\n window.removeEventListener(\"resize\", closeOnResize);\n window.removeEventListener(\"scroll\", closeOnScroll, true);\n resizeObserver?.disconnect();\n };\n /** Clamps the rendered menu into the viewport beside the point that opened it. */\n const place = () => {\n if (!isOpen() || !menu?.isConnected) return;\n const point = dropdownPosition(\n new DOMRect(anchor.x, anchor.y, 0, 0),\n menu.getBoundingClientRect(),\n \"bottom-right\" satisfies DropdownPosition,\n { width: window.innerWidth, height: window.innerHeight },\n 0,\n );\n setPosition({ x: point.left, y: point.top });\n };\n const close = () => {\n detachOpenListeners();\n if (!isOpen()) return;\n // An activated entry closes the menu before its action runs: focus goes back to where the menu opened from, as\n // Escape returns it, so the keyboard never lands on the page body.\n if (menu?.contains(document.activeElement)) returnFocus(opener?.isConnected ? opener : host, hostRing);\n setPosition();\n if (closeActiveContextMenu === close) closeActiveContextMenu = undefined;\n props.onClose?.();\n };\n const focusItem = (index: number) => {\n const items = focusableItems(menu);\n if (items.length === 0) return;\n items[(index + items.length) % items.length]?.focus();\n };\n const open = (x: number, y: number) => {\n if (props.disabled) return;\n closeActiveContextMenu?.();\n // Focus may sit on a descendant of the host, as in composite widgets; Escape returns it there.\n const focused = document.activeElement;\n opener = focused instanceof HTMLElement && focused !== host && host?.contains(focused) ? focused : undefined;\n hostRing = ringOnReturn(focused);\n anchor = { x, y };\n viewport = { width: window.innerWidth, height: window.innerHeight };\n setPosition({ x, y });\n closeActiveContextMenu = close;\n attachOpenListeners();\n props.onOpen?.();\n // The menu sizes to its entries, so it is measured once rendered and\n // clamped into the viewport before the next paint, and again whenever its\n // entries change its size while it is open.\n queueMicrotask(() => {\n if (!isOpen() || !menu?.isConnected) return;\n place();\n if (typeof ResizeObserver !== \"undefined\") {\n resizeObserver ??= new ResizeObserver(place);\n resizeObserver.observe(menu);\n }\n focusItem(0);\n });\n };\n const keyDown = (event: KeyboardEvent) => {\n if (!isOpen()) return;\n const items = focusableItems(menu);\n const current = items.indexOf(document.activeElement as HTMLElement);\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n focusItem(current + (event.key === \"ArrowDown\" ? 1 : -1));\n } else if (focusRowSegment(items, event.key)) {\n event.preventDefault();\n } else if (event.key === \"Home\" || event.key === \"End\") {\n event.preventDefault();\n focusItem(event.key === \"Home\" ? 0 : -1);\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n close();\n returnFocus(opener?.isConnected ? opener : host, hostRing);\n } else if (event.key === \"Tab\") {\n close();\n }\n };\n\n props.openAt?.(open);\n // A close from outside, such as a drag taking over the press, returns focus that sits in the menu to where it came from.\n props.closeWith?.(() => {\n if (isOpen() && menu?.contains(document.activeElement)) returnFocus(opener?.isConnected ? opener : host, hostRing);\n close();\n });\n\n onMount(() => {\n onCleanup(() => {\n detachOpenListeners();\n if (closeActiveContextMenu === close) closeActiveContextMenu = undefined;\n });\n });\n\n return (\n <>\n {/* biome-ignore lint/a11y/useAriaPropsSupportedByRole: the named group owns the context-menu relationship without pretending arbitrary children form one button. */}\n <div\n ref={host}\n id={props.id ?? generatedId}\n class={hostClass()}\n role=\"group\"\n tabIndex={props.disabled ? undefined : (props.tabIndex ?? 0)}\n aria-label={props.label ?? messages().contextMenu}\n aria-haspopup=\"menu\"\n aria-expanded={isOpen()}\n aria-controls={isOpen() ? menuId : undefined}\n aria-disabled={props.disabled ? \"true\" : undefined}\n onContextMenu={(event) => {\n if (props.disabled) return;\n // An enclosing context menu must not take over the browser's own either.\n if (keepsNativeMenu(event.target)) return void event.stopPropagation();\n event.preventDefault();\n event.stopPropagation();\n open(event.clientX, event.clientY);\n }}\n onKeyDown={(event) => {\n if (props.disabled || (event.key !== \"ContextMenu\" && !(event.shiftKey && event.key === \"F10\"))) return;\n // A nested context menu already opened its own; a field keeps the browser's.\n if (\n event.defaultPrevented ||\n (props.keepNativeMenu && event.target instanceof Element && event.target.closest(NATIVE_MENU_TARGETS))\n )\n return;\n event.preventDefault();\n // Beside the focused element, such as a row of a composite widget, rather than the corner of the whole host.\n const focused = document.activeElement;\n const rect = (focused instanceof HTMLElement && host?.contains(focused) ? focused : host)?.getBoundingClientRect();\n if (rect) open(rect.left + 12, rect.top + 12);\n }}\n >\n {props.children}\n </div>\n <Show when={position()}>\n {(point) => (\n <Portal>\n <div\n ref={menu}\n id={menuId}\n class=\"k2b-ui k2b-context-menu\"\n role=\"menu\"\n aria-label={props.label ?? messages().contextMenu}\n style={{ left: `${point().x}px`, top: `${point().y}px` }}\n onPointerDown={(event) => event.stopPropagation()}\n >\n {/* The menu carries the same name for assistive technology. */}\n <Show when={props.title}>\n <div class=\"k2b-context-menu__title\" aria-hidden=\"true\">\n {props.title}\n </div>\n </Show>\n <DropdownItems items={items()} close={close} />\n </div>\n </Portal>\n )}\n </Show>\n </>\n );\n}\n\nexport default ContextMenu;\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,190 @@
1
+ import { chartMessages } from "../../content/chart-message-catalog";
2
+ import { chartDatumSvg, escapeXml, niceStep } from "./svg";
3
+ /** Missing observations interrupt the entire stack; zero-sum columns are gaps only in normalized mode. */
4
+ export function areaLayout(spec, size) {
5
+ if (![size.width, size.height].every(v => Number.isFinite(v) && v > 0)) throw new RangeError("Invalid area size");
6
+ if (spec.x.some(x => typeof x === "number" && !Number.isFinite(x))) throw new RangeError("Area X values must be finite");
7
+ if (spec.series.some(s => s.data.length !== spec.x.length)) throw new RangeError("Area series must match x length");
8
+ const totals = spec.x.map((_, i) => {
9
+ let total = 0,
10
+ missing = false;
11
+ for (const series of spec.series) {
12
+ const value = series.data[i];
13
+ if (value === null) {
14
+ missing = true;
15
+ continue;
16
+ }
17
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new RangeError("Area values must be nonnegative finite numbers or null");
18
+ total += value;
19
+ }
20
+ if (!Number.isFinite(total)) throw new RangeError("Area totals must be finite");
21
+ return missing || spec.normalize && total === 0 ? null : total;
22
+ });
23
+ const layers = spec.series.map(() => []);
24
+ totals.forEach((total, index) => {
25
+ let lower = 0;
26
+ spec.series.forEach((series, s) => {
27
+ const amount = series.data[index];
28
+ if (total === null || amount === null || amount === undefined) {
29
+ layers[s].push(null);
30
+ return;
31
+ }
32
+ const share = total === 0 ? 0 : amount / total;
33
+ const upper = lower + (spec.normalize ? share : amount);
34
+ layers[s].push({
35
+ index,
36
+ amount,
37
+ share,
38
+ lower,
39
+ upper
40
+ });
41
+ lower = upper;
42
+ });
43
+ });
44
+ let rawMax = 0;
45
+ for (const total of totals) if (total !== null) rawMax = Math.max(rawMax, total);
46
+ const step = spec.normalize ? 0.25 : niceStep(rawMax || 1, 5);
47
+ const max = spec.normalize ? 1 : Math.ceil((rawMax || 1) / step) * step;
48
+ if (!(step > 0) || !Number.isFinite(max)) throw new RangeError("Area values cannot form a finite axis; rescale them first");
49
+ const ticks = Array.from({
50
+ length: Math.round(max / step) + 1
51
+ }, (_, i) => i * step);
52
+ const format = spec.format ?? new Intl.NumberFormat(spec.locale ?? "en", {
53
+ maximumFractionDigits: 2
54
+ }).format;
55
+ const percent = new Intl.NumberFormat(spec.locale ?? "en", {
56
+ style: "percent",
57
+ minimumFractionDigits: 1,
58
+ maximumFractionDigits: 1
59
+ }).format;
60
+ const axisFormat = spec.normalize ? new Intl.NumberFormat(spec.locale ?? "en", {
61
+ style: "percent",
62
+ maximumFractionDigits: 0
63
+ }).format : format;
64
+ const numericX = new Intl.NumberFormat(spec.locale ?? "en", {
65
+ useGrouping: "min2",
66
+ maximumFractionDigits: 6
67
+ }).format;
68
+ const xFormat = value => typeof value === "number" ? numericX(value) : value;
69
+ const displayWidth = spec.displayWidth ?? 296;
70
+ if (!Number.isFinite(displayWidth) || displayWidth <= 0) throw new RangeError("Invalid area display width");
71
+ const ratio = size.width / displayWidth;
72
+ const left = Math.min(size.width * 0.6, Math.max(44, ...ticks.map(v => axisFormat(v).length * 7 + 12)) * ratio);
73
+ const right = size.width - 12,
74
+ top = (spec.title || spec.subtitle ? 50 : 16) + (spec.legend === false ? 0 : spec.series.length <= 2 ? 20 : 40);
75
+ const bottom = Math.max(top + 1, size.height - 30);
76
+ const projectX = index => spec.x.length < 2 ? (left + right) / 2 : left + index / (spec.x.length - 1) * (right - left);
77
+ const projectY = value => bottom - value / max * (bottom - top);
78
+ return {
79
+ layers,
80
+ totals,
81
+ max,
82
+ ticks,
83
+ format,
84
+ percent,
85
+ axisFormat,
86
+ xFormat,
87
+ ratio,
88
+ left,
89
+ right,
90
+ top,
91
+ bottom,
92
+ projectX,
93
+ projectY
94
+ };
95
+ }
96
+ const f = value => String(Math.round(value * 100) / 100);
97
+ const text = (x, y, label, anchor = "middle", extra = "") => `<text x="${f(x)}" y="${f(y)}" text-anchor="${anchor}" fill="currentColor" stroke="none" font-family="var(--k2b-font-sans, sans-serif)" font-size="11" ${extra}>${escapeXml(label)}</text>`;
98
+ export function renderAreaSvg(spec) {
99
+ const g = areaLayout(spec, spec),
100
+ parts = [];
101
+ if (spec.title) parts.push(text(spec.width / 2, 16, spec.title));
102
+ if (spec.subtitle) parts.push(text(spec.width / 2, spec.title ? 32 : 16, spec.subtitle));
103
+ for (const tick of g.ticks) {
104
+ const y = g.projectY(tick);
105
+ parts.push(`<line x1="${f(g.left)}" x2="${f(g.right)}" y1="${f(y)}" y2="${f(y)}" stroke="currentColor" stroke-opacity="${tick === 0 ? 0.25 : 0.09}"/>`, text(g.left - 8, y + 4, g.axisFormat(tick), "end", 'class="stdlib-chart-tick-label"'));
106
+ }
107
+ parts.push(`<line class="stdlib-chart-axis" x1="${f(g.left)}" x2="${f(g.right)}" y1="${f(g.bottom)}" y2="${f(g.bottom)}" stroke="none"/><line class="stdlib-chart-axis" x1="${f(g.left)}" x2="${f(g.left)}" y1="${f(g.top)}" y2="${f(g.bottom)}" stroke="none"/>`);
108
+ const palette = ["#2563eb", "#16a34a", "#d97706", "#dc2626", "#a855f7", "#0891b2", "#db2777", "#65a30d"];
109
+ if (spec.legend !== false) {
110
+ const entries = spec.series.slice(0, 4);
111
+ const columns = 2;
112
+ const cell = spec.width / columns;
113
+ entries.forEach((series, i) => {
114
+ const x = i % columns * cell + 4,
115
+ y = g.top - (entries.length <= 2 ? 11 : 28) + Math.floor(i / columns) * 17;
116
+ const color = i === 3 && spec.series.length > 4 ? "var(--k2b-text-muted, #8b8b93)" : `var(--stdlib-chart-c${i % 8 + 1}, ${palette[i % 8]})`;
117
+ const point = g.layers[i]?.at(-1);
118
+ const value = point ? spec.normalize ? g.percent(point.share) : g.format(point.amount) : "—";
119
+ const room = Math.floor(cell * (1 / g.ratio) / 6) - 4;
120
+ const showValue = value.length + 3 < room;
121
+ const maxChars = Math.max(1, room - (showValue ? value.length + 1 : 0));
122
+ const label = series.label.length > maxChars ? series.label.slice(0, maxChars - 1) + "…" : series.label;
123
+ const full = i === 3 && spec.series.length > 4 ? spec.series.slice(3).map(s => s.label).join(", ") : series.label;
124
+ const shown = i === 3 && spec.series.length > 4 ? `+${new Intl.NumberFormat(spec.locale ?? "en").format(spec.series.length - 3)}` : label;
125
+ const valueSvg = showValue && !(i === 3 && spec.series.length > 4) ? `<tspan dx="6" font-weight="600" data-chart-legend-value data-series-index="${i}" data-field="${spec.normalize ? "percent" : "value"}" data-default="${escapeXml(value)}">${escapeXml(value)}</tspan>` : "";
126
+ const labelSvg = text(x + 12, y, shown, "start").replace("</text>", `${valueSvg}</text>`);
127
+ parts.push(`<g><title>${escapeXml(full)}</title><rect x="${f(x)}" y="${f(y - 8)}" width="8" height="8" rx="2" fill="${color}" stroke="none"/>${labelSvg}</g>`);
128
+ });
129
+ }
130
+ g.layers.forEach((layer, s) => {
131
+ const color = `var(--stdlib-chart-c${s % 8 + 1}, ${palette[s % 8]})`;
132
+ let run = [];
133
+ const flush = () => {
134
+ if (!run.length) return;
135
+ const halfSlot = Math.min(10 * g.ratio, (g.right - g.left) / Math.max(2, spec.x.length * 2));
136
+ const upper = run.length === 1 ? [[Math.max(g.left, g.projectX(run[0].index) - halfSlot), g.projectY(run[0].upper)], [Math.min(g.right, g.projectX(run[0].index) + halfSlot), g.projectY(run[0].upper)]] : run.map(p => [g.projectX(p.index), g.projectY(p.upper)]);
137
+ const lower = run.length === 1 ? [[upper[1][0], g.projectY(run[0].lower)], [upper[0][0], g.projectY(run[0].lower)]] : run.toReversed().map(p => [g.projectX(p.index), g.projectY(p.lower)]);
138
+ const points = [...upper, ...lower].map(([x, y]) => `${f(x)},${f(y)}`);
139
+ parts.push(`<polygon class="k2b-area-layer" points="${points.join(" ")}" fill="color-mix(in srgb, ${color} 55%, var(--k2b-surface, #ffffff))" stroke="none"/>`, `<polyline class="k2b-area-edge k2b-area-edge-${s % 8}" points="${upper.map(([x, y]) => `${f(x)},${f(y)}`).join(" ")}" fill="none" stroke="${color}" stroke-width="1.5"/>`);
140
+ run = [];
141
+ };
142
+ for (const point of layer) {
143
+ if (point) run.push(point);else flush();
144
+ }
145
+ flush();
146
+ layer.forEach(point => {
147
+ if (!point) return;
148
+ const x = g.projectX(point.index),
149
+ y = g.projectY((point.lower + point.upper) / 2);
150
+ const circle = `<circle class="k2b-area-inspection-point" cx="${f(x)}" cy="${f(y)}" r="3" fill="${color}" opacity="0" stroke="none"/>`;
151
+ parts.push(chartDatumSvg(circle, {
152
+ role: "point",
153
+ index: point.index,
154
+ seriesIndex: s,
155
+ label: spec.series[s].label,
156
+ anchor: [x, g.top],
157
+ values: [{
158
+ key: "x",
159
+ value: spec.x[point.index],
160
+ formatted: g.xFormat(spec.x[point.index])
161
+ }, {
162
+ key: "value",
163
+ value: point.amount,
164
+ formatted: g.format(point.amount)
165
+ }, {
166
+ key: "total",
167
+ value: g.totals[point.index],
168
+ formatted: g.format(g.totals[point.index])
169
+ }, ...(spec.normalize ? [{
170
+ key: "percent",
171
+ value: point.share * 100,
172
+ formatted: g.percent(point.share)
173
+ }] : [])]
174
+ }, spec.inspect ?? false));
175
+ });
176
+ });
177
+ const slot = (g.right - g.left) / Math.max(1, spec.x.length) * (1 / g.ratio);
178
+ const labelWidth = Math.max(1, ...spec.x.map(x => Math.min(24, String(x).length))) * 6;
179
+ const stride = Math.max(1, Math.ceil(labelWidth / Math.max(1, slot)));
180
+ spec.x.forEach((value, i) => {
181
+ if (i % stride !== 0) return;
182
+ const label = g.xFormat(value);
183
+ parts.push(text(g.projectX(i), g.bottom + 20, label.length > 24 ? label.slice(0, 23) + "…" : label, i === 0 ? "start" : i === spec.x.length - 1 ? "end" : "middle", 'class="stdlib-chart-tick-label"'));
184
+ });
185
+ const totalLabel = chartMessages.resolve([spec.locale ?? "en"]).t.chartFields({
186
+ key: "areaTotal"
187
+ });
188
+ return `<svg data-chart-total-label="${escapeXml(totalLabel)}" xmlns="http://www.w3.org/2000/svg" class="stdlib-chart k2b-area ${escapeXml(spec.className ?? "")}" width="${spec.width}" height="${spec.height}" viewBox="0 0 ${spec.width} ${spec.height}" aria-hidden="true" focusable="false">${parts.join("")}</svg>`;
189
+ }
190
+ //# sourceMappingURL=area.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sources":["charts/core/area.ts"],"sourcesContent":["import { chartMessages } from \"../../content/chart-message-catalog\";\nimport { chartDatumSvg, escapeXml, niceStep } from \"./svg\";\n\nexport type AreaOptions = {\n layout: \"stacked\";\n normalize?: boolean;\n x: (string | number)[];\n series: { label: string; data: (number | null)[] }[];\n title?: string;\n subtitle?: string;\n className?: string;\n legend?: boolean;\n format?: (value: number) => string;\n locale?: string;\n /** Narrowest displayed width in CSS pixels; measured hosts pass their actual width. */\n displayWidth?: number;\n};\nexport type AreaPoint = { index: number; amount: number; share: number; lower: number; upper: number };\n\n/** Missing observations interrupt the entire stack; zero-sum columns are gaps only in normalized mode. */\nexport function areaLayout(spec: AreaOptions, size: { width: number; height: number }) {\n if (![size.width, size.height].every((v) => Number.isFinite(v) && v > 0)) throw new RangeError(\"Invalid area size\");\n if (spec.x.some((x) => typeof x === \"number\" && !Number.isFinite(x))) throw new RangeError(\"Area X values must be finite\");\n if (spec.series.some((s) => s.data.length !== spec.x.length)) throw new RangeError(\"Area series must match x length\");\n const totals = spec.x.map((_, i) => {\n let total = 0,\n missing = false;\n for (const series of spec.series) {\n const value = series.data[i];\n if (value === null) {\n missing = true;\n continue;\n }\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0)\n throw new RangeError(\"Area values must be nonnegative finite numbers or null\");\n total += value;\n }\n if (!Number.isFinite(total)) throw new RangeError(\"Area totals must be finite\");\n return missing || (spec.normalize && total === 0) ? null : total;\n });\n const layers: (AreaPoint | null)[][] = spec.series.map(() => []);\n totals.forEach((total, index) => {\n let lower = 0;\n spec.series.forEach((series, s) => {\n const amount = series.data[index];\n if (total === null || amount === null || amount === undefined) {\n layers[s]!.push(null);\n return;\n }\n const share = total === 0 ? 0 : amount / total;\n const upper = lower + (spec.normalize ? share : amount);\n layers[s]!.push({ index, amount, share, lower, upper });\n lower = upper;\n });\n });\n let rawMax = 0;\n for (const total of totals) if (total !== null) rawMax = Math.max(rawMax, total);\n const step = spec.normalize ? 0.25 : niceStep(rawMax || 1, 5);\n const max = spec.normalize ? 1 : Math.ceil((rawMax || 1) / step) * step;\n if (!(step > 0) || !Number.isFinite(max)) throw new RangeError(\"Area values cannot form a finite axis; rescale them first\");\n const ticks = Array.from({ length: Math.round(max / step) + 1 }, (_, i) => i * step);\n const format = spec.format ?? new Intl.NumberFormat(spec.locale ?? \"en\", { maximumFractionDigits: 2 }).format;\n const percent = new Intl.NumberFormat(spec.locale ?? \"en\", { style: \"percent\", minimumFractionDigits: 1, maximumFractionDigits: 1 })\n .format;\n const axisFormat = spec.normalize\n ? new Intl.NumberFormat(spec.locale ?? \"en\", { style: \"percent\", maximumFractionDigits: 0 }).format\n : format;\n const numericX = new Intl.NumberFormat(spec.locale ?? \"en\", { useGrouping: \"min2\", maximumFractionDigits: 6 }).format;\n const xFormat = (value: string | number) => (typeof value === \"number\" ? numericX(value) : value);\n const displayWidth = spec.displayWidth ?? 296;\n if (!Number.isFinite(displayWidth) || displayWidth <= 0) throw new RangeError(\"Invalid area display width\");\n const ratio = size.width / displayWidth;\n const left = Math.min(size.width * 0.6, Math.max(44, ...ticks.map((v) => axisFormat(v).length * 7 + 12)) * ratio);\n const right = size.width - 12,\n top = (spec.title || spec.subtitle ? 50 : 16) + (spec.legend === false ? 0 : spec.series.length <= 2 ? 20 : 40);\n const bottom = Math.max(top + 1, size.height - 30);\n const projectX = (index: number) => (spec.x.length < 2 ? (left + right) / 2 : left + (index / (spec.x.length - 1)) * (right - left));\n const projectY = (value: number) => bottom - (value / max) * (bottom - top);\n return { layers, totals, max, ticks, format, percent, axisFormat, xFormat, ratio, left, right, top, bottom, projectX, projectY };\n}\nconst f = (value: number) => String(Math.round(value * 100) / 100);\nconst text = (x: number, y: number, label: string, anchor = \"middle\", extra = \"\") =>\n `<text x=\"${f(x)}\" y=\"${f(y)}\" text-anchor=\"${anchor}\" fill=\"currentColor\" stroke=\"none\" font-family=\"var(--k2b-font-sans, sans-serif)\" font-size=\"11\" ${extra}>${escapeXml(label)}</text>`;\n\nexport function renderAreaSvg(spec: AreaOptions & { width: number; height: number; inspect?: boolean }): string {\n const g = areaLayout(spec, spec),\n parts: string[] = [];\n if (spec.title) parts.push(text(spec.width / 2, 16, spec.title));\n if (spec.subtitle) parts.push(text(spec.width / 2, spec.title ? 32 : 16, spec.subtitle));\n for (const tick of g.ticks) {\n const y = g.projectY(tick);\n parts.push(\n `<line x1=\"${f(g.left)}\" x2=\"${f(g.right)}\" y1=\"${f(y)}\" y2=\"${f(y)}\" stroke=\"currentColor\" stroke-opacity=\"${tick === 0 ? 0.25 : 0.09}\"/>`,\n text(g.left - 8, y + 4, g.axisFormat(tick), \"end\", 'class=\"stdlib-chart-tick-label\"'),\n );\n }\n parts.push(\n `<line class=\"stdlib-chart-axis\" x1=\"${f(g.left)}\" x2=\"${f(g.right)}\" y1=\"${f(g.bottom)}\" y2=\"${f(g.bottom)}\" stroke=\"none\"/><line class=\"stdlib-chart-axis\" x1=\"${f(g.left)}\" x2=\"${f(g.left)}\" y1=\"${f(g.top)}\" y2=\"${f(g.bottom)}\" stroke=\"none\"/>`,\n );\n const palette = [\"#2563eb\", \"#16a34a\", \"#d97706\", \"#dc2626\", \"#a855f7\", \"#0891b2\", \"#db2777\", \"#65a30d\"];\n if (spec.legend !== false) {\n const entries = spec.series.slice(0, 4);\n const columns = 2;\n const cell = spec.width / columns;\n entries.forEach((series, i) => {\n const x = (i % columns) * cell + 4,\n y = g.top - (entries.length <= 2 ? 11 : 28) + Math.floor(i / columns) * 17;\n const color =\n i === 3 && spec.series.length > 4 ? \"var(--k2b-text-muted, #8b8b93)\" : `var(--stdlib-chart-c${(i % 8) + 1}, ${palette[i % 8]})`;\n const point = g.layers[i]?.at(-1);\n const value = point ? (spec.normalize ? g.percent(point.share) : g.format(point.amount)) : \"—\";\n const room = Math.floor((cell * (1 / g.ratio)) / 6) - 4;\n const showValue = value.length + 3 < room;\n const maxChars = Math.max(1, room - (showValue ? value.length + 1 : 0));\n const label = series.label.length > maxChars ? series.label.slice(0, maxChars - 1) + \"…\" : series.label;\n const full =\n i === 3 && spec.series.length > 4\n ? spec.series\n .slice(3)\n .map((s) => s.label)\n .join(\", \")\n : series.label;\n const shown =\n i === 3 && spec.series.length > 4 ? `+${new Intl.NumberFormat(spec.locale ?? \"en\").format(spec.series.length - 3)}` : label;\n const valueSvg =\n showValue && !(i === 3 && spec.series.length > 4)\n ? `<tspan dx=\"6\" font-weight=\"600\" data-chart-legend-value data-series-index=\"${i}\" data-field=\"${spec.normalize ? \"percent\" : \"value\"}\" data-default=\"${escapeXml(value)}\">${escapeXml(value)}</tspan>`\n : \"\";\n const labelSvg = text(x + 12, y, shown, \"start\").replace(\"</text>\", `${valueSvg}</text>`);\n parts.push(\n `<g><title>${escapeXml(full)}</title><rect x=\"${f(x)}\" y=\"${f(y - 8)}\" width=\"8\" height=\"8\" rx=\"2\" fill=\"${color}\" stroke=\"none\"/>${labelSvg}</g>`,\n );\n });\n }\n g.layers.forEach((layer, s) => {\n const color = `var(--stdlib-chart-c${(s % 8) + 1}, ${palette[s % 8]})`;\n let run: AreaPoint[] = [];\n const flush = () => {\n if (!run.length) return;\n const halfSlot = Math.min(10 * g.ratio, (g.right - g.left) / Math.max(2, spec.x.length * 2));\n const upper =\n run.length === 1\n ? [\n [Math.max(g.left, g.projectX(run[0]!.index) - halfSlot), g.projectY(run[0]!.upper)],\n [Math.min(g.right, g.projectX(run[0]!.index) + halfSlot), g.projectY(run[0]!.upper)],\n ]\n : run.map((p) => [g.projectX(p.index), g.projectY(p.upper)]);\n const lower =\n run.length === 1\n ? [\n [upper[1]![0]!, g.projectY(run[0]!.lower)],\n [upper[0]![0]!, g.projectY(run[0]!.lower)],\n ]\n : run.toReversed().map((p) => [g.projectX(p.index), g.projectY(p.lower)]);\n const points = [...upper, ...lower].map(([x, y]) => `${f(x!)},${f(y!)}`);\n parts.push(\n `<polygon class=\"k2b-area-layer\" points=\"${points.join(\" \")}\" fill=\"color-mix(in srgb, ${color} 55%, var(--k2b-surface, #ffffff))\" stroke=\"none\"/>`,\n `<polyline class=\"k2b-area-edge k2b-area-edge-${s % 8}\" points=\"${upper.map(([x, y]) => `${f(x!)},${f(y!)}`).join(\" \")}\" fill=\"none\" stroke=\"${color}\" stroke-width=\"1.5\"/>`,\n );\n run = [];\n };\n for (const point of layer) {\n if (point) run.push(point);\n else flush();\n }\n flush();\n layer.forEach((point) => {\n if (!point) return;\n const x = g.projectX(point.index),\n y = g.projectY((point.lower + point.upper) / 2);\n const circle = `<circle class=\"k2b-area-inspection-point\" cx=\"${f(x)}\" cy=\"${f(y)}\" r=\"3\" fill=\"${color}\" opacity=\"0\" stroke=\"none\"/>`;\n parts.push(\n chartDatumSvg(\n circle,\n {\n role: \"point\",\n index: point.index,\n seriesIndex: s,\n label: spec.series[s]!.label,\n anchor: [x, g.top],\n values: [\n {\n key: \"x\",\n value: spec.x[point.index]!,\n formatted: g.xFormat(spec.x[point.index]!),\n },\n { key: \"value\", value: point.amount, formatted: g.format(point.amount) },\n { key: \"total\", value: g.totals[point.index]!, formatted: g.format(g.totals[point.index]!) },\n ...(spec.normalize ? [{ key: \"percent\", value: point.share * 100, formatted: g.percent(point.share) }] : []),\n ],\n },\n spec.inspect ?? false,\n ),\n );\n });\n });\n const slot = ((g.right - g.left) / Math.max(1, spec.x.length)) * (1 / g.ratio);\n const labelWidth = Math.max(1, ...spec.x.map((x) => Math.min(24, String(x).length))) * 6;\n const stride = Math.max(1, Math.ceil(labelWidth / Math.max(1, slot)));\n spec.x.forEach((value, i) => {\n if (i % stride !== 0) return;\n const label = g.xFormat(value);\n parts.push(\n text(\n g.projectX(i),\n g.bottom + 20,\n label.length > 24 ? label.slice(0, 23) + \"…\" : label,\n i === 0 ? \"start\" : i === spec.x.length - 1 ? \"end\" : \"middle\",\n 'class=\"stdlib-chart-tick-label\"',\n ),\n );\n });\n const totalLabel = chartMessages.resolve([spec.locale ?? \"en\"]).t.chartFields({ key: \"areaTotal\" });\n return `<svg data-chart-total-label=\"${escapeXml(totalLabel)}\" xmlns=\"http://www.w3.org/2000/svg\" class=\"stdlib-chart k2b-area ${escapeXml(spec.className ?? \"\")}\" width=\"${spec.width}\" height=\"${spec.height}\" viewBox=\"0 0 ${spec.width} ${spec.height}\" aria-hidden=\"true\" focusable=\"false\">${parts.join(\"\")}</svg>`;\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,186 @@
1
+ import { chartDatumSvg, escapeXml, heatmap, renderHeader, svgRoot } from "./svg";
2
+ import { chartTextWidth } from "./text";
3
+ const DAY = 86400000;
4
+ /** Bound sparse inputs too: three leap years of daily observations. */
5
+ export const CALENDAR_MAX_DAYS = 1098;
6
+ export function calendarDay(value) {
7
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new RangeError("Calendar dates must be YYYY-MM-DD.");
8
+ const date = new Date(`${value}T00:00:00Z`);
9
+ if (!Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value) throw new RangeError("Invalid calendar date.");
10
+ return date.getTime();
11
+ }
12
+ /** Intl uses 1..7, JS dates use 0..6. Old engines use an explicit locale fallback. */
13
+ export function calendarWeekStart(locale = "en") {
14
+ const info = new Intl.Locale(locale);
15
+ const fallback = {
16
+ "en-US": 0
17
+ };
18
+ const key = `${info.language}-${info.region ?? info.maximize().region}`;
19
+ return (info.getWeekInfo?.().firstDay ?? info.weekInfo?.firstDay ?? fallback[key] ?? 1) % 7;
20
+ }
21
+ export const calendarWords = (locale = "en") => locale.split("-")[0] === "de" ? {
22
+ day: "Tag",
23
+ less: "weniger",
24
+ more: "mehr",
25
+ moreIn: "mehr rein",
26
+ moreOut: "mehr raus"
27
+ } : {
28
+ day: "Day",
29
+ less: "less",
30
+ more: "more",
31
+ moreIn: "more in",
32
+ moreOut: "more out"
33
+ };
34
+ /** Explicit fields avoid the engine-dependent dateStyle pattern. */
35
+ const calendarDateParts = locale => {
36
+ const formatter = new Intl.DateTimeFormat(locale, {
37
+ weekday: "long",
38
+ month: "long",
39
+ day: "numeric",
40
+ year: "numeric",
41
+ timeZone: "UTC"
42
+ });
43
+ return time => Object.fromEntries(formatter.formatToParts(time).map(part => [part.type, part.value]));
44
+ };
45
+ export function calendarLayout(spec, size = {
46
+ width: spec.width ?? 480,
47
+ height: spec.height ?? 280
48
+ }) {
49
+ const seen = new Set();
50
+ const dates = spec.data.map(([date, value], index) => {
51
+ if (seen.has(date)) throw new RangeError("Calendar dates must be unique.");
52
+ seen.add(date);
53
+ const time = calendarDay(date);
54
+ if (value !== null && !Number.isNaN(value) && !Number.isFinite(value)) throw new RangeError("Calendar values must be finite.");
55
+ return {
56
+ date,
57
+ value,
58
+ index,
59
+ time
60
+ };
61
+ }).sort((a, b) => a.time - b.time);
62
+ if (dates.length && (dates.at(-1).time - dates[0].time) / DAY >= CALENDAR_MAX_DAYS) throw new RangeError("Calendar span must not exceed three years.");
63
+ const points = dates.filter(p => p.value !== null && Number.isFinite(p.value));
64
+ const weekStart = calendarWeekStart(spec.locale);
65
+ const first = dates[0]?.time ?? 0;
66
+ const monday = first - (new Date(first).getUTCDay() - weekStart + 7) % 7 * DAY;
67
+ const weeks = dates.length ? Math.floor((dates.at(-1).time - monday) / DAY / 7) + 1 : 0;
68
+ const headerHeight = renderHeader(size.width, spec.title, spec.subtitle).height;
69
+ // Seven square rows use the available panel height. Wider calendars scroll rather than flattening their cells.
70
+ const cell = Math.max(14, Math.floor((size.height - headerHeight - 32 - 20 - 18) / 7)),
71
+ gap = 3,
72
+ left = 32;
73
+ const width = Math.max(spec.displayWidth ?? size.width, left + weeks * (cell + gap) + 4);
74
+ const top = 32 + renderHeader(width, spec.title, spec.subtitle).height;
75
+ const min = Math.min(...points.map(p => p.value), 0),
76
+ max = Math.max(...points.map(p => p.value), 0);
77
+ const absMax = Math.max(-min, max);
78
+ return {
79
+ width,
80
+ height: size.height,
81
+ minWidth: width,
82
+ firstMonday: monday,
83
+ weekStart,
84
+ weeks,
85
+ min,
86
+ max,
87
+ absMax,
88
+ cell,
89
+ cellWidth: cell,
90
+ top,
91
+ cells: points.map(p => {
92
+ const day = Math.round((p.time - monday) / DAY),
93
+ week = Math.floor(day / 7),
94
+ weekday = day % 7;
95
+ const intensity = spec.scale === "diverging" ? Math.abs(p.value) / (absMax || 1) : (p.value - min) / (max - min || 1);
96
+ const color = spec.scale === "diverging" ? p.value < 0 ? "var(--k2b-flow-out-mark,#8b5cf6)" : "var(--k2b-flow-in-mark,#0891b2)" : "var(--stdlib-chart-c1,#3b82f6)";
97
+ return {
98
+ ...p,
99
+ week,
100
+ weekday,
101
+ x: left + week * (cell + gap),
102
+ y: top + weekday * (cell + gap),
103
+ intensity,
104
+ color
105
+ };
106
+ })
107
+ };
108
+ }
109
+ const paint = (color, intensity) => `color-mix(in srgb,${color} ${12 + 88 * Math.sqrt(intensity)}%,var(--k2b-surface-muted,#f4f4f5))`;
110
+ export function renderCalendarSvg(spec) {
111
+ const s = calendarLayout(spec),
112
+ locale = spec.locale ?? "en",
113
+ words = calendarWords(locale);
114
+ const number = spec.format ?? new Intl.NumberFormat(locale, {
115
+ maximumSignificantDigits: 15
116
+ }).format;
117
+ const parts = calendarDateParts(locale);
118
+ const weekday = new Intl.DateTimeFormat(locale, {
119
+ weekday: "short",
120
+ timeZone: "UTC"
121
+ });
122
+ const month = new Intl.DateTimeFormat(locale, {
123
+ month: "short",
124
+ timeZone: "UTC"
125
+ });
126
+ // Left aligned direct-core header; interactive hosts keep it at the visible scroll edge.
127
+ const header = renderHeader(s.width, spec.title, spec.subtitle).svg.replace(/x="[^"]*"/g, 'x="0"').replace(/text-anchor="middle"/g, 'text-anchor="start"').replace(/<text /g, '<text fill="var(--k2b-text,#27272a)" font-size="12" ');
128
+ const body = [`<g style="transform:translateX(var(--k2b-calendar-header-x,0px))">${header}</g>`];
129
+ const days = [`<g data-calendar-weekdays="true" pointer-events="none" style="transform:translateX(var(--k2b-calendar-header-x,0px))"><rect x="-1" y="${s.top - 18}" width="33" height="${7 * (s.cell + 3) + 18}" fill="var(--k2b-surface,#fff)"/>`];
130
+ for (const day of [0, 2, 4]) days.push(`<text x="0" y="${s.top + day * (s.cell + 3) + s.cell - 2}" fill="var(--k2b-text-muted,#45454e)" font-size="11">${escapeXml(weekday.format(Date.UTC(2026, 0, 4 + s.weekStart + day)))}</text>`);
131
+ let lastRight = -Infinity,
132
+ lastMonth = "";
133
+ for (let week = 0; week < s.weeks; week++) {
134
+ const time = s.firstMonday + week * 7 * DAY,
135
+ next = time + 6 * DAY;
136
+ const nextDate = new Date(next),
137
+ ym = nextDate.toISOString().slice(0, 7);
138
+ if (ym === lastMonth) continue;
139
+ lastMonth = ym;
140
+ const text = month.format(next);
141
+ const textWidth = chartTextWidth(text, 11);
142
+ const x = Math.min(s.width - textWidth - 2, Math.max(32 + week * (s.cell + 3), lastRight + 3));
143
+ const visibility = `visibility:var(--k2b-calendar-month-${week},visible)`;
144
+ // The year has its own row, so a partial first month cannot suppress the following month.
145
+ if (week === 0 || nextDate.getUTCMonth() === 0) body.push(`<g style="${visibility}"><text x="${x}" y="${s.top - 24}" font-size="10" fill="var(--k2b-text-muted,#45454e)">${nextDate.getUTCFullYear()}</text></g>`);
146
+ body.push(`<text data-calendar-month="${week}" style="${visibility}" x="${x}" y="${s.top - 10}" font-size="11" fill="var(--k2b-text-muted,#45454e)">${escapeXml(text)}</text>`);
147
+ lastRight = x + textWidth;
148
+ }
149
+ for (const c of s.cells) {
150
+ const d = parts(c.time);
151
+ const date = locale.startsWith("de") ? `${d.day}. ${d.month} ${d.year}` : /^en(?:-US)?$/.test(locale) ? `${d.month} ${d.day}, ${d.year}` : `${d.day} ${d.month} ${d.year}`;
152
+ const fill = spec.scale === "diverging" && c.value === 0 ? "var(--k2b-surface-muted,#f4f4f5)" : paint(c.color, c.intensity);
153
+ const value = spec.scale === "diverging" && c.value !== 0 ? `${number(Math.abs(c.value))} ${c.value < 0 ? words.moreOut : words.moreIn}` : number(c.value);
154
+ body.push(chartDatumSvg(`<rect x="${c.x}" y="${c.y}" width="${s.cell}" height="${s.cell}" rx="2" fill="${fill}" stroke="none"/>`, {
155
+ role: "cell",
156
+ index: c.index,
157
+ label: `${d.weekday}, ${date}`,
158
+ anchor: [c.x + s.cell / 2, c.y + s.cell / 2],
159
+ grid: [c.week, c.weekday],
160
+ values: [{
161
+ key: "value",
162
+ value: c.value,
163
+ formatted: value
164
+ }]
165
+ }, spec.inspect));
166
+ }
167
+ days.push("</g>");
168
+ body.push(days.join(""));
169
+ const legendY = s.top + 7 * (s.cell + 3) + 12;
170
+ const start = spec.scale === "diverging" ? words.moreOut : words.less,
171
+ end = spec.scale === "diverging" ? words.moreIn : words.more;
172
+ const swatches = spec.scale === "diverging" ? [-1, -0.5, 0, 0.5, 1] : [0, 0.25, 0.5, 0.75, 1];
173
+ const offset = 32 + start.length * 6 + 8;
174
+ body.push(`<g data-calendar-legend="true" style="transform:translateX(var(--k2b-calendar-header-x,0px))"><text x="32" y="${legendY}" fill="var(--k2b-text-muted,#45454e)" font-size="11">${escapeXml(start)}</text>`);
175
+ swatches.forEach((v, i) => {
176
+ const color = spec.scale === "diverging" ? v < 0 ? "var(--k2b-flow-out-mark,#8b5cf6)" : "var(--k2b-flow-in-mark,#0891b2)" : "var(--stdlib-chart-c1,#3b82f6)";
177
+ body.push(`<rect x="${offset + i * 14}" y="${legendY - 10}" width="11" height="11" rx="2" fill="${spec.scale === "diverging" && v === 0 ? "var(--k2b-surface-muted,#f4f4f5)" : paint(color, Math.abs(v))}"/>`);
178
+ });
179
+ body.push(`<text x="${offset + swatches.length * 14 + 4}" y="${legendY}" fill="var(--k2b-text-muted,#45454e)" font-size="11">${escapeXml(end)}</text></g>`);
180
+ return svgRoot({
181
+ ...s,
182
+ className: spec.className
183
+ }, body.join("")).replace("<svg ", `<svg data-calendar="true" data-chart-min-width="${s.minWidth}" `);
184
+ }
185
+ export const renderHeatmapSvg = spec => spec.layout === "calendar" ? renderCalendarSvg(spec) : heatmap(spec);
186
+ //# sourceMappingURL=calendar.js.map