@north-light/crouter 0.3.198 → 0.3.200

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 (316) hide show
  1. package/dist/api/client.d.ts +18 -18
  2. package/dist/api/client.js +29 -37
  3. package/dist/api/dto/broker-ops.d.ts +3 -1
  4. package/dist/api/dto/broker.d.ts +13 -2
  5. package/dist/api/dto/broker.js +4 -4
  6. package/dist/api/dto/human.d.ts +6 -5
  7. package/dist/api/dto/inbox.d.ts +81 -34
  8. package/dist/api/dto/nodes.d.ts +8 -2
  9. package/dist/api/dto/profiles.d.ts +23 -0
  10. package/dist/api/dto/profiles.js +2 -2
  11. package/dist/api/dto/worktree.d.ts +3 -5
  12. package/dist/api/routes.d.ts +3 -5
  13. package/dist/api/routes.js +4 -6
  14. package/dist/builtin-memory/00-runtime-base.md +6 -6
  15. package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
  16. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
  17. package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
  18. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
  19. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
  20. package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
  21. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
  22. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
  23. package/dist/builtin-memory/insights/capture.md +2 -2
  24. package/dist/builtin-memory/insights/init.md +3 -3
  25. package/dist/builtin-memory/internal/INDEX.md +1 -1
  26. package/dist/builtin-memory/internal/memory-loading.md +1 -1
  27. package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
  28. package/dist/builtin-memory/internal/plugins.md +59 -2
  29. package/dist/builtin-memory/internal/storage-tiers.md +3 -1
  30. package/dist/builtin-memory/spec/roadmap.md +2 -2
  31. package/dist/builtin-memory/testing.md +1 -1
  32. package/dist/clients/attach/chrome/bash-jobs.js +3 -3
  33. package/dist/clients/attach/render/chat-view.d.ts +26 -1
  34. package/dist/clients/attach/render/chat-view.js +61 -11
  35. package/dist/clients/attach/render/page-block.d.ts +34 -0
  36. package/dist/clients/attach/render/page-block.js +226 -0
  37. package/dist/clients/attach/session/whip.d.ts +2 -0
  38. package/dist/clients/attach/session/whip.js +2 -0
  39. package/dist/clients/attach/viewer.js +1419 -993
  40. package/dist/clients/conversation/projection.js +18 -3
  41. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
  42. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -21
  43. package/dist/clients/inbox/controller.js +10 -16
  44. package/dist/clients/inbox/page-adapter.d.ts +1 -3
  45. package/dist/clients/inbox/page-adapter.js +3 -4
  46. package/dist/clients/inbox/resolve.d.ts +1 -1
  47. package/dist/clients/inbox/resolve.js +3 -3
  48. package/dist/clients/inbox/review/document-surface.js +3 -1
  49. package/dist/clients/inbox/review/state.d.ts +2 -1
  50. package/dist/clients/inbox/review/state.js +5 -16
  51. package/dist/clients/inbox/tui/input.js +37 -19
  52. package/dist/clients/inbox/tui/panel.js +5 -31
  53. package/dist/clients/inbox/tui/render.d.ts +11 -0
  54. package/dist/clients/inbox/tui/render.js +71 -27
  55. package/dist/clients/inbox/tui/slots.d.ts +3 -1
  56. package/dist/clients/inbox/tui/slots.js +16 -12
  57. package/dist/clients/inbox/tui/types.d.ts +2 -7
  58. package/dist/clients/inbox/tui.js +2 -2
  59. package/dist/commands/__tests__/human.test.js +71 -114
  60. package/dist/commands/__tests__/node-message.test.js +14 -0
  61. package/dist/commands/attention.js +4 -4
  62. package/dist/commands/canvas-snapshot.js +1 -1
  63. package/dist/commands/canvas.js +1 -1
  64. package/dist/commands/dashboard.js +1 -1
  65. package/dist/commands/human/{html.d.ts → components.d.ts} +1 -1
  66. package/dist/commands/human/components.js +77 -0
  67. package/dist/commands/human/feedback.d.ts +2 -0
  68. package/dist/commands/human/feedback.js +108 -0
  69. package/dist/commands/human/prompts.d.ts +13 -5
  70. package/dist/commands/human/prompts.js +160 -97
  71. package/dist/commands/human/queue.js +57 -88
  72. package/dist/commands/human/shared.d.ts +2 -12
  73. package/dist/commands/human/shared.js +17 -11
  74. package/dist/commands/human.js +29 -21
  75. package/dist/commands/memory/origin.js +2 -2
  76. package/dist/commands/node/bash.js +4 -4
  77. package/dist/commands/node/inspect.js +3 -3
  78. package/dist/commands/node/lifecycle.js +1 -1
  79. package/dist/commands/node/message.js +2 -2
  80. package/dist/commands/node-worktree.js +8 -8
  81. package/dist/commands/pkg/browse.js +1 -1
  82. package/dist/commands/pkg/plugin-manage.js +129 -10
  83. package/dist/commands/profile/delete.js +41 -15
  84. package/dist/commands/profile.js +1 -1
  85. package/dist/commands/push.js +1 -1
  86. package/dist/commands/surface/node/focus.js +29 -14
  87. package/dist/commands/surface-edit.js +3 -3
  88. package/dist/commands/surface-inbox.d.ts +2 -0
  89. package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
  90. package/dist/commands/surface.js +2 -1
  91. package/dist/commands/sys/doctor.js +60 -3
  92. package/dist/commands/sys/feedback.js +79 -25
  93. package/dist/commands/sys/settings.js +1 -1
  94. package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
  95. package/dist/core/__tests__/extension-abort.test.js +5 -2
  96. package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
  97. package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
  98. package/dist/core/__tests__/helpers/harness.js +17 -55
  99. package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
  100. package/dist/core/__tests__/human-deliver.test.js +31 -26
  101. package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
  102. package/dist/core/__tests__/plugin-page-components.test.js +157 -0
  103. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +4 -5
  104. package/dist/core/__tests__/seam/dormancy-release.test.js +40 -0
  105. package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
  106. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -6
  107. package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
  108. package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
  109. package/dist/core/__tests__/serial/worktree.test.js +24 -198
  110. package/dist/core/__tests__/session-cycles.test.js +22 -0
  111. package/dist/core/__tests__/stop-guard.test.js +4 -4
  112. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
  113. package/dist/core/bash-jobs.d.ts +6 -0
  114. package/dist/core/bash-jobs.js +10 -0
  115. package/dist/core/canvas/__tests__/attention.test.js +8 -5
  116. package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
  117. package/dist/core/canvas/attention.js +8 -6
  118. package/dist/core/canvas/browse/model.d.ts +8 -2
  119. package/dist/core/canvas/browse/model.js +11 -4
  120. package/dist/core/canvas/canvas.d.ts +7 -0
  121. package/dist/core/canvas/canvas.js +37 -0
  122. package/dist/core/canvas/crons.d.ts +9 -0
  123. package/dist/core/canvas/crons.js +51 -0
  124. package/dist/core/canvas/node-order.d.ts +22 -1
  125. package/dist/core/canvas/node-order.js +26 -1
  126. package/dist/core/canvas/render-source.d.ts +5 -0
  127. package/dist/core/canvas/render-source.js +1 -0
  128. package/dist/core/canvas/types.d.ts +9 -3
  129. package/dist/core/command-plugins/bundle.d.ts +6 -1
  130. package/dist/core/command-plugins/bundle.js +26 -13
  131. package/dist/core/command.js +2 -1
  132. package/dist/core/config.d.ts +47 -1
  133. package/dist/core/config.js +137 -4
  134. package/dist/core/help.js +1 -1
  135. package/dist/core/human/__tests__/page-catalog.test.js +4 -0
  136. package/dist/core/human/__tests__/page-tickets.test.js +65 -52
  137. package/dist/core/human/__tests__/page.test.js +56 -98
  138. package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -6
  139. package/dist/core/human/answer-text.js +3 -3
  140. package/dist/core/human/answer.d.ts +6 -4
  141. package/dist/core/human/answer.js +4 -10
  142. package/dist/core/human/component-docs.d.ts +11 -7
  143. package/dist/core/human/component-docs.js +361 -88
  144. package/dist/core/human/convention.d.ts +17 -3
  145. package/dist/core/human/convention.js +36 -5
  146. package/dist/core/human/feedback-companion.d.ts +20 -0
  147. package/dist/core/human/feedback-companion.js +98 -0
  148. package/dist/core/human/feedback.d.ts +65 -0
  149. package/dist/core/human/feedback.js +116 -0
  150. package/dist/core/human/page-catalog.d.ts +40 -5
  151. package/dist/core/human/page-catalog.js +102 -30
  152. package/dist/core/human/page-errors.d.ts +4 -0
  153. package/dist/core/human/page-errors.js +7 -0
  154. package/dist/core/human/page-eval.d.ts +20 -0
  155. package/dist/core/human/page-eval.js +123 -0
  156. package/dist/core/human/page-schema.d.ts +23 -191
  157. package/dist/core/human/page-schema.js +60 -89
  158. package/dist/core/human/page-synth.d.ts +1 -1
  159. package/dist/core/human/page-synth.js +15 -18
  160. package/dist/core/human/page.d.ts +12 -27
  161. package/dist/core/human/page.js +142 -474
  162. package/dist/core/human/root.d.ts +6 -2
  163. package/dist/core/human/root.js +12 -4
  164. package/dist/core/human/scan.d.ts +3 -4
  165. package/dist/core/human/scan.js +16 -15
  166. package/dist/core/human/summary.d.ts +1 -2
  167. package/dist/core/human/summary.js +2 -2
  168. package/dist/core/human/tickets.d.ts +20 -14
  169. package/dist/core/human/tickets.js +91 -31
  170. package/dist/core/human/types.d.ts +10 -2
  171. package/dist/core/preview-registry.js +8 -10
  172. package/dist/core/profiles/default-binding.d.ts +4 -0
  173. package/dist/core/profiles/default-binding.js +24 -0
  174. package/dist/core/profiles/deletion-reservation.d.ts +8 -0
  175. package/dist/core/profiles/deletion-reservation.js +38 -0
  176. package/dist/core/profiles/manifest.d.ts +3 -0
  177. package/dist/core/profiles/manifest.js +12 -0
  178. package/dist/core/profiles/select.js +2 -2
  179. package/dist/core/review/realize.js +3 -3
  180. package/dist/core/review/store.d.ts +6 -0
  181. package/dist/core/review/store.js +31 -0
  182. package/dist/core/runtime/bearings.d.ts +0 -4
  183. package/dist/core/runtime/bearings.js +4 -62
  184. package/dist/core/runtime/bin-contributions.d.ts +44 -0
  185. package/dist/core/runtime/bin-contributions.js +186 -0
  186. package/dist/core/runtime/broker/client-registry.d.ts +4 -0
  187. package/dist/core/runtime/broker/client-registry.js +13 -1
  188. package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
  189. package/dist/core/runtime/broker/extension-abort.js +4 -2
  190. package/dist/core/runtime/broker/fault-retry.js +4 -0
  191. package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
  192. package/dist/core/runtime/broker/frame-dispatch.js +5 -6
  193. package/dist/core/runtime/broker/read-ops.d.ts +1 -1
  194. package/dist/core/runtime/broker/read-ops.js +2 -2
  195. package/dist/core/runtime/broker/rebind.d.ts +1 -0
  196. package/dist/core/runtime/broker/rebind.js +6 -1
  197. package/dist/core/runtime/broker-extension-render.js +19 -0
  198. package/dist/core/runtime/broker-protocol.d.ts +14 -0
  199. package/dist/core/runtime/broker.d.ts +1 -1
  200. package/dist/core/runtime/broker.js +17 -6
  201. package/dist/core/runtime/front-door-env.d.ts +5 -0
  202. package/dist/core/runtime/front-door-env.js +16 -0
  203. package/dist/core/runtime/front-door.d.ts +1 -5
  204. package/dist/core/runtime/front-door.js +2 -5
  205. package/dist/core/runtime/host.d.ts +1 -1
  206. package/dist/core/runtime/invocation.d.ts +6 -0
  207. package/dist/core/runtime/invocation.js +10 -0
  208. package/dist/core/runtime/kickoff.js +34 -0
  209. package/dist/core/runtime/launch.d.ts +2 -6
  210. package/dist/core/runtime/node-read.js +4 -1
  211. package/dist/core/runtime/nodes.js +2 -0
  212. package/dist/core/runtime/promote.d.ts +1 -0
  213. package/dist/core/runtime/promote.js +3 -1
  214. package/dist/core/runtime/session-cycles.d.ts +6 -0
  215. package/dist/core/runtime/session-cycles.js +15 -1
  216. package/dist/core/runtime/session-visibility.d.ts +17 -8
  217. package/dist/core/runtime/session-visibility.js +19 -10
  218. package/dist/core/runtime/spawn-env.d.ts +9 -1
  219. package/dist/core/runtime/spawn-env.js +62 -2
  220. package/dist/core/runtime/stop-guard.d.ts +2 -2
  221. package/dist/core/runtime/stop-guard.js +2 -2
  222. package/dist/core/runtime/stop-signals.d.ts +1 -1
  223. package/dist/core/runtime/stop-signals.js +2 -2
  224. package/dist/core/runtime/tmux-bindings.js +1 -1
  225. package/dist/core/session-model/session-state.d.ts +4 -3
  226. package/dist/core/session-model/session-state.js +14 -1
  227. package/dist/core/user-settings.d.ts +21 -2
  228. package/dist/core/user-settings.js +32 -11
  229. package/dist/core/worktree.d.ts +15 -45
  230. package/dist/core/worktree.js +59 -261
  231. package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
  232. package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
  233. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  234. package/dist/daemon/api/handlers/broker-ops.js +9 -5
  235. package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
  236. package/dist/daemon/api/handlers/feedback-comments.js +207 -0
  237. package/dist/daemon/api/handlers/human.js +24 -20
  238. package/dist/daemon/api/handlers/inbox.d.ts +10 -0
  239. package/dist/daemon/api/handlers/inbox.js +39 -115
  240. package/dist/daemon/api/handlers/profiles.js +11 -29
  241. package/dist/daemon/api/map.d.ts +6 -1
  242. package/dist/daemon/api/map.js +23 -0
  243. package/dist/daemon/api/server.js +3 -1
  244. package/dist/daemon/companion-retire.d.ts +2 -0
  245. package/dist/daemon/companion-retire.js +33 -0
  246. package/dist/daemon/cron-run.js +14 -16
  247. package/dist/daemon/human/finish.d.ts +12 -7
  248. package/dist/daemon/human/finish.js +101 -38
  249. package/dist/daemon/human/sweep.js +5 -1
  250. package/dist/daemon/profile-delete.d.ts +7 -0
  251. package/dist/daemon/profile-delete.js +306 -0
  252. package/dist/daemon/reconcilers/broker-supervision.js +3 -1
  253. package/dist/daemon/review/deliver.js +3 -3
  254. package/dist/daemon/review/finish.d.ts +0 -2
  255. package/dist/daemon/review/finish.js +1 -29
  256. package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
  257. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
  258. package/dist/pi-extensions/canvas-bash-valve.js +19 -5
  259. package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
  260. package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
  261. package/dist/pi-extensions/canvas-review-boundary.js +18 -9
  262. package/dist/pi-extensions/canvas-stophook.js +4 -4
  263. package/dist/shared/generated-context.js +2 -2
  264. package/dist/types.d.ts +44 -2
  265. package/dist/types.js +7 -2
  266. package/package.json +6 -5
  267. package/runtime.lock.json +5 -33
  268. package/dist/builtin-memory/init.md +0 -38
  269. package/dist/builtin-memory/plan.md +0 -19
  270. package/dist/builtin-memory/spec.md +0 -19
  271. package/dist/commands/human/doc.d.ts +0 -2
  272. package/dist/commands/human/doc.js +0 -91
  273. package/dist/commands/human/html.js +0 -81
  274. package/dist/commands/human/inbox.d.ts +0 -2
  275. package/dist/core/human/__tests__/html-markdown.test.js +0 -52
  276. package/dist/core/human/__tests__/page-render.test.js +0 -66
  277. package/dist/core/human/html-markdown.d.ts +0 -3
  278. package/dist/core/human/html-markdown.js +0 -288
  279. package/dist/core/human/page-render.d.ts +0 -4
  280. package/dist/core/human/page-render.js +0 -105
  281. package/dist/pages/bundle.css +0 -1
  282. package/dist/pages/bundle.js +0 -1778
  283. package/dist/pages/comments.d.ts +0 -72
  284. package/dist/pages/comments.js +0 -176
  285. package/dist/pages/controls.d.ts +0 -98
  286. package/dist/pages/controls.js +0 -261
  287. package/dist/pages/elements/cards.d.ts +0 -28
  288. package/dist/pages/elements/cards.js +0 -915
  289. package/dist/pages/elements/chart.d.ts +0 -23
  290. package/dist/pages/elements/chart.js +0 -664
  291. package/dist/pages/elements/options.d.ts +0 -30
  292. package/dist/pages/elements/options.js +0 -742
  293. package/dist/pages/elements/pages.d.ts +0 -30
  294. package/dist/pages/elements/pages.js +0 -377
  295. package/dist/pages/elements/slot.d.ts +0 -26
  296. package/dist/pages/elements/slot.js +0 -112
  297. package/dist/pages/elements/table.d.ts +0 -36
  298. package/dist/pages/elements/table.js +0 -1043
  299. package/dist/pages/elements/text.d.ts +0 -31
  300. package/dist/pages/elements/text.js +0 -1175
  301. package/dist/pages/entry.d.ts +0 -8
  302. package/dist/pages/entry.js +0 -8
  303. package/dist/pages/host.d.ts +0 -134
  304. package/dist/pages/host.js +0 -165
  305. package/dist/pages/readonly.d.ts +0 -24
  306. package/dist/pages/readonly.js +0 -31
  307. package/dist/pages/register.d.ts +0 -2
  308. package/dist/pages/register.js +0 -3
  309. package/dist/pages/responses.d.ts +0 -37
  310. package/dist/pages/responses.js +0 -56
  311. package/dist/pages/slot-config.d.ts +0 -36
  312. package/dist/pages/slot-config.js +0 -50
  313. package/dist/pages/types.d.ts +0 -98
  314. package/dist/pages/types.js +0 -23
  315. /package/dist/{core/human/__tests__/html-markdown.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
  316. /package/dist/core/{human/__tests__/page-render.test.d.ts → __tests__/plugin-page-components.test.d.ts} +0 -0
@@ -1,23 +0,0 @@
1
- /**
2
- * `<crtr-chart>` — the line, bar and area plots the page bundle draws.
3
- *
4
- * ## Every value here is a `--crtr-chart-*` token
5
- *
6
- * The frame, the legend, the axes and the marks themselves are painted through
7
- * `var(--crtr-chart-<part>-<knob>, <kit default>)`, so a host restyles this element by setting
8
- * properties on the document root — custom properties inherit into shadow roots, which is the
9
- * whole mechanism. Series colour keeps coming from the bundle-wide `--crtr-chart-1..5` ramp,
10
- * which is the theme layer rather than a component knob.
11
- *
12
- * Mark paint that SVG would otherwise carry as a presentation attribute — the grid's stroke,
13
- * the line's width and caps, the area's fill opacity, the point radius, the bar's corner
14
- * radius — is written as CSS instead, because an attribute cannot hold a `var()` chain and so
15
- * cannot be overridden. The rendered plot height is styling and reads
16
- * `--crtr-chart-plot-height`; what stays in TypeScript is the coordinate system's layout
17
- * arithmetic — the drawing height, margins, tick budget and label metrics are inputs to the
18
- * geometry this element computes, not declarations a stylesheet could carry.
19
- *
20
- * Defaults stay inline at the rule that spends them and no `--crtr-chart-*` component token is
21
- * ever declared: an unset token is what carries the kit default down the chain.
22
- */
23
- export {};
@@ -1,664 +0,0 @@
1
- /**
2
- * `<crtr-chart>` — the line, bar and area plots the page bundle draws.
3
- *
4
- * ## Every value here is a `--crtr-chart-*` token
5
- *
6
- * The frame, the legend, the axes and the marks themselves are painted through
7
- * `var(--crtr-chart-<part>-<knob>, <kit default>)`, so a host restyles this element by setting
8
- * properties on the document root — custom properties inherit into shadow roots, which is the
9
- * whole mechanism. Series colour keeps coming from the bundle-wide `--crtr-chart-1..5` ramp,
10
- * which is the theme layer rather than a component knob.
11
- *
12
- * Mark paint that SVG would otherwise carry as a presentation attribute — the grid's stroke,
13
- * the line's width and caps, the area's fill opacity, the point radius, the bar's corner
14
- * radius — is written as CSS instead, because an attribute cannot hold a `var()` chain and so
15
- * cannot be overridden. The rendered plot height is styling and reads
16
- * `--crtr-chart-plot-height`; what stays in TypeScript is the coordinate system's layout
17
- * arithmetic — the drawing height, margins, tick budget and label metrics are inputs to the
18
- * geometry this element computes, not declarations a stylesheet could carry.
19
- *
20
- * Defaults stay inline at the rule that spends them and no `--crtr-chart-*` component token is
21
- * ever declared: an unset token is what carries the kit default down the chain.
22
- */
23
- import { registerElement } from '../register.js';
24
- import { readSlotConfig } from '../slot-config.js';
25
- /** The five chart tokens, cycled when a config declares more than five series. */
26
- const SERIES_COLORS = [
27
- 'var(--crtr-chart-1)',
28
- 'var(--crtr-chart-2)',
29
- 'var(--crtr-chart-3)',
30
- 'var(--crtr-chart-4)',
31
- 'var(--crtr-chart-5)',
32
- ];
33
- const SVG_NS = 'http://www.w3.org/2000/svg';
34
- const HEIGHT = 260;
35
- const MARGIN_TOP = 12;
36
- const MARGIN_RIGHT = 12;
37
- const MARGIN_BOTTOM = 28;
38
- const Y_TICK_TARGET = 5;
39
- /** Horizontal room one x tick label needs before labels start colliding. */
40
- const X_TICK_SPACING = 88;
41
- /** Rough advance width of one character at tick size, used to keep edge labels in the box. */
42
- const LABEL_CHAR_WIDTH = 5.8;
43
- const MAX_X_LABEL_CHARS = 14;
44
- const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(?:[T ]|$)/;
45
- const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
46
- const STYLE = `
47
- :host {
48
- display: block;
49
- }
50
-
51
- [part="container"] {
52
- display: flex;
53
- flex-direction: column;
54
- gap: var(--crtr-chart-container-gap, var(--crtr-space-2));
55
- width: 100%;
56
- }
57
-
58
- [part="title"] {
59
- color: var(--crtr-chart-title-text-color, var(--crtr-color-text));
60
- font-weight: var(--crtr-chart-title-font-weight, 500);
61
- }
62
-
63
- [part="legend"] {
64
- display: flex;
65
- flex-wrap: wrap;
66
- align-items: center;
67
- gap: var(--crtr-chart-legend-gap, var(--crtr-space-1) var(--crtr-space-4));
68
- color: var(--crtr-chart-legend-text-color, var(--crtr-color-text-muted));
69
- font-size: var(--crtr-chart-legend-font-size, 0.875em);
70
- }
71
-
72
- [part="legend-item"] {
73
- display: flex;
74
- align-items: center;
75
- gap: var(--crtr-chart-legend-item-gap, var(--crtr-space-1));
76
- min-width: 0;
77
- }
78
-
79
- /* Sized against the legend's own text, not a spacing token doing a sizing job. One knob:
80
- the swatch is a square, so its width and height are the same value. */
81
- [part="legend-swatch"] {
82
- flex: none;
83
- width: var(--crtr-chart-legend-swatch-size, 0.75em);
84
- height: var(--crtr-chart-legend-swatch-size, 0.75em);
85
- border-radius: var(--crtr-chart-legend-swatch-radius, calc(var(--crtr-radius-small) * 0.5));
86
- }
87
-
88
- [part="legend-label"] {
89
- overflow: hidden;
90
- text-overflow: ellipsis;
91
- white-space: nowrap;
92
- }
93
-
94
- [part="plot"] {
95
- width: 100%;
96
- }
97
-
98
- [part="chart"] {
99
- display: block;
100
- height: var(--crtr-chart-plot-height, 260px);
101
- overflow: visible;
102
- }
103
-
104
- /* Waiting for data and having none hold the same space, but each stays in its own scope:
105
- one scope never names two different parts. */
106
- [part="empty"] {
107
- display: flex;
108
- align-items: center;
109
- justify-content: center;
110
- padding: var(--crtr-chart-empty-padding, 0 var(--crtr-space-4));
111
- color: var(--crtr-chart-empty-text-color, var(--crtr-color-text-muted));
112
- font-size: var(--crtr-chart-empty-font-size, 0.875em);
113
- border: var(--crtr-chart-empty-border-width, 1px)
114
- var(--crtr-chart-empty-border-style, dashed)
115
- var(--crtr-chart-empty-border-color, var(--crtr-color-border));
116
- border-radius: var(--crtr-chart-empty-radius, var(--crtr-radius-medium));
117
- }
118
-
119
- [part="status"] {
120
- display: flex;
121
- align-items: center;
122
- justify-content: center;
123
- padding: var(--crtr-chart-status-padding, 0 var(--crtr-space-4));
124
- color: var(--crtr-chart-status-text-color, var(--crtr-color-text-muted));
125
- font-size: var(--crtr-chart-status-font-size, 0.875em);
126
- border: var(--crtr-chart-status-border-width, 1px)
127
- var(--crtr-chart-status-border-style, dashed)
128
- var(--crtr-chart-status-border-color, var(--crtr-color-border));
129
- border-radius: var(--crtr-chart-status-radius, var(--crtr-radius-medium));
130
- }
131
-
132
- /* Flat: this sits in the flow of the page. A shadow in this bundle means floating. */
133
- [part="unavailable"] {
134
- padding: var(--crtr-chart-unavailable-padding, var(--crtr-space-3));
135
- color: var(--crtr-chart-unavailable-text-color, var(--crtr-color-text-muted));
136
- background: var(--crtr-chart-unavailable-fill, var(--crtr-color-surface));
137
- border: var(--crtr-chart-unavailable-border-width, 1px)
138
- var(--crtr-chart-unavailable-border-style, solid)
139
- var(--crtr-chart-unavailable-border-color, var(--crtr-color-border));
140
- border-radius: var(--crtr-chart-unavailable-radius, var(--crtr-radius-medium));
141
- }
142
-
143
- [part="reason"] {
144
- color: var(--crtr-chart-reason-text-color, var(--crtr-color-text));
145
- font-family: var(--crtr-chart-reason-font-family, var(--crtr-font-mono));
146
- }
147
-
148
- .grid {
149
- stroke: var(--crtr-chart-grid-stroke-color, var(--crtr-color-border));
150
- stroke-width: var(--crtr-chart-grid-stroke-width, 1);
151
- opacity: var(--crtr-chart-grid-opacity, 0.45);
152
- }
153
-
154
- /* The gridline the marks are read against carries more weight than the rest. */
155
- .grid.baseline {
156
- opacity: var(--crtr-chart-grid-baseline-opacity, 0.9);
157
- }
158
-
159
- .tick {
160
- fill: var(--crtr-chart-tick-text-color, var(--crtr-color-text-muted));
161
- font-size: var(--crtr-chart-tick-font-size, 0.75em);
162
- }
163
-
164
- /* Mark paint. Series colour arrives per node from the theme's chart ramp; everything
165
- these marks are drawn WITH is a knob, which is why none of it is an SVG attribute. */
166
- [part="line"] {
167
- stroke-width: var(--crtr-chart-line-stroke-width, 2);
168
- stroke-linecap: var(--crtr-chart-line-line-cap, round);
169
- stroke-linejoin: var(--crtr-chart-line-line-join, round);
170
- }
171
-
172
- [part="area"] {
173
- fill-opacity: var(--crtr-chart-area-fill-opacity, 0.18);
174
- }
175
-
176
- [part="point"] {
177
- r: var(--crtr-chart-point-radius, 2.5);
178
- }
179
-
180
- /* A bar never rounds away more than a third of its own width, and --bar-max-radius is
181
- that width — the one number here the stylesheet cannot know, so the plot writes it onto
182
- the SVG and the knob is clamped against it. */
183
- [part="bar"] {
184
- rx: min(var(--crtr-chart-bar-radius, 2px), var(--bar-max-radius));
185
- }
186
- `;
187
- function normalizeConfig(raw) {
188
- const kind = raw.kind;
189
- if (kind !== 'line' && kind !== 'bar' && kind !== 'area') {
190
- return { status: 'invalid', reason: 'chart kind must be line, bar or area' };
191
- }
192
- const x = raw.x;
193
- if (typeof x !== 'string' || x === '')
194
- return { status: 'invalid', reason: 'chart config has no x field' };
195
- if (!Array.isArray(raw.y) || raw.y.length === 0) {
196
- return { status: 'invalid', reason: 'chart config has no y series' };
197
- }
198
- const y = [];
199
- for (const [index, entry] of raw.y.entries()) {
200
- if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
201
- return { status: 'invalid', reason: `chart series #${index} is not { field, label? }` };
202
- }
203
- const definition = entry;
204
- if (typeof definition.field !== 'string' || definition.field === '') {
205
- return { status: 'invalid', reason: `chart series #${index} has no field` };
206
- }
207
- if (definition.label !== undefined && typeof definition.label !== 'string') {
208
- return { status: 'invalid', reason: `chart series "${definition.field}" label is not a string` };
209
- }
210
- y.push({ field: definition.field, label: definition.label });
211
- }
212
- if (raw.title !== undefined && typeof raw.title !== 'string') {
213
- return { status: 'invalid', reason: 'chart title is not a string' };
214
- }
215
- return {
216
- status: 'ok',
217
- config: {
218
- kind,
219
- x,
220
- y,
221
- title: raw.title,
222
- data: raw.data,
223
- },
224
- };
225
- }
226
- function isRows(value) {
227
- return Array.isArray(value) && value.every((row) => row !== null && typeof row === 'object' && !Array.isArray(row));
228
- }
229
- function getByPath(row, path) {
230
- return path.split('.').reduce((current, key) => {
231
- if (current === null || current === undefined || typeof current !== 'object')
232
- return undefined;
233
- return current[key];
234
- }, row);
235
- }
236
- /** Convert a row field name into the human-readable label used by the legend and summary. */
237
- function titleCaseHeader(field) {
238
- if (field.includes(' '))
239
- return field;
240
- const lastSegment = field.includes('.') ? (field.split('.').pop() ?? field) : field;
241
- return lastSegment
242
- .replace(/[_-]+/g, ' ')
243
- .replace(/([a-z])([A-Z])/g, '$1 $2')
244
- .split(/\s+/)
245
- .filter(Boolean)
246
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
247
- .join(' ');
248
- }
249
- /** Coerce a cell into a plottable number, tolerating the string numerics agents emit. */
250
- function toNumber(value) {
251
- if (typeof value === 'number')
252
- return Number.isFinite(value) ? value : null;
253
- if (typeof value === 'boolean')
254
- return value ? 1 : 0;
255
- if (typeof value === 'bigint')
256
- return Number(value);
257
- if (typeof value === 'string') {
258
- const cleaned = value.replace(/[\s,_]/g, '').replace(/^[$£€¥]/, '').replace(/%$/, '');
259
- if (cleaned === '' || cleaned === '-')
260
- return null;
261
- const parsed = Number(cleaned);
262
- return Number.isFinite(parsed) ? parsed : null;
263
- }
264
- return null;
265
- }
266
- /** Render an x value as an axis category label. */
267
- function formatCategory(value) {
268
- if (value === null || value === undefined)
269
- return '';
270
- if (value instanceof Date) {
271
- return Number.isNaN(value.getTime())
272
- ? ''
273
- : value.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
274
- }
275
- if (typeof value === 'string') {
276
- const trimmed = value.trim();
277
- if (ISO_DATE_RE.test(trimmed)) {
278
- const parsed = new Date(trimmed);
279
- if (!Number.isNaN(parsed.getTime())) {
280
- // A date-only string parses as UTC midnight, so it has to be read back in UTC
281
- // or every label west of Greenwich names the previous day.
282
- return parsed.toLocaleDateString(undefined, DATE_ONLY_RE.test(trimmed)
283
- ? { month: 'short', day: 'numeric', timeZone: 'UTC' }
284
- : { month: 'short', day: 'numeric' });
285
- }
286
- }
287
- return value;
288
- }
289
- if (typeof value === 'object')
290
- return '';
291
- return String(value);
292
- }
293
- function truncateLabel(label) {
294
- return label.length > MAX_X_LABEL_CHARS ? `${label.slice(0, MAX_X_LABEL_CHARS - 1)}…` : label;
295
- }
296
- /**
297
- * Axis tick text. `compact` is decided once for the whole axis rather than per
298
- * value, so one axis never reads `5,000` above `10K`.
299
- */
300
- function formatTick(value, step, compact) {
301
- const decimals = step >= 1 ? 0 : Math.min(4, Math.ceil(-Math.log10(step)));
302
- return new Intl.NumberFormat(undefined, {
303
- maximumFractionDigits: decimals,
304
- notation: compact ? 'compact' : 'standard',
305
- }).format(value);
306
- }
307
- /** Tooltip text — always the full value, never rounded to the axis step. */
308
- function formatExact(value) {
309
- return new Intl.NumberFormat(undefined, { maximumFractionDigits: 4 }).format(value);
310
- }
311
- /** A 1 / 2 / 5 × 10ⁿ tick scale covering [min, max]. */
312
- function niceScale(min, max, target) {
313
- let low = min;
314
- let high = max;
315
- if (!Number.isFinite(low) || !Number.isFinite(high)) {
316
- low = 0;
317
- high = 1;
318
- }
319
- if (low === high) {
320
- const pad = Math.abs(low) > 0 ? Math.abs(low) * 0.1 : 1;
321
- low -= pad;
322
- high += pad;
323
- }
324
- const rawStep = (high - low) / Math.max(1, target);
325
- const magnitude = 10 ** Math.floor(Math.log10(rawStep));
326
- const normalized = rawStep / magnitude;
327
- const step = (normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10) * magnitude;
328
- const domainMin = Math.floor(low / step) * step;
329
- const domainMax = Math.ceil(high / step) * step;
330
- const ticks = [];
331
- for (let value = domainMin; value <= domainMax + step / 2; value += step) {
332
- // Re-round each accumulated step so 0.1 increments do not print as 0.30000000000000004.
333
- ticks.push(Number(value.toFixed(10)));
334
- }
335
- return { ticks, domainMin, domainMax, step };
336
- }
337
- /** Split a series into runs of consecutive plottable points, so gaps break the line. */
338
- function segmentsOf(points) {
339
- const segments = [];
340
- let current = [];
341
- for (const point of points) {
342
- const previous = current[current.length - 1];
343
- if (previous === undefined || point.index === previous.index + 1) {
344
- current.push(point);
345
- continue;
346
- }
347
- segments.push(current);
348
- current = [point];
349
- }
350
- if (current.length > 0)
351
- segments.push(current);
352
- return segments;
353
- }
354
- function svgNode(name, part) {
355
- const node = document.createElementNS(SVG_NS, name);
356
- if (part !== undefined)
357
- node.setAttribute('part', part);
358
- return node;
359
- }
360
- function setAttributes(node, attributes) {
361
- for (const [name, value] of Object.entries(attributes))
362
- node.setAttribute(name, String(value));
363
- }
364
- function tooltip(text) {
365
- const node = svgNode('title');
366
- node.textContent = text;
367
- return node;
368
- }
369
- class CrtrChartElement extends HTMLElement {
370
- #container = document.createElement('div');
371
- #title = document.createElement('div');
372
- #legend = document.createElement('div');
373
- #plot = document.createElement('div');
374
- #config = null;
375
- #rows = null;
376
- #observer = null;
377
- #width = 0;
378
- #generation = 0;
379
- constructor() {
380
- super();
381
- const root = this.attachShadow({ mode: 'open' });
382
- const style = document.createElement('style');
383
- style.textContent = STYLE;
384
- this.#container.part.add('container');
385
- this.#title.part.add('title');
386
- this.#legend.part.add('legend');
387
- this.#plot.part.add('plot');
388
- root.append(style, this.#container);
389
- }
390
- connectedCallback() {
391
- ++this.#generation;
392
- this.#config = null;
393
- this.#rows = null;
394
- const read = readSlotConfig(this);
395
- if (read.status !== 'ok') {
396
- this.#renderUnavailable(read.reason);
397
- return;
398
- }
399
- const normalized = normalizeConfig(read.config);
400
- if (normalized.status !== 'ok') {
401
- this.#renderUnavailable(normalized.reason);
402
- return;
403
- }
404
- this.#config = normalized.config;
405
- this.#renderFrame();
406
- this.#observe();
407
- const { data } = normalized.config;
408
- if (data !== undefined && !isRows(data)) {
409
- this.#renderUnavailable('chart data is not a list of rows');
410
- return;
411
- }
412
- this.#rows = data ?? [];
413
- this.#renderPlot();
414
- }
415
- disconnectedCallback() {
416
- this.#generation += 1;
417
- this.#observer?.disconnect();
418
- this.#observer = null;
419
- }
420
- #observe() {
421
- this.#observer?.disconnect();
422
- this.#width = this.#plot.getBoundingClientRect().width;
423
- const observer = new ResizeObserver((entries) => {
424
- const next = entries[0]?.contentRect.width ?? 0;
425
- if (Math.abs(this.#width - next) < 0.5)
426
- return;
427
- this.#width = next;
428
- this.#renderPlot();
429
- });
430
- observer.observe(this.#plot);
431
- this.#observer = observer;
432
- }
433
- #renderUnavailable(reason) {
434
- this.#observer?.disconnect();
435
- this.#observer = null;
436
- const box = document.createElement('div');
437
- box.part.add('unavailable');
438
- box.setAttribute('role', 'status');
439
- const detail = document.createElement('span');
440
- detail.part.add('reason');
441
- detail.textContent = reason;
442
- box.append('Chart unavailable: ', detail);
443
- this.#container.replaceChildren(box);
444
- }
445
- /** Title and legend come from the config alone. */
446
- #renderFrame() {
447
- const config = this.#config;
448
- if (config === null)
449
- return;
450
- const children = [];
451
- const title = config.title?.trim();
452
- if (title !== undefined && title.length > 0) {
453
- this.#title.textContent = title;
454
- children.push(this.#title);
455
- }
456
- if (config.y.length > 1) {
457
- this.#legend.replaceChildren(...config.y.map((definition, index) => {
458
- const item = document.createElement('div');
459
- item.part.add('legend-item');
460
- const swatch = document.createElement('span');
461
- swatch.part.add('legend-swatch');
462
- swatch.setAttribute('aria-hidden', 'true');
463
- swatch.style.background = SERIES_COLORS[index % SERIES_COLORS.length] ?? SERIES_COLORS[0];
464
- const label = document.createElement('span');
465
- label.part.add('legend-label');
466
- label.textContent = definition.label ?? titleCaseHeader(definition.field);
467
- item.append(swatch, label);
468
- return item;
469
- }));
470
- children.push(this.#legend);
471
- }
472
- children.push(this.#plot);
473
- this.#container.replaceChildren(...children);
474
- }
475
- #renderPlot() {
476
- const config = this.#config;
477
- const rows = this.#rows;
478
- if (config === null || rows === null)
479
- return;
480
- const categories = rows.map((row) => formatCategory(getByPath(row, config.x)));
481
- const series = config.y.map((definition, seriesIndex) => {
482
- const points = [];
483
- rows.forEach((row, index) => {
484
- const value = toNumber(getByPath(row, definition.field));
485
- if (value !== null)
486
- points.push({ index, value });
487
- });
488
- return {
489
- label: definition.label ?? titleCaseHeader(definition.field),
490
- color: SERIES_COLORS[seriesIndex % SERIES_COLORS.length] ?? SERIES_COLORS[0],
491
- points,
492
- };
493
- });
494
- // Accumulated rather than spread over `Math.min(...values)`, which blows the call
495
- // stack on a dataset of a few hundred thousand points.
496
- let plotted = 0;
497
- let low = 0;
498
- let high = 0;
499
- for (const entry of series) {
500
- for (const point of entry.points) {
501
- if (plotted === 0) {
502
- low = point.value;
503
- high = point.value;
504
- }
505
- else {
506
- if (point.value < low)
507
- low = point.value;
508
- if (point.value > high)
509
- high = point.value;
510
- }
511
- plotted += 1;
512
- }
513
- }
514
- // Bars and areas are read against a baseline, so their domain must contain zero.
515
- if (config.kind !== 'line') {
516
- low = Math.min(low, 0);
517
- high = Math.max(high, 0);
518
- }
519
- if (plotted === 0 || rows.length === 0) {
520
- const empty = document.createElement('div');
521
- empty.part.add('empty');
522
- empty.style.height = 'var(--crtr-chart-plot-height, 260px)';
523
- empty.textContent = 'No data to chart';
524
- this.#plot.replaceChildren(empty);
525
- return;
526
- }
527
- const width = this.#width;
528
- if (width <= 0) {
529
- // First paint, before the container has been measured: hold the height so
530
- // the surrounding document does not reflow when the SVG arrives.
531
- const spacer = document.createElement('div');
532
- spacer.style.height = 'var(--crtr-chart-plot-height, 260px)';
533
- this.#plot.replaceChildren(spacer);
534
- return;
535
- }
536
- const { ticks, domainMin, domainMax, step } = niceScale(low, high, Y_TICK_TARGET);
537
- const compactTicks = Math.max(...ticks.map((tick) => Math.abs(tick))) >= 10_000;
538
- const tickLabels = ticks.map((tick) => formatTick(tick, step, compactTicks));
539
- const marginLeft = Math.min(88, Math.max(36, Math.max(...tickLabels.map((label) => label.length)) * 6.4 + 12));
540
- const plotWidth = Math.max(0, width - marginLeft - MARGIN_RIGHT);
541
- const plotHeight = Math.max(0, HEIGHT - MARGIN_TOP - MARGIN_BOTTOM);
542
- const plotBottom = MARGIN_TOP + plotHeight;
543
- const band = categories.length > 0 ? plotWidth / categories.length : plotWidth;
544
- const xOf = (index) => marginLeft + band * (index + 0.5);
545
- const span = domainMax - domainMin || 1;
546
- const yOf = (value) => plotBottom - ((value - domainMin) / span) * plotHeight;
547
- const baselineValue = Math.min(Math.max(0, domainMin), domainMax);
548
- const baselineY = yOf(baselineValue);
549
- const svg = svgNode('svg', 'chart');
550
- setAttributes(svg, { role: 'img', width, height: HEIGHT, viewBox: `0 0 ${width} ${HEIGHT}` });
551
- svg.setAttribute('aria-label', `${config.kind} chart of ${series.map((entry) => entry.label).join(', ')} by ${titleCaseHeader(config.x)}`);
552
- ticks.forEach((tick, tickIndex) => {
553
- const line = svgNode('line');
554
- line.setAttribute('class', tick === baselineValue ? 'grid baseline' : 'grid');
555
- setAttributes(line, {
556
- x1: marginLeft,
557
- x2: marginLeft + plotWidth,
558
- y1: yOf(tick),
559
- y2: yOf(tick),
560
- });
561
- const label = svgNode('text');
562
- label.setAttribute('class', 'tick');
563
- setAttributes(label, {
564
- x: marginLeft - 8,
565
- y: yOf(tick),
566
- 'text-anchor': 'end',
567
- 'dominant-baseline': 'central',
568
- });
569
- label.textContent = tickLabels[tickIndex] ?? '';
570
- svg.append(line, label);
571
- });
572
- // Label a whole-number stride of categories so the gaps stay even, then pin the
573
- // last one — replacing rather than appending when it would crowd its neighbour.
574
- const xTickIndices = [];
575
- if (categories.length > 0) {
576
- const maxTicks = Math.max(2, Math.min(categories.length, Math.floor(plotWidth / X_TICK_SPACING) || 1));
577
- const stride = Math.max(1, Math.ceil((categories.length - 1) / (maxTicks - 1)));
578
- for (let index = 0; index < categories.length; index += stride)
579
- xTickIndices.push(index);
580
- const last = xTickIndices[xTickIndices.length - 1] ?? 0;
581
- if (last !== categories.length - 1) {
582
- if ((categories.length - 1 - last) * band >= X_TICK_SPACING)
583
- xTickIndices.push(categories.length - 1);
584
- else
585
- xTickIndices[xTickIndices.length - 1] = categories.length - 1;
586
- }
587
- }
588
- for (const index of xTickIndices) {
589
- const text = truncateLabel(categories[index] ?? '');
590
- const center = xOf(index);
591
- const half = (text.length * LABEL_CHAR_WIDTH) / 2;
592
- const anchor = center - half < 0 ? 'start' : center + half > width ? 'end' : 'middle';
593
- const label = svgNode('text');
594
- label.setAttribute('class', 'tick');
595
- setAttributes(label, {
596
- x: anchor === 'start' ? 0 : anchor === 'end' ? width : center,
597
- y: plotBottom + 16,
598
- 'text-anchor': anchor,
599
- 'dominant-baseline': 'hanging',
600
- });
601
- label.textContent = text;
602
- svg.append(label);
603
- }
604
- if (config.kind === 'bar') {
605
- const groupWidth = band * 0.72;
606
- const slot = groupWidth / series.length;
607
- const barWidth = Math.max(1, slot - (slot > 4 ? 1 : 0));
608
- svg.style.setProperty('--bar-max-radius', `${barWidth / 3}px`);
609
- series.forEach((entry, seriesIndex) => {
610
- const group = svgNode('g');
611
- for (const point of entry.points) {
612
- const y = yOf(point.value);
613
- const bar = svgNode('rect', 'bar');
614
- setAttributes(bar, {
615
- x: xOf(point.index) - groupWidth / 2 + seriesIndex * slot,
616
- y: Math.min(y, baselineY),
617
- width: barWidth,
618
- height: Math.max(1, Math.abs(baselineY - y)),
619
- });
620
- bar.style.fill = entry.color;
621
- bar.append(tooltip(`${entry.label} · ${categories[point.index] ?? point.index}: ${formatExact(point.value)}`));
622
- group.append(bar);
623
- }
624
- svg.append(group);
625
- });
626
- }
627
- else {
628
- const showDots = categories.length <= 40;
629
- for (const entry of series) {
630
- const group = svgNode('g');
631
- const segments = segmentsOf(entry.points);
632
- for (const segment of segments) {
633
- const trace = segment.map((point) => `${xOf(point.index)},${yOf(point.value)}`).join(' L ');
634
- const first = segment[0];
635
- const last = segment[segment.length - 1];
636
- if (config.kind === 'area' && first !== undefined && last !== undefined) {
637
- const area = svgNode('path', 'area');
638
- setAttributes(area, {
639
- d: `M ${xOf(first.index)},${baselineY} L ${trace} L ${xOf(last.index)},${baselineY} Z`,
640
- });
641
- area.style.fill = entry.color;
642
- group.append(area);
643
- }
644
- const line = svgNode('path', 'line');
645
- setAttributes(line, { d: `M ${trace}`, fill: 'none' });
646
- line.style.stroke = entry.color;
647
- group.append(line);
648
- if (!showDots && segment.length > 1)
649
- continue;
650
- for (const point of segment) {
651
- const dot = svgNode('circle', 'point');
652
- setAttributes(dot, { cx: xOf(point.index), cy: yOf(point.value) });
653
- dot.style.fill = entry.color;
654
- dot.append(tooltip(`${entry.label} · ${categories[point.index] ?? point.index}: ${formatExact(point.value)}`));
655
- group.append(dot);
656
- }
657
- }
658
- svg.append(group);
659
- }
660
- }
661
- this.#plot.replaceChildren(svg);
662
- }
663
- }
664
- registerElement('crtr-chart', CrtrChartElement);