@founderhq/journeys 0.3.65 → 0.3.67

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
@@ -728,7 +728,8 @@ function JourneyProvider({
728
728
  const currentStep = config.steps[currentStepIndex];
729
729
  const routing = currentStep.routing;
730
730
  if (routing) {
731
- for (const rule of routing.conditions) {
731
+ const conditions = Array.isArray(routing.conditions) ? routing.conditions : [];
732
+ for (const rule of conditions) {
732
733
  if (evaluateRoutingRule(rule, currentAnswers)) {
733
734
  const idx = config.steps.findIndex((s) => s.id === rule.goTo);
734
735
  if (idx >= 0) return idx;
@@ -1172,19 +1173,225 @@ var renderTemplate = (template, variables, fallback) => {
1172
1173
  };
1173
1174
 
1174
1175
  // src/blocks/resolve-template.ts
1175
- function resolveTemplate(template, answers) {
1176
+ var VALID_IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
1177
+ var IDENTIFIER_TOKEN_RE = /[A-Za-z_$][A-Za-z0-9_$]*/g;
1178
+ var RESERVED_IDENTIFIERS = /* @__PURE__ */ new Set([
1179
+ "arguments",
1180
+ "await",
1181
+ "break",
1182
+ "case",
1183
+ "catch",
1184
+ "class",
1185
+ "const",
1186
+ "continue",
1187
+ "debugger",
1188
+ "default",
1189
+ "delete",
1190
+ "do",
1191
+ "else",
1192
+ "enum",
1193
+ "eval",
1194
+ "export",
1195
+ "extends",
1196
+ "false",
1197
+ "finally",
1198
+ "for",
1199
+ "function",
1200
+ "if",
1201
+ "implements",
1202
+ "import",
1203
+ "in",
1204
+ "instanceof",
1205
+ "interface",
1206
+ "let",
1207
+ "new",
1208
+ "null",
1209
+ "package",
1210
+ "private",
1211
+ "protected",
1212
+ "public",
1213
+ "return",
1214
+ "static",
1215
+ "super",
1216
+ "switch",
1217
+ "this",
1218
+ "throw",
1219
+ "true",
1220
+ "try",
1221
+ "typeof",
1222
+ "undefined",
1223
+ "var",
1224
+ "void",
1225
+ "while",
1226
+ "with",
1227
+ "yield"
1228
+ ]);
1229
+ var GLOBAL_IDENTIFIERS = /* @__PURE__ */ new Set([
1230
+ "Array",
1231
+ "BigInt",
1232
+ "Boolean",
1233
+ "Date",
1234
+ "Error",
1235
+ "Infinity",
1236
+ "Intl",
1237
+ "JSON",
1238
+ "Map",
1239
+ "Math",
1240
+ "NaN",
1241
+ "Number",
1242
+ "Object",
1243
+ "Promise",
1244
+ "RegExp",
1245
+ "Set",
1246
+ "String",
1247
+ "Symbol",
1248
+ "URL",
1249
+ "URLSearchParams",
1250
+ "WeakMap",
1251
+ "WeakSet",
1252
+ "console",
1253
+ "decodeURI",
1254
+ "decodeURIComponent",
1255
+ "encodeURI",
1256
+ "encodeURIComponent",
1257
+ "globalThis",
1258
+ "isFinite",
1259
+ "isNaN",
1260
+ "parseFloat",
1261
+ "parseInt"
1262
+ ]);
1263
+ function previousNonWhitespace(value, index) {
1264
+ for (let i = index - 1; i >= 0; i--) {
1265
+ if (!/\s/.test(value[i])) return value[i];
1266
+ }
1267
+ return void 0;
1268
+ }
1269
+ function stripLiteralsAndComments(expression) {
1270
+ let stripped = "";
1271
+ let i = 0;
1272
+ while (i < expression.length) {
1273
+ const char = expression[i];
1274
+ const next = expression[i + 1];
1275
+ if (char === "/" && next === "/") {
1276
+ stripped += " ";
1277
+ i += 2;
1278
+ while (i < expression.length && expression[i] !== "\n") {
1279
+ stripped += " ";
1280
+ i++;
1281
+ }
1282
+ continue;
1283
+ }
1284
+ if (char === "/" && next === "*") {
1285
+ stripped += " ";
1286
+ i += 2;
1287
+ while (i < expression.length) {
1288
+ const end = expression[i] === "*" && expression[i + 1] === "/";
1289
+ stripped += " ";
1290
+ i++;
1291
+ if (end) {
1292
+ stripped += " ";
1293
+ i++;
1294
+ break;
1295
+ }
1296
+ }
1297
+ continue;
1298
+ }
1299
+ if (char === "'" || char === '"' || char === "`") {
1300
+ const quote = char;
1301
+ stripped += " ";
1302
+ i++;
1303
+ while (i < expression.length) {
1304
+ const current = expression[i];
1305
+ stripped += " ";
1306
+ i++;
1307
+ if (current === "\\") {
1308
+ if (i < expression.length) {
1309
+ stripped += " ";
1310
+ i++;
1311
+ }
1312
+ continue;
1313
+ }
1314
+ if (current === quote) break;
1315
+ }
1316
+ continue;
1317
+ }
1318
+ stripped += char;
1319
+ i++;
1320
+ }
1321
+ return stripped;
1322
+ }
1323
+ function extractTemplateExpressions(template) {
1324
+ const expressions = [];
1325
+ let i = 0;
1326
+ while (i < template.length) {
1327
+ if (template[i] !== "$" || template[i + 1] !== "{") {
1328
+ i++;
1329
+ continue;
1330
+ }
1331
+ let depth = 1;
1332
+ let quote = null;
1333
+ let escaped = false;
1334
+ const start = i + 2;
1335
+ i = start;
1336
+ while (i < template.length) {
1337
+ const char = template[i];
1338
+ if (quote) {
1339
+ if (escaped) {
1340
+ escaped = false;
1341
+ } else if (char === "\\") {
1342
+ escaped = true;
1343
+ } else if (char === quote) {
1344
+ quote = null;
1345
+ }
1346
+ i++;
1347
+ continue;
1348
+ }
1349
+ if (char === "'" || char === '"' || char === "`") {
1350
+ quote = char;
1351
+ i++;
1352
+ continue;
1353
+ }
1354
+ if (char === "{") {
1355
+ depth++;
1356
+ } else if (char === "}") {
1357
+ depth--;
1358
+ if (depth === 0) {
1359
+ expressions.push(template.slice(start, i));
1360
+ i++;
1361
+ break;
1362
+ }
1363
+ }
1364
+ i++;
1365
+ }
1366
+ }
1367
+ return expressions;
1368
+ }
1369
+ function collectTemplateVariables(expressions, answers) {
1176
1370
  var _a;
1177
1371
  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;
1372
+ for (const expression of expressions) {
1373
+ const stripped = stripLiteralsAndComments(expression);
1374
+ IDENTIFIER_TOKEN_RE.lastIndex = 0;
1375
+ let match;
1376
+ while ((match = IDENTIFIER_TOKEN_RE.exec(stripped)) !== null) {
1377
+ const key = match[0];
1378
+ const isPropertyAccess = previousNonWhitespace(stripped, match.index) === ".";
1379
+ if (isPropertyAccess || !VALID_IDENTIFIER_RE.test(key) || RESERVED_IDENTIFIERS.has(key) || GLOBAL_IDENTIFIERS.has(key) && !(key in answers)) {
1380
+ continue;
1381
+ }
1382
+ vars[key] = (_a = answers[key]) != null ? _a : void 0;
1383
+ }
1183
1384
  }
1184
- if (Object.keys(vars).length === 0) return template;
1385
+ return vars;
1386
+ }
1387
+ function resolveTemplate(template, answers) {
1388
+ if (!template.includes("${")) return template;
1389
+ const expressions = extractTemplateExpressions(template);
1390
+ if (expressions.length === 0) return template;
1391
+ const vars = collectTemplateVariables(expressions, answers);
1185
1392
  return renderTemplate(template, vars, "");
1186
1393
  }
1187
- var WHOLE_REF_RE = /^\$\{(\w+)\}$/;
1394
+ var WHOLE_REF_RE = /^\$\{([A-Za-z_$][A-Za-z0-9_$]*)\}$/;
1188
1395
  function resolveStringField(value, answers) {
1189
1396
  const wholeRef = value.match(WHOLE_REF_RE);
1190
1397
  if (wholeRef) {
@@ -3691,16 +3898,17 @@ function ChecklistItemRow({
3691
3898
  animate: animate2,
3692
3899
  entranceDelay
3693
3900
  }) {
3901
+ var _a, _b;
3694
3902
  const hasLoadSequence = item.loadDelay != null && !item.checked;
3695
3903
  const uid = sanitizeSvgId(useId());
3696
3904
  const gradientId = `checklist-grad-${uid}-${index}`;
3697
3905
  const initialPhase = item.checked ? "checked" : "unchecked";
3698
3906
  const [phase, setPhase] = useState(initialPhase);
3699
3907
  useEffect(() => {
3700
- var _a;
3908
+ var _a2;
3701
3909
  if (!hasLoadSequence) return;
3702
3910
  const loadDelay = item.loadDelay * 1e3;
3703
- const loadDuration = ((_a = item.loadDuration) != null ? _a : 4) * 1e3;
3911
+ const loadDuration = ((_a2 = item.loadDuration) != null ? _a2 : 4) * 1e3;
3704
3912
  const loadTimer = setTimeout(() => setPhase("loading"), loadDelay);
3705
3913
  const checkTimer = setTimeout(
3706
3914
  () => setPhase("checked"),
@@ -3714,6 +3922,8 @@ function ChecklistItemRow({
3714
3922
  const CustomIcon = item.icon ? lucideIcons7[item.icon] : void 0;
3715
3923
  const IconComp = CustomIcon && ("render" in CustomIcon || typeof CustomIcon === "function") ? CustomIcon : Check;
3716
3924
  const hasCustomIcon = IconComp !== Check;
3925
+ const textContent = (_b = (_a = item.segments) != null ? _a : item.text) != null ? _b : "";
3926
+ const textStyle = item.textColor || item.lineHeight != null ? { color: item.textColor, lineHeight: item.lineHeight } : void 0;
3717
3927
  const iconProps = item.iconGradient ? { stroke: `url(#${gradientId})` } : item.iconColor ? { color: item.iconColor } : {};
3718
3928
  const content = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3719
3929
  /* @__PURE__ */ jsx(
@@ -3776,13 +3986,14 @@ function ChecklistItemRow({
3776
3986
  }
3777
3987
  ),
3778
3988
  /* @__PURE__ */ jsx(
3779
- "span",
3989
+ RichText,
3780
3990
  {
3991
+ content: textContent,
3781
3992
  className: cn(
3782
3993
  "text-sm transition-colors duration-300",
3783
3994
  phase === "checked" ? "text-foreground" : "text-muted-foreground"
3784
3995
  ),
3785
- children: item.text
3996
+ style: textStyle
3786
3997
  }
3787
3998
  )
3788
3999
  ] });
@@ -4128,6 +4339,9 @@ function CircularProgressBlock({
4128
4339
  );
4129
4340
  }
4130
4341
  var lucideIcons8 = icons;
4342
+ var DEFAULT_CHART_HEIGHT = 280;
4343
+ var DEFAULT_Y_AXIS_LABEL_GUTTER = 64;
4344
+ var Y_AXIS_LABEL_GAP = 8;
4131
4345
  function toSvg(points, w, h, pad) {
4132
4346
  const drawW = w - pad.left - pad.right;
4133
4347
  const drawH = h - pad.top - pad.bottom;
@@ -4193,10 +4407,36 @@ function xValToPercent(xVal, svgW, pad) {
4193
4407
  const drawW = svgW - pad.left - pad.right;
4194
4408
  return (pad.left + xVal / 100 * drawW) / svgW * 100;
4195
4409
  }
4410
+ function toCssLength(value, fallback) {
4411
+ if (value === void 0) return fallback;
4412
+ if (typeof value === "number") {
4413
+ return Number.isFinite(value) && value > 0 ? `${value}px` : fallback;
4414
+ }
4415
+ const trimmed = value.trim();
4416
+ if (!trimmed) return fallback;
4417
+ return /^-?\d+(\.\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;
4418
+ }
4419
+ function toViewBoxHeight(value) {
4420
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
4421
+ return value;
4422
+ }
4423
+ if (typeof value === "string") {
4424
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)(?:px)?$/i);
4425
+ if (match) {
4426
+ const parsed = Number(match[1]);
4427
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
4428
+ }
4429
+ }
4430
+ return DEFAULT_CHART_HEIGHT;
4431
+ }
4432
+ function toPositiveNumber(value, fallback) {
4433
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
4434
+ }
4196
4435
  function LineChartBlock({
4197
4436
  lines = [],
4198
4437
  width = "100%",
4199
4438
  height = 280,
4439
+ yLabelGutter,
4200
4440
  yLabels,
4201
4441
  xLabels,
4202
4442
  animationDuration = 1.5,
@@ -4211,12 +4451,19 @@ function LineChartBlock({
4211
4451
  const [animate2, setAnimate] = useState(false);
4212
4452
  const pathRefs = useRef([]);
4213
4453
  const dotRefs = useRef([]);
4214
- const svgH = height;
4454
+ const svgH = toViewBoxHeight(height);
4455
+ const chartWidth = toCssLength(width, "100%");
4456
+ const chartHeight = toCssLength(height, `${DEFAULT_CHART_HEIGHT}px`);
4215
4457
  const hasEndLabels = lines.some((l) => l.endLabel || l.endIcon);
4216
4458
  const hasEndDots = lines.some((l) => l.endDot);
4217
4459
  const hasYLabels = !!((yLabels == null ? void 0 : yLabels.top) || (yLabels == null ? void 0 : yLabels.bottom));
4218
4460
  const padRight = hasEndDots ? 80 : hasEndLabels ? 60 : 16;
4219
- const pad = { top: 16, right: padRight, bottom: 16, left: hasYLabels ? 48 : 16 };
4461
+ const pad = {
4462
+ top: 16,
4463
+ right: padRight,
4464
+ bottom: 16,
4465
+ left: hasYLabels ? toPositiveNumber(yLabelGutter, DEFAULT_Y_AXIS_LABEL_GUTTER) : 16
4466
+ };
4220
4467
  useEffect(() => {
4221
4468
  const timer = setTimeout(() => setAnimate(true), startDelay * 1e3);
4222
4469
  return () => clearTimeout(timer);
@@ -4231,6 +4478,7 @@ function LineChartBlock({
4231
4478
  });
4232
4479
  const durationMs = animationDuration * 1e3;
4233
4480
  const hasDots = lines.some((l) => l.endDot);
4481
+ const yAxisLabelWidth = `max(0px, calc(${pad.left / svgW * 100}% - ${Y_AXIS_LABEL_GAP}px))`;
4234
4482
  useEffect(() => {
4235
4483
  if (!animate2 || !hasDots) return;
4236
4484
  const startTime = performance.now();
@@ -4256,299 +4504,329 @@ function LineChartBlock({
4256
4504
  return () => cancelAnimationFrame(rafId);
4257
4505
  }, [animate2, hasDots, durationMs, svgW, svgH]);
4258
4506
  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) => {
4507
+ const yAxisLabelStyle = (top) => ({
4508
+ top,
4509
+ left: 0,
4510
+ width: yAxisLabelWidth,
4511
+ maxWidth: yAxisLabelWidth,
4512
+ transform: "translateY(-50%)",
4513
+ color: labelColor,
4514
+ textAlign: "right",
4515
+ whiteSpace: "pre-line",
4516
+ overflowWrap: "anywhere",
4517
+ lineHeight: 1.1,
4518
+ pointerEvents: "none"
4519
+ });
4520
+ return /* @__PURE__ */ jsxs(
4521
+ "div",
4522
+ {
4523
+ className: cn("w-full overflow-visible", className),
4524
+ style: { width: chartWidth },
4525
+ children: [
4526
+ /* @__PURE__ */ jsxs(
4527
+ "div",
4528
+ {
4529
+ className: "relative overflow-visible",
4530
+ style: { height: chartHeight },
4531
+ children: [
4532
+ (yLabels == null ? void 0 : yLabels.top) && /* @__PURE__ */ jsx(
4533
+ "span",
4534
+ {
4535
+ className: "absolute text-xs font-medium",
4536
+ style: yAxisLabelStyle(
4537
+ toPctY(
4538
+ yLabels.topAt != null ? yValToPixel(yLabels.topAt, svgH, pad) : pad.top
4539
+ )
4540
+ ),
4541
+ children: yLabels.top
4542
+ }
4543
+ ),
4544
+ (yLabels == null ? void 0 : yLabels.bottom) && /* @__PURE__ */ jsx(
4545
+ "span",
4546
+ {
4547
+ className: "absolute text-xs font-medium",
4548
+ style: yAxisLabelStyle(
4549
+ toPctY(
4550
+ yLabels.bottomAt != null ? yValToPixel(yLabels.bottomAt, svgH, pad) : svgH - pad.bottom
4551
+ )
4552
+ ),
4553
+ children: yLabels.bottom
4554
+ }
4555
+ ),
4556
+ /* @__PURE__ */ jsxs(
4557
+ "svg",
4558
+ {
4559
+ viewBox: `0 0 ${svgW} ${svgH}`,
4560
+ preserveAspectRatio: "none",
4561
+ className: "w-full h-full",
4562
+ style: { overflow: "visible" },
4563
+ children: [
4564
+ /* @__PURE__ */ jsxs("defs", { children: [
4565
+ linesData.map(
4566
+ (line, i) => {
4567
+ var _a2;
4568
+ return line.fill ? /* @__PURE__ */ jsxs(
4569
+ "linearGradient",
4570
+ {
4571
+ id: `lc-fill-${uid}-${i}`,
4572
+ x1: "0",
4573
+ y1: "0",
4574
+ x2: "0",
4575
+ y2: "1",
4576
+ children: [
4577
+ /* @__PURE__ */ jsx(
4578
+ "stop",
4579
+ {
4580
+ offset: "0%",
4581
+ stopColor: line.fill.from,
4582
+ stopOpacity: (_a2 = line.fill.opacity) != null ? _a2 : 0.4
4583
+ }
4584
+ ),
4585
+ /* @__PURE__ */ jsx(
4586
+ "stop",
4587
+ {
4588
+ offset: "100%",
4589
+ stopColor: line.fill.to,
4590
+ stopOpacity: 0
4591
+ }
4592
+ )
4593
+ ]
4594
+ },
4595
+ `fill-${i}`
4596
+ ) : null;
4597
+ }
4598
+ ),
4599
+ linesData.map(
4600
+ (line, i) => line.fill ? /* @__PURE__ */ jsxs(
4601
+ "linearGradient",
4602
+ {
4603
+ id: `lc-mask-grad-${uid}-${i}`,
4604
+ x1: "0",
4605
+ y1: "0",
4606
+ x2: "0",
4607
+ y2: "1",
4608
+ children: [
4609
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "white" }),
4610
+ /* @__PURE__ */ jsx("stop", { offset: "70%", stopColor: "white" }),
4611
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "black" })
4612
+ ]
4613
+ },
4614
+ `mask-${i}`
4615
+ ) : null
4616
+ ),
4617
+ linesData.map(
4618
+ (line, i) => line.fill ? /* @__PURE__ */ jsx("mask", { id: `lc-mask-${uid}-${i}`, children: /* @__PURE__ */ jsx(
4619
+ "rect",
4620
+ {
4621
+ x: "0",
4622
+ y: animate2 ? "0" : `-${svgH}`,
4623
+ width: svgW,
4624
+ height: svgH,
4625
+ fill: `url(#lc-mask-grad-${uid}-${i})`,
4626
+ style: {
4627
+ transition: `y ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4628
+ }
4629
+ }
4630
+ ) }, `mask-el-${i}`) : null
4631
+ )
4632
+ ] }),
4633
+ showGrid && [25, 50, 75].map((pct) => {
4634
+ const y = pad.top + (100 - pct) / 100 * (svgH - pad.top - pad.bottom);
4635
+ return /* @__PURE__ */ jsx(
4636
+ "line",
4637
+ {
4638
+ x1: pad.left,
4639
+ y1: y,
4640
+ x2: svgW - pad.right,
4641
+ y2: y,
4642
+ stroke: axisColor,
4643
+ strokeWidth: 0.5,
4644
+ strokeDasharray: "4 4"
4645
+ },
4646
+ pct
4647
+ );
4648
+ }),
4649
+ linesData.map(
4650
+ (line, i) => line.fillDPath ? /* @__PURE__ */ jsx(
4651
+ "path",
4652
+ {
4653
+ d: line.fillDPath,
4654
+ fill: `url(#lc-fill-${uid}-${i})`,
4655
+ mask: `url(#lc-mask-${uid}-${i})`
4656
+ },
4657
+ `area-${i}`
4658
+ ) : null
4659
+ ),
4660
+ linesData.map((line, i) => {
4305
4661
  var _a2;
4306
- return line.fill ? /* @__PURE__ */ jsxs(
4307
- "linearGradient",
4662
+ return /* @__PURE__ */ jsx(
4663
+ "path",
4308
4664
  {
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
- ]
4665
+ ref: (el) => {
4666
+ pathRefs.current[i] = el;
4667
+ },
4668
+ d: line.lineDPath,
4669
+ fill: "none",
4670
+ stroke: line.color,
4671
+ strokeWidth: (_a2 = line.strokeWidth) != null ? _a2 : 3,
4672
+ strokeLinecap: "round",
4673
+ strokeLinejoin: "round",
4674
+ pathLength: 1,
4675
+ style: line.dashed ? {
4676
+ strokeDasharray: "8 6",
4677
+ opacity: animate2 ? 1 : 0,
4678
+ transition: `opacity ${durationMs}ms ease`
4679
+ } : {
4680
+ strokeDasharray: 1,
4681
+ strokeDashoffset: animate2 ? 0 : 1,
4682
+ transition: `stroke-dashoffset ${durationMs}ms cubic-bezier(0.25, 0.4, 0.25, 1)`
4683
+ }
4332
4684
  },
4333
- `fill-${i}`
4334
- ) : null;
4335
- }
4336
- ),
4337
- linesData.map(
4338
- (line, i) => line.fill ? /* @__PURE__ */ jsxs(
4339
- "linearGradient",
4685
+ `line-${i}`
4686
+ );
4687
+ }),
4688
+ /* @__PURE__ */ jsx(
4689
+ "line",
4340
4690
  {
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",
4691
+ x1: pad.left,
4692
+ y1: pad.top,
4693
+ x2: pad.left,
4694
+ y2: svgH - pad.bottom,
4695
+ stroke: axisColor,
4696
+ strokeWidth: 2
4697
+ }
4698
+ ),
4699
+ /* @__PURE__ */ jsx(
4700
+ "line",
4358
4701
  {
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
- }
4702
+ x1: pad.left,
4703
+ y1: svgH - pad.bottom,
4704
+ x2: svgW - pad.right,
4705
+ y2: svgH - pad.bottom,
4706
+ stroke: axisColor,
4707
+ strokeWidth: 2
4367
4708
  }
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"
4709
+ )
4710
+ ]
4711
+ }
4712
+ ),
4713
+ linesData.map((line, i) => {
4714
+ var _a2, _b2, _c, _d, _e;
4715
+ if (!line.endDot) return null;
4716
+ const dotSize = (_a2 = line.endDot.size) != null ? _a2 : 28;
4717
+ const iconSize = dotSize * 0.55;
4718
+ const imageScale = typeof line.endDot.imageScale === "number" && Number.isFinite(line.endDot.imageScale) ? Math.min(Math.max(line.endDot.imageScale, 0), 1) : 0.72;
4719
+ const imageSize = dotSize * imageScale;
4720
+ const LucideIcon = line.endDot.lucideIcon ? lucideIcons8[line.endDot.lucideIcon] : null;
4721
+ return /* @__PURE__ */ jsx(
4722
+ "div",
4723
+ {
4724
+ ref: (el) => {
4725
+ dotRefs.current[i] = el;
4383
4726
  },
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})`
4727
+ className: "absolute flex items-center justify-center rounded-full shadow-lg",
4728
+ style: {
4729
+ width: dotSize,
4730
+ height: dotSize,
4731
+ 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,
4732
+ transform: "translate(-50%, -50%)",
4733
+ opacity: 0,
4734
+ left: 0,
4735
+ top: 0,
4736
+ zIndex: 10
4394
4737
  },
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)`
4738
+ children: line.endDot.imageSrc ? /* @__PURE__ */ jsx(
4739
+ "img",
4740
+ {
4741
+ src: line.endDot.imageSrc,
4742
+ alt: (_d = line.endDot.imageAlt) != null ? _d : "",
4743
+ draggable: false,
4744
+ className: "rounded-full object-contain",
4745
+ style: {
4746
+ width: imageSize,
4747
+ height: imageSize
4748
+ }
4421
4749
  }
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",
4750
+ ) : LucideIcon ? /* @__PURE__ */ jsx(
4751
+ LucideIcon,
4752
+ {
4753
+ size: iconSize,
4754
+ color: (_e = line.endDot.lucideIconColor) != null ? _e : "#fff",
4755
+ strokeWidth: 2.5
4756
+ }
4757
+ ) : line.endDot.icon ? /* @__PURE__ */ jsx("span", { style: { fontSize: iconSize, lineHeight: 1 }, children: line.endDot.icon }) : null
4758
+ },
4759
+ `dot-${i}`
4760
+ );
4761
+ }),
4762
+ linesData.map((line, i) => {
4763
+ var _a2, _b2, _c, _d, _e, _f, _g;
4764
+ if (!line.endIcon && !line.endLabel) return null;
4765
+ const cx = line.lastPt[0];
4766
+ const cy = line.lastPt[1];
4767
+ const labelLines = (_b2 = (_a2 = line.endLabel) == null ? void 0 : _a2.split("\n")) != null ? _b2 : [];
4768
+ const hasDot = !!line.endDot;
4769
+ const offX = (_d = (_c = line.endLabelOffset) == null ? void 0 : _c.x) != null ? _d : 0;
4770
+ const offY = (_f = (_e = line.endLabelOffset) == null ? void 0 : _e.y) != null ? _f : 0;
4771
+ return /* @__PURE__ */ jsxs(
4772
+ "div",
4439
4773
  {
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
- ]
4774
+ className: "absolute flex flex-col items-center",
4775
+ style: __spreadProps(__spreadValues({}, hasDot ? {
4776
+ left: `calc(${cx / svgW * 100}% + ${offX}px)`,
4777
+ transform: "translateX(-50%)"
4778
+ } : { right: 0 }), {
4779
+ top: toPctY(cy + offY),
4780
+ opacity: animate2 ? 1 : 0,
4781
+ transition: `opacity 400ms ease ${durationMs * 0.8}ms`,
4782
+ textAlign: "center",
4783
+ whiteSpace: "nowrap"
4784
+ }),
4785
+ children: [
4786
+ line.endIcon && /* @__PURE__ */ jsx("span", { className: "text-lg", children: line.endIcon }),
4787
+ labelLines.length > 0 && /* @__PURE__ */ jsx(
4788
+ "span",
4789
+ {
4790
+ className: "text-xs font-semibold leading-tight",
4791
+ style: { color: (_g = line.endLabelColor) != null ? _g : labelColor },
4792
+ children: labelLines.map((ln, j) => /* @__PURE__ */ jsx("span", { className: "block", children: ln }, j))
4793
+ }
4794
+ )
4795
+ ]
4796
+ },
4797
+ `end-${i}`
4798
+ );
4799
+ })
4800
+ ]
4801
+ }
4802
+ ),
4803
+ xLabels && /* @__PURE__ */ jsxs("div", { className: "relative text-xs font-medium mt-1", style: { color: labelColor, height: 20 }, children: [
4804
+ xLabels.left && /* @__PURE__ */ jsx(
4805
+ "span",
4806
+ {
4807
+ className: "absolute",
4808
+ style: {
4809
+ left: `${xValToPercent((_a = xLabels.leftAt) != null ? _a : 0, svgW, pad)}%`,
4810
+ transform: "translateX(-50%)"
4811
+ },
4812
+ children: xLabels.left
4449
4813
  }
4450
4814
  ),
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
- ]
4815
+ xLabels.right && /* @__PURE__ */ jsx(
4816
+ "span",
4817
+ {
4818
+ className: "absolute",
4819
+ style: {
4820
+ left: `${xValToPercent((_b = xLabels.rightAt) != null ? _b : 100, svgW, pad)}%`,
4821
+ transform: "translateX(-50%)"
4520
4822
  },
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
- ] });
4823
+ children: xLabels.right
4824
+ }
4825
+ )
4826
+ ] })
4827
+ ]
4828
+ }
4829
+ );
4552
4830
  }
4553
4831
  function getInitials2(name) {
4554
4832
  return name.split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0].toUpperCase()).join("");
@@ -5535,6 +5813,7 @@ function TimelineBlock({
5535
5813
  } : {};
5536
5814
  const renderNodeIcon = iconPlacement === "node" && item.icon;
5537
5815
  const renderContentIcon = iconPlacement === "content" && item.icon;
5816
+ const renderTitleIcon = iconPlacement === "before-title" && item.icon;
5538
5817
  const showLabel = labelPlacement === "above" && item.label;
5539
5818
  const showOutgoing = !isLast || !!(endLine == null ? void 0 : endLine.show);
5540
5819
  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 +5829,45 @@ function TimelineBlock({
5550
5829
  const defaultItemSpacing = Math.max(16, nodeSize * 0.4);
5551
5830
  const bottomPad = isLast ? (endLine == null ? void 0 : endLine.show) ? EDGE_LINE_HEIGHT : 0 : itemSpacing != null ? itemSpacing : defaultItemSpacing;
5552
5831
  const nodeTopMargin = iconPlacement === "node" ? 0 : 4;
5832
+ const contentBody = /* @__PURE__ */ jsxs(Fragment, { children: [
5833
+ renderContentIcon && /* @__PURE__ */ jsx("div", { className: "mb-1.5", children: renderIcon3(item.icon) }),
5834
+ /* @__PURE__ */ jsxs(
5835
+ "div",
5836
+ {
5837
+ style: iconPlacement === "node" ? {
5838
+ minHeight: nodeSize,
5839
+ display: "flex",
5840
+ flexDirection: "column",
5841
+ justifyContent: "center"
5842
+ } : void 0,
5843
+ children: [
5844
+ showLabel && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium text-muted-foreground/70 uppercase tracking-wider", children: item.label }),
5845
+ /* @__PURE__ */ jsx(
5846
+ "h4",
5847
+ {
5848
+ className: cn(
5849
+ "font-semibold text-foreground",
5850
+ iconPlacement === "node" ? "text-xl leading-tight" : "text-sm",
5851
+ renderTitleIcon ? "flex items-center gap-2" : null
5852
+ ),
5853
+ children: renderTitleIcon ? /* @__PURE__ */ jsxs(Fragment, { children: [
5854
+ /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0", children: renderIcon3(item.icon) }),
5855
+ /* @__PURE__ */ jsx("span", { className: "min-w-0", children: item.title })
5856
+ ] }) : item.title
5857
+ }
5858
+ )
5859
+ ]
5860
+ }
5861
+ ),
5862
+ item.description && /* @__PURE__ */ jsx(
5863
+ "p",
5864
+ {
5865
+ className: "text-sm text-muted-foreground",
5866
+ style: { marginTop: resolvedTitleDescGap },
5867
+ children: item.description
5868
+ }
5869
+ )
5870
+ ] });
5553
5871
  return /* @__PURE__ */ jsxs(Wrapper, __spreadProps(__spreadValues({ className: "relative flex" }, animProps), { children: [
5554
5872
  /* @__PURE__ */ jsxs(
5555
5873
  "div",
@@ -5613,46 +5931,19 @@ function TimelineBlock({
5613
5931
  ]
5614
5932
  }
5615
5933
  ),
5616
- /* @__PURE__ */ jsxs(
5934
+ /* @__PURE__ */ jsx(
5617
5935
  "div",
5618
5936
  {
5619
5937
  className: "flex-1 min-w-0",
5620
5938
  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
- ]
5939
+ children: item.contentCard ? /* @__PURE__ */ jsx(
5940
+ "div",
5941
+ {
5942
+ className: "rounded-lg border border-border/60 bg-card/80 p-3 shadow-sm",
5943
+ "data-timeline-content-card": "true",
5944
+ children: contentBody
5945
+ }
5946
+ ) : contentBody
5656
5947
  }
5657
5948
  )
5658
5949
  ] }), i);
@@ -7629,13 +7920,13 @@ function MotionAnimatedBlock({
7629
7920
  const exitMotion = exitPreset && exitPreset !== "none" ? EXIT_PRESETS[exitPreset] : void 0;
7630
7921
  const rawExitDuration = (_b = exitAnim == null ? void 0 : exitAnim.duration) != null ? _b : DEFAULT_EXIT_DURATION_S;
7631
7922
  const rawExitDelay = (_c = exitAnim == null ? void 0 : exitAnim.delay) != null ? _c : 0;
7632
- const exitDuration = rawExitDuration > 10 ? rawExitDuration / 1e3 : rawExitDuration;
7633
- const exitDelay = rawExitDelay > 10 ? rawExitDelay / 1e3 : rawExitDelay;
7923
+ const exitDuration = rawExitDuration;
7924
+ const exitDelay = rawExitDelay;
7634
7925
  const motionConfig = preset !== "none" ? PRESETS[preset] : null;
7635
7926
  const rawDuration = (_d = anim == null ? void 0 : anim.duration) != null ? _d : DEFAULT_DURATION_S;
7636
7927
  const rawDelay = (_e = anim == null ? void 0 : anim.delay) != null ? _e : visibleIndex * DEFAULT_STAGGER_S;
7637
- const duration = rawDuration > 10 ? rawDuration / 1e3 : rawDuration;
7638
- const delay = rawDelay > 10 ? rawDelay / 1e3 : rawDelay;
7928
+ const duration = rawDuration;
7929
+ const delay = rawDelay;
7639
7930
  const delayMs = delay * 1e3;
7640
7931
  const [delayedIn, setDelayedIn] = useState(delayMs === 0);
7641
7932
  useEffect(() => {
@@ -7644,7 +7935,7 @@ function MotionAnimatedBlock({
7644
7935
  return () => clearTimeout(id);
7645
7936
  }, [delayMs]);
7646
7937
  const rawAutoHide = exitAnim == null ? void 0 : exitAnim.autoHideAfter;
7647
- const autoHideS = rawAutoHide != null && rawAutoHide > 0 ? rawAutoHide > 1e4 ? rawAutoHide / 1e3 : rawAutoHide : null;
7938
+ const autoHideS = rawAutoHide != null && rawAutoHide > 0 ? rawAutoHide : null;
7648
7939
  const [autoHidden, setAutoHidden] = useState(false);
7649
7940
  useEffect(() => {
7650
7941
  if (autoHideS == null) return;