jfs-components 0.0.79 → 0.0.85

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 (138) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/commonjs/components/AppBar/AppBar.js +70 -6
  3. package/lib/commonjs/components/AreaLineChart/AreaLineChart.js +866 -0
  4. package/lib/commonjs/components/AreaLineChart/chartMath.js +252 -0
  5. package/lib/commonjs/components/Attached/Attached.js +76 -7
  6. package/lib/commonjs/components/BubbleChart/BubbleChart.js +191 -0
  7. package/lib/commonjs/components/BubbleChart/bubblePacking.js +378 -0
  8. package/lib/commonjs/components/Checkbox/Checkbox.js +18 -2
  9. package/lib/commonjs/components/ClusterBubble/ClusterBubble.js +272 -0
  10. package/lib/commonjs/components/Drawer/Drawer.js +6 -1
  11. package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -6
  12. package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
  13. package/lib/commonjs/components/FormField/FormField.js +1 -14
  14. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +5 -1
  15. package/lib/commonjs/components/ListItem/ListItem.js +6 -11
  16. package/lib/commonjs/components/MessageField/MessageField.js +1 -13
  17. package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +7 -1
  18. package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +12 -9
  19. package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +69 -160
  20. package/lib/commonjs/components/Spinner/Spinner.js +217 -0
  21. package/lib/commonjs/components/TextInput/TextInput.js +33 -18
  22. package/lib/commonjs/components/index.js +34 -0
  23. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  24. package/lib/commonjs/icons/components/IconArrowdown.js +19 -0
  25. package/lib/commonjs/icons/components/IconArrowup.js +19 -0
  26. package/lib/commonjs/icons/components/IconChevrondowncircle.js +19 -0
  27. package/lib/commonjs/icons/components/IconChevronleftcircle.js +19 -0
  28. package/lib/commonjs/icons/components/IconChevronrightcircle.js +19 -0
  29. package/lib/commonjs/icons/components/IconChevronupcircle.js +19 -0
  30. package/lib/commonjs/icons/components/IconOsnavback.js +19 -0
  31. package/lib/commonjs/icons/components/IconOsnavcenter.js +19 -0
  32. package/lib/commonjs/icons/components/IconOsnavhome.js +19 -0
  33. package/lib/commonjs/icons/components/IconOsnavtask.js +19 -0
  34. package/lib/commonjs/icons/components/IconSignin.js +19 -0
  35. package/lib/commonjs/icons/components/IconSignout.js +19 -0
  36. package/lib/commonjs/icons/components/index.js +132 -0
  37. package/lib/commonjs/icons/registry.js +2 -2
  38. package/lib/module/components/AppBar/AppBar.js +70 -6
  39. package/lib/module/components/AreaLineChart/AreaLineChart.js +859 -0
  40. package/lib/module/components/AreaLineChart/chartMath.js +242 -0
  41. package/lib/module/components/Attached/Attached.js +76 -7
  42. package/lib/module/components/BubbleChart/BubbleChart.js +185 -0
  43. package/lib/module/components/BubbleChart/bubblePacking.js +370 -0
  44. package/lib/module/components/Checkbox/Checkbox.js +18 -2
  45. package/lib/module/components/ClusterBubble/ClusterBubble.js +267 -0
  46. package/lib/module/components/Drawer/Drawer.js +6 -1
  47. package/lib/module/components/DropdownInput/DropdownInput.js +30 -6
  48. package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
  49. package/lib/module/components/FormField/FormField.js +3 -16
  50. package/lib/module/components/FullscreenModal/FullscreenModal.js +5 -1
  51. package/lib/module/components/ListItem/ListItem.js +6 -11
  52. package/lib/module/components/MessageField/MessageField.js +3 -15
  53. package/lib/module/components/MetricLegendItem/MetricLegendItem.js +7 -1
  54. package/lib/module/components/PaymentFeedback/PaymentFeedback.js +13 -9
  55. package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +72 -160
  56. package/lib/module/components/Spinner/Spinner.js +212 -0
  57. package/lib/module/components/TextInput/TextInput.js +34 -19
  58. package/lib/module/components/index.js +4 -0
  59. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  60. package/lib/module/icons/components/IconArrowdown.js +12 -0
  61. package/lib/module/icons/components/IconArrowup.js +12 -0
  62. package/lib/module/icons/components/IconChevrondowncircle.js +12 -0
  63. package/lib/module/icons/components/IconChevronleftcircle.js +12 -0
  64. package/lib/module/icons/components/IconChevronrightcircle.js +12 -0
  65. package/lib/module/icons/components/IconChevronupcircle.js +12 -0
  66. package/lib/module/icons/components/IconOsnavback.js +12 -0
  67. package/lib/module/icons/components/IconOsnavcenter.js +12 -0
  68. package/lib/module/icons/components/IconOsnavhome.js +12 -0
  69. package/lib/module/icons/components/IconOsnavtask.js +12 -0
  70. package/lib/module/icons/components/IconSignin.js +12 -0
  71. package/lib/module/icons/components/IconSignout.js +12 -0
  72. package/lib/module/icons/components/index.js +12 -0
  73. package/lib/module/icons/registry.js +2 -2
  74. package/lib/typescript/src/components/AppBar/AppBar.d.ts +12 -1
  75. package/lib/typescript/src/components/AreaLineChart/AreaLineChart.d.ts +212 -0
  76. package/lib/typescript/src/components/AreaLineChart/chartMath.d.ts +90 -0
  77. package/lib/typescript/src/components/Attached/Attached.d.ts +19 -16
  78. package/lib/typescript/src/components/BubbleChart/BubbleChart.d.ts +81 -0
  79. package/lib/typescript/src/components/BubbleChart/bubblePacking.d.ts +83 -0
  80. package/lib/typescript/src/components/ClusterBubble/ClusterBubble.d.ts +76 -0
  81. package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +3 -2
  82. package/lib/typescript/src/components/ListItem/ListItem.d.ts +3 -3
  83. package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +7 -1
  84. package/lib/typescript/src/components/PaymentFeedback/PaymentFeedback.d.ts +5 -1
  85. package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +10 -8
  86. package/lib/typescript/src/components/Spinner/Spinner.d.ts +45 -0
  87. package/lib/typescript/src/components/index.d.ts +4 -0
  88. package/lib/typescript/src/icons/components/IconArrowdown.d.ts +3 -0
  89. package/lib/typescript/src/icons/components/IconArrowup.d.ts +3 -0
  90. package/lib/typescript/src/icons/components/IconChevrondowncircle.d.ts +3 -0
  91. package/lib/typescript/src/icons/components/IconChevronleftcircle.d.ts +3 -0
  92. package/lib/typescript/src/icons/components/IconChevronrightcircle.d.ts +3 -0
  93. package/lib/typescript/src/icons/components/IconChevronupcircle.d.ts +3 -0
  94. package/lib/typescript/src/icons/components/IconOsnavback.d.ts +3 -0
  95. package/lib/typescript/src/icons/components/IconOsnavcenter.d.ts +3 -0
  96. package/lib/typescript/src/icons/components/IconOsnavhome.d.ts +3 -0
  97. package/lib/typescript/src/icons/components/IconOsnavtask.d.ts +3 -0
  98. package/lib/typescript/src/icons/components/IconSignin.d.ts +3 -0
  99. package/lib/typescript/src/icons/components/IconSignout.d.ts +3 -0
  100. package/lib/typescript/src/icons/components/index.d.ts +12 -0
  101. package/lib/typescript/src/icons/registry.d.ts +1 -1
  102. package/package.json +3 -2
  103. package/src/components/AppBar/AppBar.tsx +92 -12
  104. package/src/components/AreaLineChart/AreaLineChart.tsx +1161 -0
  105. package/src/components/AreaLineChart/chartMath.ts +265 -0
  106. package/src/components/Attached/Attached.tsx +94 -7
  107. package/src/components/BubbleChart/BubbleChart.tsx +319 -0
  108. package/src/components/BubbleChart/bubblePacking.ts +397 -0
  109. package/src/components/Checkbox/Checkbox.tsx +14 -2
  110. package/src/components/ClusterBubble/ClusterBubble.tsx +359 -0
  111. package/src/components/Drawer/Drawer.tsx +4 -0
  112. package/src/components/DropdownInput/DropdownInput.tsx +54 -20
  113. package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +13 -9
  114. package/src/components/FormField/FormField.tsx +3 -19
  115. package/src/components/FullscreenModal/FullscreenModal.tsx +3 -0
  116. package/src/components/ListItem/ListItem.tsx +14 -16
  117. package/src/components/MessageField/MessageField.tsx +3 -18
  118. package/src/components/MetricLegendItem/MetricLegendItem.tsx +20 -6
  119. package/src/components/PaymentFeedback/PaymentFeedback.tsx +15 -8
  120. package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +82 -192
  121. package/src/components/Spinner/Spinner.tsx +273 -0
  122. package/src/components/TextInput/TextInput.tsx +37 -19
  123. package/src/components/index.ts +4 -0
  124. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  125. package/src/icons/components/IconArrowdown.tsx +11 -0
  126. package/src/icons/components/IconArrowup.tsx +11 -0
  127. package/src/icons/components/IconChevrondowncircle.tsx +11 -0
  128. package/src/icons/components/IconChevronleftcircle.tsx +11 -0
  129. package/src/icons/components/IconChevronrightcircle.tsx +11 -0
  130. package/src/icons/components/IconChevronupcircle.tsx +11 -0
  131. package/src/icons/components/IconOsnavback.tsx +11 -0
  132. package/src/icons/components/IconOsnavcenter.tsx +11 -0
  133. package/src/icons/components/IconOsnavhome.tsx +11 -0
  134. package/src/icons/components/IconOsnavtask.tsx +11 -0
  135. package/src/icons/components/IconSignin.tsx +11 -0
  136. package/src/icons/components/IconSignout.tsx +11 -0
  137. package/src/icons/components/index.ts +12 -0
  138. package/src/icons/registry.ts +49 -1
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.buildAreaPath = buildAreaPath;
7
+ exports.buildLineSegments = buildLineSegments;
8
+ exports.createLinearScale = createLinearScale;
9
+ exports.extent = extent;
10
+ exports.nearestIndex = nearestIndex;
11
+ exports.niceTicks = niceTicks;
12
+ exports.resolvePoints = resolvePoints;
13
+ /**
14
+ * Pure, framework-agnostic math helpers for `AreaLineChart`.
15
+ *
16
+ * Everything here is intentionally dependency-free (no d3, no react-native)
17
+ * so the chart can stay lightweight and be unit-reasoned in isolation. The
18
+ * functions cover the four primitives a line/area chart needs:
19
+ * 1. linear scales (data domain -> pixel range)
20
+ * 2. "nice" tick generation for the value axis
21
+ * 3. SVG path generation for lines and filled areas (straight + smooth)
22
+ * 4. interaction lookup (which data index is nearest a touch x)
23
+ */
24
+
25
+ /** A resolved point in data space. `x` is the categorical/linear index. */
26
+
27
+ /** A point already projected into pixel space. */
28
+
29
+ /** Linear interpolation scale: maps a value from `domain` into `range`. */
30
+
31
+ /**
32
+ * Create a linear scale mapping `domain` -> `range`. Mirrors the subset of
33
+ * `d3-scale.scaleLinear` we rely on, without the dependency. A zero-width
34
+ * domain collapses to the midpoint of the range so we never divide by zero.
35
+ */
36
+ function createLinearScale(domain, range) {
37
+ const [d0, d1] = domain;
38
+ const [r0, r1] = range;
39
+ const domainSpan = d1 - d0;
40
+ const scale = value => {
41
+ if (domainSpan === 0) {
42
+ return (r0 + r1) / 2;
43
+ }
44
+ const t = (value - d0) / domainSpan;
45
+ return r0 + t * (r1 - r0);
46
+ };
47
+ scale.domain = domain;
48
+ scale.range = range;
49
+ return scale;
50
+ }
51
+
52
+ /**
53
+ * Generate up to ~`count` "nice" evenly-spaced tick values spanning
54
+ * `[min, max]`, snapping the step to a 1/2/5 * 10^n progression so labels
55
+ * read cleanly (e.g. 0, 30K, 60K, 90K). Returns ascending values that
56
+ * always include the rounded-down min and rounded-up max bounds.
57
+ */
58
+ function niceTicks(min, max, count = 5) {
59
+ if (!Number.isFinite(min) || !Number.isFinite(max)) {
60
+ return [];
61
+ }
62
+ if (min === max) {
63
+ return [min];
64
+ }
65
+ const safeCount = Math.max(1, Math.floor(count));
66
+ const span = max - min;
67
+ const rawStep = span / safeCount;
68
+ const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
69
+ const normalized = rawStep / magnitude;
70
+ let niceNormalized;
71
+ if (normalized <= 1) niceNormalized = 1;else if (normalized <= 2) niceNormalized = 2;else if (normalized <= 5) niceNormalized = 5;else niceNormalized = 10;
72
+ const step = niceNormalized * magnitude;
73
+ const niceMin = Math.floor(min / step) * step;
74
+ const niceMax = Math.ceil(max / step) * step;
75
+ const ticks = [];
76
+ // Guard against floating point drift by rounding to the step's precision.
77
+ const decimals = Math.max(0, -Math.floor(Math.log10(step)));
78
+ for (let v = niceMin; v <= niceMax + step / 2; v += step) {
79
+ ticks.push(Number(v.toFixed(decimals)));
80
+ }
81
+ return ticks;
82
+ }
83
+
84
+ /**
85
+ * Compute the [min, max] extent of an array of resolved points along the
86
+ * requested axis. Returns `[0, 0]` for an empty list.
87
+ */
88
+ function extent(points, axis) {
89
+ if (points.length === 0) {
90
+ return [0, 0];
91
+ }
92
+ let min = Infinity;
93
+ let max = -Infinity;
94
+ for (const p of points) {
95
+ const v = p[axis];
96
+ if (v < min) min = v;
97
+ if (v > max) max = v;
98
+ }
99
+ return [min, max];
100
+ }
101
+
102
+ /**
103
+ * Normalize the loose `number[] | ChartPoint[]` series data into a uniform
104
+ * `ResolvedPoint[]`. Bare numbers use their array index as the x value.
105
+ */
106
+ function resolvePoints(data) {
107
+ return data.map((entry, index) => {
108
+ if (typeof entry === 'number') {
109
+ return {
110
+ x: index,
111
+ y: entry,
112
+ projected: false
113
+ };
114
+ }
115
+ const x = typeof entry.x === 'number' ? entry.x : index;
116
+ return {
117
+ x,
118
+ y: entry.y,
119
+ projected: entry.projected === true
120
+ };
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Catmull-Rom -> cubic Bézier control point helper. Produces a smooth,
126
+ * monotone-ish curve through the points without overshooting wildly. Used
127
+ * for the `curve="monotone"` mode. `tension` of 0 yields a standard
128
+ * Catmull-Rom spline.
129
+ */
130
+ function controlPoints(p0, p1, p2, p3) {
131
+ const t = 1 / 6;
132
+ return {
133
+ cp1x: p1.x + (p2.x - p0.x) * t,
134
+ cp1y: p1.y + (p2.y - p0.y) * t,
135
+ cp2x: p2.x - (p3.x - p1.x) * t,
136
+ cp2y: p2.y - (p3.y - p1.y) * t
137
+ };
138
+ }
139
+
140
+ /**
141
+ * One drawable line segment. `dashed` mirrors the `projected` flag of the
142
+ * point the segment ends at, letting the renderer split the polyline into
143
+ * solid and dashed `Path`s.
144
+ */
145
+
146
+ /**
147
+ * Build the SVG path(s) for a polyline through `points`, split into runs of
148
+ * solid vs dashed segments. Each individual segment ("move to A, line/curve
149
+ * to B") is emitted as its own sub-path so that a single projected point can
150
+ * dash exactly one segment while leaving its neighbours solid.
151
+ */
152
+ function buildLineSegments(points, curve) {
153
+ if (points.length < 2) {
154
+ return [];
155
+ }
156
+ const segments = [];
157
+ for (let i = 0; i < points.length - 1; i++) {
158
+ const start = points[i];
159
+ const end = points[i + 1];
160
+ const dashed = end.projected;
161
+ let d;
162
+ if (curve === 'monotone') {
163
+ const p0 = points[i - 1] ?? start;
164
+ const p3 = points[i + 2] ?? end;
165
+ const {
166
+ cp1x,
167
+ cp1y,
168
+ cp2x,
169
+ cp2y
170
+ } = controlPoints(p0, start, end, p3);
171
+ d = `M ${start.x} ${start.y} C ${cp1x} ${cp1y} ${cp2x} ${cp2y} ${end.x} ${end.y}`;
172
+ } else {
173
+ d = `M ${start.x} ${start.y} L ${end.x} ${end.y}`;
174
+ }
175
+ segments.push({
176
+ d,
177
+ dashed
178
+ });
179
+ }
180
+
181
+ // Merge adjacent segments that share the same dashed-ness into one path
182
+ // string to minimize the number of rendered <Path> nodes.
183
+ const merged = [];
184
+ for (const seg of segments) {
185
+ const last = merged[merged.length - 1];
186
+ if (last && last.dashed === seg.dashed) {
187
+ // Drop the redundant leading "M x y" of the appended segment.
188
+ const continuation = seg.d.replace(/^M [^A-Za-z]+/, '');
189
+ last.d += ' ' + continuation;
190
+ } else {
191
+ merged.push({
192
+ ...seg
193
+ });
194
+ }
195
+ }
196
+ return merged;
197
+ }
198
+
199
+ /**
200
+ * Build a single closed area path (the line, then down to `baselineY` and
201
+ * back) for a filled area fill. Curve smoothing matches `buildLineSegments`.
202
+ */
203
+ function buildAreaPath(points, baselineY, curve) {
204
+ if (points.length === 0) {
205
+ return '';
206
+ }
207
+ if (points.length === 1) {
208
+ const p = points[0];
209
+ return `M ${p.x} ${baselineY} L ${p.x} ${p.y} Z`;
210
+ }
211
+ let d = `M ${points[0].x} ${baselineY} L ${points[0].x} ${points[0].y}`;
212
+ for (let i = 0; i < points.length - 1; i++) {
213
+ const start = points[i];
214
+ const end = points[i + 1];
215
+ if (curve === 'monotone') {
216
+ const p0 = points[i - 1] ?? start;
217
+ const p3 = points[i + 2] ?? end;
218
+ const {
219
+ cp1x,
220
+ cp1y,
221
+ cp2x,
222
+ cp2y
223
+ } = controlPoints(p0, start, end, p3);
224
+ d += ` C ${cp1x} ${cp1y} ${cp2x} ${cp2y} ${end.x} ${end.y}`;
225
+ } else {
226
+ d += ` L ${end.x} ${end.y}`;
227
+ }
228
+ }
229
+ const lastX = points[points.length - 1].x;
230
+ d += ` L ${lastX} ${baselineY} Z`;
231
+ return d;
232
+ }
233
+
234
+ /**
235
+ * Find the index of the point whose pixel-x is nearest to `targetX`. Used to
236
+ * snap the crosshair / tooltip to the closest data point during hover/drag.
237
+ */
238
+ function nearestIndex(pixelXs, targetX) {
239
+ if (pixelXs.length === 0) {
240
+ return -1;
241
+ }
242
+ let best = 0;
243
+ let bestDist = Infinity;
244
+ for (let i = 0; i < pixelXs.length; i++) {
245
+ const dist = Math.abs(pixelXs[i] - targetX);
246
+ if (dist < bestDist) {
247
+ bestDist = dist;
248
+ best = i;
249
+ }
250
+ }
251
+ return best;
252
+ }
@@ -7,6 +7,7 @@ exports.default = void 0;
7
7
  var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
10
+ var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
10
11
  var _reactUtils = require("../../utils/react-utils");
11
12
  var _jsxRuntime = require("react/jsx-runtime");
12
13
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
@@ -48,9 +49,27 @@ function resolveAnchorFractions(position) {
48
49
  * </Attached>
49
50
  * ```
50
51
  */
52
+ /**
53
+ * Stretches the immediate badge child/children to fill the enforced badge box.
54
+ * Merges `{ width: '100%', height: '100%' }` into each top-level element's
55
+ * `style` so an arbitrary node (e.g. an `Image` with its own width/aspectRatio)
56
+ * fills the fixed `badgeSize` box instead of laying out at its intrinsic size.
57
+ * The wrapping box's `overflow: 'hidden'` clips anything that still overflows.
58
+ */
59
+ function forceBadgeFill(children) {
60
+ return _react.default.Children.map(children, child => {
61
+ if (! /*#__PURE__*/_react.default.isValidElement(child)) return child;
62
+ const childStyle = child.props?.style;
63
+ return /*#__PURE__*/_react.default.cloneElement(child, {
64
+ style: [FILL_STYLE, childStyle]
65
+ });
66
+ });
67
+ }
51
68
  function Attached({
52
69
  children,
53
70
  badge,
71
+ badgeSize,
72
+ badgeRadius,
54
73
  position = 'bottom-right',
55
74
  circular = true,
56
75
  modes: propModes = _reactUtils.EMPTY_MODES,
@@ -65,7 +84,7 @@ function Attached({
65
84
  ...propModes
66
85
  }, [globalModes, propModes]);
67
86
  const [mainSize, setMainSize] = (0, _react.useState)(ZERO_SIZE);
68
- const [badgeSize, setBadgeSize] = (0, _react.useState)(ZERO_SIZE);
87
+ const [measuredBadgeSize, setMeasuredBadgeSize] = (0, _react.useState)(ZERO_SIZE);
69
88
  const onMainLayout = (0, _react.useCallback)(e => {
70
89
  const {
71
90
  width,
@@ -81,19 +100,54 @@ function Attached({
81
100
  width,
82
101
  height
83
102
  } = e.nativeEvent.layout;
84
- setBadgeSize(prev => prev.width === width && prev.height === height ? prev : {
103
+ setMeasuredBadgeSize(prev => prev.width === width && prev.height === height ? prev : {
85
104
  width,
86
105
  height
87
106
  });
88
107
  }, []);
89
108
  const mainChildren = (0, _react.useMemo)(() => children != null ? (0, _reactUtils.cloneChildrenWithModes)(children, modes) : null, [children, modes]);
90
109
  const badgeChildren = (0, _react.useMemo)(() => badge != null ? (0, _reactUtils.cloneChildrenWithModes)(badge, modes) : null, [badge, modes]);
110
+
111
+ // When a fixed size is requested, the badge is wrapped in a clipped box and
112
+ // its content is force-stretched to fill it (see `forceBadgeFill`).
113
+ const badgeBoxStyle = (0, _react.useMemo)(() => {
114
+ if (badgeSize == null) return null;
115
+ return {
116
+ width: badgeSize,
117
+ height: badgeSize,
118
+ borderRadius: badgeRadius ?? badgeSize / 2,
119
+ overflow: 'hidden'
120
+ };
121
+ }, [badgeSize, badgeRadius]);
122
+
123
+ // Forced slot ring. Like the corner radius, the border color/width are driven
124
+ // by design tokens (`attached/slot/border`, `attached/slot/borderWidth`) and
125
+ // applied to the slot regardless of the node passed. Rendered as an *outside*
126
+ // border: the ring lives on a wrapper that is pulled out by `-borderWidth` on
127
+ // every side (negative margin) so it grows outward instead of eating into the
128
+ // content, and its layout footprint stays equal to the slot box (keeping the
129
+ // anchor centering intact).
130
+ const slotBorderStyle = (0, _react.useMemo)(() => {
131
+ const borderColor = (0, _figmaVariablesResolver.getVariableByName)('attached/slot/border', modes);
132
+ const borderWidth = parseFloat((0, _figmaVariablesResolver.getVariableByName)('attached/slot/borderWidth', modes) || '0');
133
+ if (!borderColor || !(borderWidth > 0)) return null;
134
+
135
+ // Match the inner clip radius so the ring stays concentric. The outer edge
136
+ // sits `borderWidth` further out, hence `innerRadius + borderWidth`.
137
+ const innerRadius = badgeBoxStyle != null ? badgeBoxStyle.borderRadius : badgeRadius ?? 9999;
138
+ return {
139
+ borderWidth,
140
+ borderColor,
141
+ borderRadius: (typeof innerRadius === 'number' ? innerRadius : 9999) + borderWidth,
142
+ margin: -borderWidth
143
+ };
144
+ }, [modes, badgeBoxStyle, badgeRadius]);
91
145
  const badgePlacement = (0, _react.useMemo)(() => {
92
146
  const {
93
147
  fx,
94
148
  fy
95
149
  } = resolveAnchorFractions(position);
96
- const measured = mainSize.width > 0 && badgeSize.width > 0;
150
+ const measured = mainSize.width > 0 && measuredBadgeSize.width > 0;
97
151
  let anchorX;
98
152
  let anchorY;
99
153
  if (circular) {
@@ -113,12 +167,12 @@ function Attached({
113
167
  }
114
168
  return {
115
169
  position: 'absolute',
116
- left: anchorX - badgeSize.width / 2,
117
- top: anchorY - badgeSize.height / 2,
170
+ left: anchorX - measuredBadgeSize.width / 2,
171
+ top: anchorY - measuredBadgeSize.height / 2,
118
172
  // Hide until both elements are measured to avoid a one-frame flash at (0,0).
119
173
  opacity: measured ? 1 : 0
120
174
  };
121
- }, [position, circular, mainSize, badgeSize]);
175
+ }, [position, circular, mainSize, measuredBadgeSize]);
122
176
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
123
177
  style: [styles.container, style],
124
178
  ...rest,
@@ -129,7 +183,16 @@ function Attached({
129
183
  style: badgePlacement,
130
184
  onLayout: onBadgeLayout,
131
185
  pointerEvents: "box-none",
132
- children: badgeChildren
186
+ children: (() => {
187
+ const slot = badgeBoxStyle != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
188
+ style: badgeBoxStyle,
189
+ children: forceBadgeFill(badgeChildren)
190
+ }) : badgeChildren;
191
+ return slotBorderStyle != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
192
+ style: slotBorderStyle,
193
+ children: slot
194
+ }) : slot;
195
+ })()
133
196
  })]
134
197
  });
135
198
  }
@@ -141,4 +204,10 @@ const styles = {
141
204
  alignSelf: 'flex-start'
142
205
  }
143
206
  };
207
+
208
+ /** Fill style merged into badge content when `badgeSize` enforces a fixed box. */
209
+ const FILL_STYLE = {
210
+ width: '100%',
211
+ height: '100%'
212
+ };
144
213
  var _default = exports.default = /*#__PURE__*/_react.default.memo(Attached);
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _reactUtils = require("../../utils/react-utils");
10
+ var _ClusterBubble = _interopRequireDefault(require("../ClusterBubble/ClusterBubble"));
11
+ var _bubblePacking = require("./bubblePacking");
12
+ var _jsxRuntime = require("react/jsx-runtime");
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
15
+ /** One bubble in the chart. */
16
+
17
+ const DEFAULT_APPEARANCE_CYCLE = ['Primary', 'Secondary', 'Tertiary', 'Quaternary', 'Quinary', 'Senary', 'Neutral'];
18
+ const toText = (node, fallback = '') => typeof node === 'string' || typeof node === 'number' ? String(node) : fallback;
19
+ /**
20
+ * `BubbleChart` arranges a set of `ClusterBubble`s with a lightweight **force
21
+ * simulation** — bubbles repel one another (collision), a gentle gravity keeps
22
+ * the cluster balanced, and the container box acts as the walls of a pool, so
23
+ * nothing ever overflows. Each datum's `value` drives its area (`sqrt`-scaled),
24
+ * colors resolve from the `dataViz/bg` token via a cycling `appearance`, and the
25
+ * emphasis comes from the `Emphasis / DataViz` mode. When a bubble is too small
26
+ * to hold its text, the label is anchored just outside the circle on whichever
27
+ * side has the most free space.
28
+ *
29
+ * @component
30
+ */
31
+ function BubbleChart({
32
+ data,
33
+ minBubbleSize = 48,
34
+ maxBubbleSize = 170,
35
+ gap = 8,
36
+ labelGap,
37
+ height,
38
+ labelPlacement = 'auto',
39
+ autoInsideMinSize = 88,
40
+ appearanceCycle = DEFAULT_APPEARANCE_CYCLE,
41
+ iterations = 500,
42
+ onBubblePress,
43
+ modes: propModes = _reactUtils.EMPTY_MODES,
44
+ style,
45
+ accessibilityLabel
46
+ }) {
47
+ const modes = propModes;
48
+ const resolvedLabelGap = labelGap ?? gap;
49
+ const [width, setWidth] = (0, _react.useState)(0);
50
+ const handleLayout = (0, _react.useCallback)(e => {
51
+ const w = e.nativeEvent.layout.width;
52
+ setWidth(prev => Math.abs(prev - w) > 0.5 ? w : prev);
53
+ }, []);
54
+ const layout = (0, _react.useMemo)(() => {
55
+ if (data.length === 0 || width <= 0) {
56
+ return {
57
+ bubbles: [],
58
+ poolHeight: height ?? 0
59
+ };
60
+ }
61
+ const minR = Math.max(1, minBubbleSize / 2);
62
+ const maxR = Math.max(minR, maxBubbleSize / 2);
63
+ const baseRadii = (0, _bubblePacking.scaleRadii)(data.map(d => d.value), minR, maxR);
64
+
65
+ // Estimate label sizes up front (independent of radius) so we can
66
+ // reserve a margin band around the bubbles for any outside labels.
67
+ const labelEstimates = data.map(d => (0, _bubblePacking.estimateLabelBox)(toText(d.display, String(d.value)), toText(d.label)));
68
+ const mayHaveOutside = labelPlacement !== 'inside' && (minBubbleSize < autoInsideMinSize || data.some(d => d.labelPlacement === 'outside'));
69
+ let insetX = gap;
70
+ let insetY = gap;
71
+ if (mayHaveOutside) {
72
+ let maxHalfW = 0;
73
+ let maxH = 0;
74
+ for (const e of labelEstimates) {
75
+ if (e.w / 2 > maxHalfW) maxHalfW = e.w / 2;
76
+ if (e.h > maxH) maxH = e.h;
77
+ }
78
+ insetX = Math.max(gap, maxHalfW + resolvedLabelGap);
79
+ insetY = Math.max(gap, maxH + resolvedLabelGap);
80
+ }
81
+
82
+ // Derive a pool height from the bubble area when none is supplied, then
83
+ // shrink radii (if needed) so everything fits inside the inner box.
84
+ let circleArea = 0;
85
+ for (const r of baseRadii) circleArea += Math.PI * r * r;
86
+ const derivedHeight = Math.min(width * 1.35, Math.max(width * 0.6, circleArea / 0.42 / width + 2 * insetY));
87
+ const poolHeight = Math.max(1, height ?? derivedHeight);
88
+ const innerW = Math.max(1, width - 2 * insetX);
89
+ const innerH = Math.max(1, poolHeight - 2 * insetY);
90
+ const radii = (0, _bubblePacking.fitRadiiToBox)(baseRadii, innerW, innerH, {
91
+ density: 0.5,
92
+ labelArea: 0,
93
+ minRadius: 6
94
+ });
95
+
96
+ // Resolve placement per bubble (outside-labelled bubbles want the
97
+ // perimeter so their labels reach the open band along the walls).
98
+ const placements = data.map((d, i) => {
99
+ const pref = d.labelPlacement ?? labelPlacement;
100
+ const diameter = (radii[i] ?? 0) * 2;
101
+ if (pref === 'auto') return diameter >= autoInsideMinSize ? 'inside' : 'outside';
102
+ return pref;
103
+ });
104
+ const perimeter = placements.map(p => p === 'outside');
105
+ const nodes = (0, _bubblePacking.simulateCluster)(radii, {
106
+ width,
107
+ height: poolHeight,
108
+ gap,
109
+ iterations,
110
+ insetX,
111
+ insetY,
112
+ perimeter
113
+ });
114
+ const labelBoxes = data.map((d, i) => {
115
+ if (placements[i] !== 'outside') return null;
116
+ const v = toText(d.display, String(d.value));
117
+ const l = toText(d.label);
118
+ if (!v && !l) return null;
119
+ return labelEstimates[i] ?? (0, _bubblePacking.estimateLabelBox)(v, l);
120
+ });
121
+ const directions = (0, _bubblePacking.chooseLabelDirections)(nodes, labelBoxes, {
122
+ width,
123
+ height: poolHeight,
124
+ gap: resolvedLabelGap
125
+ });
126
+ const bubbles = data.map((datum, index) => {
127
+ const node = nodes[index];
128
+ const r = radii[index] ?? 0;
129
+ return {
130
+ datum,
131
+ index,
132
+ appearance: datum.appearance ?? appearanceCycle[index % appearanceCycle.length] ?? 'Primary',
133
+ placement: placements[index] ?? 'inside',
134
+ direction: directions[index] ?? 'bottom',
135
+ left: node.x - r,
136
+ top: node.y - r,
137
+ size: r * 2
138
+ };
139
+ });
140
+ return {
141
+ bubbles,
142
+ poolHeight
143
+ };
144
+ }, [data, width, height, minBubbleSize, maxBubbleSize, gap, resolvedLabelGap, labelPlacement, autoInsideMinSize, appearanceCycle, iterations]);
145
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
146
+ style: [styles.container, {
147
+ height: layout.poolHeight
148
+ }, style],
149
+ onLayout: handleLayout,
150
+ accessibilityRole: "image",
151
+ accessibilityLabel: accessibilityLabel,
152
+ children: layout.bubbles.map(b => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
153
+ style: [styles.bubble, {
154
+ left: b.left,
155
+ top: b.top
156
+ }],
157
+ pointerEvents: "box-none",
158
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_ClusterBubble.default, {
159
+ size: b.size,
160
+ value: b.datum.display ?? String(b.datum.value),
161
+ label: b.datum.label,
162
+ appearance: b.appearance,
163
+ labelPlacement: b.placement,
164
+ labelDirection: b.direction,
165
+ labelGap: resolvedLabelGap,
166
+ autoInsideMinSize: autoInsideMinSize,
167
+ modes: modes,
168
+ ...(b.datum.color ? {
169
+ color: b.datum.color
170
+ } : null),
171
+ ...(b.datum.accessibilityLabel ? {
172
+ accessibilityLabel: b.datum.accessibilityLabel
173
+ } : null),
174
+ ...(onBubblePress ? {
175
+ onPress: () => onBubblePress(b.datum, b.index)
176
+ } : null)
177
+ })
178
+ }, b.datum.id ?? b.index))
179
+ });
180
+ }
181
+ const styles = _reactNative.StyleSheet.create({
182
+ container: {
183
+ width: '100%',
184
+ position: 'relative',
185
+ overflow: 'hidden'
186
+ },
187
+ bubble: {
188
+ position: 'absolute'
189
+ }
190
+ });
191
+ var _default = exports.default = BubbleChart;