@ponchia/ui 0.5.0 → 0.6.3

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 (196) hide show
  1. package/CHANGELOG.md +386 -4
  2. package/MIGRATIONS.json +14 -0
  3. package/README.md +29 -6
  4. package/annotations/index.d.ts +398 -276
  5. package/annotations/index.d.ts.map +1 -0
  6. package/annotations/index.js +350 -77
  7. package/behaviors/carousel.d.ts +28 -0
  8. package/behaviors/carousel.d.ts.map +1 -0
  9. package/behaviors/carousel.js +20 -16
  10. package/behaviors/combobox.d.ts +40 -0
  11. package/behaviors/combobox.d.ts.map +1 -0
  12. package/behaviors/combobox.js +111 -29
  13. package/behaviors/command.d.ts +41 -0
  14. package/behaviors/command.d.ts.map +1 -0
  15. package/behaviors/command.js +27 -15
  16. package/behaviors/connectors.d.ts +17 -0
  17. package/behaviors/connectors.d.ts.map +1 -0
  18. package/behaviors/connectors.js +7 -5
  19. package/behaviors/crosshair.d.ts +42 -0
  20. package/behaviors/crosshair.d.ts.map +1 -0
  21. package/behaviors/crosshair.js +23 -6
  22. package/behaviors/dialog.d.ts +20 -0
  23. package/behaviors/dialog.d.ts.map +1 -0
  24. package/behaviors/dialog.js +6 -2
  25. package/behaviors/disclosure.d.ts +10 -0
  26. package/behaviors/disclosure.d.ts.map +1 -0
  27. package/behaviors/disclosure.js +6 -2
  28. package/behaviors/dismissible.d.ts +10 -0
  29. package/behaviors/dismissible.d.ts.map +1 -0
  30. package/behaviors/dismissible.js +6 -2
  31. package/behaviors/forms.d.ts +27 -0
  32. package/behaviors/forms.d.ts.map +1 -0
  33. package/behaviors/forms.js +54 -13
  34. package/behaviors/glyph.d.ts +14 -0
  35. package/behaviors/glyph.d.ts.map +1 -0
  36. package/behaviors/glyph.js +28 -5
  37. package/behaviors/index.d.ts +31 -237
  38. package/behaviors/index.d.ts.map +1 -0
  39. package/behaviors/index.js +17 -0
  40. package/behaviors/inert.d.ts +20 -0
  41. package/behaviors/inert.d.ts.map +1 -0
  42. package/behaviors/inert.js +46 -0
  43. package/behaviors/internal.d.ts +25 -0
  44. package/behaviors/internal.d.ts.map +1 -0
  45. package/behaviors/internal.js +77 -1
  46. package/behaviors/legend.d.ts +35 -0
  47. package/behaviors/legend.d.ts.map +1 -0
  48. package/behaviors/legend.js +32 -2
  49. package/behaviors/menu.d.ts +16 -0
  50. package/behaviors/menu.d.ts.map +1 -0
  51. package/behaviors/menu.js +6 -2
  52. package/behaviors/modal.d.ts +41 -0
  53. package/behaviors/modal.d.ts.map +1 -0
  54. package/behaviors/modal.js +124 -0
  55. package/behaviors/popover.d.ts +28 -0
  56. package/behaviors/popover.d.ts.map +1 -0
  57. package/behaviors/popover.js +78 -7
  58. package/behaviors/spotlight.d.ts +17 -0
  59. package/behaviors/spotlight.d.ts.map +1 -0
  60. package/behaviors/spotlight.js +7 -5
  61. package/behaviors/table.d.ts +36 -0
  62. package/behaviors/table.d.ts.map +1 -0
  63. package/behaviors/table.js +84 -17
  64. package/behaviors/tabs.d.ts +20 -0
  65. package/behaviors/tabs.d.ts.map +1 -0
  66. package/behaviors/tabs.js +17 -14
  67. package/behaviors/theme.d.ts +54 -0
  68. package/behaviors/theme.d.ts.map +1 -0
  69. package/behaviors/theme.js +22 -3
  70. package/behaviors/toast.d.ts +49 -0
  71. package/behaviors/toast.d.ts.map +1 -0
  72. package/behaviors/toast.js +47 -3
  73. package/classes/classes.json +2527 -0
  74. package/classes/index.d.ts +134 -15
  75. package/classes/index.js +280 -80
  76. package/classes/vscode.css-custom-data.json +12 -0
  77. package/connectors/index.d.ts +201 -69
  78. package/connectors/index.d.ts.map +1 -0
  79. package/connectors/index.js +142 -25
  80. package/css/app.css +69 -13
  81. package/css/base.css +15 -10
  82. package/css/bullet.css +108 -0
  83. package/css/code.css +98 -0
  84. package/css/connectors.css +17 -0
  85. package/css/content.css +22 -3
  86. package/css/crosshair.css +7 -7
  87. package/css/dataviz.css +5 -1
  88. package/css/diff.css +153 -0
  89. package/css/disclosure.css +53 -7
  90. package/css/dots.css +94 -7
  91. package/css/feedback.css +97 -7
  92. package/css/forms.css +113 -4
  93. package/css/legend.css +16 -9
  94. package/css/marks.css +38 -8
  95. package/css/motion.css +98 -53
  96. package/css/navigation.css +7 -0
  97. package/css/overlay.css +90 -3
  98. package/css/primitives.css +158 -13
  99. package/css/report.css +73 -56
  100. package/css/sidenote.css +67 -0
  101. package/css/site.css +16 -2
  102. package/css/sources.css +43 -1
  103. package/css/spark.css +62 -0
  104. package/css/spotlight.css +1 -1
  105. package/css/table.css +9 -2
  106. package/css/term.css +110 -0
  107. package/css/textref.css +63 -0
  108. package/css/toc.css +91 -0
  109. package/css/tokens.css +49 -1
  110. package/css/tree.css +134 -0
  111. package/css/workbench.css +1 -1
  112. package/dist/bronto.css +1 -1
  113. package/dist/css/analytical.css +1 -1
  114. package/dist/css/app.css +1 -1
  115. package/dist/css/base.css +1 -1
  116. package/dist/css/bullet.css +1 -0
  117. package/dist/css/code.css +1 -0
  118. package/dist/css/connectors.css +1 -1
  119. package/dist/css/content.css +1 -1
  120. package/dist/css/crosshair.css +1 -1
  121. package/dist/css/diff.css +1 -0
  122. package/dist/css/disclosure.css +1 -1
  123. package/dist/css/dots.css +1 -1
  124. package/dist/css/feedback.css +1 -1
  125. package/dist/css/forms.css +1 -1
  126. package/dist/css/legend.css +1 -1
  127. package/dist/css/marks.css +1 -1
  128. package/dist/css/motion.css +1 -1
  129. package/dist/css/navigation.css +1 -1
  130. package/dist/css/overlay.css +1 -1
  131. package/dist/css/primitives.css +1 -1
  132. package/dist/css/report.css +1 -1
  133. package/dist/css/sidenote.css +1 -0
  134. package/dist/css/site.css +1 -1
  135. package/dist/css/sources.css +1 -1
  136. package/dist/css/spark.css +1 -0
  137. package/dist/css/spotlight.css +1 -1
  138. package/dist/css/table.css +1 -1
  139. package/dist/css/term.css +1 -0
  140. package/dist/css/textref.css +1 -0
  141. package/dist/css/toc.css +1 -0
  142. package/dist/css/tokens.css +1 -1
  143. package/dist/css/tree.css +1 -0
  144. package/dist/css/workbench.css +1 -1
  145. package/docs/adr/0003-theme-model.md +1 -1
  146. package/docs/annotations.md +133 -14
  147. package/docs/architecture.md +49 -6
  148. package/docs/bullet.md +78 -0
  149. package/docs/code.md +76 -0
  150. package/docs/contrast.md +116 -92
  151. package/docs/d2.md +196 -0
  152. package/docs/diff.md +146 -0
  153. package/docs/legends.md +23 -3
  154. package/docs/marks.md +9 -2
  155. package/docs/mermaid.md +169 -0
  156. package/docs/reference.md +201 -26
  157. package/docs/reporting.md +416 -57
  158. package/docs/sidenote.md +64 -0
  159. package/docs/sources.md +27 -0
  160. package/docs/spark.md +78 -0
  161. package/docs/stability.md +10 -2
  162. package/docs/term.md +81 -0
  163. package/docs/textref.md +78 -0
  164. package/docs/theming.md +44 -5
  165. package/docs/toc.md +83 -0
  166. package/docs/tree.md +74 -0
  167. package/docs/usage.md +354 -16
  168. package/docs/vega.md +244 -0
  169. package/docs/workbench.md +7 -1
  170. package/glyphs/glyphs.js +13 -5
  171. package/llms.txt +285 -14
  172. package/package.json +95 -17
  173. package/qwik/index.d.ts +44 -59
  174. package/qwik/index.d.ts.map +1 -0
  175. package/qwik/index.js +65 -3
  176. package/react/index.d.ts +41 -61
  177. package/react/index.d.ts.map +1 -0
  178. package/react/index.js +63 -3
  179. package/solid/index.d.ts +68 -61
  180. package/solid/index.d.ts.map +1 -0
  181. package/solid/index.js +66 -3
  182. package/tokens/d2.d.ts +38 -0
  183. package/tokens/d2.js +71 -0
  184. package/tokens/d2.json +43 -0
  185. package/tokens/index.d.ts +5 -5
  186. package/tokens/index.js +15 -1
  187. package/tokens/index.json +9 -0
  188. package/tokens/mermaid.d.ts +23 -0
  189. package/tokens/mermaid.js +181 -0
  190. package/tokens/mermaid.json +163 -0
  191. package/tokens/resolved.json +45 -1
  192. package/tokens/skins.js +3 -2
  193. package/tokens/tokens.dtcg.json +26 -0
  194. package/tokens/vega.d.ts +34 -0
  195. package/tokens/vega.js +155 -0
  196. package/tokens/vega.json +179 -0
@@ -1,53 +1,229 @@
1
1
  // Shared SVG geometry primitives live in the connectors kernel; annotations
2
2
  // (figure callouts) build on them so a line/curve/arrow/dot is drawn one way.
3
+
4
+ /**
5
+ * @ponchia/ui — SVG annotation geometry helpers.
6
+ *
7
+ * The public types below are JSDoc `@typedef`s; the shipped `index.d.ts` is
8
+ * generated from them (and these signatures) by `tsc --emitDeclarationOnly`.
9
+ *
10
+ * @typedef {{ x: number, y: number }} AnnotationPoint
11
+ * @typedef {{ dx: number, dy: number }} AnnotationOffset
12
+ * @typedef {'callout' | 'elbow' | 'curve'} AnnotationConnectorType
13
+ * @typedef {'start' | 'middle' | 'end'} AnnotationAlign
14
+ * @typedef {'top' | 'middle' | 'bottom'} AnnotationValign
15
+ * @typedef {'horizontal' | 'vertical'} AxisOrientation
16
+ * @typedef {'up' | 'down' | 'left' | 'right'} TimelineDirection
17
+ *
18
+ * @typedef {object} CircleSubject
19
+ * @property {'circle'} type
20
+ * @property {number} radius
21
+ * @property {number} [radiusPadding]
22
+ *
23
+ * @typedef {object} RectSubject
24
+ * @property {'rect'} type
25
+ * @property {number} width
26
+ * @property {number} height
27
+ * @property {number} [x]
28
+ * @property {number} [y]
29
+ * @property {number} [padding]
30
+ *
31
+ * @typedef {CircleSubject | RectSubject} ConnectorSubject
32
+ *
33
+ * @typedef {AnnotationOffset & { subject?: ConnectorSubject, mid?: number }} ConnectorOptions
34
+ *
35
+ * @typedef {object} CircleSubjectOptions
36
+ * @property {number} radius
37
+ *
38
+ * @typedef {object} RectSubjectOptions
39
+ * @property {number} width
40
+ * @property {number} height
41
+ * @property {number} [x]
42
+ * @property {number} [y]
43
+ * @property {number} [padding]
44
+ *
45
+ * @typedef {object} ThresholdOptions
46
+ * @property {number} [x1]
47
+ * @property {number} [y1]
48
+ * @property {number} x2
49
+ * @property {number} y2
50
+ *
51
+ * @typedef {object} AxisThresholdOptions
52
+ * @property {AxisOrientation} [orientation]
53
+ * @property {number} [value]
54
+ * @property {number} [start]
55
+ * @property {number} end
56
+ *
57
+ * @typedef {object} BracketSubjectOptions
58
+ * @property {number} x1
59
+ * @property {number} y1
60
+ * @property {number} x2
61
+ * @property {number} y2
62
+ * @property {number} [depth]
63
+ *
64
+ * @typedef {object} BandSubjectOptions
65
+ * @property {number} [x]
66
+ * @property {number} [y]
67
+ * @property {number} width
68
+ * @property {number} height
69
+ * @property {number} [padding]
70
+ *
71
+ * @typedef {object} SlopeSubjectOptions
72
+ * @property {number} x1
73
+ * @property {number} y1
74
+ * @property {number} x2
75
+ * @property {number} y2
76
+ *
77
+ * @typedef {object} ComparisonBraceOptions
78
+ * @property {number} x1
79
+ * @property {number} y1
80
+ * @property {number} x2
81
+ * @property {number} y2
82
+ * @property {number} [depth]
83
+ *
84
+ * @typedef {object} OutlierClusterOptions
85
+ * @property {AnnotationPoint[]} points
86
+ * @property {number} [radius]
87
+ *
88
+ * @typedef {object} TimelineEventOptions
89
+ * @property {number} [size]
90
+ * @property {TimelineDirection} [direction]
91
+ *
92
+ * @typedef {object} EvidenceMarkerOptions
93
+ * @property {number} [x]
94
+ * @property {number} [y]
95
+ * @property {number} [width]
96
+ * @property {number} [height]
97
+ * @property {number} [padding]
98
+ *
99
+ * @typedef {AnnotationPoint & { radius?: number }} ConnectorEndDotOptions
100
+ *
101
+ * @typedef {object} ConnectorEndArrowOptions
102
+ * @property {number} [x1]
103
+ * @property {number} [y1]
104
+ * @property {number} x2
105
+ * @property {number} y2
106
+ * @property {number} [size]
107
+ * @property {number} [spread] Half-angle of the arrowhead in radians (default
108
+ * 0.32 ≈ a crisp 37° included angle). Larger = blunter.
109
+ *
110
+ * @typedef {object} NoteTransformOptions
111
+ * @property {number} [dx]
112
+ * @property {number} [dy]
113
+ * @property {number} [x]
114
+ * @property {number} [y]
115
+ * @property {AnnotationAlign} [align]
116
+ * @property {AnnotationValign} [valign]
117
+ * @property {number} [width]
118
+ * @property {number} [height]
119
+ *
120
+ * @typedef {object} AnnotationBounds
121
+ * @property {number} [x]
122
+ * @property {number} [y]
123
+ * @property {number} width
124
+ * @property {number} height
125
+ *
126
+ * @typedef {object} NotePlacementOptions
127
+ * @property {number} [x]
128
+ * @property {number} [y]
129
+ * @property {number} width
130
+ * @property {number} height
131
+ * @property {AnnotationBounds} bounds
132
+ * @property {number} [padding]
133
+ * @property {number} [gap]
134
+ * @property {'right' | 'left' | 'top' | 'bottom'} [preferred]
135
+ * @property {number} [inset] Extra margin (user units) the note must keep from
136
+ * the bounds edge, on top of `padding`. Reserve the note's title stroke-halo
137
+ * (~3) or a leader stub so a placement that "fits" doesn't clip. Default 0.
138
+ *
139
+ * @typedef {object} NotePlacement
140
+ * @property {number} dx
141
+ * @property {number} dy
142
+ * @property {AnnotationAlign} align
143
+ * @property {AnnotationValign} valign
144
+ * @property {string} transform
145
+ *
146
+ * @typedef {(
147
+ * | CircleSubject
148
+ * | RectSubject
149
+ * | ({ type: 'threshold' } & ThresholdOptions)
150
+ * | ({ type: 'bracket' } & BracketSubjectOptions)
151
+ * | ({ type: 'band' } & BandSubjectOptions)
152
+ * | ({ type: 'slope' } & SlopeSubjectOptions)
153
+ * | ({ type: 'compare' } & ComparisonBraceOptions)
154
+ * | ({ type: 'cluster' } & OutlierClusterOptions)
155
+ * | ({ type: 'axis' } & AxisThresholdOptions)
156
+ * | ({ type: 'timeline' } & TimelineEventOptions)
157
+ * | ({ type: 'evidence' } & EvidenceMarkerOptions)
158
+ * )} AnnotationPartsSubject
159
+ *
160
+ * @typedef {object} AnnotationPartsOptions
161
+ * @property {AnnotationConnectorType} [type]
162
+ * @property {number} [x]
163
+ * @property {number} [y]
164
+ * @property {number} [dx]
165
+ * @property {number} [dy]
166
+ * @property {AnnotationPartsSubject} [subject]
167
+ *
168
+ * @typedef {object} AnnotationParts
169
+ * @property {string} transform
170
+ * @property {string} subject
171
+ * @property {string} connector
172
+ * @property {string} note
173
+ *
174
+ * @typedef {object} DeclutterLabelItem
175
+ * @property {number} pos Desired centre coordinate along the axis.
176
+ * @property {number} size The label's extent along the axis.
177
+ *
178
+ * @typedef {object} DeclutterLabelsOptions
179
+ * @property {number} [gap] Minimum gap kept between adjacent labels. Default 0.
180
+ * @property {number} [min] Lower bound of the axis. Default -Infinity.
181
+ * @property {number} [max] Upper bound of the axis. Default Infinity.
182
+ *
183
+ * @typedef {object} DirectLabelItem
184
+ * @property {AnnotationPoint} anchor The true data point the label refers to (figure coordinates).
185
+ * @property {number} size The label's extent along the layout axis.
186
+ * @property {string | number} [key] Optional identifier, echoed back on the matching output (input order).
187
+ *
188
+ * @typedef {object} DirectLabelsOptions
189
+ * @property {'x' | 'y'} [axis] Axis the labels declutter along. 'y' = a vertical column. Default 'y'.
190
+ * @property {number} [cross] Fixed coordinate on the other axis where the label column/row sits. Default 0.
191
+ * @property {number} [gap] Minimum gap kept between adjacent labels. Default 0.
192
+ * @property {number} [min] Lower bound of the layout axis. Default -Infinity.
193
+ * @property {number} [max] Upper bound of the layout axis. Default Infinity.
194
+ * @property {'straight' | 'elbow' | 'curve'} [shape] Leader-line shape. Default 'straight'.
195
+ *
196
+ * @typedef {object} DirectLabel
197
+ * @property {number} x Placed label point — the leader's label-side end.
198
+ * @property {number} y
199
+ * @property {AnnotationPoint} anchor The echoed input anchor.
200
+ * @property {string | number} [key] The echoed input key, if any.
201
+ * @property {string} d SVG path for the leader (anchor → label point); '' if they coincide.
202
+ */
203
+
3
204
  import {
4
205
  straightPath,
5
206
  curvePath,
6
207
  connectorPath,
208
+ elbowPath,
7
209
  arrowHead,
8
210
  dotMark,
9
211
  angleBetween,
212
+ // Shared scalar/geometry kernel — single source of truth (was copy-pasted,
213
+ // and the local clamp had silently diverged from the connectors one).
214
+ roundNumber,
215
+ finite,
216
+ dimension,
217
+ fmt,
218
+ point,
219
+ clamp,
220
+ rectPath,
10
221
  } from '../connectors/index.js';
11
222
 
12
- const PRECISION = 1000;
13
-
14
- function finite(name, value, fallback) {
15
- const v = value ?? fallback;
16
- if (!Number.isFinite(v)) throw new TypeError(`${name} must be a finite number`);
17
- return v;
18
- }
19
-
20
- function dimension(name, value, fallback) {
21
- const v = finite(name, value, fallback);
22
- if (v < 0) throw new RangeError(`${name} must be greater than or equal to 0`);
23
- return v;
24
- }
25
-
26
- function fmt(value) {
27
- const rounded = Math.round((Object.is(value, -0) ? 0 : value) * PRECISION) / PRECISION;
28
- return String(Object.is(rounded, -0) ? 0 : rounded);
29
- }
30
-
31
- function roundedNumber(value) {
32
- const rounded = Math.round((Object.is(value, -0) ? 0 : value) * PRECISION) / PRECISION;
33
- return Object.is(rounded, -0) ? 0 : rounded;
34
- }
35
-
36
- function point(x, y) {
37
- return `${fmt(x)},${fmt(y)}`;
38
- }
39
-
40
- function clamp(value, min, max) {
41
- if (max < min) return min;
42
- return Math.min(max, Math.max(min, value));
43
- }
44
-
223
+ // A circle subject is just a filled dot at (x, y) — delegate to the kernel's
224
+ // dotMark so the arc geometry/precision can't diverge. (code-quality audit Q5.)
45
225
  function circlePathAt(x, y, radius) {
46
- const r = dimension('radius', radius);
47
- if (r === 0) return '';
48
- return `M${point(x, y - r)}A${fmt(r)},${fmt(r)} 0 1 1 ${point(x, y + r)}A${fmt(r)},${fmt(
49
- r,
50
- )} 0 1 1 ${point(x, y - r)}Z`;
226
+ return dotMark({ x, y }, radius);
51
227
  }
52
228
 
53
229
  function samePoint(a, b) {
@@ -106,10 +282,18 @@ function linePath(start, end) {
106
282
  return `M${point(start.x, start.y)}L${point(end.x, end.y)}`;
107
283
  }
108
284
 
285
+ /**
286
+ * @param {Partial<AnnotationPoint>} [point]
287
+ * @returns {string}
288
+ */
109
289
  export function annotationTransform({ x = 0, y = 0 } = {}) {
110
290
  return `translate(${fmt(finite('x', x))}, ${fmt(finite('y', y))})`;
111
291
  }
112
292
 
293
+ /**
294
+ * @param {NoteTransformOptions} [options]
295
+ * @returns {string}
296
+ */
113
297
  export function noteTransform({
114
298
  dx,
115
299
  dy,
@@ -172,6 +356,10 @@ function noteRect(x, y, width, height, placement) {
172
356
  };
173
357
  }
174
358
 
359
+ /**
360
+ * @param {NotePlacementOptions} options
361
+ * @returns {NotePlacement}
362
+ */
175
363
  export function notePlacement({
176
364
  x = 0,
177
365
  y = 0,
@@ -181,6 +369,7 @@ export function notePlacement({
181
369
  padding = 8,
182
370
  gap = 32,
183
371
  preferred = 'right',
372
+ inset = 0,
184
373
  } = {}) {
185
374
  const anchorX = finite('x', x);
186
375
  const anchorY = finite('y', y);
@@ -188,22 +377,25 @@ export function notePlacement({
188
377
  const h = dimension('height', height);
189
378
  const p = dimension('padding', padding);
190
379
  const g = dimension('gap', gap);
380
+ const ins = dimension('inset', inset, 0);
191
381
  const bx = finite('bounds.x', bounds?.x, 0);
192
382
  const by = finite('bounds.y', bounds?.y, 0);
193
383
  const bw = dimension('bounds.width', bounds?.width);
194
384
  const bh = dimension('bounds.height', bounds?.height);
195
- const minX = bx + p;
196
- const minY = by + p;
197
- const maxX = bx + bw - p;
198
- const maxY = by + bh - p;
385
+ // `inset` reserves an extra margin (e.g. the title stroke-halo) inside the
386
+ // padded bounds, so a placement that "just fits" doesn't clip the halo/leader.
387
+ const minX = bx + p + ins;
388
+ const minY = by + p + ins;
389
+ const maxX = bx + bw - p - ins;
390
+ const maxY = by + bh - p - ins;
199
391
 
200
392
  for (const side of placementOrder(preferred)) {
201
393
  const placement = candidatePlacement(side, g);
202
394
  const rect = noteRect(anchorX, anchorY, w, h, placement);
203
395
  if (rect.left >= minX && rect.top >= minY && rect.right <= maxX && rect.bottom <= maxY) {
204
396
  return {
205
- dx: roundedNumber(placement.dx),
206
- dy: roundedNumber(placement.dy),
397
+ dx: roundNumber(placement.dx),
398
+ dy: roundNumber(placement.dy),
207
399
  align: placement.align,
208
400
  valign: placement.valign,
209
401
  transform: noteTransform({ ...placement, width: w, height: h }),
@@ -215,8 +407,8 @@ export function notePlacement({
215
407
  const rect = noteRect(anchorX, anchorY, w, h, fallback);
216
408
  const left = clamp(rect.left, minX, maxX - w);
217
409
  const top = clamp(rect.top, minY, maxY - h);
218
- const dx = roundedNumber(left - anchorX);
219
- const dy = roundedNumber(top - anchorY);
410
+ const dx = roundNumber(left - anchorX);
411
+ const dy = roundNumber(top - anchorY);
220
412
  return {
221
413
  dx,
222
414
  dy,
@@ -226,10 +418,18 @@ export function notePlacement({
226
418
  };
227
419
  }
228
420
 
421
+ /**
422
+ * @param {CircleSubjectOptions} options
423
+ * @returns {string}
424
+ */
229
425
  export function circleSubjectPath({ radius } = {}) {
230
426
  return circlePathAt(0, 0, radius);
231
427
  }
232
428
 
429
+ /**
430
+ * @param {RectSubjectOptions} options
431
+ * @returns {string}
432
+ */
233
433
  export function rectSubjectPath({ width, height, x, y, padding = 0 } = {}) {
234
434
  const w = dimension('width', width);
235
435
  const h = dimension('height', height);
@@ -239,15 +439,23 @@ export function rectSubjectPath({ width, height, x, y, padding = 0 } = {}) {
239
439
  const top = finite('y', y, -h / 2) - p;
240
440
  const right = left + w + p * 2;
241
441
  const bottom = top + h + p * 2;
242
- return `M${point(left, top)}H${fmt(right)}V${fmt(bottom)}H${fmt(left)}Z`;
442
+ return rectPath(left, top, right, bottom);
243
443
  }
244
444
 
445
+ /**
446
+ * @param {ThresholdOptions} options
447
+ * @returns {string}
448
+ */
245
449
  export function thresholdPath({ x1 = 0, y1 = 0, x2, y2 } = {}) {
246
450
  const start = { x: finite('x1', x1), y: finite('y1', y1) };
247
451
  const end = { x: finite('x2', x2), y: finite('y2', y2) };
248
452
  return linePath(start, end);
249
453
  }
250
454
 
455
+ /**
456
+ * @param {AxisThresholdOptions} options
457
+ * @returns {string}
458
+ */
251
459
  export function axisThresholdPath({ orientation = 'horizontal', value = 0, start = 0, end } = {}) {
252
460
  const v = finite('value', value);
253
461
  const s = finite('start', start);
@@ -257,6 +465,10 @@ export function axisThresholdPath({ orientation = 'horizontal', value = 0, start
257
465
  throw new TypeError('orientation must be "horizontal" or "vertical"');
258
466
  }
259
467
 
468
+ /**
469
+ * @param {BracketSubjectOptions} options
470
+ * @returns {string}
471
+ */
260
472
  export function bracketSubjectPath({ x1, y1, x2, y2, depth = 12 } = {}) {
261
473
  const start = { x: finite('x1', x1), y: finite('y1', y1) };
262
474
  const end = { x: finite('x2', x2), y: finite('y2', y2) };
@@ -268,14 +480,26 @@ export function bracketSubjectPath({ x1, y1, x2, y2, depth = 12 } = {}) {
268
480
  return `M${point(start.x, start.y)}H${fmt(start.x + d)}V${fmt(end.y)}H${fmt(end.x)}`;
269
481
  }
270
482
 
483
+ /**
484
+ * @param {BandSubjectOptions} options
485
+ * @returns {string}
486
+ */
271
487
  export function bandSubjectPath({ x = 0, y = 0, width, height, padding = 0 } = {}) {
272
488
  return rectSubjectPath({ x, y, width, height, padding });
273
489
  }
274
490
 
491
+ /**
492
+ * @param {SlopeSubjectOptions} options
493
+ * @returns {string}
494
+ */
275
495
  export function slopeSubjectPath({ x1, y1, x2, y2 } = {}) {
276
496
  return thresholdPath({ x1, y1, x2, y2 });
277
497
  }
278
498
 
499
+ /**
500
+ * @param {ComparisonBraceOptions} options
501
+ * @returns {string}
502
+ */
279
503
  export function comparisonBracePath({ x1, y1, x2, y2, depth = 14 } = {}) {
280
504
  const start = { x: finite('x1', x1), y: finite('y1', y1) };
281
505
  const end = { x: finite('x2', x2), y: finite('y2', y2) };
@@ -313,6 +537,10 @@ export function comparisonBracePath({ x1, y1, x2, y2, depth = 14 } = {}) {
313
537
  )} ${point(x, end.y)}`;
314
538
  }
315
539
 
540
+ /**
541
+ * @param {OutlierClusterOptions} options
542
+ * @returns {string}
543
+ */
316
544
  export function outlierClusterPath({ points, radius = 6 } = {}) {
317
545
  if (!Array.isArray(points)) throw new TypeError('points must be an array');
318
546
  return points
@@ -323,6 +551,10 @@ export function outlierClusterPath({ points, radius = 6 } = {}) {
323
551
  .join('');
324
552
  }
325
553
 
554
+ /**
555
+ * @param {TimelineEventOptions} [options]
556
+ * @returns {string}
557
+ */
326
558
  export function timelineEventPath({ size = 10, direction = 'down' } = {}) {
327
559
  const s = dimension('size', size);
328
560
  if (s === 0) return '';
@@ -333,6 +565,10 @@ export function timelineEventPath({ size = 10, direction = 'down' } = {}) {
333
565
  throw new TypeError('direction must be "up", "down", "left" or "right"');
334
566
  }
335
567
 
568
+ /**
569
+ * @param {EvidenceMarkerOptions} [options]
570
+ * @returns {string}
571
+ */
336
572
  export function evidenceMarkerPath({ x = 0, y = 0, width = 36, height = 36, padding = 0 } = {}) {
337
573
  const w = dimension('width', width);
338
574
  const h = dimension('height', height);
@@ -344,21 +580,33 @@ export function evidenceMarkerPath({ x = 0, y = 0, width = 36, height = 36, padd
344
580
  const top = cy - h / 2 - p;
345
581
  const right = left + w + p * 2;
346
582
  const bottom = top + h + p * 2;
347
- return `M${point(left, top)}H${fmt(right)}V${fmt(bottom)}H${fmt(left)}Z`;
583
+ return rectPath(left, top, right, bottom);
348
584
  }
349
585
 
586
+ /**
587
+ * @param {ConnectorEndDotOptions} options
588
+ * @returns {string}
589
+ */
350
590
  export function connectorEndDot({ x, y, radius = 3 } = {}) {
351
591
  return dotMark({ x: finite('x', x), y: finite('y', y) }, radius);
352
592
  }
353
593
 
354
- export function connectorEndArrow({ x1 = 0, y1 = 0, x2, y2, size = 7 } = {}) {
594
+ /**
595
+ * @param {ConnectorEndArrowOptions} options
596
+ * @returns {string}
597
+ */
598
+ export function connectorEndArrow({ x1 = 0, y1 = 0, x2, y2, size = 8, spread = 0.32 } = {}) {
355
599
  const start = { x: finite('x1', x1), y: finite('y1', y1) };
356
600
  const end = { x: finite('x2', x2), y: finite('y2', y2) };
357
601
  const s = dimension('size', size);
358
602
  if (s === 0 || (end.x === start.x && end.y === start.y)) return '';
359
- return arrowHead(end, angleBetween(start, end), s);
603
+ return arrowHead(end, angleBetween(start, end), s, spread);
360
604
  }
361
605
 
606
+ /**
607
+ * @param {ConnectorOptions} opts
608
+ * @returns {string}
609
+ */
362
610
  export function connectorLine(opts = {}) {
363
611
  const { dx, dy } = validateOffset(opts);
364
612
  if (dx === 0 && dy === 0) return '';
@@ -370,25 +618,29 @@ export function connectorLine(opts = {}) {
370
618
  return straightPath(start, end);
371
619
  }
372
620
 
621
+ /**
622
+ * @param {ConnectorOptions} opts
623
+ * @returns {string}
624
+ */
373
625
  export function connectorElbow(opts = {}) {
374
626
  const { dx, dy } = validateOffset(opts);
375
627
  if (dx === 0 && dy === 0) return '';
376
628
  const start = connectorStart(dx, dy, opts.subject);
377
629
  if (!start) return '';
378
630
  const end = { x: dx, y: dy };
379
- const vx = end.x - start.x;
380
- const vy = end.y - start.y;
381
- if (vx === 0 || vy === 0) return linePath(start, end);
382
-
383
- const elbow =
384
- Math.abs(vx) >= Math.abs(vy)
385
- ? { x: start.x + Math.sign(vx) * Math.abs(vy), y: end.y }
386
- : { x: end.x, y: start.y + Math.sign(vy) * Math.abs(vx) };
387
-
388
- if (samePoint(start, elbow) || samePoint(elbow, end)) return linePath(start, end);
389
- return `M${point(start.x, start.y)}L${point(elbow.x, elbow.y)}L${point(end.x, end.y)}`;
631
+ if (samePoint(start, end)) return '';
632
+ // A true right-angle dogleg (H/V/H), turning on the dominant axis at `mid`
633
+ // (0..1, default 0.5). Delegated to the connectors geometry kernel so an
634
+ // annotation leader and a node connector draw the same elbow. (The former
635
+ // inline form turned by min(|dx|,|dy|), i.e. a 45° chamfer that read as a
636
+ // diagonal stub, not an elbow — which the `stroke-linejoin` bevel assumes.)
637
+ return elbowPath(start, end, { mid: opts.mid });
390
638
  }
391
639
 
640
+ /**
641
+ * @param {ConnectorOptions} opts
642
+ * @returns {string}
643
+ */
392
644
  export function connectorCurve(opts = {}) {
393
645
  const { dx, dy } = validateOffset(opts);
394
646
  if (dx === 0 && dy === 0) return '';
@@ -400,6 +652,26 @@ export function connectorCurve(opts = {}) {
400
652
  return curvePath(start, end, { curvature: 0.35 });
401
653
  }
402
654
 
655
+ // subject.type → its path builder. A flat dispatch table (replaces an 11-arm
656
+ // if/else) keyed by the SubjectType union; an unknown type throws below. (Q10.)
657
+ const SUBJECT_BUILDERS = {
658
+ circle: circleSubjectPath,
659
+ rect: rectSubjectPath,
660
+ threshold: thresholdPath,
661
+ bracket: bracketSubjectPath,
662
+ band: bandSubjectPath,
663
+ slope: slopeSubjectPath,
664
+ compare: comparisonBracePath,
665
+ cluster: outlierClusterPath,
666
+ axis: axisThresholdPath,
667
+ timeline: timelineEventPath,
668
+ evidence: evidenceMarkerPath,
669
+ };
670
+
671
+ /**
672
+ * @param {AnnotationPartsOptions} [opts]
673
+ * @returns {AnnotationParts}
674
+ */
403
675
  export function annotationParts(opts = {}) {
404
676
  const type = opts.type ?? 'callout';
405
677
  const transform = annotationTransform({ x: opts.x ?? 0, y: opts.y ?? 0 });
@@ -416,18 +688,11 @@ export function annotationParts(opts = {}) {
416
688
  const note = noteTransform({ dx, dy });
417
689
  let subject = '';
418
690
 
419
- if (opts.subject?.type === 'circle') subject = circleSubjectPath(opts.subject);
420
- else if (opts.subject?.type === 'rect') subject = rectSubjectPath(opts.subject);
421
- else if (opts.subject?.type === 'threshold') subject = thresholdPath(opts.subject);
422
- else if (opts.subject?.type === 'bracket') subject = bracketSubjectPath(opts.subject);
423
- else if (opts.subject?.type === 'band') subject = bandSubjectPath(opts.subject);
424
- else if (opts.subject?.type === 'slope') subject = slopeSubjectPath(opts.subject);
425
- else if (opts.subject?.type === 'compare') subject = comparisonBracePath(opts.subject);
426
- else if (opts.subject?.type === 'cluster') subject = outlierClusterPath(opts.subject);
427
- else if (opts.subject?.type === 'axis') subject = axisThresholdPath(opts.subject);
428
- else if (opts.subject?.type === 'timeline') subject = timelineEventPath(opts.subject);
429
- else if (opts.subject?.type === 'evidence') subject = evidenceMarkerPath(opts.subject);
430
- else if (opts.subject != null) throw new TypeError('unsupported subject.type');
691
+ if (opts.subject != null) {
692
+ const build = SUBJECT_BUILDERS[opts.subject.type];
693
+ if (!build) throw new TypeError('unsupported subject.type');
694
+ subject = build(opts.subject);
695
+ }
431
696
 
432
697
  return { transform, subject, connector, note };
433
698
  }
@@ -442,6 +707,10 @@ export function annotationParts(opts = {}) {
442
707
  * `items`: `[{ pos, size }]` — `pos` is the desired centre coordinate along the
443
708
  * axis, `size` the label's extent along it. Returns the adjusted centre per
444
709
  * input item, in the original order.
710
+ *
711
+ * @param {DeclutterLabelItem[]} items
712
+ * @param {DeclutterLabelsOptions} [opts]
713
+ * @returns {number[]}
445
714
  */
446
715
  export function declutterLabels(items, opts = {}) {
447
716
  if (!Array.isArray(items)) throw new TypeError('items must be an array');
@@ -470,7 +739,7 @@ export function declutterLabels(items, opts = {}) {
470
739
  }
471
740
 
472
741
  const out = new Array(nodes.length);
473
- for (const n of nodes) out[n.index] = roundedNumber(n.pos);
742
+ for (const n of nodes) out[n.index] = roundNumber(n.pos);
474
743
  return out;
475
744
  }
476
745
 
@@ -488,6 +757,10 @@ export function declutterLabels(items, opts = {}) {
488
757
  * in input order, the placed label point `{x, y}`, the echoed `anchor` and
489
758
  * `key`, and the leader path `d` (anchor → label; `''` if they coincide) ready
490
759
  * for a `<path class="ui-annotation__connector">`.
760
+ *
761
+ * @param {DirectLabelItem[]} items
762
+ * @param {DirectLabelsOptions} [opts]
763
+ * @returns {DirectLabel[]}
491
764
  */
492
765
  export function directLabels(items, opts = {}) {
493
766
  if (!Array.isArray(items)) throw new TypeError('items must be an array');
@@ -512,9 +785,9 @@ export function directLabels(items, opts = {}) {
512
785
  ? ''
513
786
  : connectorPath({ from: a.anchor, to: labelPoint, shape });
514
787
  return {
515
- x: roundedNumber(labelPoint.x),
516
- y: roundedNumber(labelPoint.y),
517
- anchor: { x: roundedNumber(a.anchor.x), y: roundedNumber(a.anchor.y) },
788
+ x: roundNumber(labelPoint.x),
789
+ y: roundNumber(labelPoint.y),
790
+ anchor: { x: roundNumber(a.anchor.x), y: roundNumber(a.anchor.y) },
518
791
  key: a.key,
519
792
  d,
520
793
  };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Image carousel / gallery, built on CSS scroll-snap so touch + trackpad
3
+ * swipe (and momentum) are the browser's, not hand-rolled. This wires the
4
+ * parts scroll-snap can't do alone: prev/next buttons, keyboard nav, a
5
+ * thumbnail strip, the position counter, and ARIA — keeping a JS index in
6
+ * sync with the scroll position both ways.
7
+ *
8
+ * Markup: `[data-bronto-carousel]` containing a `.ui-carousel__viewport`
9
+ * of `.ui-carousel__slide` children; optionally
10
+ * `[data-bronto-carousel-prev]` / `[data-bronto-carousel-next]` controls,
11
+ * a `.ui-carousel__thumbs` list of `.ui-carousel__thumb` buttons, and a
12
+ * `.ui-carousel__status` counter slot. Add `data-bronto-carousel-loop` to
13
+ * wrap at the ends, `data-bronto-carousel-label` to name the region.
14
+ *
15
+ * A full-screen **lightbox** is the same markup inside a native
16
+ * `<dialog class="ui-lightbox">` opened by {@link initDialog}: the
17
+ * `<dialog>` provides the top layer, focus-trap, Escape and focus-return,
18
+ * so this behavior never touches focus management.
19
+ *
20
+ * Emits `bronto:change` ({ detail: { index } }) on every index change
21
+ * (button, key, thumbnail, or swipe). SSR-safe, idempotent per carousel;
22
+ * returns a cleanup function.
23
+ *
24
+ * @param {import('./internal.js').DelegateOpts} [opts]
25
+ * @returns {import('./internal.js').Cleanup}
26
+ */
27
+ export function initCarousel({ root }?: import("./internal.js").DelegateOpts): import("./internal.js").Cleanup;
28
+ //# sourceMappingURL=carousel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"carousel.d.ts","sourceRoot":"","sources":["carousel.js"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wCAHW,OAAO,eAAe,EAAE,YAAY,GAClC,OAAO,eAAe,EAAE,OAAO,CAuK3C"}