@terraware/web-components 5.0.12 → 5.0.13

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.
@@ -0,0 +1,16 @@
1
+ import type { BuildLayoutParams, ColorWeight, LayoutMark, PositionedMark, TimelineCluster, TimelineLayout } from './types';
2
+ export declare const DOT_SIZE_PX = 12;
3
+ export declare const CLUSTER_DOT_SIZE_PX = 18;
4
+ export declare const EXPANDED_SPACING_PX = 20;
5
+ export declare const MIN_SQUEEZE_PITCH_PX = 12;
6
+ export declare const BAND_PADDING_PX = 8;
7
+ export declare const SQUEEZE_GAP_PX = 12;
8
+ export declare const MAX_BAND_RATIO = 0.8;
9
+ export declare const DEFAULT_CLUSTER_THRESHOLD_PX = 16;
10
+ export declare const DIMMED_OPACITY = 0.35;
11
+ export declare const normalizePositions: (marks: LayoutMark[], containerWidth: number) => PositionedMark[];
12
+ export declare const toColorWeights: (colors: string[]) => ColorWeight[];
13
+ export declare const buildClusters: (marks: LayoutMark[], containerWidth: number, thresholdPx: number) => TimelineCluster[];
14
+ export declare const toConicGradient: (colorWeights: ColorWeight[]) => string;
15
+ export declare const buildLayout: ({ containerWidth, expandedClusterId, marks, thresholdPx, }: BuildLayoutParams) => TimelineLayout;
16
+ //# sourceMappingURL=layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/components/TimelineSliderV2/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,cAAc,EACd,eAAe,EACf,cAAc,EAEf,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,oBAAoB,KAAc,CAAC;AAChD,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,cAAc,MAAM,CAAC;AAClC,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAC/C,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,EAAE,EAAE,gBAAgB,MAAM,KAAG,cAAc,EAkB9F,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,EAAE,KAAG,WAAW,EAQ5D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,OAAO,UAAU,EAAE,EAAE,gBAAgB,MAAM,EAAE,aAAa,MAAM,KAAG,eAAe,EAkC/G,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,cAAc,WAAW,EAAE,KAAG,MAiB7D,CAAC;AAkCF,eAAO,MAAM,WAAW,GAAI,4DAKzB,iBAAiB,KAAG,cAyDtB,CAAC"}
@@ -0,0 +1,168 @@
1
+ const DOT_SIZE_PX = 12;
2
+ const CLUSTER_DOT_SIZE_PX = 18;
3
+ const EXPANDED_SPACING_PX = 20;
4
+ const MIN_SQUEEZE_PITCH_PX = DOT_SIZE_PX;
5
+ const BAND_PADDING_PX = 8;
6
+ const SQUEEZE_GAP_PX = 12;
7
+ const MAX_BAND_RATIO = 0.8;
8
+ const DEFAULT_CLUSTER_THRESHOLD_PX = 16;
9
+ const DIMMED_OPACITY = 0.35;
10
+ const normalizePositions = (marks, containerWidth) => {
11
+ if (marks.length === 0) {
12
+ return [];
13
+ }
14
+ const values = marks.map(mark => mark.value);
15
+ const minValue = Math.min(...values);
16
+ const maxValue = Math.max(...values);
17
+ const range = maxValue - minValue;
18
+
19
+ // Mirrors TimelineSlider's behavior: when every mark shares one value, they all land at the right edge.
20
+ return marks.map(mark => ({
21
+ color: mark.color,
22
+ id: mark.id,
23
+ positionPx: (range === 0 ? 1 : (mark.value - minValue) / range) * containerWidth
24
+ })).sort((a, b) => a.positionPx - b.positionPx);
25
+ };
26
+ const toColorWeights = colors => {
27
+ const weights = new Map();
28
+ colors.forEach(color => {
29
+ weights.set(color, (weights.get(color) ?? 0) + 1);
30
+ });
31
+ return [...weights.entries()].map(([color, weight]) => ({
32
+ color,
33
+ weight
34
+ }));
35
+ };
36
+ const buildClusters = (marks, containerWidth, thresholdPx) => {
37
+ const positioned = normalizePositions(marks, containerWidth);
38
+ const clusters = [];
39
+ let current = [];
40
+ const flush = () => {
41
+ if (current.length === 0) {
42
+ return;
43
+ }
44
+ const total = current.reduce((sum, member) => sum + member.positionPx, 0);
45
+ clusters.push({
46
+ anchorPx: total / current.length,
47
+ id: `cluster-${current[0].id}`,
48
+ members: current
49
+ });
50
+ current = [];
51
+ };
52
+ positioned.forEach(mark => {
53
+ const previous = current[current.length - 1];
54
+ if (previous !== undefined && mark.positionPx - previous.positionPx >= thresholdPx) {
55
+ flush();
56
+ }
57
+ current.push(mark);
58
+ });
59
+ flush();
60
+ return clusters;
61
+ };
62
+ const toConicGradient = colorWeights => {
63
+ if (colorWeights.length <= 1) {
64
+ return colorWeights[0]?.color ?? 'transparent';
65
+ }
66
+ const total = colorWeights.reduce((sum, {
67
+ weight
68
+ }) => sum + weight, 0);
69
+ let consumed = 0;
70
+ const stops = colorWeights.map(({
71
+ color,
72
+ weight
73
+ }) => {
74
+ const startDeg = consumed / total * 360;
75
+ consumed += weight;
76
+ const endDeg = consumed / total * 360;
77
+ return `${color} ${startDeg}deg ${endDeg}deg`;
78
+ });
79
+ return `conic-gradient(${stops.join(', ')})`;
80
+ };
81
+ const toCollapsedNode = (cluster, dimmed) => ({
82
+ colorWeights: toColorWeights(cluster.members.map(member => member.color)),
83
+ dimmed,
84
+ id: cluster.id,
85
+ leftPx: cluster.anchorPx,
86
+ markIds: cluster.members.map(member => member.id),
87
+ sizePx: cluster.members.length > 1 ? CLUSTER_DOT_SIZE_PX : DOT_SIZE_PX
88
+ });
89
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
90
+ const expandedSpacing = (memberCount, containerWidth, maxBandWidth) => {
91
+ if (memberCount <= 1) {
92
+ return EXPANDED_SPACING_PX;
93
+ }
94
+ const cap = Math.min(containerWidth * MAX_BAND_RATIO, maxBandWidth);
95
+ const fitted = (cap - DOT_SIZE_PX - 2 * BAND_PADDING_PX) / (memberCount - 1);
96
+ return Math.max(0, Math.min(EXPANDED_SPACING_PX, fitted));
97
+ };
98
+ const rescale = (value, fromStart, fromEnd, toStart, toEnd) => {
99
+ const fromSpan = fromEnd - fromStart;
100
+ if (fromSpan <= 0) {
101
+ return toStart;
102
+ }
103
+ return toStart + (value - fromStart) / fromSpan * (toEnd - toStart);
104
+ };
105
+ const buildLayout = ({
106
+ containerWidth,
107
+ expandedClusterId,
108
+ marks,
109
+ thresholdPx = DEFAULT_CLUSTER_THRESHOLD_PX
110
+ }) => {
111
+ if (containerWidth <= 0 || marks.length === 0) {
112
+ return {
113
+ nodes: []
114
+ };
115
+ }
116
+ const clusters = buildClusters(marks, containerWidth, thresholdPx);
117
+ const expanded = clusters.find(cluster => cluster.id === expandedClusterId && cluster.members.length > 1);
118
+ if (expanded === undefined) {
119
+ return {
120
+ nodes: clusters.map(cluster => toCollapsedNode(cluster, false))
121
+ };
122
+ }
123
+ const sideClusters = clusters.filter(cluster => cluster.id !== expanded.id);
124
+ const leftCount = sideClusters.filter(cluster => cluster.anchorPx < expanded.anchorPx).length;
125
+ const rightCount = sideClusters.length - leftCount;
126
+ const reserve = count => count === 0 ? 0 : SQUEEZE_GAP_PX + (count - 1) * MIN_SQUEEZE_PITCH_PX;
127
+ const leftReserve = reserve(leftCount);
128
+ const rightReserve = reserve(rightCount);
129
+ const minBandWidth = DOT_SIZE_PX + 2 * BAND_PADDING_PX;
130
+ const maxBandWidth = Math.max(minBandWidth, containerWidth - leftReserve - rightReserve);
131
+ const spacing = expandedSpacing(expanded.members.length, containerWidth, maxBandWidth);
132
+ const widthPx = Math.min(maxBandWidth, (expanded.members.length - 1) * spacing + minBandWidth);
133
+ const leftPx = clamp(expanded.anchorPx - widthPx / 2, leftReserve, Math.max(leftReserve, containerWidth - widthPx - rightReserve));
134
+ const band = {
135
+ leftPx,
136
+ memberCount: expanded.members.length,
137
+ widthPx
138
+ };
139
+ const leftLimit = Math.max(0, leftPx - SQUEEZE_GAP_PX);
140
+ const rightLimit = Math.min(containerWidth, leftPx + widthPx + SQUEEZE_GAP_PX);
141
+ const nodes = clusters.flatMap(cluster => {
142
+ if (cluster.id === expanded.id) {
143
+ return cluster.members.map((member, index) => ({
144
+ colorWeights: [{
145
+ color: member.color,
146
+ weight: 1
147
+ }],
148
+ dimmed: false,
149
+ id: member.id,
150
+ leftPx: leftPx + BAND_PADDING_PX + DOT_SIZE_PX / 2 + index * spacing,
151
+ markIds: [member.id],
152
+ sizePx: DOT_SIZE_PX
153
+ }));
154
+ }
155
+ const collapsed = toCollapsedNode(cluster, true);
156
+ const squeezed = cluster.anchorPx < expanded.anchorPx ? rescale(cluster.anchorPx, 0, expanded.anchorPx, 0, leftLimit) : rescale(cluster.anchorPx, expanded.anchorPx, containerWidth, rightLimit, containerWidth);
157
+ return [{
158
+ ...collapsed,
159
+ leftPx: squeezed
160
+ }];
161
+ });
162
+ return {
163
+ band,
164
+ nodes
165
+ };
166
+ };
167
+
168
+ export { BAND_PADDING_PX, CLUSTER_DOT_SIZE_PX, DEFAULT_CLUSTER_THRESHOLD_PX, DIMMED_OPACITY, DOT_SIZE_PX, EXPANDED_SPACING_PX, MAX_BAND_RATIO, MIN_SQUEEZE_PITCH_PX, SQUEEZE_GAP_PX, buildClusters, buildLayout, normalizePositions, toColorWeights, toConicGradient };
@@ -0,0 +1,43 @@
1
+ export type LayoutMark = {
2
+ color: string;
3
+ id: string;
4
+ value: number;
5
+ };
6
+ export type ColorWeight = {
7
+ color: string;
8
+ weight: number;
9
+ };
10
+ export type PositionedMark = {
11
+ color: string;
12
+ id: string;
13
+ positionPx: number;
14
+ };
15
+ export type TimelineCluster = {
16
+ anchorPx: number;
17
+ id: string;
18
+ members: PositionedMark[];
19
+ };
20
+ export type TimelineNode = {
21
+ colorWeights: ColorWeight[];
22
+ dimmed: boolean;
23
+ id: string;
24
+ leftPx: number;
25
+ markIds: string[];
26
+ sizePx: number;
27
+ };
28
+ export type ExpandedBand = {
29
+ leftPx: number;
30
+ memberCount: number;
31
+ widthPx: number;
32
+ };
33
+ export type TimelineLayout = {
34
+ band?: ExpandedBand;
35
+ nodes: TimelineNode[];
36
+ };
37
+ export type BuildLayoutParams = {
38
+ containerWidth: number;
39
+ expandedClusterId?: string;
40
+ marks: LayoutMark[];
41
+ thresholdPx?: number;
42
+ };
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/TimelineSliderV2/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraware/web-components",
3
- "version": "5.0.12",
3
+ "version": "5.0.13",
4
4
  "author": "Terraformation Inc.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {