@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.
- package/components/TimelineSliderV2/layout.d.ts +16 -0
- package/components/TimelineSliderV2/layout.d.ts.map +1 -0
- package/components/TimelineSliderV2/layout.js +168 -0
- package/components/TimelineSliderV2/types.d.ts +43 -0
- package/components/TimelineSliderV2/types.d.ts.map +1 -0
- package/components/TimelineSliderV2/types.js +1 -0
- package/package.json +1 -1
|
@@ -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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|