@empoweredvote/ev-ui 0.7.0 → 0.8.0
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/dist/index.js +532 -486
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +198 -152
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/RadarChartCore.jsx
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
3
3
|
import { animated, useSpring } from "@react-spring/web";
|
|
4
|
-
import { useRef, useEffect } from "react";
|
|
5
4
|
function RadarChartCore({
|
|
6
5
|
topics,
|
|
7
6
|
data,
|
|
@@ -16,11 +15,16 @@ function RadarChartCore({
|
|
|
16
15
|
labelFontSize = 18,
|
|
17
16
|
padding = 80,
|
|
18
17
|
labelOffset = 20,
|
|
19
|
-
tightFit = false
|
|
18
|
+
tightFit = false,
|
|
19
|
+
activeSpoke = null,
|
|
20
|
+
writeIns = {}
|
|
20
21
|
}) {
|
|
22
|
+
var _a;
|
|
21
23
|
const radius = size / 2 - 40;
|
|
24
|
+
const extRadius = radius * 1.15;
|
|
22
25
|
const centerX = size / 2;
|
|
23
26
|
const centerY = size / 2;
|
|
27
|
+
const [tooltip, setTooltip] = useState(null);
|
|
24
28
|
const spokes = Object.entries(data);
|
|
25
29
|
const numSpokes = Math.max(spokes.length, 1);
|
|
26
30
|
const prevCountRef = useRef(numSpokes);
|
|
@@ -28,42 +32,40 @@ function RadarChartCore({
|
|
|
28
32
|
useEffect(() => {
|
|
29
33
|
prevCountRef.current = numSpokes;
|
|
30
34
|
}, [numSpokes]);
|
|
31
|
-
|
|
32
|
-
var
|
|
35
|
+
function calcAdjusted(value, shortTitle) {
|
|
36
|
+
var _a2;
|
|
37
|
+
if (!value || Number(value) === 0) return 0;
|
|
33
38
|
const topic = topics.find((t) => t.short_title === shortTitle);
|
|
34
|
-
const max = ((
|
|
35
|
-
const pct =
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
const max = ((_a2 = topic == null ? void 0 : topic.stances) == null ? void 0 : _a2.length) || 10;
|
|
40
|
+
const pct = Number(value) / max * 10;
|
|
41
|
+
return invertedSpokes[shortTitle] ? (max + 1) * 10 / max - pct : pct;
|
|
42
|
+
}
|
|
43
|
+
const pointsArr = spokes.map(([shortTitle, value], index) => {
|
|
44
|
+
const adjusted = calcAdjusted(value, shortTitle);
|
|
38
45
|
const r = adjusted / 10 * radius;
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
return [x, y];
|
|
46
|
+
const angle = 2 * Math.PI * index / numSpokes;
|
|
47
|
+
return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
|
|
42
48
|
});
|
|
43
49
|
const targetPoints = pointsArr.map((p) => p.join(",")).join(" ");
|
|
44
|
-
const
|
|
50
|
+
const springBase = {
|
|
45
51
|
to: { points: targetPoints },
|
|
46
52
|
immediate: countChanged,
|
|
47
|
-
reset: countChanged,
|
|
48
53
|
config: { tension: 300, friction: 30 }
|
|
49
|
-
}
|
|
54
|
+
};
|
|
55
|
+
if (countChanged) springBase.from = { points: targetPoints };
|
|
56
|
+
const spring = useSpring(springBase);
|
|
50
57
|
const hasCompareData = Object.keys(compareData).length > 0;
|
|
51
58
|
const centerPoints = spokes.map(() => `${centerX},${centerY}`).join(" ");
|
|
52
59
|
let comparePoints = null;
|
|
53
60
|
let compareCoords = null;
|
|
54
61
|
if (hasCompareData) {
|
|
55
62
|
const ccoords = spokes.map(([shortTitle], index) => {
|
|
56
|
-
var
|
|
57
|
-
const value = (
|
|
58
|
-
const
|
|
59
|
-
const max = ((_b = topic == null ? void 0 : topic.stances) == null ? void 0 : _b.length) || 10;
|
|
60
|
-
const pct = Math.min(value / max * 10, 10);
|
|
61
|
-
const angle = 2 * Math.PI * index / numSpokes;
|
|
62
|
-
const adjusted = value === 0 ? 0 : invertedSpokes[shortTitle] ? 11 - pct : pct;
|
|
63
|
+
var _a2;
|
|
64
|
+
const value = (_a2 = compareData[shortTitle]) != null ? _a2 : 0;
|
|
65
|
+
const adjusted = calcAdjusted(value, shortTitle);
|
|
63
66
|
const r = adjusted / 10 * radius;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
return [x, y];
|
|
67
|
+
const angle = 2 * Math.PI * index / numSpokes;
|
|
68
|
+
return [centerX + r * Math.sin(angle), centerY - r * Math.cos(angle)];
|
|
67
69
|
});
|
|
68
70
|
compareCoords = ccoords;
|
|
69
71
|
comparePoints = ccoords.map((p) => p.join(",")).join(" ");
|
|
@@ -73,21 +75,18 @@ function RadarChartCore({
|
|
|
73
75
|
useEffect(() => {
|
|
74
76
|
hadCompareDataRef.current = hasCompareData;
|
|
75
77
|
}, [hasCompareData]);
|
|
76
|
-
const
|
|
78
|
+
const compareSpringBase = {
|
|
77
79
|
to: { points: comparePoints || centerPoints || `${centerX},${centerY}` },
|
|
78
80
|
immediate: countChanged || compareJustAppeared,
|
|
79
|
-
reset: countChanged || compareJustAppeared,
|
|
80
81
|
config: { tension: 300, friction: 30 }
|
|
81
|
-
}
|
|
82
|
+
};
|
|
83
|
+
if (countChanged || compareJustAppeared) compareSpringBase.from = compareSpringBase.to;
|
|
84
|
+
const compareSpring = useSpring(compareSpringBase);
|
|
82
85
|
const labelMeta = spokes.map(([shortTitle], i) => {
|
|
83
86
|
const angle = 2 * Math.PI * i / numSpokes;
|
|
84
|
-
const baseFSize = labelFontSize;
|
|
85
87
|
const lines = wrapLabel(shortTitle, 12);
|
|
86
|
-
const longestLineLen = lines.reduce(
|
|
87
|
-
|
|
88
|
-
0
|
|
89
|
-
);
|
|
90
|
-
const fSize = adaptiveFontSize(longestLineLen, baseFSize);
|
|
88
|
+
const longestLineLen = lines.reduce((m, ln) => ln.length > m ? ln.length : m, 0);
|
|
89
|
+
const fSize = adaptiveFontSize(longestLineLen, labelFontSize);
|
|
91
90
|
const estimatedWidth = longestLineLen * fSize * 0.6;
|
|
92
91
|
const sinA = Math.sin(angle);
|
|
93
92
|
const side = sinA < -0.1 ? "left" : sinA > 0.1 ? "right" : "center";
|
|
@@ -98,30 +97,31 @@ function RadarChartCore({
|
|
|
98
97
|
const minPadding = 40;
|
|
99
98
|
const maxPadding = padding * 2;
|
|
100
99
|
const rawLeftPadding = Math.min(
|
|
101
|
-
Math.max(
|
|
102
|
-
leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0,
|
|
103
|
-
minPadding
|
|
104
|
-
),
|
|
100
|
+
Math.max(leftLabelWidths.length ? Math.max(...leftLabelWidths) + labelOffset : 0, minPadding),
|
|
105
101
|
maxPadding
|
|
106
102
|
);
|
|
107
103
|
const rawRightPadding = Math.min(
|
|
108
|
-
Math.max(
|
|
109
|
-
rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0,
|
|
110
|
-
minPadding
|
|
111
|
-
),
|
|
104
|
+
Math.max(rightLabelWidths.length ? Math.max(...rightLabelWidths) + labelOffset : 0, minPadding),
|
|
112
105
|
maxPadding
|
|
113
106
|
);
|
|
114
107
|
const symmetricPadding = Math.max(rawLeftPadding, rawRightPadding);
|
|
115
108
|
const leftPadding = symmetricPadding;
|
|
116
109
|
const rightPadding = symmetricPadding;
|
|
117
110
|
const verticalPadding = padding;
|
|
111
|
+
const spokeExtension = spokes.reduce((acc, [shortTitle]) => {
|
|
112
|
+
const userAdj = calcAdjusted(data[shortTitle], shortTitle);
|
|
113
|
+
const cmpAdj = hasCompareData && compareData[shortTitle] ? calcAdjusted(compareData[shortTitle], shortTitle) : 0;
|
|
114
|
+
acc[shortTitle] = userAdj > 10 || cmpAdj > 10;
|
|
115
|
+
return acc;
|
|
116
|
+
}, {});
|
|
118
117
|
let tightTop = -verticalPadding;
|
|
119
118
|
let tightHeight = size + verticalPadding * 2;
|
|
120
119
|
if (tightFit && numSpokes > 0) {
|
|
121
120
|
let yMin = Infinity, yMax = -Infinity;
|
|
122
121
|
for (let i = 0; i < numSpokes; i++) {
|
|
123
122
|
const angle = 2 * Math.PI * i / numSpokes;
|
|
124
|
-
const
|
|
123
|
+
const effectiveR = spokeExtension[(_a = spokes[i]) == null ? void 0 : _a[0]] ? extRadius : radius;
|
|
124
|
+
const y = centerY - effectiveR * Math.cos(angle);
|
|
125
125
|
if (y < yMin) yMin = y;
|
|
126
126
|
if (y > yMax) yMax = y;
|
|
127
127
|
}
|
|
@@ -135,75 +135,94 @@ function RadarChartCore({
|
|
|
135
135
|
const ring = [];
|
|
136
136
|
for (let i = 0; i < numSpokes; i++) {
|
|
137
137
|
const angle = 2 * Math.PI * i / numSpokes;
|
|
138
|
-
|
|
139
|
-
const y = centerY - radius * scale * Math.cos(angle);
|
|
140
|
-
ring.push(`${x},${y}`);
|
|
138
|
+
ring.push(`${centerX + radius * scale * Math.sin(angle)},${centerY - radius * scale * Math.cos(angle)}`);
|
|
141
139
|
}
|
|
142
140
|
guidePolygons.push(
|
|
143
141
|
/* @__PURE__ */ React.createElement("polygon", { key: level, points: ring.join(" "), fill: "none", stroke: "#ccc" })
|
|
144
142
|
);
|
|
145
143
|
}
|
|
144
|
+
const getStanceText = (shortTitle, value) => {
|
|
145
|
+
if (writeIns[shortTitle]) return writeIns[shortTitle];
|
|
146
|
+
const topic = topics.find((t) => t.short_title === shortTitle);
|
|
147
|
+
const stances = (topic == null ? void 0 : topic.stances) || [];
|
|
148
|
+
const idx = Math.round(Number(value)) - 1;
|
|
149
|
+
if (idx >= 0 && idx < stances.length) {
|
|
150
|
+
return stances[idx].text || shortTitle;
|
|
151
|
+
}
|
|
152
|
+
return shortTitle;
|
|
153
|
+
};
|
|
146
154
|
return /* @__PURE__ */ React.createElement(
|
|
147
155
|
"svg",
|
|
148
156
|
{
|
|
149
157
|
className: "w-full h-auto max-h-full",
|
|
150
158
|
viewBox: `-${leftPadding} ${tightTop} ${size + leftPadding + rightPadding} ${tightHeight}`,
|
|
151
|
-
preserveAspectRatio: "xMidYMid meet"
|
|
159
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
160
|
+
onMouseLeave: () => setTooltip(null)
|
|
152
161
|
},
|
|
153
162
|
guidePolygons,
|
|
154
163
|
spokes.map(([shortTitle], i) => {
|
|
155
164
|
const angle = 2 * Math.PI * i / numSpokes;
|
|
156
|
-
const
|
|
157
|
-
const
|
|
165
|
+
const endX = centerX + radius * Math.sin(angle);
|
|
166
|
+
const endY = centerY - radius * Math.cos(angle);
|
|
158
167
|
const isUnanswered = !!unansweredSpokes[shortTitle];
|
|
159
|
-
|
|
168
|
+
const needsExt = spokeExtension[shortTitle];
|
|
169
|
+
const extX = centerX + extRadius * Math.sin(angle);
|
|
170
|
+
const extY = centerY - extRadius * Math.cos(angle);
|
|
171
|
+
return /* @__PURE__ */ React.createElement("g", { key: `spoke-${shortTitle}` }, /* @__PURE__ */ React.createElement(
|
|
160
172
|
"line",
|
|
161
173
|
{
|
|
162
|
-
key: `line-${shortTitle}`,
|
|
163
174
|
x1: centerX,
|
|
164
175
|
y1: centerY,
|
|
165
|
-
x2:
|
|
166
|
-
y2:
|
|
176
|
+
x2: endX,
|
|
177
|
+
y2: endY,
|
|
167
178
|
stroke: isUnanswered ? "#9ca3af" : "black",
|
|
168
179
|
strokeDasharray: isUnanswered ? "4 3" : void 0,
|
|
169
180
|
opacity: isUnanswered ? 0.6 : 1
|
|
170
181
|
}
|
|
171
|
-
)
|
|
182
|
+
), needsExt && /* @__PURE__ */ React.createElement(
|
|
183
|
+
"line",
|
|
184
|
+
{
|
|
185
|
+
x1: endX,
|
|
186
|
+
y1: endY,
|
|
187
|
+
x2: extX,
|
|
188
|
+
y2: extY,
|
|
189
|
+
stroke: "black",
|
|
190
|
+
strokeDasharray: "4 4",
|
|
191
|
+
opacity: 0.4
|
|
192
|
+
}
|
|
193
|
+
));
|
|
172
194
|
}),
|
|
173
195
|
spokes.map(([shortTitle], i) => {
|
|
174
196
|
const angle = 2 * Math.PI * i / numSpokes;
|
|
175
|
-
const anchor = angle > Math.PI ? "end" : angle < Math.PI && angle > 0 ? "start" : "middle";
|
|
176
197
|
const cosA = Math.cos(angle);
|
|
177
|
-
const isTop = cosA > 0.5;
|
|
178
198
|
const isBottom = cosA < -0.5;
|
|
179
|
-
const
|
|
199
|
+
const isTop = cosA > 0.5;
|
|
180
200
|
const lines = wrapLabel(shortTitle, 12);
|
|
181
|
-
const longestLine = lines.reduce(
|
|
182
|
-
|
|
183
|
-
0
|
|
184
|
-
);
|
|
185
|
-
const fSize = adaptiveFontSize(longestLine, baseFSize);
|
|
201
|
+
const longestLine = lines.reduce((m, ln) => ln.length > m ? ln.length : m, 0);
|
|
202
|
+
const fSize = adaptiveFontSize(longestLine, labelFontSize);
|
|
186
203
|
const lineHeight = fSize * 1.1;
|
|
187
204
|
const dynamicLabelOffset = lines.length > 1 ? labelOffset + 8 : labelOffset;
|
|
188
205
|
const offset2 = radius + dynamicLabelOffset;
|
|
189
206
|
const labelX = centerX + offset2 * Math.sin(angle);
|
|
190
207
|
let labelY = centerY - offset2 * Math.cos(angle);
|
|
191
|
-
if (isTop && lines.length > 1)
|
|
192
|
-
|
|
193
|
-
}
|
|
208
|
+
if (isTop && lines.length > 1) labelY -= (lines.length - 1) * lineHeight;
|
|
209
|
+
const isActive = shortTitle === activeSpoke;
|
|
194
210
|
const isUnansweredLabel = !!unansweredSpokes[shortTitle];
|
|
211
|
+
const activeFSize = fSize * 1.5;
|
|
212
|
+
const displayFSize = isActive ? activeFSize : fSize;
|
|
195
213
|
return /* @__PURE__ */ React.createElement(
|
|
196
214
|
"text",
|
|
197
215
|
{
|
|
198
216
|
key: `label-${shortTitle}`,
|
|
199
217
|
x: labelX,
|
|
200
218
|
y: labelY,
|
|
201
|
-
textAnchor:
|
|
219
|
+
textAnchor: "middle",
|
|
202
220
|
dominantBaseline: isBottom ? "hanging" : "auto",
|
|
203
221
|
onClick: () => onReplaceTopic(shortTitle),
|
|
204
222
|
className: "text-xl font-medium mb-1 md:text-base md:font-normal",
|
|
205
|
-
fill: isUnansweredLabel ? "#9ca3af" : void 0,
|
|
206
|
-
|
|
223
|
+
fill: isActive ? "#E85D26" : isUnansweredLabel ? "#9ca3af" : void 0,
|
|
224
|
+
fontWeight: isActive ? "bold" : void 0,
|
|
225
|
+
style: { cursor: "pointer", userSelect: "none", fontSize: displayFSize }
|
|
207
226
|
},
|
|
208
227
|
isUnansweredLabel && /* @__PURE__ */ React.createElement("title", null, "No stance on file for this topic."),
|
|
209
228
|
lines.map((ln, idx) => /* @__PURE__ */ React.createElement("tspan", { key: idx, x: labelX, dy: idx === 0 ? "0" : `${lineHeight}` }, ln))
|
|
@@ -214,22 +233,14 @@ function RadarChartCore({
|
|
|
214
233
|
{
|
|
215
234
|
key: "user-static",
|
|
216
235
|
points: targetPoints,
|
|
217
|
-
style: {
|
|
218
|
-
fill: "rgba(124, 107, 158, 0.4)",
|
|
219
|
-
stroke: "#7C6B9E",
|
|
220
|
-
strokeWidth: 3
|
|
221
|
-
}
|
|
236
|
+
style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
|
|
222
237
|
}
|
|
223
238
|
) : /* @__PURE__ */ React.createElement(
|
|
224
239
|
animated.polygon,
|
|
225
240
|
{
|
|
226
241
|
key: "user-animated",
|
|
227
242
|
points: spring.points,
|
|
228
|
-
style: {
|
|
229
|
-
fill: "rgba(124, 107, 158, 0.4)",
|
|
230
|
-
stroke: "#7C6B9E",
|
|
231
|
-
strokeWidth: 3
|
|
232
|
-
}
|
|
243
|
+
style: { fill: "rgba(124, 107, 158, 0.4)", stroke: "#7C6B9E", strokeWidth: 3 }
|
|
233
244
|
}
|
|
234
245
|
),
|
|
235
246
|
pointsArr.map(([cx, cy], i) => {
|
|
@@ -247,7 +258,10 @@ function RadarChartCore({
|
|
|
247
258
|
r: matches ? 8 : 7,
|
|
248
259
|
fill: matches ? "#fed12e" : "#7C6B9E",
|
|
249
260
|
stroke: "#FFFFFF",
|
|
250
|
-
strokeWidth: 3
|
|
261
|
+
strokeWidth: 3,
|
|
262
|
+
style: { cursor: "default" },
|
|
263
|
+
onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, userVal) }),
|
|
264
|
+
onMouseLeave: () => setTooltip(null)
|
|
251
265
|
}
|
|
252
266
|
);
|
|
253
267
|
}),
|
|
@@ -256,22 +270,14 @@ function RadarChartCore({
|
|
|
256
270
|
{
|
|
257
271
|
key: "compare-static",
|
|
258
272
|
points: comparePoints,
|
|
259
|
-
style: {
|
|
260
|
-
fill: "rgba(90, 154, 110, 0.3)",
|
|
261
|
-
stroke: "#5A9A6E",
|
|
262
|
-
strokeWidth: 2
|
|
263
|
-
}
|
|
273
|
+
style: { fill: "rgba(90, 154, 110, 0.3)", stroke: "#5A9A6E", strokeWidth: 2 }
|
|
264
274
|
}
|
|
265
275
|
) : /* @__PURE__ */ React.createElement(
|
|
266
276
|
animated.polygon,
|
|
267
277
|
{
|
|
268
278
|
key: "compare-animated",
|
|
269
279
|
points: compareSpring.points,
|
|
270
|
-
style: {
|
|
271
|
-
fill: "rgba(90, 154, 110, 0.3)",
|
|
272
|
-
stroke: "#5A9A6E",
|
|
273
|
-
strokeWidth: 2
|
|
274
|
-
}
|
|
280
|
+
style: { fill: "rgba(90, 154, 110, 0.3)", stroke: "#5A9A6E", strokeWidth: 2 }
|
|
275
281
|
}
|
|
276
282
|
) : null,
|
|
277
283
|
hasCompareData && compareCoords && compareCoords.map(([cx, cy], i) => {
|
|
@@ -279,7 +285,7 @@ function RadarChartCore({
|
|
|
279
285
|
const userVal = spokes[i][1];
|
|
280
286
|
const compareVal = compareData[shortTitle];
|
|
281
287
|
if (!compareVal || Number(compareVal) === 0) return null;
|
|
282
|
-
const matches = userVal !== 0 && compareVal
|
|
288
|
+
const matches = userVal !== 0 && compareVal != null && Number(userVal) === Number(compareVal);
|
|
283
289
|
return /* @__PURE__ */ React.createElement(
|
|
284
290
|
"circle",
|
|
285
291
|
{
|
|
@@ -289,14 +295,17 @@ function RadarChartCore({
|
|
|
289
295
|
r: matches ? 8 : 7,
|
|
290
296
|
fill: matches ? "#fed12e" : "#5A9A6E",
|
|
291
297
|
stroke: "#FFFFFF",
|
|
292
|
-
strokeWidth: 3
|
|
298
|
+
strokeWidth: 3,
|
|
299
|
+
style: { cursor: "default" },
|
|
300
|
+
onMouseEnter: () => setTooltip({ x: cx, y: cy, text: getStanceText(shortTitle, compareVal) }),
|
|
301
|
+
onMouseLeave: () => setTooltip(null)
|
|
293
302
|
}
|
|
294
303
|
);
|
|
295
304
|
}),
|
|
296
305
|
spokes.map(([shortTitle], i) => {
|
|
297
306
|
const angle = 2 * Math.PI * i / numSpokes;
|
|
298
|
-
const
|
|
299
|
-
const
|
|
307
|
+
const endX = centerX + (spokeExtension[shortTitle] ? extRadius : radius) * Math.sin(angle);
|
|
308
|
+
const endY = centerY - (spokeExtension[shortTitle] ? extRadius : radius) * Math.cos(angle);
|
|
300
309
|
const isUnansweredHitbox = !!unansweredSpokes[shortTitle];
|
|
301
310
|
return /* @__PURE__ */ React.createElement(
|
|
302
311
|
"line",
|
|
@@ -304,15 +313,51 @@ function RadarChartCore({
|
|
|
304
313
|
key: `hitbox-${shortTitle}`,
|
|
305
314
|
x1: centerX,
|
|
306
315
|
y1: centerY,
|
|
307
|
-
x2:
|
|
308
|
-
y2:
|
|
316
|
+
x2: endX,
|
|
317
|
+
y2: endY,
|
|
309
318
|
stroke: "transparent",
|
|
310
319
|
strokeWidth: 14,
|
|
311
320
|
onClick: () => isUnansweredHitbox ? onReplaceTopic(shortTitle) : onToggleInversion(shortTitle),
|
|
312
321
|
style: { cursor: "pointer" }
|
|
313
322
|
}
|
|
314
323
|
);
|
|
315
|
-
})
|
|
324
|
+
}),
|
|
325
|
+
tooltip && (() => {
|
|
326
|
+
const tipW = 190;
|
|
327
|
+
const tipH = 70;
|
|
328
|
+
const svgW = size + leftPadding + rightPadding;
|
|
329
|
+
const tx = tooltip.x + leftPadding + 12 > svgW - tipW ? tooltip.x - tipW - 12 : tooltip.x + 12;
|
|
330
|
+
const ty = tooltip.y - 10;
|
|
331
|
+
return /* @__PURE__ */ React.createElement(
|
|
332
|
+
"foreignObject",
|
|
333
|
+
{
|
|
334
|
+
x: tx,
|
|
335
|
+
y: ty,
|
|
336
|
+
width: tipW,
|
|
337
|
+
height: tipH,
|
|
338
|
+
style: { overflow: "visible", pointerEvents: "none" }
|
|
339
|
+
},
|
|
340
|
+
/* @__PURE__ */ React.createElement(
|
|
341
|
+
"div",
|
|
342
|
+
{
|
|
343
|
+
xmlns: "http://www.w3.org/1999/xhtml",
|
|
344
|
+
style: {
|
|
345
|
+
background: "white",
|
|
346
|
+
border: "1px solid #d1d5db",
|
|
347
|
+
borderRadius: 6,
|
|
348
|
+
padding: "6px 10px",
|
|
349
|
+
fontSize: 11,
|
|
350
|
+
lineHeight: 1.4,
|
|
351
|
+
color: "#111",
|
|
352
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
|
|
353
|
+
width: tipW + "px",
|
|
354
|
+
wordWrap: "break-word"
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
tooltip.text
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
})()
|
|
316
361
|
);
|
|
317
362
|
}
|
|
318
363
|
function adaptiveFontSize(longestLineLen, baseFSize) {
|
|
@@ -351,7 +396,7 @@ function wrapLabel(label, maxChars = 12) {
|
|
|
351
396
|
}
|
|
352
397
|
|
|
353
398
|
// src/Header.jsx
|
|
354
|
-
import React2, { useState as
|
|
399
|
+
import React2, { useState as useState3, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
355
400
|
|
|
356
401
|
// src/tokens.js
|
|
357
402
|
var colorScales = {
|
|
@@ -747,9 +792,9 @@ var breakpoints = {
|
|
|
747
792
|
};
|
|
748
793
|
|
|
749
794
|
// src/useMediaQuery.js
|
|
750
|
-
import { useState, useEffect as useEffect2 } from "react";
|
|
795
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
751
796
|
function useMediaQuery(query) {
|
|
752
|
-
const [matches, setMatches] =
|
|
797
|
+
const [matches, setMatches] = useState2(() => {
|
|
753
798
|
if (typeof window === "undefined") return false;
|
|
754
799
|
return window.matchMedia(query).matches;
|
|
755
800
|
});
|
|
@@ -768,6 +813,7 @@ function useMediaQuery(query) {
|
|
|
768
813
|
function Header({
|
|
769
814
|
logoSrc,
|
|
770
815
|
logoAlt = "Empowered Vote",
|
|
816
|
+
logoHref = "https://login.empowered.vote/profile",
|
|
771
817
|
navItems = [],
|
|
772
818
|
ctaButton,
|
|
773
819
|
secondaryAction,
|
|
@@ -776,9 +822,9 @@ function Header({
|
|
|
776
822
|
profileMenu,
|
|
777
823
|
style = {}
|
|
778
824
|
}) {
|
|
779
|
-
const [mobileMenuOpen, setMobileMenuOpen] =
|
|
780
|
-
const [openDropdown, setOpenDropdown] =
|
|
781
|
-
const [profileOpen, setProfileOpen] =
|
|
825
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
|
|
826
|
+
const [openDropdown, setOpenDropdown] = useState3(null);
|
|
827
|
+
const [profileOpen, setProfileOpen] = useState3(false);
|
|
782
828
|
const isMobile = useMediaQuery("(max-width: 768px)");
|
|
783
829
|
const profileRef = useRef2(null);
|
|
784
830
|
useEffect3(() => {
|
|
@@ -1051,8 +1097,8 @@ function Header({
|
|
|
1051
1097
|
return /* @__PURE__ */ React2.createElement("header", { style: styles2.header }, /* @__PURE__ */ React2.createElement("div", { style: styles2.container }, /* @__PURE__ */ React2.createElement(
|
|
1052
1098
|
"a",
|
|
1053
1099
|
{
|
|
1054
|
-
href:
|
|
1055
|
-
onClick: (e) => handleNavClick(e,
|
|
1100
|
+
href: logoHref,
|
|
1101
|
+
onClick: (e) => handleNavClick(e, logoHref),
|
|
1056
1102
|
style: { display: "flex", alignItems: "center" }
|
|
1057
1103
|
},
|
|
1058
1104
|
logoSrc ? /* @__PURE__ */ React2.createElement("img", { src: logoSrc, alt: logoAlt, style: styles2.logo }) : /* @__PURE__ */ React2.createElement(
|
|
@@ -1692,7 +1738,7 @@ function FilterSidebar({
|
|
|
1692
1738
|
}
|
|
1693
1739
|
|
|
1694
1740
|
// src/PoliticianCard.jsx
|
|
1695
|
-
import React6, { useState as
|
|
1741
|
+
import React6, { useState as useState4, useEffect as useEffect4 } from "react";
|
|
1696
1742
|
function PoliticianCard({
|
|
1697
1743
|
id,
|
|
1698
1744
|
imageSrc,
|
|
@@ -1887,7 +1933,7 @@ function PoliticianCard({
|
|
|
1887
1933
|
}
|
|
1888
1934
|
)
|
|
1889
1935
|
);
|
|
1890
|
-
const [imgError, setImgError] =
|
|
1936
|
+
const [imgError, setImgError] = useState4(false);
|
|
1891
1937
|
useEffect4(() => {
|
|
1892
1938
|
setImgError(false);
|
|
1893
1939
|
}, [imageSrc]);
|
|
@@ -1954,7 +2000,7 @@ function PoliticianCard({
|
|
|
1954
2000
|
}
|
|
1955
2001
|
|
|
1956
2002
|
// src/CompassCardHorizontal.jsx
|
|
1957
|
-
import React11, { useState as
|
|
2003
|
+
import React11, { useState as useState6, useEffect as useEffect5 } from "react";
|
|
1958
2004
|
|
|
1959
2005
|
// src/PlaceholderRadar.jsx
|
|
1960
2006
|
import React7 from "react";
|
|
@@ -2037,7 +2083,7 @@ function PlaceholderRadar({ size = 250, name = "" }) {
|
|
|
2037
2083
|
import React10 from "react";
|
|
2038
2084
|
|
|
2039
2085
|
// src/IconOverlay.jsx
|
|
2040
|
-
import React9, { useState as
|
|
2086
|
+
import React9, { useState as useState5 } from "react";
|
|
2041
2087
|
import {
|
|
2042
2088
|
useFloating,
|
|
2043
2089
|
useHover,
|
|
@@ -2126,7 +2172,7 @@ function BranchIcon({ size = 16, color = "currentColor", branch }) {
|
|
|
2126
2172
|
|
|
2127
2173
|
// src/IconOverlay.jsx
|
|
2128
2174
|
function IconWithTooltip({ IconComponent, color, tooltip, size = 14, extraProps = {}, onClick }) {
|
|
2129
|
-
const [isOpen, setIsOpen] =
|
|
2175
|
+
const [isOpen, setIsOpen] = useState5(false);
|
|
2130
2176
|
const { refs, floatingStyles, context } = useFloating({
|
|
2131
2177
|
open: isOpen,
|
|
2132
2178
|
onOpenChange: setIsOpen,
|
|
@@ -2316,10 +2362,10 @@ function CompassCardHorizontal({
|
|
|
2316
2362
|
onClick
|
|
2317
2363
|
}) {
|
|
2318
2364
|
var _a;
|
|
2319
|
-
const [hovered, setHovered] =
|
|
2320
|
-
const [focused, setFocused] =
|
|
2321
|
-
const [imgError, setImgError] =
|
|
2322
|
-
const [emptyCtaHovered, setEmptyCtaHovered] =
|
|
2365
|
+
const [hovered, setHovered] = useState6(false);
|
|
2366
|
+
const [focused, setFocused] = useState6(false);
|
|
2367
|
+
const [imgError, setImgError] = useState6(false);
|
|
2368
|
+
const [emptyCtaHovered, setEmptyCtaHovered] = useState6(false);
|
|
2323
2369
|
useEffect5(() => {
|
|
2324
2370
|
setImgError(false);
|
|
2325
2371
|
}, [politician == null ? void 0 : politician.photo_origin_url]);
|
|
@@ -2569,7 +2615,7 @@ function CompassCardHorizontal({
|
|
|
2569
2615
|
}
|
|
2570
2616
|
|
|
2571
2617
|
// src/CompassCardVertical.jsx
|
|
2572
|
-
import React12, { useState as
|
|
2618
|
+
import React12, { useState as useState7, useEffect as useEffect6 } from "react";
|
|
2573
2619
|
var RADAR_SIZE2 = 400;
|
|
2574
2620
|
function CompassCardVertical({
|
|
2575
2621
|
politician,
|
|
@@ -2583,10 +2629,10 @@ function CompassCardVertical({
|
|
|
2583
2629
|
onClick
|
|
2584
2630
|
}) {
|
|
2585
2631
|
var _a;
|
|
2586
|
-
const [hovered, setHovered] =
|
|
2587
|
-
const [focused, setFocused] =
|
|
2588
|
-
const [imgError, setImgError] =
|
|
2589
|
-
const [emptyCtaHovered, setEmptyCtaHovered] =
|
|
2632
|
+
const [hovered, setHovered] = useState7(false);
|
|
2633
|
+
const [focused, setFocused] = useState7(false);
|
|
2634
|
+
const [imgError, setImgError] = useState7(false);
|
|
2635
|
+
const [emptyCtaHovered, setEmptyCtaHovered] = useState7(false);
|
|
2590
2636
|
useEffect6(() => {
|
|
2591
2637
|
setImgError(false);
|
|
2592
2638
|
}, [politician == null ? void 0 : politician.photo_origin_url]);
|
|
@@ -2921,7 +2967,7 @@ function CompassCardVertical({
|
|
|
2921
2967
|
}
|
|
2922
2968
|
|
|
2923
2969
|
// src/CompassKey.jsx
|
|
2924
|
-
import React13, { useEffect as useEffect7, useState as
|
|
2970
|
+
import React13, { useEffect as useEffect7, useState as useState8 } from "react";
|
|
2925
2971
|
|
|
2926
2972
|
// src/evContext.js
|
|
2927
2973
|
var DEFAULT_BROKER_URL = "https://ev-context.empowered.vote/";
|
|
@@ -3166,8 +3212,8 @@ var evContext = {
|
|
|
3166
3212
|
|
|
3167
3213
|
// src/CompassKey.jsx
|
|
3168
3214
|
function CompassKey({ compact = false } = {}) {
|
|
3169
|
-
const [dismissed, setDismissed] =
|
|
3170
|
-
const [loaded, setLoaded] =
|
|
3215
|
+
const [dismissed, setDismissed] = useState8(false);
|
|
3216
|
+
const [loaded, setLoaded] = useState8(false);
|
|
3171
3217
|
useEffect7(() => {
|
|
3172
3218
|
let cancelled = false;
|
|
3173
3219
|
evContext.get().then((shared) => {
|
|
@@ -3294,7 +3340,7 @@ function CompassKey({ compact = false } = {}) {
|
|
|
3294
3340
|
}
|
|
3295
3341
|
|
|
3296
3342
|
// src/CategorySection.jsx
|
|
3297
|
-
import React14, { useState as
|
|
3343
|
+
import React14, { useState as useState9 } from "react";
|
|
3298
3344
|
function CategorySection({
|
|
3299
3345
|
title,
|
|
3300
3346
|
infoTooltip,
|
|
@@ -3304,7 +3350,7 @@ function CategorySection({
|
|
|
3304
3350
|
tier
|
|
3305
3351
|
}) {
|
|
3306
3352
|
var _a, _b;
|
|
3307
|
-
const [showTooltip, setShowTooltip] =
|
|
3353
|
+
const [showTooltip, setShowTooltip] = useState9(false);
|
|
3308
3354
|
const tierStyle = tier ? tierColors[tier] : null;
|
|
3309
3355
|
const styles2 = {
|
|
3310
3356
|
section: {
|
|
@@ -3465,7 +3511,7 @@ function SubGroupSection({ title, websiteUrl, children, gridTemplateColumns, gap
|
|
|
3465
3511
|
}
|
|
3466
3512
|
|
|
3467
3513
|
// src/GovernmentBodySection.jsx
|
|
3468
|
-
import React16, { useState as
|
|
3514
|
+
import React16, { useState as useState10 } from "react";
|
|
3469
3515
|
function GovernmentBodySection({
|
|
3470
3516
|
title,
|
|
3471
3517
|
websiteUrl,
|
|
@@ -3474,7 +3520,7 @@ function GovernmentBodySection({
|
|
|
3474
3520
|
children
|
|
3475
3521
|
}) {
|
|
3476
3522
|
var _a;
|
|
3477
|
-
const [expanded, setExpanded] =
|
|
3523
|
+
const [expanded, setExpanded] = useState10(defaultExpanded);
|
|
3478
3524
|
const tierStyle = tier ? tierColors[tier] : null;
|
|
3479
3525
|
const accentColor = (_a = tierStyle == null ? void 0 : tierStyle.accent) != null ? _a : colors.borderMedium;
|
|
3480
3526
|
const styles2 = {
|
|
@@ -3803,7 +3849,7 @@ function IssueTags({
|
|
|
3803
3849
|
}
|
|
3804
3850
|
|
|
3805
3851
|
// src/CommitteeTable.jsx
|
|
3806
|
-
import React19, { useState as
|
|
3852
|
+
import React19, { useState as useState11 } from "react";
|
|
3807
3853
|
var ROLE_ORDER = {
|
|
3808
3854
|
"chair": 0,
|
|
3809
3855
|
"co-chair": 1,
|
|
@@ -3830,7 +3876,7 @@ function CommitteeTable({
|
|
|
3830
3876
|
maxVisible = 6,
|
|
3831
3877
|
style = {}
|
|
3832
3878
|
}) {
|
|
3833
|
-
const [showAll, setShowAll] =
|
|
3879
|
+
const [showAll, setShowAll] = useState11(false);
|
|
3834
3880
|
if (committees.length === 0) {
|
|
3835
3881
|
return null;
|
|
3836
3882
|
}
|
|
@@ -3934,7 +3980,7 @@ function CommitteeTable({
|
|
|
3934
3980
|
}
|
|
3935
3981
|
|
|
3936
3982
|
// src/PoliticianProfile.jsx
|
|
3937
|
-
import React22, { useState as
|
|
3983
|
+
import React22, { useState as useState12, useEffect as useEffect8 } from "react";
|
|
3938
3984
|
|
|
3939
3985
|
// src/LegislativeInlineSummary.jsx
|
|
3940
3986
|
import React20 from "react";
|
|
@@ -4377,7 +4423,7 @@ function PoliticianProfile({
|
|
|
4377
4423
|
return pol.photo_origin_url;
|
|
4378
4424
|
})();
|
|
4379
4425
|
const initials = [pol.first_name, pol.last_name].filter(Boolean).map((n) => n[0]).join("");
|
|
4380
|
-
const [imgError, setImgError] =
|
|
4426
|
+
const [imgError, setImgError] = useState12(false);
|
|
4381
4427
|
useEffect8(() => {
|
|
4382
4428
|
setImgError(false);
|
|
4383
4429
|
}, [profileImageUrl]);
|
|
@@ -4787,7 +4833,7 @@ function PoliticianProfile({
|
|
|
4787
4833
|
}
|
|
4788
4834
|
|
|
4789
4835
|
// src/LegislativeRecord.jsx
|
|
4790
|
-
import React23, { useState as
|
|
4836
|
+
import React23, { useState as useState13 } from "react";
|
|
4791
4837
|
var DEFAULT_LIMIT = 25;
|
|
4792
4838
|
function normalizeText(str) {
|
|
4793
4839
|
if (!str) return "";
|
|
@@ -4966,7 +5012,7 @@ function Spinner() {
|
|
|
4966
5012
|
} }), /* @__PURE__ */ React23.createElement("style", null, `@keyframes ev-spin { to { transform: rotate(360deg); } }`));
|
|
4967
5013
|
}
|
|
4968
5014
|
function CommitteesSection({ committees, leadership }) {
|
|
4969
|
-
const [showAll, setShowAll] =
|
|
5015
|
+
const [showAll, setShowAll] = useState13(false);
|
|
4970
5016
|
const visible = showAll ? committees : committees.slice(0, DEFAULT_LIMIT);
|
|
4971
5017
|
return /* @__PURE__ */ React23.createElement("div", { style: { marginBottom: spacing[8] } }, /* @__PURE__ */ React23.createElement(SectionHeader, { title: "Committees & Leadership" }), leadership && leadership.length > 0 && /* @__PURE__ */ React23.createElement("div", { style: { display: "flex", flexWrap: "wrap", gap: spacing[2], marginBottom: spacing[4] } }, leadership.map((role, i) => /* @__PURE__ */ React23.createElement("div", { key: i, style: {
|
|
4972
5018
|
display: "inline-flex",
|
|
@@ -5006,8 +5052,8 @@ function CommitteesSection({ committees, leadership }) {
|
|
|
5006
5052
|
}
|
|
5007
5053
|
function LegislationSection({ bills, isMobile }) {
|
|
5008
5054
|
const years = getYears(bills, "introduced_at");
|
|
5009
|
-
const [yearFilter, setYearFilter] =
|
|
5010
|
-
const [showAll, setShowAll] =
|
|
5055
|
+
const [yearFilter, setYearFilter] = useState13("All");
|
|
5056
|
+
const [showAll, setShowAll] = useState13(false);
|
|
5011
5057
|
const handleYearChange = (y) => {
|
|
5012
5058
|
setYearFilter(y);
|
|
5013
5059
|
setShowAll(false);
|
|
@@ -5068,8 +5114,8 @@ function LegislationSection({ bills, isMobile }) {
|
|
|
5068
5114
|
}
|
|
5069
5115
|
function VotingSection({ votes, isMobile }) {
|
|
5070
5116
|
const years = getYears(votes, "vote_date");
|
|
5071
|
-
const [yearFilter, setYearFilter] =
|
|
5072
|
-
const [showAll, setShowAll] =
|
|
5117
|
+
const [yearFilter, setYearFilter] = useState13("All");
|
|
5118
|
+
const [showAll, setShowAll] = useState13(false);
|
|
5073
5119
|
const handleYearChange = (y) => {
|
|
5074
5120
|
setYearFilter(y);
|
|
5075
5121
|
setShowAll(false);
|
|
@@ -5281,7 +5327,7 @@ function JudicialRecordDetail({ politician = {}, judicialRecord, onBack }) {
|
|
|
5281
5327
|
}
|
|
5282
5328
|
|
|
5283
5329
|
// src/AuthForm.jsx
|
|
5284
|
-
import React25, { useState as
|
|
5330
|
+
import React25, { useState as useState14 } from "react";
|
|
5285
5331
|
function AuthForm({
|
|
5286
5332
|
logoSrc = "/EVLogo.svg",
|
|
5287
5333
|
appName = "Empowered Vote",
|
|
@@ -5292,11 +5338,11 @@ function AuthForm({
|
|
|
5292
5338
|
error = null,
|
|
5293
5339
|
submitting = false
|
|
5294
5340
|
}) {
|
|
5295
|
-
const [username, setUsername] =
|
|
5296
|
-
const [password, setPassword] =
|
|
5297
|
-
const [confirmPassword, setConfirmPassword] =
|
|
5298
|
-
const [showPasswords, setShowPasswords] =
|
|
5299
|
-
const [passwordMismatch, setPasswordMismatch] =
|
|
5341
|
+
const [username, setUsername] = useState14("");
|
|
5342
|
+
const [password, setPassword] = useState14("");
|
|
5343
|
+
const [confirmPassword, setConfirmPassword] = useState14("");
|
|
5344
|
+
const [showPasswords, setShowPasswords] = useState14(false);
|
|
5345
|
+
const [passwordMismatch, setPasswordMismatch] = useState14(false);
|
|
5300
5346
|
const isLogin = mode === "login";
|
|
5301
5347
|
const handleSubmit = (e) => {
|
|
5302
5348
|
e.preventDefault();
|
|
@@ -5744,7 +5790,7 @@ function TopicTierBadge({
|
|
|
5744
5790
|
}
|
|
5745
5791
|
|
|
5746
5792
|
// src/CompassCoverageCallout.jsx
|
|
5747
|
-
import React27, { useState as
|
|
5793
|
+
import React27, { useState as useState15 } from "react";
|
|
5748
5794
|
var TIER_LABELS = {
|
|
5749
5795
|
federal: "federal",
|
|
5750
5796
|
state: "state",
|
|
@@ -5757,7 +5803,7 @@ function CompassCoverageCallout({
|
|
|
5757
5803
|
compassUrl,
|
|
5758
5804
|
onDismiss
|
|
5759
5805
|
}) {
|
|
5760
|
-
const [dismissed, setDismissed] =
|
|
5806
|
+
const [dismissed, setDismissed] = useState15(false);
|
|
5761
5807
|
if (dismissed) return null;
|
|
5762
5808
|
const tierLabel = TIER_LABELS[tier] || tier;
|
|
5763
5809
|
const tierStyle = tierColors[tier] || tierColors.federal;
|
|
@@ -5897,7 +5943,7 @@ function computeTierCoverage(topics) {
|
|
|
5897
5943
|
}
|
|
5898
5944
|
|
|
5899
5945
|
// src/useEvContextPromotion.js
|
|
5900
|
-
import { useEffect as useEffect9, useState as
|
|
5946
|
+
import { useEffect as useEffect9, useState as useState16, useCallback, useRef as useRef3 } from "react";
|
|
5901
5947
|
function useEvContextPromotion({
|
|
5902
5948
|
domain,
|
|
5903
5949
|
isLoggedIn,
|
|
@@ -5906,10 +5952,10 @@ function useEvContextPromotion({
|
|
|
5906
5952
|
apiWriter,
|
|
5907
5953
|
enabled = true
|
|
5908
5954
|
}) {
|
|
5909
|
-
const [shouldPrompt, setShouldPrompt] =
|
|
5910
|
-
const [payload, setPayload] =
|
|
5911
|
-
const [status, setStatus] =
|
|
5912
|
-
const [error, setError] =
|
|
5955
|
+
const [shouldPrompt, setShouldPrompt] = useState16(false);
|
|
5956
|
+
const [payload, setPayload] = useState16(null);
|
|
5957
|
+
const [status, setStatus] = useState16("idle");
|
|
5958
|
+
const [error, setError] = useState16(null);
|
|
5913
5959
|
const apiDataRef = useRef3(apiData);
|
|
5914
5960
|
apiDataRef.current = apiData;
|
|
5915
5961
|
const userIdRef = useRef3(userId);
|
|
@@ -6056,7 +6102,7 @@ function isGuestPopulated(domain, fullEvContext) {
|
|
|
6056
6102
|
}
|
|
6057
6103
|
|
|
6058
6104
|
// src/StanceAccordion.jsx
|
|
6059
|
-
import React30, { useState as
|
|
6105
|
+
import React30, { useState as useState17, useRef as useRef4, useCallback as useCallback2, useEffect as useEffect10 } from "react";
|
|
6060
6106
|
|
|
6061
6107
|
// src/Favicon.jsx
|
|
6062
6108
|
import React29 from "react";
|
|
@@ -6117,9 +6163,9 @@ function StanceAccordion({
|
|
|
6117
6163
|
apiUrl = "https://api.empowered.vote"
|
|
6118
6164
|
}) {
|
|
6119
6165
|
var _a;
|
|
6120
|
-
const [expandedTopicId, setExpandedTopicId] =
|
|
6121
|
-
const [loadingId, setLoadingId] =
|
|
6122
|
-
const [showAll, setShowAll] =
|
|
6166
|
+
const [expandedTopicId, setExpandedTopicId] = useState17(null);
|
|
6167
|
+
const [loadingId, setLoadingId] = useState17(null);
|
|
6168
|
+
const [showAll, setShowAll] = useState17(false);
|
|
6123
6169
|
const contextCache = useRef4(/* @__PURE__ */ new Map());
|
|
6124
6170
|
const quotesCache = useRef4(null);
|
|
6125
6171
|
const polAnswerMap = {};
|