@founderhq/journeys 0.3.64 → 0.3.66

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 CHANGED
@@ -1172,19 +1172,225 @@ var renderTemplate = (template, variables, fallback) => {
1172
1172
  };
1173
1173
 
1174
1174
  // src/blocks/resolve-template.ts
1175
- function resolveTemplate(template, answers) {
1175
+ var VALID_IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1176
+ var IDENTIFIER_TOKEN_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
1177
+ var RESERVED_IDENTIFIERS = /* @__PURE__ */ new Set([
1178
+ "arguments",
1179
+ "await",
1180
+ "break",
1181
+ "case",
1182
+ "catch",
1183
+ "class",
1184
+ "const",
1185
+ "continue",
1186
+ "debugger",
1187
+ "default",
1188
+ "delete",
1189
+ "do",
1190
+ "else",
1191
+ "enum",
1192
+ "eval",
1193
+ "export",
1194
+ "extends",
1195
+ "false",
1196
+ "finally",
1197
+ "for",
1198
+ "function",
1199
+ "if",
1200
+ "implements",
1201
+ "import",
1202
+ "in",
1203
+ "instanceof",
1204
+ "interface",
1205
+ "let",
1206
+ "new",
1207
+ "null",
1208
+ "package",
1209
+ "private",
1210
+ "protected",
1211
+ "public",
1212
+ "return",
1213
+ "static",
1214
+ "super",
1215
+ "switch",
1216
+ "this",
1217
+ "throw",
1218
+ "true",
1219
+ "try",
1220
+ "typeof",
1221
+ "undefined",
1222
+ "var",
1223
+ "void",
1224
+ "while",
1225
+ "with",
1226
+ "yield"
1227
+ ]);
1228
+ var GLOBAL_IDENTIFIERS = /* @__PURE__ */ new Set([
1229
+ "Array",
1230
+ "BigInt",
1231
+ "Boolean",
1232
+ "Date",
1233
+ "Error",
1234
+ "Infinity",
1235
+ "Intl",
1236
+ "JSON",
1237
+ "Map",
1238
+ "Math",
1239
+ "NaN",
1240
+ "Number",
1241
+ "Object",
1242
+ "Promise",
1243
+ "RegExp",
1244
+ "Set",
1245
+ "String",
1246
+ "Symbol",
1247
+ "URL",
1248
+ "URLSearchParams",
1249
+ "WeakMap",
1250
+ "WeakSet",
1251
+ "console",
1252
+ "decodeURI",
1253
+ "decodeURIComponent",
1254
+ "encodeURI",
1255
+ "encodeURIComponent",
1256
+ "globalThis",
1257
+ "isFinite",
1258
+ "isNaN",
1259
+ "parseFloat",
1260
+ "parseInt"
1261
+ ]);
1262
+ function previousNonWhitespace(value, index) {
1263
+ for (let i = index - 1; i >= 0; i--) {
1264
+ if (!/\s/.test(value[i])) return value[i];
1265
+ }
1266
+ return void 0;
1267
+ }
1268
+ function stripLiteralsAndComments(expression) {
1269
+ let stripped = "";
1270
+ let i = 0;
1271
+ while (i < expression.length) {
1272
+ const char = expression[i];
1273
+ const next = expression[i + 1];
1274
+ if (char === "/" && next === "/") {
1275
+ stripped += " ";
1276
+ i += 2;
1277
+ while (i < expression.length && expression[i] !== "\n") {
1278
+ stripped += " ";
1279
+ i++;
1280
+ }
1281
+ continue;
1282
+ }
1283
+ if (char === "/" && next === "*") {
1284
+ stripped += " ";
1285
+ i += 2;
1286
+ while (i < expression.length) {
1287
+ const end = expression[i] === "*" && expression[i + 1] === "/";
1288
+ stripped += " ";
1289
+ i++;
1290
+ if (end) {
1291
+ stripped += " ";
1292
+ i++;
1293
+ break;
1294
+ }
1295
+ }
1296
+ continue;
1297
+ }
1298
+ if (char === "'" || char === '"' || char === "`") {
1299
+ const quote = char;
1300
+ stripped += " ";
1301
+ i++;
1302
+ while (i < expression.length) {
1303
+ const current = expression[i];
1304
+ stripped += " ";
1305
+ i++;
1306
+ if (current === "\\") {
1307
+ if (i < expression.length) {
1308
+ stripped += " ";
1309
+ i++;
1310
+ }
1311
+ continue;
1312
+ }
1313
+ if (current === quote) break;
1314
+ }
1315
+ continue;
1316
+ }
1317
+ stripped += char;
1318
+ i++;
1319
+ }
1320
+ return stripped;
1321
+ }
1322
+ function extractTemplateExpressions(template) {
1323
+ const expressions = [];
1324
+ let i = 0;
1325
+ while (i < template.length) {
1326
+ if (template[i] !== "$" || template[i + 1] !== "{") {
1327
+ i++;
1328
+ continue;
1329
+ }
1330
+ let depth = 1;
1331
+ let quote = null;
1332
+ let escaped = false;
1333
+ const start = i + 2;
1334
+ i = start;
1335
+ while (i < template.length) {
1336
+ const char = template[i];
1337
+ if (quote) {
1338
+ if (escaped) {
1339
+ escaped = false;
1340
+ } else if (char === "\\") {
1341
+ escaped = true;
1342
+ } else if (char === quote) {
1343
+ quote = null;
1344
+ }
1345
+ i++;
1346
+ continue;
1347
+ }
1348
+ if (char === "'" || char === '"' || char === "`") {
1349
+ quote = char;
1350
+ i++;
1351
+ continue;
1352
+ }
1353
+ if (char === "{") {
1354
+ depth++;
1355
+ } else if (char === "}") {
1356
+ depth--;
1357
+ if (depth === 0) {
1358
+ expressions.push(template.slice(start, i));
1359
+ i++;
1360
+ break;
1361
+ }
1362
+ }
1363
+ i++;
1364
+ }
1365
+ }
1366
+ return expressions;
1367
+ }
1368
+ function collectTemplateVariables(expressions, answers) {
1176
1369
  var _a;
1177
1370
  const vars = {};
1178
- const regex = /\$\{(\w+)/g;
1179
- let match;
1180
- while ((match = regex.exec(template)) !== null) {
1181
- const key = match[1];
1182
- vars[key] = (_a = answers[key]) != null ? _a : void 0;
1371
+ for (const expression of expressions) {
1372
+ const stripped = stripLiteralsAndComments(expression);
1373
+ IDENTIFIER_TOKEN_RE.lastIndex = 0;
1374
+ let match;
1375
+ while ((match = IDENTIFIER_TOKEN_RE.exec(stripped)) !== null) {
1376
+ const key = match[0];
1377
+ const isPropertyAccess = previousNonWhitespace(stripped, match.index) === ".";
1378
+ if (isPropertyAccess || !VALID_IDENTIFIER_RE.test(key) || RESERVED_IDENTIFIERS.has(key) || GLOBAL_IDENTIFIERS.has(key) && !(key in answers)) {
1379
+ continue;
1380
+ }
1381
+ vars[key] = (_a = answers[key]) != null ? _a : void 0;
1382
+ }
1183
1383
  }
1184
- if (Object.keys(vars).length === 0) return template;
1384
+ return vars;
1385
+ }
1386
+ function resolveTemplate(template, answers) {
1387
+ if (!template.includes("${")) return template;
1388
+ const expressions = extractTemplateExpressions(template);
1389
+ if (expressions.length === 0) return template;
1390
+ const vars = collectTemplateVariables(expressions, answers);
1185
1391
  return renderTemplate(template, vars, "");
1186
1392
  }
1187
- var WHOLE_REF_RE = /^\$\{(\w+)\}$/;
1393
+ var WHOLE_REF_RE = /^\$\{([A-Za-z_$][A-Za-z0-9_$]*)\}$/;
1188
1394
  function resolveStringField(value, answers) {
1189
1395
  const wholeRef = value.match(WHOLE_REF_RE);
1190
1396
  if (wholeRef) {
@@ -1243,7 +1449,7 @@ function RadioGroupItem(_a) {
1243
1449
  __spreadProps(__spreadValues({
1244
1450
  "data-slot": "radio-group-item",
1245
1451
  className: cn(
1246
- "aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
1452
+ "aspect-square size-4 shrink-0 rounded-full border border-input text-primary shadow-xs transition-[color,border-color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
1247
1453
  className
1248
1454
  )
1249
1455
  }, props), {
@@ -1621,7 +1827,7 @@ function SingleSelectWidget({
1621
1827
  RadioGroupItem,
1622
1828
  {
1623
1829
  value: opt.id,
1624
- className: isGrid ? "sr-only" : "mt-1"
1830
+ className: isGrid ? "sr-only" : opt.description ? "mt-1" : ""
1625
1831
  }
1626
1832
  ),
1627
1833
  /* @__PURE__ */ jsxs(
@@ -3691,16 +3897,17 @@ function ChecklistItemRow({
3691
3897
  animate: animate2,
3692
3898
  entranceDelay
3693
3899
  }) {
3900
+ var _a, _b;
3694
3901
  const hasLoadSequence = item.loadDelay != null && !item.checked;
3695
3902
  const uid = sanitizeSvgId(useId());
3696
3903
  const gradientId = `checklist-grad-${uid}-${index}`;
3697
3904
  const initialPhase = item.checked ? "checked" : "unchecked";
3698
3905
  const [phase, setPhase] = useState(initialPhase);
3699
3906
  useEffect(() => {
3700
- var _a;
3907
+ var _a2;
3701
3908
  if (!hasLoadSequence) return;
3702
3909
  const loadDelay = item.loadDelay * 1e3;
3703
- const loadDuration = ((_a = item.loadDuration) != null ? _a : 4) * 1e3;
3910
+ const loadDuration = ((_a2 = item.loadDuration) != null ? _a2 : 4) * 1e3;
3704
3911
  const loadTimer = setTimeout(() => setPhase("loading"), loadDelay);
3705
3912
  const checkTimer = setTimeout(
3706
3913
  () => setPhase("checked"),
@@ -3714,6 +3921,8 @@ function ChecklistItemRow({
3714
3921
  const CustomIcon = item.icon ? lucideIcons7[item.icon] : void 0;
3715
3922
  const IconComp = CustomIcon && ("render" in CustomIcon || typeof CustomIcon === "function") ? CustomIcon : Check;
3716
3923
  const hasCustomIcon = IconComp !== Check;
3924
+ const textContent = (_b = (_a = item.segments) != null ? _a : item.text) != null ? _b : "";
3925
+ const textStyle = item.textColor || item.lineHeight != null ? { color: item.textColor, lineHeight: item.lineHeight } : void 0;
3717
3926
  const iconProps = item.iconGradient ? { stroke: `url(#${gradientId})` } : item.iconColor ? { color: item.iconColor } : {};
3718
3927
  const content = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3719
3928
  /* @__PURE__ */ jsx(
@@ -3776,13 +3985,14 @@ function ChecklistItemRow({
3776
3985
  }
3777
3986
  ),
3778
3987
  /* @__PURE__ */ jsx(
3779
- "span",
3988
+ RichText,
3780
3989
  {
3990
+ content: textContent,
3781
3991
  className: cn(
3782
3992
  "text-sm transition-colors duration-300",
3783
3993
  phase === "checked" ? "text-foreground" : "text-muted-foreground"
3784
3994
  ),
3785
- children: item.text
3995
+ style: textStyle
3786
3996
  }
3787
3997
  )
3788
3998
  ] });
@@ -4128,6 +4338,9 @@ function CircularProgressBlock({
4128
4338
  );
4129
4339
  }
4130
4340
  var lucideIcons8 = icons;
4341
+ var DEFAULT_CHART_HEIGHT = 280;
4342
+ var DEFAULT_Y_AXIS_LABEL_GUTTER = 64;
4343
+ var Y_AXIS_LABEL_GAP = 8;
4131
4344
  function toSvg(points, w, h, pad) {
4132
4345
  const drawW = w - pad.left - pad.right;
4133
4346
  const drawH = h - pad.top - pad.bottom;
@@ -4193,10 +4406,36 @@ function xValToPercent(xVal, svgW, pad) {
4193
4406
  const drawW = svgW - pad.left - pad.right;
4194
4407
  return (pad.left + xVal / 100 * drawW) / svgW * 100;
4195
4408
  }
4409
+ function toCssLength(value, fallback) {
4410
+ if (value === void 0) return fallback;
4411
+ if (typeof value === "number") {
4412
+ return Number.isFinite(value) && value > 0 ? `${value}px` : fallback;
4413
+ }
4414
+ const trimmed = value.trim();
4415
+ if (!trimmed) return fallback;
4416
+ return /^-?\d+(\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
4417
+ }
4418
+ function toViewBoxHeight(value) {
4419
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
4420
+ return value;
4421
+ }
4422
+ if (typeof value === "string") {
4423
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)(?:px)?$/i);
4424
+ if (match) {
4425
+ const parsed = Number(match[1]);
4426
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
4427
+ }
4428
+ }
4429
+ return DEFAULT_CHART_HEIGHT;
4430
+ }
4431
+ function toPositiveNumber(value, fallback) {
4432
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
4433
+ }
4196
4434
  function LineChartBlock({
4197
4435
  lines = [],
4198
4436
  width = "100%",
4199
4437
  height = 280,
4438
+ yLabelGutter,
4200
4439
  yLabels,
4201
4440
  xLabels,
4202
4441
  animationDuration = 1.5,
@@ -4211,12 +4450,19 @@ function LineChartBlock({
4211
4450
  const [animate2, setAnimate] = useState(false);
4212
4451
  const pathRefs = useRef([]);
4213
4452
  const dotRefs = useRef([]);
4214
- const svgH = height;
4453
+ const svgH = toViewBoxHeight(height);
4454
+ const chartWidth = toCssLength(width, "100%");
4455
+ const chartHeight = toCssLength(height, `${DEFAULT_CHART_HEIGHT}px`);
4215
4456
  const hasEndLabels = lines.some((l) => l.endLabel || l.endIcon);
4216
4457
  const hasEndDots = lines.some((l) => l.endDot);
4217
4458
  const hasYLabels = !!((yLabels == null ? void 0 : yLabels.top) || (yLabels == null ? void 0 : yLabels.bottom));
4218
4459
  const padRight = hasEndDots ? 80 : hasEndLabels ? 60 : 16;
4219
- const pad = { top: 16, right: padRight, bottom: 16, left: hasYLabels ? 48 : 16 };
4460
+ const pad = {
4461
+ top: 16,
4462
+ right: padRight,
4463
+ bottom: 16,
4464
+ left: hasYLabels ? toPositiveNumber(yLabelGutter, DEFAULT_Y_AXIS_LABEL_GUTTER) : 16
4465
+ };
4220
4466
  useEffect(() => {
4221
4467
  const timer = setTimeout(() => setAnimate(true), startDelay * 1e3);
4222
4468
  return () => clearTimeout(timer);
@@ -4231,6 +4477,7 @@ function LineChartBlock({
4231
4477
  });
4232
4478
  const durationMs = animationDuration * 1e3;
4233
4479
  const hasDots = lines.some((l) => l.endDot);
4480
+ const yAxisLabelWidth = `max(0px, calc(${pad.left / svgW * 100}% - ${Y_AXIS_LABEL_GAP}px))`;
4234
4481
  useEffect(() => {
4235
4482
  if (!animate2 || !hasDots) return;
4236
4483
  const startTime = performance.now();
@@ -4256,299 +4503,329 @@ function LineChartBlock({
4256
4503
  return () => cancelAnimationFrame(rafId);
4257
4504
  }, [animate2, hasDots, durationMs, svgW, svgH]);
4258
4505
  const toPctY = (px) => `${px / svgH * 100}%`;
4259
- return /* @__PURE__ */ jsxs("div", { className: cn("w-full overflow-visible", className), style: { width }, children: [
4260
- /* @__PURE__ */ jsxs(
4261
- "div",
4262
- {
4263
- className: "relative overflow-visible",
4264
- style: { aspectRatio: `5 / 4`, minHeight: 220 },
4265
- children: [
4266
- (yLabels == null ? void 0 : yLabels.top) && /* @__PURE__ */ jsx(
4267
- "span",
4268
- {
4269
- className: "absolute left-0 text-xs font-medium",
4270
- style: {
4271
- top: toPctY(
4272
- yLabels.topAt != null ? yValToPixel(yLabels.topAt, svgH, pad) : pad.top
4273
- ),
4274
- transform: "translateY(-50%)",
4275
- color: labelColor
4276
- },
4277
- children: yLabels.top
4278
- }
4279
- ),
4280
- (yLabels == null ? void 0 : yLabels.bottom) && /* @__PURE__ */ jsx(
4281
- "span",
4282
- {
4283
- className: "absolute left-0 text-xs font-medium",
4284
- style: {
4285
- top: toPctY(
4286
- yLabels.bottomAt != null ? yValToPixel(yLabels.bottomAt, svgH, pad) : svgH - pad.bottom
4287
- ),
4288
- transform: "translateY(-50%)",
4289
- color: labelColor
4290
- },
4291
- children: yLabels.bottom
4292
- }
4293
- ),
4294
- /* @__PURE__ */ jsxs(
4295
- "svg",
4296
- {
4297
- viewBox: `0 0 ${svgW} ${svgH}`,
4298
- preserveAspectRatio: "none",
4299
- className: "w-full h-full",
4300
- style: { overflow: "visible" },
4301
- children: [
4302
- /* @__PURE__ */ jsxs("defs", { children: [
4303
- linesData.map(
4304
- (line, i) => {
4506
+ const yAxisLabelStyle = (top) => ({
4507
+ top,
4508
+ left: 0,
4509
+ width: yAxisLabelWidth,
4510
+ maxWidth: yAxisLabelWidth,
4511
+ transform: "translateY(-50%)",
4512
+ color: labelColor,
4513
+ textAlign: "right",
4514
+ whiteSpace: "pre-line",
4515
+ overflowWrap: "anywhere",
4516
+ lineHeight: 1.1,
4517
+ pointerEvents: "none"
4518
+ });
4519
+ return /* @__PURE__ */ jsxs(
4520
+ "div",
4521
+ {
4522
+ className: cn("w-full overflow-visible", className),
4523
+ style: { width: chartWidth },
4524
+ children: [
4525
+ /* @__PURE__ */ jsxs(
4526
+ "div",
4527
+ {
4528
+ className: "relative overflow-visible",
4529
+ style: { height: chartHeight },
4530
+ children: [
4531
+ (yLabels == null ? void 0 : yLabels.top) && /* @__PURE__ */ jsx(
4532
+ "span",
4533
+ {
4534
+ className: "absolute text-xs font-medium",
4535
+ style: yAxisLabelStyle(
4536
+ toPctY(
4537
+ yLabels.topAt != null ? yValToPixel(yLabels.topAt, svgH, pad) : pad.top
4538
+ )
4539
+ ),
4540
+ children: yLabels.top
4541
+ }
4542
+ ),
4543
+ (yLabels == null ? void 0 : yLabels.bottom) && /* @__PURE__ */ jsx(
4544
+ "span",
4545
+ {
4546
+ className: "absolute text-xs font-medium",
4547
+ style: yAxisLabelStyle(
4548
+ toPctY(
4549
+ yLabels.bottomAt != null ? yValToPixel(yLabels.bottomAt, svgH, pad) : svgH - pad.bottom
4550
+ )
4551
+ ),
4552
+ children: yLabels.bottom
4553
+ }
4554
+ ),
4555
+ /* @__PURE__ */ jsxs(
4556
+ "svg",
4557
+ {
4558
+ viewBox: `0 0 ${svgW} ${svgH}`,
4559
+ preserveAspectRatio: "none",
4560
+ className: "w-full h-full",
4561
+ style: { overflow: "visible" },
4562
+ children: [
4563
+ /* @__PURE__ */ jsxs("defs", { children: [
4564
+ linesData.map(
4565
+ (line, i) => {
4566
+ var _a2;
4567
+ return line.fill ? /* @__PURE__ */ jsxs(
4568
+ "linearGradient",
4569
+ {
4570
+ id: `lc-fill-${uid}-${i}`,
4571
+ x1: "0",
4572
+ y1: "0",
4573
+ x2: "0",
4574
+ y2: "1",
4575
+ children: [
4576
+ /* @__PURE__ */ jsx(
4577
+ "stop",
4578
+ {
4579
+ offset: "0%",
4580
+ stopColor: line.fill.from,
4581
+ stopOpacity: (_a2 = line.fill.opacity) != null ? _a2 : 0.4
4582
+ }
4583
+ ),
4584
+ /* @__PURE__ */ jsx(
4585
+ "stop",
4586
+ {
4587
+ offset: "100%",
4588
+ stopColor: line.fill.to,
4589
+ stopOpacity: 0
4590
+ }
4591
+ )
4592
+ ]
4593
+ },
4594
+ `fill-${i}`
4595
+ ) : null;
4596
+ }
4597
+ ),
4598
+ linesData.map(
4599
+ (line, i) => line.fill ? /* @__PURE__ */ jsxs(
4600
+ "linearGradient",
4601
+ {
4602
+ id: `lc-mask-grad-${uid}-${i}`,
4603
+ x1: "0",
4604
+ y1: "0",
4605
+ x2: "0",
4606
+ y2: "1",
4607
+ children: [
4608
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "white" }),
4609
+ /* @__PURE__ */ jsx("stop", { offset: "70%", stopColor: "white" }),
4610
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "black" })
4611
+ ]
4612
+ },
4613
+ `mask-${i}`
4614
+ ) : null
4615
+ ),
4616
+ linesData.map(
4617
+ (line, i) => line.fill ? /* @__PURE__ */ jsx("mask", { id: `lc-mask-${uid}-${i}`, children: /* @__PURE__ */ jsx(
4618
+ "rect",
4619
+ {
4620
+ x: "0",
4621
+ y: animate2 ? "0" : `-${svgH}`,
4622
+ width: svgW,
4623
+ height: svgH,
4624
+ fill: `url(#lc-mask-grad-${uid}-${i})`,
4625
+ style: {
4626
+ transition: `y ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4627
+ }
4628
+ }
4629
+ ) }, `mask-el-${i}`) : null
4630
+ )
4631
+ ] }),
4632
+ showGrid && [25, 50, 75].map((pct) => {
4633
+ const y = pad.top + (100 - pct) / 100 * (svgH - pad.top - pad.bottom);
4634
+ return /* @__PURE__ */ jsx(
4635
+ "line",
4636
+ {
4637
+ x1: pad.left,
4638
+ y1: y,
4639
+ x2: svgW - pad.right,
4640
+ y2: y,
4641
+ stroke: axisColor,
4642
+ strokeWidth: 0.5,
4643
+ strokeDasharray: "4 4"
4644
+ },
4645
+ pct
4646
+ );
4647
+ }),
4648
+ linesData.map(
4649
+ (line, i) => line.fillDPath ? /* @__PURE__ */ jsx(
4650
+ "path",
4651
+ {
4652
+ d: line.fillDPath,
4653
+ fill: `url(#lc-fill-${uid}-${i})`,
4654
+ mask: `url(#lc-mask-${uid}-${i})`
4655
+ },
4656
+ `area-${i}`
4657
+ ) : null
4658
+ ),
4659
+ linesData.map((line, i) => {
4305
4660
  var _a2;
4306
- return line.fill ? /* @__PURE__ */ jsxs(
4307
- "linearGradient",
4661
+ return /* @__PURE__ */ jsx(
4662
+ "path",
4308
4663
  {
4309
- id: `lc-fill-${uid}-${i}`,
4310
- x1: "0",
4311
- y1: "0",
4312
- x2: "0",
4313
- y2: "1",
4314
- children: [
4315
- /* @__PURE__ */ jsx(
4316
- "stop",
4317
- {
4318
- offset: "0%",
4319
- stopColor: line.fill.from,
4320
- stopOpacity: (_a2 = line.fill.opacity) != null ? _a2 : 0.4
4321
- }
4322
- ),
4323
- /* @__PURE__ */ jsx(
4324
- "stop",
4325
- {
4326
- offset: "100%",
4327
- stopColor: line.fill.to,
4328
- stopOpacity: 0
4329
- }
4330
- )
4331
- ]
4664
+ ref: (el) => {
4665
+ pathRefs.current[i] = el;
4666
+ },
4667
+ d: line.lineDPath,
4668
+ fill: "none",
4669
+ stroke: line.color,
4670
+ strokeWidth: (_a2 = line.strokeWidth) != null ? _a2 : 3,
4671
+ strokeLinecap: "round",
4672
+ strokeLinejoin: "round",
4673
+ pathLength: 1,
4674
+ style: line.dashed ? {
4675
+ strokeDasharray: "8 6",
4676
+ opacity: animate2 ? 1 : 0,
4677
+ transition: `opacity ${durationMs}ms ease`
4678
+ } : {
4679
+ strokeDasharray: 1,
4680
+ strokeDashoffset: animate2 ? 0 : 1,
4681
+ transition: `stroke-dashoffset ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4682
+ }
4332
4683
  },
4333
- `fill-${i}`
4334
- ) : null;
4335
- }
4336
- ),
4337
- linesData.map(
4338
- (line, i) => line.fill ? /* @__PURE__ */ jsxs(
4339
- "linearGradient",
4684
+ `line-${i}`
4685
+ );
4686
+ }),
4687
+ /* @__PURE__ */ jsx(
4688
+ "line",
4340
4689
  {
4341
- id: `lc-mask-grad-${uid}-${i}`,
4342
- x1: "0",
4343
- y1: "0",
4344
- x2: "0",
4345
- y2: "1",
4346
- children: [
4347
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "white" }),
4348
- /* @__PURE__ */ jsx("stop", { offset: "70%", stopColor: "white" }),
4349
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "black" })
4350
- ]
4351
- },
4352
- `mask-${i}`
4353
- ) : null
4354
- ),
4355
- linesData.map(
4356
- (line, i) => line.fill ? /* @__PURE__ */ jsx("mask", { id: `lc-mask-${uid}-${i}`, children: /* @__PURE__ */ jsx(
4357
- "rect",
4690
+ x1: pad.left,
4691
+ y1: pad.top,
4692
+ x2: pad.left,
4693
+ y2: svgH - pad.bottom,
4694
+ stroke: axisColor,
4695
+ strokeWidth: 2
4696
+ }
4697
+ ),
4698
+ /* @__PURE__ */ jsx(
4699
+ "line",
4358
4700
  {
4359
- x: "0",
4360
- y: animate2 ? "0" : `-${svgH}`,
4361
- width: svgW,
4362
- height: svgH,
4363
- fill: `url(#lc-mask-grad-${uid}-${i})`,
4364
- style: {
4365
- transition: `y ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4366
- }
4701
+ x1: pad.left,
4702
+ y1: svgH - pad.bottom,
4703
+ x2: svgW - pad.right,
4704
+ y2: svgH - pad.bottom,
4705
+ stroke: axisColor,
4706
+ strokeWidth: 2
4367
4707
  }
4368
- ) }, `mask-el-${i}`) : null
4369
- )
4370
- ] }),
4371
- showGrid && [25, 50, 75].map((pct) => {
4372
- const y = pad.top + (100 - pct) / 100 * (svgH - pad.top - pad.bottom);
4373
- return /* @__PURE__ */ jsx(
4374
- "line",
4375
- {
4376
- x1: pad.left,
4377
- y1: y,
4378
- x2: svgW - pad.right,
4379
- y2: y,
4380
- stroke: axisColor,
4381
- strokeWidth: 0.5,
4382
- strokeDasharray: "4 4"
4708
+ )
4709
+ ]
4710
+ }
4711
+ ),
4712
+ linesData.map((line, i) => {
4713
+ var _a2, _b2, _c, _d, _e;
4714
+ if (!line.endDot) return null;
4715
+ const dotSize = (_a2 = line.endDot.size) != null ? _a2 : 28;
4716
+ const iconSize = dotSize * 0.55;
4717
+ const imageScale = typeof line.endDot.imageScale === "number" && Number.isFinite(line.endDot.imageScale) ? Math.min(Math.max(line.endDot.imageScale, 0), 1) : 0.72;
4718
+ const imageSize = dotSize * imageScale;
4719
+ const LucideIcon = line.endDot.lucideIcon ? lucideIcons8[line.endDot.lucideIcon] : null;
4720
+ return /* @__PURE__ */ jsx(
4721
+ "div",
4722
+ {
4723
+ ref: (el) => {
4724
+ dotRefs.current[i] = el;
4383
4725
  },
4384
- pct
4385
- );
4386
- }),
4387
- linesData.map(
4388
- (line, i) => line.fillDPath ? /* @__PURE__ */ jsx(
4389
- "path",
4390
- {
4391
- d: line.fillDPath,
4392
- fill: `url(#lc-fill-${uid}-${i})`,
4393
- mask: `url(#lc-mask-${uid}-${i})`
4726
+ className: "absolute flex items-center justify-center rounded-full shadow-lg",
4727
+ style: {
4728
+ width: dotSize,
4729
+ height: dotSize,
4730
+ background: line.endDot.gradient ? `linear-gradient(${(_b2 = line.endDot.gradient.angle) != null ? _b2 : 180}deg, ${line.endDot.gradient.from}, ${line.endDot.gradient.to})` : (_c = line.endDot.color) != null ? _c : line.color,
4731
+ transform: "translate(-50%, -50%)",
4732
+ opacity: 0,
4733
+ left: 0,
4734
+ top: 0,
4735
+ zIndex: 10
4394
4736
  },
4395
- `area-${i}`
4396
- ) : null
4397
- ),
4398
- linesData.map((line, i) => {
4399
- var _a2;
4400
- return /* @__PURE__ */ jsx(
4401
- "path",
4402
- {
4403
- ref: (el) => {
4404
- pathRefs.current[i] = el;
4405
- },
4406
- d: line.lineDPath,
4407
- fill: "none",
4408
- stroke: line.color,
4409
- strokeWidth: (_a2 = line.strokeWidth) != null ? _a2 : 3,
4410
- strokeLinecap: "round",
4411
- strokeLinejoin: "round",
4412
- pathLength: 1,
4413
- style: line.dashed ? {
4414
- strokeDasharray: "8 6",
4415
- opacity: animate2 ? 1 : 0,
4416
- transition: `opacity ${durationMs}ms ease`
4417
- } : {
4418
- strokeDasharray: 1,
4419
- strokeDashoffset: animate2 ? 0 : 1,
4420
- transition: `stroke-dashoffset ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4737
+ children: line.endDot.imageSrc ? /* @__PURE__ */ jsx(
4738
+ "img",
4739
+ {
4740
+ src: line.endDot.imageSrc,
4741
+ alt: (_d = line.endDot.imageAlt) != null ? _d : "",
4742
+ draggable: false,
4743
+ className: "rounded-full object-contain",
4744
+ style: {
4745
+ width: imageSize,
4746
+ height: imageSize
4747
+ }
4421
4748
  }
4422
- },
4423
- `line-${i}`
4424
- );
4425
- }),
4426
- /* @__PURE__ */ jsx(
4427
- "line",
4428
- {
4429
- x1: pad.left,
4430
- y1: pad.top,
4431
- x2: pad.left,
4432
- y2: svgH - pad.bottom,
4433
- stroke: axisColor,
4434
- strokeWidth: 2
4435
- }
4436
- ),
4437
- /* @__PURE__ */ jsx(
4438
- "line",
4749
+ ) : LucideIcon ? /* @__PURE__ */ jsx(
4750
+ LucideIcon,
4751
+ {
4752
+ size: iconSize,
4753
+ color: (_e = line.endDot.lucideIconColor) != null ? _e : "#fff",
4754
+ strokeWidth: 2.5
4755
+ }
4756
+ ) : line.endDot.icon ? /* @__PURE__ */ jsx("span", { style: { fontSize: iconSize, lineHeight: 1 }, children: line.endDot.icon }) : null
4757
+ },
4758
+ `dot-${i}`
4759
+ );
4760
+ }),
4761
+ linesData.map((line, i) => {
4762
+ var _a2, _b2, _c, _d, _e, _f, _g;
4763
+ if (!line.endIcon && !line.endLabel) return null;
4764
+ const cx = line.lastPt[0];
4765
+ const cy = line.lastPt[1];
4766
+ const labelLines = (_b2 = (_a2 = line.endLabel) == null ? void 0 : _a2.split("\n")) != null ? _b2 : [];
4767
+ const hasDot = !!line.endDot;
4768
+ const offX = (_d = (_c = line.endLabelOffset) == null ? void 0 : _c.x) != null ? _d : 0;
4769
+ const offY = (_f = (_e = line.endLabelOffset) == null ? void 0 : _e.y) != null ? _f : 0;
4770
+ return /* @__PURE__ */ jsxs(
4771
+ "div",
4439
4772
  {
4440
- x1: pad.left,
4441
- y1: svgH - pad.bottom,
4442
- x2: svgW - pad.right,
4443
- y2: svgH - pad.bottom,
4444
- stroke: axisColor,
4445
- strokeWidth: 2
4446
- }
4447
- )
4448
- ]
4773
+ className: "absolute flex flex-col items-center",
4774
+ style: __spreadProps(__spreadValues({}, hasDot ? {
4775
+ left: `calc(${cx / svgW * 100}% + ${offX}px)`,
4776
+ transform: "translateX(-50%)"
4777
+ } : { right: 0 }), {
4778
+ top: toPctY(cy + offY),
4779
+ opacity: animate2 ? 1 : 0,
4780
+ transition: `opacity 400ms ease ${durationMs * 0.8}ms`,
4781
+ textAlign: "center",
4782
+ whiteSpace: "nowrap"
4783
+ }),
4784
+ children: [
4785
+ line.endIcon && /* @__PURE__ */ jsx("span", { className: "text-lg", children: line.endIcon }),
4786
+ labelLines.length > 0 && /* @__PURE__ */ jsx(
4787
+ "span",
4788
+ {
4789
+ className: "text-xs font-semibold leading-tight",
4790
+ style: { color: (_g = line.endLabelColor) != null ? _g : labelColor },
4791
+ children: labelLines.map((ln, j) => /* @__PURE__ */ jsx("span", { className: "block", children: ln }, j))
4792
+ }
4793
+ )
4794
+ ]
4795
+ },
4796
+ `end-${i}`
4797
+ );
4798
+ })
4799
+ ]
4800
+ }
4801
+ ),
4802
+ xLabels && /* @__PURE__ */ jsxs("div", { className: "relative text-xs font-medium mt-1", style: { color: labelColor, height: 20 }, children: [
4803
+ xLabels.left && /* @__PURE__ */ jsx(
4804
+ "span",
4805
+ {
4806
+ className: "absolute",
4807
+ style: {
4808
+ left: `${xValToPercent((_a = xLabels.leftAt) != null ? _a : 0, svgW, pad)}%`,
4809
+ transform: "translateX(-50%)"
4810
+ },
4811
+ children: xLabels.left
4449
4812
  }
4450
4813
  ),
4451
- linesData.map((line, i) => {
4452
- var _a2, _b2, _c, _d;
4453
- if (!line.endDot) return null;
4454
- const dotSize = (_a2 = line.endDot.size) != null ? _a2 : 28;
4455
- const iconSize = dotSize * 0.55;
4456
- const LucideIcon = line.endDot.lucideIcon ? lucideIcons8[line.endDot.lucideIcon] : null;
4457
- return /* @__PURE__ */ jsx(
4458
- "div",
4459
- {
4460
- ref: (el) => {
4461
- dotRefs.current[i] = el;
4462
- },
4463
- className: "absolute flex items-center justify-center rounded-full shadow-lg",
4464
- style: {
4465
- width: dotSize,
4466
- height: dotSize,
4467
- background: line.endDot.gradient ? `linear-gradient(${(_b2 = line.endDot.gradient.angle) != null ? _b2 : 180}deg, ${line.endDot.gradient.from}, ${line.endDot.gradient.to})` : (_c = line.endDot.color) != null ? _c : line.color,
4468
- transform: "translate(-50%, -50%)",
4469
- opacity: 0,
4470
- left: 0,
4471
- top: 0,
4472
- zIndex: 10
4473
- },
4474
- children: LucideIcon ? /* @__PURE__ */ jsx(
4475
- LucideIcon,
4476
- {
4477
- size: iconSize,
4478
- color: (_d = line.endDot.lucideIconColor) != null ? _d : "#fff",
4479
- strokeWidth: 2.5
4480
- }
4481
- ) : line.endDot.icon ? /* @__PURE__ */ jsx("span", { style: { fontSize: iconSize, lineHeight: 1 }, children: line.endDot.icon }) : null
4482
- },
4483
- `dot-${i}`
4484
- );
4485
- }),
4486
- linesData.map((line, i) => {
4487
- var _a2, _b2, _c, _d, _e, _f, _g;
4488
- if (!line.endIcon && !line.endLabel) return null;
4489
- const cx = line.lastPt[0];
4490
- const cy = line.lastPt[1];
4491
- const labelLines = (_b2 = (_a2 = line.endLabel) == null ? void 0 : _a2.split("\n")) != null ? _b2 : [];
4492
- const hasDot = !!line.endDot;
4493
- const offX = (_d = (_c = line.endLabelOffset) == null ? void 0 : _c.x) != null ? _d : 0;
4494
- const offY = (_f = (_e = line.endLabelOffset) == null ? void 0 : _e.y) != null ? _f : 0;
4495
- return /* @__PURE__ */ jsxs(
4496
- "div",
4497
- {
4498
- className: "absolute flex flex-col items-center",
4499
- style: __spreadProps(__spreadValues({}, hasDot ? {
4500
- left: `calc(${cx / svgW * 100}% + ${offX}px)`,
4501
- transform: "translateX(-50%)"
4502
- } : { right: 0 }), {
4503
- top: toPctY(cy + offY),
4504
- opacity: animate2 ? 1 : 0,
4505
- transition: `opacity 400ms ease ${durationMs * 0.8}ms`,
4506
- textAlign: "center",
4507
- whiteSpace: "nowrap"
4508
- }),
4509
- children: [
4510
- line.endIcon && /* @__PURE__ */ jsx("span", { className: "text-lg", children: line.endIcon }),
4511
- labelLines.length > 0 && /* @__PURE__ */ jsx(
4512
- "span",
4513
- {
4514
- className: "text-xs font-semibold leading-tight",
4515
- style: { color: (_g = line.endLabelColor) != null ? _g : labelColor },
4516
- children: labelLines.map((ln, j) => /* @__PURE__ */ jsx("span", { className: "block", children: ln }, j))
4517
- }
4518
- )
4519
- ]
4814
+ xLabels.right && /* @__PURE__ */ jsx(
4815
+ "span",
4816
+ {
4817
+ className: "absolute",
4818
+ style: {
4819
+ left: `${xValToPercent((_b = xLabels.rightAt) != null ? _b : 100, svgW, pad)}%`,
4820
+ transform: "translateX(-50%)"
4520
4821
  },
4521
- `end-${i}`
4522
- );
4523
- })
4524
- ]
4525
- }
4526
- ),
4527
- xLabels && /* @__PURE__ */ jsxs("div", { className: "relative text-xs font-medium mt-1", style: { color: labelColor, height: 20 }, children: [
4528
- xLabels.left && /* @__PURE__ */ jsx(
4529
- "span",
4530
- {
4531
- className: "absolute",
4532
- style: {
4533
- left: `${xValToPercent((_a = xLabels.leftAt) != null ? _a : 0, svgW, pad)}%`,
4534
- transform: "translateX(-50%)"
4535
- },
4536
- children: xLabels.left
4537
- }
4538
- ),
4539
- xLabels.right && /* @__PURE__ */ jsx(
4540
- "span",
4541
- {
4542
- className: "absolute",
4543
- style: {
4544
- left: `${xValToPercent((_b = xLabels.rightAt) != null ? _b : 100, svgW, pad)}%`,
4545
- transform: "translateX(-50%)"
4546
- },
4547
- children: xLabels.right
4548
- }
4549
- )
4550
- ] })
4551
- ] });
4822
+ children: xLabels.right
4823
+ }
4824
+ )
4825
+ ] })
4826
+ ]
4827
+ }
4828
+ );
4552
4829
  }
4553
4830
  function getInitials2(name) {
4554
4831
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
@@ -5535,6 +5812,7 @@ function TimelineBlock({
5535
5812
  } : {};
5536
5813
  const renderNodeIcon = iconPlacement === "node" && item.icon;
5537
5814
  const renderContentIcon = iconPlacement === "content" && item.icon;
5815
+ const renderTitleIcon = iconPlacement === "before-title" && item.icon;
5538
5816
  const showLabel = labelPlacement === "above" && item.label;
5539
5817
  const showOutgoing = !isLast || !!(endLine == null ? void 0 : endLine.show);
5540
5818
  const outgoingColor = isLast ? resolveColor((_a2 = endLine == null ? void 0 : endLine.color) != null ? _a2 : lineColor, blockLineValue) : resolveColor((_b = item.lineColor) != null ? _b : lineColor, blockLineValue);
@@ -5550,6 +5828,45 @@ function TimelineBlock({
5550
5828
  const defaultItemSpacing = Math.max(16, nodeSize * 0.4);
5551
5829
  const bottomPad = isLast ? (endLine == null ? void 0 : endLine.show) ? EDGE_LINE_HEIGHT : 0 : itemSpacing != null ? itemSpacing : defaultItemSpacing;
5552
5830
  const nodeTopMargin = iconPlacement === "node" ? 0 : 4;
5831
+ const contentBody = /* @__PURE__ */ jsxs(Fragment, { children: [
5832
+ renderContentIcon && /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: renderIcon3(item.icon) }),
5833
+ /* @__PURE__ */ jsxs(
5834
+ "div",
5835
+ {
5836
+ style: iconPlacement === "node" ? {
5837
+ minHeight: nodeSize,
5838
+ display: "flex",
5839
+ flexDirection: "column",
5840
+ justifyContent: "center"
5841
+ } : void 0,
5842
+ children: [
5843
+ showLabel && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wider", children: item.label }),
5844
+ /* @__PURE__ */ jsx(
5845
+ "h4",
5846
+ {
5847
+ className: cn(
5848
+ "font-semibold text-foreground",
5849
+ iconPlacement === "node" ? "text-xl leading-tight" : "text-sm",
5850
+ renderTitleIcon ? "flex items-center gap-2" : null
5851
+ ),
5852
+ children: renderTitleIcon ? /* @__PURE__ */ jsxs(Fragment, { children: [
5853
+ /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: renderIcon3(item.icon) }),
5854
+ /* @__PURE__ */ jsx("span", { className: "min-w-0", children: item.title })
5855
+ ] }) : item.title
5856
+ }
5857
+ )
5858
+ ]
5859
+ }
5860
+ ),
5861
+ item.description && /* @__PURE__ */ jsx(
5862
+ "p",
5863
+ {
5864
+ className: "text-sm text-muted-foreground",
5865
+ style: { marginTop: resolvedTitleDescGap },
5866
+ children: item.description
5867
+ }
5868
+ )
5869
+ ] });
5553
5870
  return /* @__PURE__ */ jsxs(Wrapper, __spreadProps(__spreadValues({ className: "relative flex" }, animProps), { children: [
5554
5871
  /* @__PURE__ */ jsxs(
5555
5872
  "div",
@@ -5613,46 +5930,19 @@ function TimelineBlock({
5613
5930
  ]
5614
5931
  }
5615
5932
  ),
5616
- /* @__PURE__ */ jsxs(
5933
+ /* @__PURE__ */ jsx(
5617
5934
  "div",
5618
5935
  {
5619
5936
  className: "flex-1 min-w-0",
5620
5937
  style: { paddingBottom: bottomPad, paddingTop: isFirst && showIncoming ? EDGE_LINE_HEIGHT : 0, paddingLeft: contentGap },
5621
- children: [
5622
- renderContentIcon && /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: renderIcon3(item.icon) }),
5623
- /* @__PURE__ */ jsxs(
5624
- "div",
5625
- {
5626
- style: iconPlacement === "node" ? {
5627
- minHeight: nodeSize,
5628
- display: "flex",
5629
- flexDirection: "column",
5630
- justifyContent: "center"
5631
- } : void 0,
5632
- children: [
5633
- showLabel && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wider", children: item.label }),
5634
- /* @__PURE__ */ jsx(
5635
- "h4",
5636
- {
5637
- className: cn(
5638
- "font-semibold text-foreground",
5639
- iconPlacement === "node" ? "text-xl leading-tight" : "text-sm"
5640
- ),
5641
- children: item.title
5642
- }
5643
- )
5644
- ]
5645
- }
5646
- ),
5647
- item.description && /* @__PURE__ */ jsx(
5648
- "p",
5649
- {
5650
- className: "text-sm text-muted-foreground",
5651
- style: { marginTop: resolvedTitleDescGap },
5652
- children: item.description
5653
- }
5654
- )
5655
- ]
5938
+ children: item.contentCard ? /* @__PURE__ */ jsx(
5939
+ "div",
5940
+ {
5941
+ className: "rounded-lg border border-border/60 bg-card/80 p-3 shadow-sm",
5942
+ "data-timeline-content-card": "true",
5943
+ children: contentBody
5944
+ }
5945
+ ) : contentBody
5656
5946
  }
5657
5947
  )
5658
5948
  ] }), i);