@trackunit/react-map 0.2.192-alpha-cb9408e01a9.0 → 0.2.192
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.
- package/index.cjs.js +125 -18
- package/index.esm.js +125 -18
- package/package.json +11 -11
- package/src/clusters/ClusterMarker.d.ts +24 -5
- package/src/clusters/formatClusterCount.d.ts +4 -2
- package/src/clusters/stories/ClusterMarkerDefaultDemo.d.ts +4 -3
- package/src/clusters/stories/ClusterMarkerPieDonutDemo.d.ts +7 -0
- package/src/clusters/stories/ClusterMarkerPlaygroundDemo.d.ts +3 -1
- package/src/clusters/stories/ClusterMarkerWithSticksDemo.d.ts +12 -4
- package/src/clusters/useClusterCountFormat.d.ts +1 -1
- package/src/clusters/utils/areClusterMarkerPropsEqual.d.ts +6 -0
- package/src/clusters/utils/resolveClusterBubbleSize.d.ts +19 -0
- package/src/markers/stories/helpers/cvaStoryCaption.d.ts +22 -0
- package/src/markers/stories/helpers/cvaStoryTitle.d.ts +22 -0
package/index.cjs.js
CHANGED
|
@@ -4462,8 +4462,10 @@ ClusterStickInner.displayName = "ClusterStick";
|
|
|
4462
4462
|
*
|
|
4463
4463
|
* Rules:
|
|
4464
4464
|
* - `n < 1 000` → raw integer string
|
|
4465
|
-
* - `1 000 ≤ n <
|
|
4466
|
-
* - `n
|
|
4465
|
+
* - `1 000 ≤ n < 10 000` → `X.Xk` (truncated to one decimal of thousands)
|
|
4466
|
+
* - `10 000 ≤ n < 1 000 000` → `Xk` (truncated to whole thousands)
|
|
4467
|
+
* - `1 000 000 ≤ n < 10 000 000` → `X.Xm` (truncated to one decimal of millions)
|
|
4468
|
+
* - `n ≥ 10 000 000` → `Xm` (truncated to whole millions)
|
|
4467
4469
|
*
|
|
4468
4470
|
* Truncation (not rounding) is used so the displayed value never overstates the count.
|
|
4469
4471
|
*/
|
|
@@ -4471,16 +4473,93 @@ const formatClusterCount = (n) => {
|
|
|
4471
4473
|
if (n < 1000) {
|
|
4472
4474
|
return String(n);
|
|
4473
4475
|
}
|
|
4476
|
+
if (n < 10000) {
|
|
4477
|
+
return `${(Math.floor(n / 100) / 10).toFixed(1)}k`;
|
|
4478
|
+
}
|
|
4474
4479
|
if (n < 1000000) {
|
|
4475
4480
|
return `${Math.floor(n / 1000)}k`;
|
|
4476
4481
|
}
|
|
4477
|
-
|
|
4482
|
+
if (n < 10000000) {
|
|
4483
|
+
return `${(Math.floor(n / 100000) / 10).toFixed(1)}m`;
|
|
4484
|
+
}
|
|
4485
|
+
return `${Math.floor(n / 1000000)}m`;
|
|
4486
|
+
};
|
|
4487
|
+
|
|
4488
|
+
/**
|
|
4489
|
+
* `memo` comparator for `ClusterMarker`. Shallow-compares every prop except `segments`,
|
|
4490
|
+
* which is compared by content since callers typically derive a fresh array per render.
|
|
4491
|
+
*/
|
|
4492
|
+
const areClusterMarkerPropsEqual = (prev, next) => {
|
|
4493
|
+
const prevKeys = Object.keys(prev);
|
|
4494
|
+
const nextKeys = Object.keys(next);
|
|
4495
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
4496
|
+
return false;
|
|
4497
|
+
}
|
|
4498
|
+
for (const key of nextKeys) {
|
|
4499
|
+
if (key === "segments") {
|
|
4500
|
+
continue;
|
|
4501
|
+
}
|
|
4502
|
+
if (!Object.is(Reflect.get(prev, key), Reflect.get(next, key))) {
|
|
4503
|
+
return false;
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
return esToolkit.isEqual(prev.segments, next.segments);
|
|
4507
|
+
};
|
|
4508
|
+
|
|
4509
|
+
/**
|
|
4510
|
+
* Open donut diameter (px), including the 1px border.
|
|
4511
|
+
* Named like marker sizes: sm, md, lg, xl. Compact formatting keeps lg labels to at
|
|
4512
|
+
* most 3 digits (`999`, `9.9k`, `99k`) and xl labels to at most 4 (`999k`, `9.9m`).
|
|
4513
|
+
*/
|
|
4514
|
+
const CLUSTER_BUBBLE_DIAMETER_PX = {
|
|
4515
|
+
sm: 24,
|
|
4516
|
+
md: 32,
|
|
4517
|
+
lg: 40,
|
|
4518
|
+
xl: 48,
|
|
4519
|
+
};
|
|
4520
|
+
const CLUSTER_PIE_BAND_STEPS = {
|
|
4521
|
+
sm: 1,
|
|
4522
|
+
md: 2,
|
|
4523
|
+
lg: 3,
|
|
4524
|
+
xl: 4,
|
|
4525
|
+
};
|
|
4526
|
+
/**
|
|
4527
|
+
* Resolves the cluster bubble diameter and form from the count, single-marker size, and state.
|
|
4528
|
+
* The resting pie is one marker step above `markerSize` for counts of 2–9, then one further step
|
|
4529
|
+
* per larger count band, clamped at xl.
|
|
4530
|
+
*/
|
|
4531
|
+
const resolveClusterBubbleSize = ({ count, markerSize, state, }) => {
|
|
4532
|
+
const safeCount = Number.isFinite(count) ? count : 0;
|
|
4533
|
+
const band = safeCount <= 9 ? "sm" : safeCount <= 99 ? "md" : safeCount < 100000 ? "lg" : "xl";
|
|
4534
|
+
const donutDiameterPx = CLUSTER_BUBBLE_DIAMETER_PX[band];
|
|
4535
|
+
if (markerSize === undefined) {
|
|
4536
|
+
return { diameterPx: donutDiameterPx, isDonut: true };
|
|
4537
|
+
}
|
|
4538
|
+
/** Outer pie diameter (px), including the 1px border. Matches a marker circle tier. */
|
|
4539
|
+
let pieSize = markerSize;
|
|
4540
|
+
for (let step = 0; step < CLUSTER_PIE_BAND_STEPS[band]; step++) {
|
|
4541
|
+
pieSize = upgradeMarkerDomSize(pieSize);
|
|
4542
|
+
}
|
|
4543
|
+
const pieDiameterPx = MARKER_SIZE_MAP[pieSize].circle;
|
|
4544
|
+
if (state === "default") {
|
|
4545
|
+
return { diameterPx: pieDiameterPx, isDonut: false };
|
|
4546
|
+
}
|
|
4547
|
+
/**
|
|
4548
|
+
* Open diameter. The count picks sm/md/lg/xl, but a larger closed pie does not shrink.
|
|
4549
|
+
* A 2–9 cluster on an lg or xl marker is a 28px pie and a 24px donut, so it stays 28px.
|
|
4550
|
+
*/
|
|
4551
|
+
return { diameterPx: Math.max(pieDiameterPx, donutDiameterPx), isDonut: true };
|
|
4478
4552
|
};
|
|
4479
4553
|
|
|
4480
4554
|
/** Radius (px) from cluster anchor at which sticks are placed during fan-out. */
|
|
4481
4555
|
const STICK_FAN_RADIUS_PX = 56;
|
|
4482
|
-
/**
|
|
4483
|
-
const PIE_LINE_WIDTH_PERCENT =
|
|
4556
|
+
/** Closed pie fills the disc. Open donut leaves a hole for the count (legacy ring thickness). */
|
|
4557
|
+
const PIE_LINE_WIDTH_PERCENT = 100;
|
|
4558
|
+
const DONUT_LINE_WIDTH_PERCENT = 25;
|
|
4559
|
+
/** The ring is `DONUT_LINE_WIDTH_PERCENT` of the radius, so the hole is inset by half that on each side. */
|
|
4560
|
+
const HOLE_INSET = `${DONUT_LINE_WIDTH_PERCENT / 2}%`;
|
|
4561
|
+
/** easeOutCubic. */
|
|
4562
|
+
const HOLE_EASING = "cubic-bezier(0.33, 1, 0.68, 1)";
|
|
4484
4563
|
const PIE_START_ANGLE = -90;
|
|
4485
4564
|
/** Stable empty sticks array — used as the default for the `sticks` prop so the useEffect
|
|
4486
4565
|
* dependency does not change on every render when no sticks are passed. */
|
|
@@ -4507,13 +4586,23 @@ const resolveBubbleBackground = (state, config) => {
|
|
|
4507
4586
|
return config.expandedBackground;
|
|
4508
4587
|
};
|
|
4509
4588
|
const ClusterMarkerInner = react.forwardRef(function ClusterMarkerComponent(props, ref) {
|
|
4510
|
-
const { count, segments, sticks = EMPTY_STICKS, onStickClick, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, state = "default", theme, colorConfig, className, style, "data-testid": dataTestId, "aria-label": ariaLabel, } = props;
|
|
4589
|
+
const { count, segments, sticks = EMPTY_STICKS, onStickClick, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, state = "default", markerSize, theme, colorConfig, className, style, "data-testid": dataTestId, "aria-label": ariaLabel, } = props;
|
|
4511
4590
|
const formatted = formatClusterCount(count);
|
|
4591
|
+
const pieData = react.useMemo(() => segments
|
|
4592
|
+
.map((segment, index) => ({
|
|
4593
|
+
color: segment.color,
|
|
4594
|
+
value: segment.weight,
|
|
4595
|
+
title: `segment-${index}`,
|
|
4596
|
+
}))
|
|
4597
|
+
.filter(entry => entry.value > 0), [segments]);
|
|
4598
|
+
const { diameterPx: bubbleDiameterPx, isDonut } = resolveClusterBubbleSize({ count, markerSize, state });
|
|
4599
|
+
const showCount = isDonut || pieData.length === 0;
|
|
4512
4600
|
const effectiveColorConfig = { ...defaultMarkerColorConfig(theme), ...colorConfig };
|
|
4513
4601
|
const colors = resolveMarkerColors("transparent", state, theme, colorConfig);
|
|
4514
4602
|
const bubbleBackground = resolveBubbleBackground(state, effectiveColorConfig);
|
|
4515
4603
|
const morphDurationMs = MARKER_TUNING.animation.morphDurationMs;
|
|
4516
4604
|
const easing = MARKER_TUNING.animation.morphEasing;
|
|
4605
|
+
const holeDurationMs = Math.round(morphDurationMs / 2);
|
|
4517
4606
|
// Single source of truth for all sticks — both active (open=true) and departing (open=false).
|
|
4518
4607
|
// Active sticks carry updated fan angles; departing sticks keep their last known positioning
|
|
4519
4608
|
// so the spring can animate them from their current position back to the cluster center.
|
|
@@ -4564,25 +4653,22 @@ const ClusterMarkerInner = react.forwardRef(function ClusterMarkerComponent(prop
|
|
|
4564
4653
|
event.currentTarget.click();
|
|
4565
4654
|
}
|
|
4566
4655
|
}, [onKeyDown]);
|
|
4567
|
-
const pieData = react.useMemo(() => segments.map((segment, index) => ({
|
|
4568
|
-
color: segment.color,
|
|
4569
|
-
value: segment.weight,
|
|
4570
|
-
title: `segment-${index}`,
|
|
4571
|
-
})), [segments]);
|
|
4572
4656
|
const bubbleTestId = dataTestId !== undefined ? `${dataTestId}-bubble` : undefined;
|
|
4657
|
+
const holeTestId = dataTestId !== undefined ? `${dataTestId}-hole` : undefined;
|
|
4573
4658
|
return (jsxRuntime.jsxs("div", { className: className, "data-testid": dataTestId, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: ref, style: {
|
|
4574
4659
|
position: "relative",
|
|
4575
4660
|
display: "inline-block",
|
|
4576
4661
|
isolation: "isolate",
|
|
4577
4662
|
...colors.cssVars,
|
|
4578
4663
|
...style,
|
|
4579
|
-
}, children: [managedSticks.map(({ input: stick, open, positioning }) => (jsxRuntime.jsx("div", { style: { position: "absolute", zIndex: 0, left: "50%", top: "50%", width: 0, height: 0 }, children: jsxRuntime.jsx(ClusterStick, { ...stick, onStickClick: open ? onStickClick : undefined, open: open, positioning: positioning }) }, stick.id))), jsxRuntime.jsxs("div", { "aria-label": accessibleName, "aria-pressed": state === "selected", className: "focus-visible:ring-info-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "data-map-marker-hit-surface": "", "data-testid": bubbleTestId, onBlur: onBlur, onClick: onClick, onFocus: onFocus, onKeyDown: handleKeyDown, role: "button", style: {
|
|
4664
|
+
}, children: [managedSticks.map(({ input: stick, open, positioning }) => (jsxRuntime.jsx("div", { style: { position: "absolute", zIndex: 0, left: "50%", top: "50%", width: 0, height: 0 }, children: jsxRuntime.jsx(ClusterStick, { ...stick, onStickClick: open ? onStickClick : undefined, open: open, positioning: positioning }) }, stick.id))), jsxRuntime.jsxs("div", { "aria-label": accessibleName, "aria-pressed": state === "selected", className: "focus-visible:ring-info-500 motion-reduce:transition-none! focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "data-map-marker-hit-surface": "", "data-testid": bubbleTestId, onBlur: onBlur, onClick: onClick, onFocus: onFocus, onKeyDown: handleKeyDown, role: "button", style: {
|
|
4580
4665
|
position: "relative",
|
|
4581
4666
|
display: "inline-flex",
|
|
4582
4667
|
alignItems: "center",
|
|
4583
4668
|
justifyContent: "center",
|
|
4584
|
-
|
|
4585
|
-
width:
|
|
4669
|
+
boxSizing: "border-box",
|
|
4670
|
+
width: bubbleDiameterPx,
|
|
4671
|
+
height: bubbleDiameterPx,
|
|
4586
4672
|
borderRadius: 9999,
|
|
4587
4673
|
overflow: "hidden",
|
|
4588
4674
|
backgroundColor: bubbleBackground,
|
|
@@ -4592,13 +4678,34 @@ const ClusterMarkerInner = react.forwardRef(function ClusterMarkerComponent(prop
|
|
|
4592
4678
|
borderStyle: "solid",
|
|
4593
4679
|
cursor: "pointer",
|
|
4594
4680
|
userSelect: "none",
|
|
4595
|
-
transitionProperty: "background-color, border-color, color",
|
|
4681
|
+
transitionProperty: "width, height, background-color, border-color, color",
|
|
4596
4682
|
transitionDuration: `${morphDurationMs}ms`,
|
|
4597
4683
|
transitionTimingFunction: easing,
|
|
4598
4684
|
zIndex: 1,
|
|
4599
|
-
}, tabIndex: 0, children: [pieData.length > 0 ? (jsxRuntime.jsx(reactMinimalPieChart.PieChart, { data: pieData, lineWidth: PIE_LINE_WIDTH_PERCENT, startAngle: PIE_START_ANGLE, style: { position: "absolute", inset: 0, width: "100%", height: "100%" } })
|
|
4685
|
+
}, tabIndex: 0, children: [pieData.length > 0 ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(reactMinimalPieChart.PieChart, { data: pieData, lineWidth: PIE_LINE_WIDTH_PERCENT, startAngle: PIE_START_ANGLE, style: { position: "absolute", inset: 0, width: "100%", height: "100%" } }), jsxRuntime.jsx("div", { "aria-hidden": true, className: "motion-reduce:transition-none!", "data-testid": holeTestId, style: {
|
|
4686
|
+
position: "absolute",
|
|
4687
|
+
inset: HOLE_INSET,
|
|
4688
|
+
borderRadius: 9999,
|
|
4689
|
+
backgroundColor: bubbleBackground,
|
|
4690
|
+
pointerEvents: "none",
|
|
4691
|
+
transform: isDonut ? "scale(1)" : "scale(0)",
|
|
4692
|
+
transitionProperty: "transform, background-color",
|
|
4693
|
+
transitionDuration: `${holeDurationMs}ms, ${morphDurationMs}ms`,
|
|
4694
|
+
transitionTimingFunction: `${HOLE_EASING}, ${easing}`,
|
|
4695
|
+
} })] })) : null, jsxRuntime.jsx("span", { className: "motion-reduce:transition-none! relative whitespace-nowrap text-xs font-semibold leading-none [text-box-trim:trim-both]", "data-testid": "ClusterMarker-count", style: {
|
|
4696
|
+
opacity: showCount ? 1 : 0,
|
|
4697
|
+
transitionProperty: "opacity",
|
|
4698
|
+
transitionDuration: `${morphDurationMs}ms`,
|
|
4699
|
+
transitionTimingFunction: easing,
|
|
4700
|
+
}, children: formatted })] })] }));
|
|
4600
4701
|
});
|
|
4601
|
-
|
|
4702
|
+
/**
|
|
4703
|
+
* Memoized with {@link areClusterMarkerPropsEqual}: `segments` is compared by content, all other props by reference.
|
|
4704
|
+
* Re-renders of the layer then skip clusters whose props are unchanged, even when callers derive `segments` per render.
|
|
4705
|
+
* On the fleet map (about 127 clusters, panning), this cut cluster renders from 1,000–3,300/s to 0–2/s
|
|
4706
|
+
* and cluster React time from 40–114 ms/s to under 3 ms/s (on local build).
|
|
4707
|
+
*/
|
|
4708
|
+
const ClusterMarker = react.memo(ClusterMarkerInner, areClusterMarkerPropsEqual);
|
|
4602
4709
|
ClusterMarkerInner.displayName = "ClusterMarker";
|
|
4603
4710
|
|
|
4604
4711
|
const getHasIcon$1 = (icon, iconPx) => {
|
|
@@ -12485,7 +12592,7 @@ const ShapeAnnotationLabel = ({ geometry, color, label, theme, isHovered = false
|
|
|
12485
12592
|
* Build-your-own hook: stable reference to the cluster count formatter.
|
|
12486
12593
|
*
|
|
12487
12594
|
* Returns `formatClusterCount` for consumers building custom cluster markers
|
|
12488
|
-
* that need consistent `"1.
|
|
12595
|
+
* that need consistent `"1.0k"` / `"10k"` / `"9.9m"` / `"99m"` formatting without reimplementing
|
|
12489
12596
|
* the truncation rules.
|
|
12490
12597
|
*
|
|
12491
12598
|
* @example
|
package/index.esm.js
CHANGED
|
@@ -4461,8 +4461,10 @@ ClusterStickInner.displayName = "ClusterStick";
|
|
|
4461
4461
|
*
|
|
4462
4462
|
* Rules:
|
|
4463
4463
|
* - `n < 1 000` → raw integer string
|
|
4464
|
-
* - `1 000 ≤ n <
|
|
4465
|
-
* - `n
|
|
4464
|
+
* - `1 000 ≤ n < 10 000` → `X.Xk` (truncated to one decimal of thousands)
|
|
4465
|
+
* - `10 000 ≤ n < 1 000 000` → `Xk` (truncated to whole thousands)
|
|
4466
|
+
* - `1 000 000 ≤ n < 10 000 000` → `X.Xm` (truncated to one decimal of millions)
|
|
4467
|
+
* - `n ≥ 10 000 000` → `Xm` (truncated to whole millions)
|
|
4466
4468
|
*
|
|
4467
4469
|
* Truncation (not rounding) is used so the displayed value never overstates the count.
|
|
4468
4470
|
*/
|
|
@@ -4470,16 +4472,93 @@ const formatClusterCount = (n) => {
|
|
|
4470
4472
|
if (n < 1000) {
|
|
4471
4473
|
return String(n);
|
|
4472
4474
|
}
|
|
4475
|
+
if (n < 10000) {
|
|
4476
|
+
return `${(Math.floor(n / 100) / 10).toFixed(1)}k`;
|
|
4477
|
+
}
|
|
4473
4478
|
if (n < 1000000) {
|
|
4474
4479
|
return `${Math.floor(n / 1000)}k`;
|
|
4475
4480
|
}
|
|
4476
|
-
|
|
4481
|
+
if (n < 10000000) {
|
|
4482
|
+
return `${(Math.floor(n / 100000) / 10).toFixed(1)}m`;
|
|
4483
|
+
}
|
|
4484
|
+
return `${Math.floor(n / 1000000)}m`;
|
|
4485
|
+
};
|
|
4486
|
+
|
|
4487
|
+
/**
|
|
4488
|
+
* `memo` comparator for `ClusterMarker`. Shallow-compares every prop except `segments`,
|
|
4489
|
+
* which is compared by content since callers typically derive a fresh array per render.
|
|
4490
|
+
*/
|
|
4491
|
+
const areClusterMarkerPropsEqual = (prev, next) => {
|
|
4492
|
+
const prevKeys = Object.keys(prev);
|
|
4493
|
+
const nextKeys = Object.keys(next);
|
|
4494
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
4495
|
+
return false;
|
|
4496
|
+
}
|
|
4497
|
+
for (const key of nextKeys) {
|
|
4498
|
+
if (key === "segments") {
|
|
4499
|
+
continue;
|
|
4500
|
+
}
|
|
4501
|
+
if (!Object.is(Reflect.get(prev, key), Reflect.get(next, key))) {
|
|
4502
|
+
return false;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
return isEqual(prev.segments, next.segments);
|
|
4506
|
+
};
|
|
4507
|
+
|
|
4508
|
+
/**
|
|
4509
|
+
* Open donut diameter (px), including the 1px border.
|
|
4510
|
+
* Named like marker sizes: sm, md, lg, xl. Compact formatting keeps lg labels to at
|
|
4511
|
+
* most 3 digits (`999`, `9.9k`, `99k`) and xl labels to at most 4 (`999k`, `9.9m`).
|
|
4512
|
+
*/
|
|
4513
|
+
const CLUSTER_BUBBLE_DIAMETER_PX = {
|
|
4514
|
+
sm: 24,
|
|
4515
|
+
md: 32,
|
|
4516
|
+
lg: 40,
|
|
4517
|
+
xl: 48,
|
|
4518
|
+
};
|
|
4519
|
+
const CLUSTER_PIE_BAND_STEPS = {
|
|
4520
|
+
sm: 1,
|
|
4521
|
+
md: 2,
|
|
4522
|
+
lg: 3,
|
|
4523
|
+
xl: 4,
|
|
4524
|
+
};
|
|
4525
|
+
/**
|
|
4526
|
+
* Resolves the cluster bubble diameter and form from the count, single-marker size, and state.
|
|
4527
|
+
* The resting pie is one marker step above `markerSize` for counts of 2–9, then one further step
|
|
4528
|
+
* per larger count band, clamped at xl.
|
|
4529
|
+
*/
|
|
4530
|
+
const resolveClusterBubbleSize = ({ count, markerSize, state, }) => {
|
|
4531
|
+
const safeCount = Number.isFinite(count) ? count : 0;
|
|
4532
|
+
const band = safeCount <= 9 ? "sm" : safeCount <= 99 ? "md" : safeCount < 100000 ? "lg" : "xl";
|
|
4533
|
+
const donutDiameterPx = CLUSTER_BUBBLE_DIAMETER_PX[band];
|
|
4534
|
+
if (markerSize === undefined) {
|
|
4535
|
+
return { diameterPx: donutDiameterPx, isDonut: true };
|
|
4536
|
+
}
|
|
4537
|
+
/** Outer pie diameter (px), including the 1px border. Matches a marker circle tier. */
|
|
4538
|
+
let pieSize = markerSize;
|
|
4539
|
+
for (let step = 0; step < CLUSTER_PIE_BAND_STEPS[band]; step++) {
|
|
4540
|
+
pieSize = upgradeMarkerDomSize(pieSize);
|
|
4541
|
+
}
|
|
4542
|
+
const pieDiameterPx = MARKER_SIZE_MAP[pieSize].circle;
|
|
4543
|
+
if (state === "default") {
|
|
4544
|
+
return { diameterPx: pieDiameterPx, isDonut: false };
|
|
4545
|
+
}
|
|
4546
|
+
/**
|
|
4547
|
+
* Open diameter. The count picks sm/md/lg/xl, but a larger closed pie does not shrink.
|
|
4548
|
+
* A 2–9 cluster on an lg or xl marker is a 28px pie and a 24px donut, so it stays 28px.
|
|
4549
|
+
*/
|
|
4550
|
+
return { diameterPx: Math.max(pieDiameterPx, donutDiameterPx), isDonut: true };
|
|
4477
4551
|
};
|
|
4478
4552
|
|
|
4479
4553
|
/** Radius (px) from cluster anchor at which sticks are placed during fan-out. */
|
|
4480
4554
|
const STICK_FAN_RADIUS_PX = 56;
|
|
4481
|
-
/**
|
|
4482
|
-
const PIE_LINE_WIDTH_PERCENT =
|
|
4555
|
+
/** Closed pie fills the disc. Open donut leaves a hole for the count (legacy ring thickness). */
|
|
4556
|
+
const PIE_LINE_WIDTH_PERCENT = 100;
|
|
4557
|
+
const DONUT_LINE_WIDTH_PERCENT = 25;
|
|
4558
|
+
/** The ring is `DONUT_LINE_WIDTH_PERCENT` of the radius, so the hole is inset by half that on each side. */
|
|
4559
|
+
const HOLE_INSET = `${DONUT_LINE_WIDTH_PERCENT / 2}%`;
|
|
4560
|
+
/** easeOutCubic. */
|
|
4561
|
+
const HOLE_EASING = "cubic-bezier(0.33, 1, 0.68, 1)";
|
|
4483
4562
|
const PIE_START_ANGLE = -90;
|
|
4484
4563
|
/** Stable empty sticks array — used as the default for the `sticks` prop so the useEffect
|
|
4485
4564
|
* dependency does not change on every render when no sticks are passed. */
|
|
@@ -4506,13 +4585,23 @@ const resolveBubbleBackground = (state, config) => {
|
|
|
4506
4585
|
return config.expandedBackground;
|
|
4507
4586
|
};
|
|
4508
4587
|
const ClusterMarkerInner = forwardRef(function ClusterMarkerComponent(props, ref) {
|
|
4509
|
-
const { count, segments, sticks = EMPTY_STICKS, onStickClick, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, state = "default", theme, colorConfig, className, style, "data-testid": dataTestId, "aria-label": ariaLabel, } = props;
|
|
4588
|
+
const { count, segments, sticks = EMPTY_STICKS, onStickClick, onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur, state = "default", markerSize, theme, colorConfig, className, style, "data-testid": dataTestId, "aria-label": ariaLabel, } = props;
|
|
4510
4589
|
const formatted = formatClusterCount(count);
|
|
4590
|
+
const pieData = useMemo(() => segments
|
|
4591
|
+
.map((segment, index) => ({
|
|
4592
|
+
color: segment.color,
|
|
4593
|
+
value: segment.weight,
|
|
4594
|
+
title: `segment-${index}`,
|
|
4595
|
+
}))
|
|
4596
|
+
.filter(entry => entry.value > 0), [segments]);
|
|
4597
|
+
const { diameterPx: bubbleDiameterPx, isDonut } = resolveClusterBubbleSize({ count, markerSize, state });
|
|
4598
|
+
const showCount = isDonut || pieData.length === 0;
|
|
4511
4599
|
const effectiveColorConfig = { ...defaultMarkerColorConfig(theme), ...colorConfig };
|
|
4512
4600
|
const colors = resolveMarkerColors("transparent", state, theme, colorConfig);
|
|
4513
4601
|
const bubbleBackground = resolveBubbleBackground(state, effectiveColorConfig);
|
|
4514
4602
|
const morphDurationMs = MARKER_TUNING.animation.morphDurationMs;
|
|
4515
4603
|
const easing = MARKER_TUNING.animation.morphEasing;
|
|
4604
|
+
const holeDurationMs = Math.round(morphDurationMs / 2);
|
|
4516
4605
|
// Single source of truth for all sticks — both active (open=true) and departing (open=false).
|
|
4517
4606
|
// Active sticks carry updated fan angles; departing sticks keep their last known positioning
|
|
4518
4607
|
// so the spring can animate them from their current position back to the cluster center.
|
|
@@ -4563,25 +4652,22 @@ const ClusterMarkerInner = forwardRef(function ClusterMarkerComponent(props, ref
|
|
|
4563
4652
|
event.currentTarget.click();
|
|
4564
4653
|
}
|
|
4565
4654
|
}, [onKeyDown]);
|
|
4566
|
-
const pieData = useMemo(() => segments.map((segment, index) => ({
|
|
4567
|
-
color: segment.color,
|
|
4568
|
-
value: segment.weight,
|
|
4569
|
-
title: `segment-${index}`,
|
|
4570
|
-
})), [segments]);
|
|
4571
4655
|
const bubbleTestId = dataTestId !== undefined ? `${dataTestId}-bubble` : undefined;
|
|
4656
|
+
const holeTestId = dataTestId !== undefined ? `${dataTestId}-hole` : undefined;
|
|
4572
4657
|
return (jsxs("div", { className: className, "data-testid": dataTestId, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: ref, style: {
|
|
4573
4658
|
position: "relative",
|
|
4574
4659
|
display: "inline-block",
|
|
4575
4660
|
isolation: "isolate",
|
|
4576
4661
|
...colors.cssVars,
|
|
4577
4662
|
...style,
|
|
4578
|
-
}, children: [managedSticks.map(({ input: stick, open, positioning }) => (jsx("div", { style: { position: "absolute", zIndex: 0, left: "50%", top: "50%", width: 0, height: 0 }, children: jsx(ClusterStick, { ...stick, onStickClick: open ? onStickClick : undefined, open: open, positioning: positioning }) }, stick.id))), jsxs("div", { "aria-label": accessibleName, "aria-pressed": state === "selected", className: "focus-visible:ring-info-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "data-map-marker-hit-surface": "", "data-testid": bubbleTestId, onBlur: onBlur, onClick: onClick, onFocus: onFocus, onKeyDown: handleKeyDown, role: "button", style: {
|
|
4663
|
+
}, children: [managedSticks.map(({ input: stick, open, positioning }) => (jsx("div", { style: { position: "absolute", zIndex: 0, left: "50%", top: "50%", width: 0, height: 0 }, children: jsx(ClusterStick, { ...stick, onStickClick: open ? onStickClick : undefined, open: open, positioning: positioning }) }, stick.id))), jsxs("div", { "aria-label": accessibleName, "aria-pressed": state === "selected", className: "focus-visible:ring-info-500 motion-reduce:transition-none! focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2", "data-map-marker-hit-surface": "", "data-testid": bubbleTestId, onBlur: onBlur, onClick: onClick, onFocus: onFocus, onKeyDown: handleKeyDown, role: "button", style: {
|
|
4579
4664
|
position: "relative",
|
|
4580
4665
|
display: "inline-flex",
|
|
4581
4666
|
alignItems: "center",
|
|
4582
4667
|
justifyContent: "center",
|
|
4583
|
-
|
|
4584
|
-
width:
|
|
4668
|
+
boxSizing: "border-box",
|
|
4669
|
+
width: bubbleDiameterPx,
|
|
4670
|
+
height: bubbleDiameterPx,
|
|
4585
4671
|
borderRadius: 9999,
|
|
4586
4672
|
overflow: "hidden",
|
|
4587
4673
|
backgroundColor: bubbleBackground,
|
|
@@ -4591,13 +4677,34 @@ const ClusterMarkerInner = forwardRef(function ClusterMarkerComponent(props, ref
|
|
|
4591
4677
|
borderStyle: "solid",
|
|
4592
4678
|
cursor: "pointer",
|
|
4593
4679
|
userSelect: "none",
|
|
4594
|
-
transitionProperty: "background-color, border-color, color",
|
|
4680
|
+
transitionProperty: "width, height, background-color, border-color, color",
|
|
4595
4681
|
transitionDuration: `${morphDurationMs}ms`,
|
|
4596
4682
|
transitionTimingFunction: easing,
|
|
4597
4683
|
zIndex: 1,
|
|
4598
|
-
}, tabIndex: 0, children: [pieData.length > 0 ? (jsx(PieChart, { data: pieData, lineWidth: PIE_LINE_WIDTH_PERCENT, startAngle: PIE_START_ANGLE, style: { position: "absolute", inset: 0, width: "100%", height: "100%" } })
|
|
4684
|
+
}, tabIndex: 0, children: [pieData.length > 0 ? (jsxs(Fragment, { children: [jsx(PieChart, { data: pieData, lineWidth: PIE_LINE_WIDTH_PERCENT, startAngle: PIE_START_ANGLE, style: { position: "absolute", inset: 0, width: "100%", height: "100%" } }), jsx("div", { "aria-hidden": true, className: "motion-reduce:transition-none!", "data-testid": holeTestId, style: {
|
|
4685
|
+
position: "absolute",
|
|
4686
|
+
inset: HOLE_INSET,
|
|
4687
|
+
borderRadius: 9999,
|
|
4688
|
+
backgroundColor: bubbleBackground,
|
|
4689
|
+
pointerEvents: "none",
|
|
4690
|
+
transform: isDonut ? "scale(1)" : "scale(0)",
|
|
4691
|
+
transitionProperty: "transform, background-color",
|
|
4692
|
+
transitionDuration: `${holeDurationMs}ms, ${morphDurationMs}ms`,
|
|
4693
|
+
transitionTimingFunction: `${HOLE_EASING}, ${easing}`,
|
|
4694
|
+
} })] })) : null, jsx("span", { className: "motion-reduce:transition-none! relative whitespace-nowrap text-xs font-semibold leading-none [text-box-trim:trim-both]", "data-testid": "ClusterMarker-count", style: {
|
|
4695
|
+
opacity: showCount ? 1 : 0,
|
|
4696
|
+
transitionProperty: "opacity",
|
|
4697
|
+
transitionDuration: `${morphDurationMs}ms`,
|
|
4698
|
+
transitionTimingFunction: easing,
|
|
4699
|
+
}, children: formatted })] })] }));
|
|
4599
4700
|
});
|
|
4600
|
-
|
|
4701
|
+
/**
|
|
4702
|
+
* Memoized with {@link areClusterMarkerPropsEqual}: `segments` is compared by content, all other props by reference.
|
|
4703
|
+
* Re-renders of the layer then skip clusters whose props are unchanged, even when callers derive `segments` per render.
|
|
4704
|
+
* On the fleet map (about 127 clusters, panning), this cut cluster renders from 1,000–3,300/s to 0–2/s
|
|
4705
|
+
* and cluster React time from 40–114 ms/s to under 3 ms/s (on local build).
|
|
4706
|
+
*/
|
|
4707
|
+
const ClusterMarker = memo(ClusterMarkerInner, areClusterMarkerPropsEqual);
|
|
4601
4708
|
ClusterMarkerInner.displayName = "ClusterMarker";
|
|
4602
4709
|
|
|
4603
4710
|
const getHasIcon$1 = (icon, iconPx) => {
|
|
@@ -12484,7 +12591,7 @@ const ShapeAnnotationLabel = ({ geometry, color, label, theme, isHovered = false
|
|
|
12484
12591
|
* Build-your-own hook: stable reference to the cluster count formatter.
|
|
12485
12592
|
*
|
|
12486
12593
|
* Returns `formatClusterCount` for consumers building custom cluster markers
|
|
12487
|
-
* that need consistent `"1.
|
|
12594
|
+
* that need consistent `"1.0k"` / `"10k"` / `"9.9m"` / `"99m"` formatting without reimplementing
|
|
12488
12595
|
* the truncation rules.
|
|
12489
12596
|
*
|
|
12490
12597
|
* @example
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-map",
|
|
3
|
-
"version": "0.2.192
|
|
3
|
+
"version": "0.2.192",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=24.x"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@trackunit/react-components": "3.5.
|
|
11
|
-
"@trackunit/css-class-variance-utilities": "2.2.
|
|
12
|
-
"@trackunit/react-form-components": "2.9.
|
|
13
|
-
"@trackunit/react-core-hooks": "1.24.
|
|
14
|
-
"@trackunit/geo-json-utils": "1.17.
|
|
15
|
-
"@trackunit/i18n-library-translation": "2.7.
|
|
10
|
+
"@trackunit/react-components": "3.5.3",
|
|
11
|
+
"@trackunit/css-class-variance-utilities": "2.2.2",
|
|
12
|
+
"@trackunit/react-form-components": "2.9.3",
|
|
13
|
+
"@trackunit/react-core-hooks": "1.24.2",
|
|
14
|
+
"@trackunit/geo-json-utils": "1.17.2",
|
|
15
|
+
"@trackunit/i18n-library-translation": "2.7.2",
|
|
16
16
|
"react-minimal-pie-chart": "^8.4.0",
|
|
17
|
-
"@trackunit/react-map-adapter-shared": "0.0.
|
|
18
|
-
"@trackunit/react-map-color-utils": "0.0.
|
|
19
|
-
"@trackunit/shared-utils": "1.18.
|
|
20
|
-
"@trackunit/ui-design-tokens": "1.17.
|
|
17
|
+
"@trackunit/react-map-adapter-shared": "0.0.149",
|
|
18
|
+
"@trackunit/react-map-color-utils": "0.0.124",
|
|
19
|
+
"@trackunit/shared-utils": "1.18.2",
|
|
20
|
+
"@trackunit/ui-design-tokens": "1.17.2",
|
|
21
21
|
"@floating-ui/react": "^0.26.25",
|
|
22
22
|
"es-toolkit": "^1.52.0",
|
|
23
23
|
"tailwind-merge": "^2.0.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CommonProps, type Refable, type Styleable } from "@trackunit/react-components";
|
|
2
2
|
import { type HTMLAttributes } from "react";
|
|
3
3
|
import type { MapTheme } from "../core/types";
|
|
4
|
-
import type { MarkerState } from "../markers/model/markerDomTypes";
|
|
4
|
+
import type { MarkerDomSize, MarkerState } from "../markers/model/markerDomTypes";
|
|
5
5
|
import { type MarkerColorConfig } from "../markers/shared/markerColors";
|
|
6
6
|
import { type ClusterStickProps } from "./ClusterStick";
|
|
7
7
|
/** Pre-computed doughnut segment input. `weight` maps directly to `value` in `BaseDataEntry`. */
|
|
@@ -21,21 +21,40 @@ type ClusterMarkerDivHandlers = Pick<HTMLAttributes<HTMLDivElement>, "onClick" |
|
|
|
21
21
|
type ClusterMarkerBaseProps = CommonProps & Styleable & Refable<HTMLDivElement> & ClusterMarkerDivHandlers & {
|
|
22
22
|
/** Total member count. Rendered via {@link formatClusterCount}. */
|
|
23
23
|
count: number;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Pie (resting) and donut ring (open) segments.
|
|
26
|
+
* Non-positive weights are ignored; without any positive weights the cluster shows only the count.
|
|
27
|
+
*/
|
|
25
28
|
segments: ReadonlyArray<ClusterSegment>;
|
|
26
29
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
30
|
+
* Highlighted member sticks fanned out around the bubble at evenly spread angles.
|
|
31
|
+
* Any cluster can highlight assets this way. At most 6 sticks; more than that and the labels crowd.
|
|
29
32
|
*/
|
|
30
33
|
sticks?: ReadonlyArray<ClusterMarkerStickInput>;
|
|
31
34
|
/** Fired by each fanned-out stick with its `id`. */
|
|
32
35
|
onStickClick?: (id: string) => void;
|
|
33
36
|
state?: MarkerState;
|
|
37
|
+
/**
|
|
38
|
+
* Size of a single marker at the current zoom. When set, the resting form is a
|
|
39
|
+
* pie one step above this size for counts of 2–9, then one further step per
|
|
40
|
+
* larger band, clamped at xl. The open donut sizes from `count` (sm, md, lg, xl)
|
|
41
|
+
* and stays at the pie diameter when that pie is already larger.
|
|
42
|
+
* When omitted, the cluster stays the expanded count donut, which is what the
|
|
43
|
+
* fleet map renders today. Without segments with a positive weight, the cluster
|
|
44
|
+
* keeps this sizing but shows only the count, since there is no pie or ring to show.
|
|
45
|
+
*/
|
|
46
|
+
markerSize?: MarkerDomSize;
|
|
34
47
|
theme: MapTheme;
|
|
35
48
|
colorConfig?: MarkerColorConfig;
|
|
36
49
|
};
|
|
37
50
|
export type ClusterMarkerProps = ClusterMarkerBaseProps & {
|
|
38
51
|
"aria-label"?: string;
|
|
39
52
|
};
|
|
40
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Memoized with {@link areClusterMarkerPropsEqual}: `segments` is compared by content, all other props by reference.
|
|
55
|
+
* Re-renders of the layer then skip clusters whose props are unchanged, even when callers derive `segments` per render.
|
|
56
|
+
* On the fleet map (about 127 clusters, panning), this cut cluster renders from 1,000–3,300/s to 0–2/s
|
|
57
|
+
* and cluster React time from 40–114 ms/s to under 3 ms/s (on local build).
|
|
58
|
+
*/
|
|
59
|
+
export declare const ClusterMarker: import("react").NamedExoticComponent<Omit<ClusterMarkerProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
41
60
|
export {};
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Rules:
|
|
5
5
|
* - `n < 1 000` → raw integer string
|
|
6
|
-
* - `1 000 ≤ n <
|
|
7
|
-
* - `n
|
|
6
|
+
* - `1 000 ≤ n < 10 000` → `X.Xk` (truncated to one decimal of thousands)
|
|
7
|
+
* - `10 000 ≤ n < 1 000 000` → `Xk` (truncated to whole thousands)
|
|
8
|
+
* - `1 000 000 ≤ n < 10 000 000` → `X.Xm` (truncated to one decimal of millions)
|
|
9
|
+
* - `n ≥ 10 000 000` → `Xm` (truncated to whole millions)
|
|
8
10
|
*
|
|
9
11
|
* Truncation (not rounding) is used so the displayed value never overstates the count.
|
|
10
12
|
*/
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
2
|
import type { MapTheme } from "../../core/types";
|
|
3
|
-
import type { MarkerState } from "../../markers/model/markerDomTypes";
|
|
3
|
+
import type { MarkerDomSize, MarkerState } from "../../markers/model/markerDomTypes";
|
|
4
4
|
type ClusterMarkerDefaultDemoProps = {
|
|
5
5
|
readonly count: number;
|
|
6
6
|
readonly state: MarkerState;
|
|
7
|
+
readonly markerSize: MarkerDomSize;
|
|
7
8
|
readonly theme: MapTheme;
|
|
8
9
|
readonly showSegments: boolean;
|
|
9
10
|
readonly showSticks: boolean;
|
|
10
11
|
};
|
|
11
|
-
export declare const ClusterMarkerDefaultDemo: ({ count, state, theme, showSegments, showSticks, }: ClusterMarkerDefaultDemoProps) => ReactElement;
|
|
12
|
+
export declare const ClusterMarkerDefaultDemo: ({ count, state, markerSize, theme, showSegments, showSticks, }: ClusterMarkerDefaultDemoProps) => ReactElement;
|
|
12
13
|
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactElement } from "react";
|
|
2
|
+
import type { MapTheme } from "../../core/types";
|
|
3
|
+
type ClusterMarkerPieDonutDemoProps = {
|
|
4
|
+
readonly theme: MapTheme;
|
|
5
|
+
};
|
|
6
|
+
export declare const ClusterMarkerPieDonutDemo: ({ theme }: ClusterMarkerPieDonutDemoProps) => ReactElement;
|
|
7
|
+
export {};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import type { MapTheme } from "../../core/types";
|
|
3
|
+
import type { MarkerDomSize } from "../../markers/model/markerDomTypes";
|
|
3
4
|
type ClusterMarkerPlaygroundDemoProps = {
|
|
4
5
|
readonly theme: MapTheme;
|
|
6
|
+
readonly markerSize: MarkerDomSize;
|
|
5
7
|
readonly stickCount: number;
|
|
6
8
|
readonly showSegments: boolean;
|
|
7
9
|
readonly showIndicators: boolean;
|
|
8
10
|
};
|
|
9
|
-
export declare const ClusterMarkerPlaygroundDemo: ({ theme, stickCount, showSegments, showIndicators, }: ClusterMarkerPlaygroundDemoProps) => ReactElement;
|
|
11
|
+
export declare const ClusterMarkerPlaygroundDemo: ({ theme, markerSize, stickCount, showSegments, showIndicators, }: ClusterMarkerPlaygroundDemoProps) => ReactElement;
|
|
10
12
|
export {};
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import type { MapTheme } from "../../core/types";
|
|
3
|
-
type
|
|
4
|
-
readonly theme: MapTheme;
|
|
5
|
-
readonly showSegments: boolean;
|
|
3
|
+
type InteractiveSticksDemoProps = {
|
|
6
4
|
readonly showIndicators: boolean;
|
|
5
|
+
readonly showSegments: boolean;
|
|
7
6
|
readonly stickCount: number;
|
|
7
|
+
readonly theme: MapTheme;
|
|
8
|
+
};
|
|
9
|
+
type HighlightedSticksDemoProps = {
|
|
10
|
+
readonly theme: MapTheme;
|
|
8
11
|
};
|
|
9
|
-
|
|
12
|
+
type ClusterMarkerWithSticksDemoProps = ({
|
|
13
|
+
readonly example?: "interactive";
|
|
14
|
+
} & InteractiveSticksDemoProps) | ({
|
|
15
|
+
readonly example: "highlighted";
|
|
16
|
+
} & HighlightedSticksDemoProps);
|
|
17
|
+
export declare const ClusterMarkerWithSticksDemo: (props: ClusterMarkerWithSticksDemoProps) => ReactElement;
|
|
10
18
|
export {};
|
|
@@ -6,7 +6,7 @@ export type ClusterCountFormatHelpers = Readonly<{
|
|
|
6
6
|
* Build-your-own hook: stable reference to the cluster count formatter.
|
|
7
7
|
*
|
|
8
8
|
* Returns `formatClusterCount` for consumers building custom cluster markers
|
|
9
|
-
* that need consistent `"1.
|
|
9
|
+
* that need consistent `"1.0k"` / `"10k"` / `"9.9m"` / `"99m"` formatting without reimplementing
|
|
10
10
|
* the truncation rules.
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ClusterMarkerProps } from "../ClusterMarker";
|
|
2
|
+
/**
|
|
3
|
+
* `memo` comparator for `ClusterMarker`. Shallow-compares every prop except `segments`,
|
|
4
|
+
* which is compared by content since callers typically derive a fresh array per render.
|
|
5
|
+
*/
|
|
6
|
+
export declare const areClusterMarkerPropsEqual: (prev: ClusterMarkerProps, next: ClusterMarkerProps) => boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MarkerDomSize, MarkerState } from "../../markers/model/markerDomTypes";
|
|
2
|
+
export type ResolveClusterBubbleSizeOptions = Readonly<{
|
|
3
|
+
count: number;
|
|
4
|
+
/** Single-marker size at the current zoom. When omitted, the bubble is always the open donut. */
|
|
5
|
+
markerSize: MarkerDomSize | undefined;
|
|
6
|
+
state: MarkerState;
|
|
7
|
+
}>;
|
|
8
|
+
export type ClusterBubbleSize = Readonly<{
|
|
9
|
+
/** Outer diameter (px), including the 1px border. */
|
|
10
|
+
diameterPx: number;
|
|
11
|
+
/** True when the bubble is the open donut with the count; false when it rests as a pie. */
|
|
12
|
+
isDonut: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the cluster bubble diameter and form from the count, single-marker size, and state.
|
|
16
|
+
* The resting pie is one marker step above `markerSize` for counts of 2–9, then one further step
|
|
17
|
+
* per larger count band, clamped at xl.
|
|
18
|
+
*/
|
|
19
|
+
export declare const resolveClusterBubbleSize: ({ count, markerSize, state, }: ResolveClusterBubbleSizeOptions) => ClusterBubbleSize;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const cvaStoryCaption: import("cva").CVAComponent<Omit<{
|
|
2
|
+
base: string[];
|
|
3
|
+
variants: {
|
|
4
|
+
theme: {
|
|
5
|
+
light: string[];
|
|
6
|
+
dark: string[];
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
}, "defaultVariants"> & {
|
|
10
|
+
variants: {
|
|
11
|
+
theme: {
|
|
12
|
+
light: string[];
|
|
13
|
+
dark: string[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
defaultVariants: Omit<{}, never>;
|
|
17
|
+
}, {
|
|
18
|
+
theme: {
|
|
19
|
+
light: string[];
|
|
20
|
+
dark: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const cvaStoryTitle: import("cva").CVAComponent<Omit<{
|
|
2
|
+
base: string[];
|
|
3
|
+
variants: {
|
|
4
|
+
theme: {
|
|
5
|
+
light: string[];
|
|
6
|
+
dark: string[];
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
}, "defaultVariants"> & {
|
|
10
|
+
variants: {
|
|
11
|
+
theme: {
|
|
12
|
+
light: string[];
|
|
13
|
+
dark: string[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
defaultVariants: Omit<{}, never>;
|
|
17
|
+
}, {
|
|
18
|
+
theme: {
|
|
19
|
+
light: string[];
|
|
20
|
+
dark: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|