@diagrammo/dgmo 0.7.0 → 0.7.2

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.cjs CHANGED
@@ -1517,24 +1517,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
1517
1517
  }
1518
1518
  return { series, names, nameColors, newIndex };
1519
1519
  }
1520
- function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
1520
+ function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
1521
+ if (segments.length > 2 && warnMultiplePipes) {
1522
+ warnMultiplePipes();
1523
+ }
1521
1524
  const metadata = {};
1522
- for (let j = 1; j < segments.length; j++) {
1523
- for (const part of segments[j].split(",")) {
1524
- const trimmedPart = part.trim();
1525
- if (!trimmedPart) continue;
1526
- const colonIdx = trimmedPart.indexOf(":");
1527
- if (colonIdx > 0) {
1528
- const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1529
- const key = aliasMap.get(rawKey) ?? rawKey;
1530
- const value = trimmedPart.substring(colonIdx + 1).trim();
1531
- metadata[key] = value;
1532
- }
1525
+ const raw = segments.slice(1).join(",");
1526
+ for (const part of raw.split(",")) {
1527
+ const trimmedPart = part.trim();
1528
+ if (!trimmedPart) continue;
1529
+ const colonIdx = trimmedPart.indexOf(":");
1530
+ if (colonIdx > 0) {
1531
+ const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1532
+ const key = aliasMap.get(rawKey) ?? rawKey;
1533
+ const value = trimmedPart.substring(colonIdx + 1).trim();
1534
+ metadata[key] = value;
1533
1535
  }
1534
1536
  }
1535
1537
  return metadata;
1536
1538
  }
1537
- var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
1539
+ var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
1538
1540
  var init_parsing = __esm({
1539
1541
  "src/utils/parsing.ts"() {
1540
1542
  "use strict";
@@ -1543,6 +1545,7 @@ var init_parsing = __esm({
1543
1545
  CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
1544
1546
  TITLE_RE = /^title\s*:\s*(.+)/i;
1545
1547
  OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
1548
+ MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
1546
1549
  }
1547
1550
  });
1548
1551
 
@@ -2043,12 +2046,13 @@ function parseSequenceDgmo(content) {
2043
2046
  const participantGroupMap = /* @__PURE__ */ new Map();
2044
2047
  let currentTagGroup = null;
2045
2048
  const aliasMap = /* @__PURE__ */ new Map();
2046
- const splitPipe = (text) => {
2049
+ const splitPipe = (text, ln) => {
2047
2050
  const idx = text.indexOf("|");
2048
2051
  if (idx < 0) return { core: text };
2049
2052
  const core = text.substring(0, idx).trimEnd();
2050
2053
  const segments = text.substring(idx).split("|");
2051
- const meta = parsePipeMetadata(segments, aliasMap);
2054
+ const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
2055
+ const meta = parsePipeMetadata(segments, aliasMap, warnFn);
2052
2056
  return Object.keys(meta).length > 0 ? { core, meta } : { core };
2053
2057
  };
2054
2058
  const blockStack = [];
@@ -2077,7 +2081,7 @@ function parseSequenceDgmo(content) {
2077
2081
  if (gpipeIdx >= 0) {
2078
2082
  const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
2079
2083
  const segments = groupName.substring(gpipeIdx).split("|");
2080
- const meta = parsePipeMetadata(segments, aliasMap);
2084
+ const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
2081
2085
  if (Object.keys(meta).length > 0) groupMeta = meta;
2082
2086
  const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
2083
2087
  if (colorSuffix) {
@@ -2200,7 +2204,7 @@ function parseSequenceDgmo(content) {
2200
2204
  continue;
2201
2205
  }
2202
2206
  }
2203
- const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
2207
+ const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
2204
2208
  const isAMatch = isACore.match(IS_A_PATTERN);
2205
2209
  if (isAMatch) {
2206
2210
  contentStarted = true;
@@ -2237,7 +2241,7 @@ function parseSequenceDgmo(content) {
2237
2241
  }
2238
2242
  continue;
2239
2243
  }
2240
- const { core: posCore, meta: posMeta } = splitPipe(trimmed);
2244
+ const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
2241
2245
  const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
2242
2246
  if (posOnlyMatch) {
2243
2247
  contentStarted = true;
@@ -2264,7 +2268,7 @@ function parseSequenceDgmo(content) {
2264
2268
  }
2265
2269
  continue;
2266
2270
  }
2267
- const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
2271
+ const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
2268
2272
  const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
2269
2273
  if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
2270
2274
  const id = coloredMatch[1];
@@ -2292,7 +2296,7 @@ function parseSequenceDgmo(content) {
2292
2296
  continue;
2293
2297
  }
2294
2298
  {
2295
- const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
2299
+ const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
2296
2300
  const inGroup = activeGroup && measureIndent(raw) > 0;
2297
2301
  if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
2298
2302
  contentStarted = true;
@@ -2328,7 +2332,7 @@ function parseSequenceDgmo(content) {
2328
2332
  }
2329
2333
  blockStack.pop();
2330
2334
  }
2331
- const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
2335
+ const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
2332
2336
  const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
2333
2337
  if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
2334
2338
  pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
@@ -3779,7 +3783,12 @@ function parseERDiagram(content, palette) {
3779
3783
  table.lineNumber = lineNumber;
3780
3784
  const pipeStr = tableDecl[3]?.trim();
3781
3785
  if (pipeStr) {
3782
- const meta = parsePipeMetadata(["", pipeStr], aliasMap);
3786
+ const pipeSegments = pipeStr.split("|");
3787
+ const meta = parsePipeMetadata(
3788
+ ["", ...pipeSegments],
3789
+ aliasMap,
3790
+ () => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
3791
+ );
3783
3792
  Object.assign(table.metadata, meta);
3784
3793
  }
3785
3794
  currentTable = table;
@@ -5716,13 +5725,13 @@ function parseOrg(content, palette) {
5716
5725
  }
5717
5726
  } else if (metadataMatch && indentStack.length === 0) {
5718
5727
  if (indent === 0) {
5719
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5728
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5720
5729
  attachNode(node, indent, indentStack, result);
5721
5730
  } else {
5722
5731
  pushError(lineNumber, "Metadata has no parent node");
5723
5732
  }
5724
5733
  } else {
5725
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5734
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5726
5735
  attachNode(node, indent, indentStack, result);
5727
5736
  }
5728
5737
  }
@@ -5744,11 +5753,11 @@ function parseOrg(content, palette) {
5744
5753
  }
5745
5754
  return result;
5746
5755
  }
5747
- function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
5756
+ function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
5748
5757
  const segments = trimmed.split("|").map((s) => s.trim());
5749
5758
  let rawLabel = segments[0];
5750
5759
  const { label, color } = extractColor(rawLabel, palette);
5751
- const metadata = parsePipeMetadata(segments, aliasMap);
5760
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
5752
5761
  return {
5753
5762
  id: `node-${counter}`,
5754
5763
  label,
@@ -6290,7 +6299,7 @@ function parseC4(content, palette) {
6290
6299
  }
6291
6300
  const segments = trimmed.split("|").map((s) => s.trim());
6292
6301
  const nodeName = segments[0];
6293
- const metadata = parsePipeMetadata(segments, aliasMap);
6302
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6294
6303
  const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
6295
6304
  const dNode = {
6296
6305
  name: nodeName,
@@ -6443,7 +6452,7 @@ function parseC4(content, palette) {
6443
6452
  }
6444
6453
  namePart = namePart.substring(0, isAMatch.index).trim();
6445
6454
  }
6446
- const metadata = parsePipeMetadata(segments, aliasMap);
6455
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6447
6456
  const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
6448
6457
  const element = {
6449
6458
  name: namePart,
@@ -7230,14 +7239,14 @@ function parseSitemap(content, palette) {
7230
7239
  }
7231
7240
  } else if (metadataMatch && indentStack.length === 0) {
7232
7241
  if (indent === 0) {
7233
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7242
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7234
7243
  attachNode2(node, indent, indentStack, result);
7235
7244
  labelToNode.set(node.label.toLowerCase(), node);
7236
7245
  } else {
7237
7246
  pushError(lineNumber, "Metadata has no parent node");
7238
7247
  }
7239
7248
  } else {
7240
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7249
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7241
7250
  attachNode2(node, indent, indentStack, result);
7242
7251
  labelToNode.set(node.label.toLowerCase(), node);
7243
7252
  }
@@ -7279,11 +7288,11 @@ function parseSitemap(content, palette) {
7279
7288
  }
7280
7289
  return result;
7281
7290
  }
7282
- function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
7291
+ function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
7283
7292
  const segments = trimmed.split("|").map((s) => s.trim());
7284
7293
  const rawLabel = segments[0];
7285
7294
  const { label, color } = extractColor(rawLabel, palette);
7286
- const metadata = parsePipeMetadata(segments, aliasMap);
7295
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
7287
7296
  return {
7288
7297
  id: `node-${counter}`,
7289
7298
  label,
@@ -7977,7 +7986,9 @@ function parseGantt(content, palette) {
7977
7986
  criticalPath: false,
7978
7987
  dependencies: false,
7979
7988
  sort: "default",
7980
- defaultSwimlaneGroup: null
7989
+ defaultSwimlaneGroup: null,
7990
+ optionLineNumbers: {},
7991
+ holidaysLineNumber: null
7981
7992
  },
7982
7993
  diagnostics,
7983
7994
  error: null
@@ -7991,6 +8002,9 @@ function parseGantt(content, palette) {
7991
8002
  const warn = (line10, message) => {
7992
8003
  diagnostics.push(makeDgmoError(line10, message, "warning"));
7993
8004
  };
8005
+ const softError = (line10, message) => {
8006
+ diagnostics.push(makeDgmoError(line10, message, "error"));
8007
+ };
7994
8008
  const aliasMap = /* @__PURE__ */ new Map();
7995
8009
  const blockStack = [];
7996
8010
  const currentContainer = () => {
@@ -8124,10 +8138,10 @@ function parseGantt(content, palette) {
8124
8138
  const targetName = depParts[0].trim();
8125
8139
  let offset;
8126
8140
  if (depParts.length > 1) {
8127
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8141
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8128
8142
  if (meta.lag || meta.lead) {
8129
8143
  const key = meta.lag ? "lag" : "lead";
8130
- return fail(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8144
+ softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8131
8145
  }
8132
8146
  if (meta.offset) {
8133
8147
  const raw = meta.offset;
@@ -8159,6 +8173,7 @@ function parseGantt(content, palette) {
8159
8173
  inHolidaysBlock = true;
8160
8174
  holidaysBlockIndent = indent;
8161
8175
  inHeaderBlock = false;
8176
+ result.options.holidaysLineNumber = lineNumber;
8162
8177
  continue;
8163
8178
  }
8164
8179
  const tagMatch = matchTagBlockHeading(line10);
@@ -8185,7 +8200,8 @@ function parseGantt(content, palette) {
8185
8200
  startDate: eraMatch[1],
8186
8201
  endDate: eraMatch[2],
8187
8202
  label: eraExtracted.label,
8188
- color: eraExtracted.color || null
8203
+ color: eraExtracted.color || null,
8204
+ lineNumber
8189
8205
  });
8190
8206
  inHeaderBlock = false;
8191
8207
  continue;
@@ -8207,6 +8223,7 @@ function parseGantt(content, palette) {
8207
8223
  if (optMatch && isKnownOption(optMatch[1].toLowerCase())) {
8208
8224
  const key = optMatch[1].toLowerCase();
8209
8225
  const value = optMatch[2].trim();
8226
+ result.options.optionLineNumbers[key] = lineNumber;
8210
8227
  switch (key) {
8211
8228
  case "start":
8212
8229
  result.options.start = value;
@@ -8266,16 +8283,18 @@ function parseGantt(content, palette) {
8266
8283
  const groupMatch = line10.match(GROUP_RE2);
8267
8284
  if (groupMatch) {
8268
8285
  if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
8269
- return fail(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8286
+ softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8287
+ continue;
8270
8288
  }
8271
8289
  const afterBrackets = groupMatch[2].trim();
8272
8290
  const segments = afterBrackets ? afterBrackets.split("|") : [];
8273
8291
  let metadata = {};
8274
8292
  let color = null;
8293
+ const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
8275
8294
  if (segments.length > 0 && segments[0].trim()) {
8276
- metadata = parsePipeMetadata(["", ...segments], aliasMap);
8295
+ metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
8277
8296
  } else if (segments.length > 1) {
8278
- metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
8297
+ metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
8279
8298
  }
8280
8299
  const nameExtracted = extractColor(groupMatch[1], palette);
8281
8300
  if (nameExtracted.color) {
@@ -8306,7 +8325,6 @@ function parseGantt(content, palette) {
8306
8325
  const uncertain = !!timelineDurMatch[4];
8307
8326
  const labelRaw = timelineDurMatch[5];
8308
8327
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
8309
- if (result.error) return result;
8310
8328
  const taskNode = { kind: "task", ...task };
8311
8329
  currentContainer().push(taskNode);
8312
8330
  lastTaskNode = taskNode;
@@ -8320,7 +8338,6 @@ function parseGantt(content, palette) {
8320
8338
  const uncertain = !!durMatch[3];
8321
8339
  const labelRaw = durMatch[4];
8322
8340
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
8323
- if (result.error) return result;
8324
8341
  const taskNode = { kind: "task", ...task };
8325
8342
  currentContainer().push(taskNode);
8326
8343
  lastTaskNode = taskNode;
@@ -8337,7 +8354,6 @@ function parseGantt(content, palette) {
8337
8354
  lineNumber,
8338
8355
  explicitDateMatch[1]
8339
8356
  );
8340
- if (result.error) return result;
8341
8357
  const taskNode = { kind: "task", ...task };
8342
8358
  currentContainer().push(taskNode);
8343
8359
  lastTaskNode = taskNode;
@@ -8347,13 +8363,14 @@ function parseGantt(content, palette) {
8347
8363
  const depMatch = line10.match(DEPENDENCY_RE);
8348
8364
  if (depMatch) {
8349
8365
  if (!lastTaskNode) {
8350
- return fail(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8366
+ softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8367
+ continue;
8351
8368
  }
8352
8369
  const depParts = depMatch[1].split("|");
8353
8370
  const targetName = depParts[0].trim();
8354
8371
  let offset;
8355
8372
  if (depParts.length > 1) {
8356
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8373
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8357
8374
  if (meta.lag || meta.lead) {
8358
8375
  const key = meta.lag ? "lag" : "lead";
8359
8376
  warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
@@ -8373,7 +8390,8 @@ function parseGantt(content, palette) {
8373
8390
  lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
8374
8391
  continue;
8375
8392
  }
8376
- return fail(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8393
+ softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8394
+ continue;
8377
8395
  }
8378
8396
  if (currentTagGroup) {
8379
8397
  result.tagGroups.push(currentTagGroup);
@@ -8387,16 +8405,16 @@ function parseGantt(content, palette) {
8387
8405
  const segments = labelRaw.split("|");
8388
8406
  const label = segments[0].trim();
8389
8407
  if (label.toLowerCase() === "parallel") {
8390
- fail(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8408
+ softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8391
8409
  }
8392
- const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
8410
+ const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
8393
8411
  let progress = null;
8394
8412
  if (metadata.progress) {
8395
8413
  progress = parseFloat(metadata.progress);
8396
8414
  delete metadata.progress;
8397
8415
  }
8398
- for (let j = 1; j < segments.length; j++) {
8399
- const seg = segments[j].trim();
8416
+ for (const part of segments.slice(1).join(",").split(",")) {
8417
+ const seg = part.trim();
8400
8418
  const progressMatch = seg.match(/^(\d+)%$/);
8401
8419
  if (progressMatch) {
8402
8420
  progress = parseInt(progressMatch[1], 10);
@@ -8404,7 +8422,7 @@ function parseGantt(content, palette) {
8404
8422
  }
8405
8423
  if (metadata.lag || metadata.lead) {
8406
8424
  const key = metadata.lag ? "lag" : "lead";
8407
- fail(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8425
+ softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8408
8426
  }
8409
8427
  let taskOffset;
8410
8428
  if (metadata.offset) {
@@ -8491,7 +8509,7 @@ var init_parser10 = __esm({
8491
8509
  DEPENDENCY_RE = /^->\s*(.+)$/;
8492
8510
  COMMENT_RE = /^\/\//;
8493
8511
  ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8494
- MARKER_RE = /^marker\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8512
+ MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
8495
8513
  HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8496
8514
  HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8497
8515
  WORKWEEK_RE = /^workweek:\s*(.+)$/i;
@@ -16116,7 +16134,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
16116
16134
  }
16117
16135
  const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
16118
16136
  const LABEL_CHAR_W = 7;
16119
- const LABEL_PAD = 8;
16137
+ const LABEL_PAD2 = 8;
16120
16138
  const LABEL_H = 16;
16121
16139
  const PERP_OFFSET = 10;
16122
16140
  const labelPositions = [];
@@ -16125,7 +16143,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
16125
16143
  if (!edge.label || edge.points.length < 2) continue;
16126
16144
  const midIdx = Math.floor(edge.points.length / 2);
16127
16145
  const midPt = edge.points[midIdx];
16128
- const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD;
16146
+ const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
16129
16147
  const prev = edge.points[Math.max(0, midIdx - 1)];
16130
16148
  const next = edge.points[Math.min(edge.points.length - 1, midIdx + 1)];
16131
16149
  const dx = next.x - prev.x;
@@ -18904,11 +18922,8 @@ function calculateSchedule(parsed) {
18904
18922
  for (const dep of task.dependencies) {
18905
18923
  const resolved = resolveTaskName(dep.targetName, allTasks);
18906
18924
  if (isResolverError(resolved)) {
18907
- if (resolved.kind === "ambiguous") {
18908
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18909
- } else {
18910
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18911
- }
18925
+ warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18926
+ continue;
18912
18927
  }
18913
18928
  const targetNode = taskMap.get(resolved.task.id);
18914
18929
  if (targetNode) {
@@ -18923,14 +18938,26 @@ function calculateSchedule(parsed) {
18923
18938
  }
18924
18939
  }
18925
18940
  }
18926
- const sortedIds = topologicalSort(taskMap);
18941
+ let sortedIds = topologicalSort(taskMap);
18927
18942
  if (!sortedIds) {
18928
18943
  const cycle = findCycle(taskMap);
18929
18944
  const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
18930
- return fail(
18945
+ warn(
18931
18946
  taskMap.get(cycle[0]).task.lineNumber,
18932
- `Circular dependency detected: ${cycleStr}`
18947
+ `Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
18933
18948
  );
18949
+ breakCycle(cycle, taskMap, depOffsetMap);
18950
+ sortedIds = topologicalSort(taskMap);
18951
+ let safety = 10;
18952
+ while (!sortedIds && safety-- > 0) {
18953
+ const nextCycle = findCycle(taskMap);
18954
+ if (nextCycle.length === 0) break;
18955
+ breakCycle(nextCycle, taskMap, depOffsetMap);
18956
+ sortedIds = topologicalSort(taskMap);
18957
+ }
18958
+ if (!sortedIds) {
18959
+ sortedIds = [...taskMap.keys()];
18960
+ }
18934
18961
  }
18935
18962
  for (const taskId of sortedIds) {
18936
18963
  const node = taskMap.get(taskId);
@@ -19214,6 +19241,19 @@ function findCycle(taskMap) {
19214
19241
  return null;
19215
19242
  }
19216
19243
  }
19244
+ function breakCycle(cycle, taskMap, depOffsetMap) {
19245
+ if (cycle.length < 3) return;
19246
+ const fromId = cycle[cycle.length - 2];
19247
+ const toId = cycle[0];
19248
+ const toNode = taskMap.get(toId);
19249
+ if (toNode) {
19250
+ const idx = toNode.predecessors.indexOf(fromId);
19251
+ if (idx !== -1) {
19252
+ toNode.predecessors.splice(idx, 1);
19253
+ depOffsetMap.delete(`${fromId}->${toId}`);
19254
+ }
19255
+ }
19256
+ }
19217
19257
  function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
19218
19258
  if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
19219
19259
  const latestEnd = /* @__PURE__ */ new Map();
@@ -19302,10 +19342,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
19302
19342
  if (!resolved?.startDate || !resolved?.endDate) continue;
19303
19343
  if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
19304
19344
  if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
19345
+ const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19346
+ totalDuration += dur;
19305
19347
  if (task.progress !== null) {
19306
- const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19307
19348
  totalProgress += task.progress * dur;
19308
- totalDuration += dur;
19309
19349
  hasProgress = true;
19310
19350
  }
19311
19351
  }
@@ -19346,9 +19386,47 @@ __export(renderer_exports9, {
19346
19386
  buildTagLaneRowList: () => buildTagLaneRowList,
19347
19387
  renderGantt: () => renderGantt
19348
19388
  });
19389
+ function computeBarLabel(label, x1, barWidth, innerWidth, textColor) {
19390
+ const textWidth = label.length * CHAR_W;
19391
+ const x2 = x1 + barWidth;
19392
+ if (textWidth < barWidth - LABEL_PAD) {
19393
+ return { x: x1 + 6, anchor: "start", fill: textColor, text: label };
19394
+ }
19395
+ if (x2 + LABEL_GAP + textWidth <= innerWidth) {
19396
+ return { x: x2 + LABEL_GAP, anchor: "start", fill: textColor, text: label };
19397
+ }
19398
+ if (x1 - LABEL_GAP - textWidth >= 0) {
19399
+ return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label };
19400
+ }
19401
+ const availWidth = x1 - LABEL_GAP;
19402
+ if (availWidth > CHAR_W * 3) {
19403
+ const maxChars = Math.floor(availWidth / CHAR_W) - 1;
19404
+ return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label.slice(0, maxChars) + "\u2026" };
19405
+ }
19406
+ return null;
19407
+ }
19408
+ function renderLabelBand(svg, y, leftMargin, color, palette, cssPrefix, dataAttr) {
19409
+ const bandX = 5;
19410
+ const bandW = leftMargin - 7;
19411
+ const bandY = y - BAR_H / 2;
19412
+ const clipId = `gantt-band-clip-${bandClipCounter++}`;
19413
+ svg.append("clipPath").attr("id", clipId).append("rect").attr("x", bandX).attr("y", bandY).attr("width", bandW).attr("height", BAR_H).attr("rx", BAND_RADIUS);
19414
+ const tint2 = svg.append("rect").attr("class", `gantt-${cssPrefix}-band-bg`).attr("x", bandX).attr("y", bandY).attr("width", bandW).attr("height", BAR_H).attr("rx", BAND_RADIUS).attr("fill", mix(color, palette.bg, 20)).style("pointer-events", "none");
19415
+ const accent = svg.append("rect").attr("class", `gantt-${cssPrefix}-band-accent`).attr("x", bandX).attr("y", bandY).attr("width", BAND_ACCENT_W).attr("height", BAR_H).attr("fill", color).attr("clip-path", `url(#${clipId})`).style("pointer-events", "none");
19416
+ if (dataAttr) {
19417
+ tint2.attr(dataAttr.key, dataAttr.value);
19418
+ accent.attr(dataAttr.key, dataAttr.value);
19419
+ }
19420
+ }
19421
+ function appendTaskIcon(textEl, label, isMilestone, iconColor, textColor) {
19422
+ const icon = isMilestone ? "\u25C6" : "\u25CF";
19423
+ textEl.append("tspan").attr("fill", iconColor).text(icon);
19424
+ textEl.append("tspan").attr("fill", textColor).text(" " + label);
19425
+ }
19349
19426
  function renderGantt(container, resolved, palette, isDark, options, exportDims) {
19350
19427
  container.innerHTML = "";
19351
- if (resolved.error || resolved.tasks.length === 0) return;
19428
+ bandClipCounter = 0;
19429
+ if (resolved.tasks.length === 0) return;
19352
19430
  const onClickItem = options?.onClickItem;
19353
19431
  const collapsedGroups = options?.collapsedGroups;
19354
19432
  const onToggleGroup = options?.onToggleGroup;
@@ -19366,10 +19444,13 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19366
19444
  const isTagMode = tagRows !== null;
19367
19445
  const allLabels = isTagMode ? [
19368
19446
  ...rows.filter((r) => r.type === "lane-header").map((r) => r.laneName),
19369
- ...rows.filter((r) => r.type === "task").map((r) => r.task.task.label)
19447
+ ...rows.filter((r) => r.type === "task").map((r) => "\u25CF " + r.task.task.label)
19370
19448
  ] : [
19371
- ...resolved.tasks.map((t) => t.task.label),
19372
- ...resolved.groups.map((g2) => " ".repeat(g2.depth) + g2.name)
19449
+ ...resolved.tasks.map((t) => "\u25CF " + t.task.label),
19450
+ ...resolved.groups.map((g2) => {
19451
+ const px = g2.depth <= 2 ? g2.depth * 14 : 2 * 14 + (g2.depth - 2) * 8;
19452
+ return " ".repeat(Math.ceil(px / 7)) + g2.name;
19453
+ })
19373
19454
  ];
19374
19455
  const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
19375
19456
  const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
@@ -19378,9 +19459,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19378
19459
  const titleHeight = title ? 50 : 20;
19379
19460
  const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
19380
19461
  const topDateLabelReserve = 22;
19381
- const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve;
19462
+ const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
19463
+ const markerLabelReserve = hasOverheadLabels ? 18 : 0;
19464
+ const CONTENT_TOP_PAD = 16;
19465
+ const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve;
19382
19466
  const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
19383
- const innerHeight = contentH;
19467
+ const innerHeight = CONTENT_TOP_PAD + contentH;
19384
19468
  const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
19385
19469
  const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
19386
19470
  const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
@@ -19406,6 +19490,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19406
19490
  isDark,
19407
19491
  hasCriticalPath,
19408
19492
  criticalPathActive,
19493
+ resolved.options.optionLineNumbers,
19409
19494
  (groupName) => {
19410
19495
  currentActiveGroup = currentActiveGroup?.toLowerCase() === groupName.toLowerCase() ? null : groupName;
19411
19496
  if (onActiveGroupChange) onActiveGroupChange(currentActiveGroup);
@@ -19418,7 +19503,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19418
19503
  },
19419
19504
  currentSwimlaneGroup,
19420
19505
  onSwimlaneChange,
19421
- viewMode
19506
+ viewMode,
19507
+ resolved.tasks
19422
19508
  );
19423
19509
  }
19424
19510
  }
@@ -19443,7 +19529,25 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19443
19529
  renderTimeScaleHorizontal(g, xScale, innerWidth, innerHeight, palette.text);
19444
19530
  renderWeekendBands(g, resolved, xScale, innerHeight, palette, isDark);
19445
19531
  renderHolidayBands(g, svg, resolved, xScale, innerHeight, palette, isDark, marginTop - 4, leftMargin, onClickItem);
19446
- renderErasAndMarkers(g, resolved, xScale, innerHeight, palette);
19532
+ renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette);
19533
+ let todayDate = null;
19534
+ let todayX = -1;
19535
+ const todayColor = palette.accent || "#e74c3c";
19536
+ const todayMarkerLineNum = resolved.options.optionLineNumbers["today-marker"];
19537
+ if (resolved.options.todayMarker !== "off") {
19538
+ if (resolved.options.todayMarker === "on") {
19539
+ todayDate = /* @__PURE__ */ new Date();
19540
+ } else {
19541
+ todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
19542
+ }
19543
+ todayX = xScale(dateToFractionalYear(todayDate));
19544
+ if (todayX >= 0 && todayX <= innerWidth) {
19545
+ const todayLine = g.append("line").attr("class", "gantt-today").attr("x1", todayX).attr("y1", 0).attr("x2", todayX).attr("y2", innerHeight + 10).attr("stroke", todayColor).attr("stroke-width", 2).attr("stroke-dasharray", "6 4").attr("opacity", 0.7).attr("pointer-events", "none");
19546
+ if (todayMarkerLineNum) todayLine.attr("data-line-number", String(todayMarkerLineNum));
19547
+ const todayLabel = g.append("text").attr("class", "gantt-today").attr("x", todayX).attr("y", innerHeight + 24).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", todayColor).attr("opacity", 0.7).attr("pointer-events", "none").text("Today");
19548
+ if (todayMarkerLineNum) todayLabel.attr("data-line-number", String(todayMarkerLineNum));
19549
+ }
19550
+ }
19447
19551
  const taskPositions = /* @__PURE__ */ new Map();
19448
19552
  const groupPositions = /* @__PURE__ */ new Map();
19449
19553
  const lanePositions = /* @__PURE__ */ new Map();
@@ -19464,7 +19568,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19464
19568
  }
19465
19569
  }
19466
19570
  }
19467
- let yOffset = 0;
19571
+ let yOffset = CONTENT_TOP_PAD;
19468
19572
  for (const row of rows) {
19469
19573
  if (row.type === "lane-header") {
19470
19574
  const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
@@ -19479,6 +19583,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19479
19583
  laneBarWidth = Math.max(lx2 - lx1, 2);
19480
19584
  }
19481
19585
  lanePositions.set(row.laneName, { x1: lx1, x2: lx1 + laneBarWidth, y: yOffset + BAR_H / 2 });
19586
+ renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, laneColor, palette, "lane", { key: "data-lane", value: row.laneName });
19482
19587
  const labelG = svg.append("g").attr("class", "gantt-lane-header").attr(`data-tag-${row.tagKey}`, row.laneName.toLowerCase()).attr("data-lane", row.laneName).style("cursor", onToggleLane ? "pointer" : "default").on("click", () => {
19483
19588
  if (onToggleLane) onToggleLane(row.laneName);
19484
19589
  }).on("mouseenter", () => {
@@ -19504,7 +19609,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19504
19609
  });
19505
19610
  laneBandG.append("rect").attr("class", "gantt-lane-band").attr("x", lx1).attr("y", yOffset).attr("width", laneBarWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", barFill).attr("stroke", laneColor).attr("stroke-width", 2);
19506
19611
  if (row.aggregateProgress !== null && row.aggregateProgress > 0) {
19507
- laneBandG.append("rect").attr("class", "gantt-lane-progress").attr("x", lx1).attr("y", yOffset).attr("width", laneBarWidth * Math.min(row.aggregateProgress / 100, 1)).attr("height", BAR_H).attr("rx", 4).attr("fill", laneColor).attr("opacity", 0.5).attr("pointer-events", "none");
19612
+ laneBandG.append("rect").attr("class", "gantt-lane-progress").attr("x", lx1).attr("y", yOffset).attr("width", laneBarWidth * Math.min(row.aggregateProgress / 100, 1)).attr("height", BAR_H).attr("fill", laneColor).attr("opacity", 0.5).attr("pointer-events", "none");
19508
19613
  }
19509
19614
  }
19510
19615
  yOffset += BAR_H + ROW_GAP;
@@ -19513,7 +19618,9 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19513
19618
  const isCollapsed = collapsedGroups?.has(group.name) ?? false;
19514
19619
  const indent = " ".repeat(group.depth);
19515
19620
  const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
19516
- const groupColor = group.color || palette.textMuted;
19621
+ const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
19622
+ const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
19623
+ renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, groupColor, palette, "group", { key: "data-group", value: group.name });
19517
19624
  const labelG = svg.append("g").attr("class", "gantt-group-label").attr("data-group", group.name).attr("data-line-number", String(group.lineNumber)).style("cursor", onToggleGroup ? "pointer" : "default").on("click", () => {
19518
19625
  if (onToggleGroup) onToggleGroup(group.name);
19519
19626
  }).on("mouseenter", () => {
@@ -19523,7 +19630,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19523
19630
  resetHighlight(g, svg);
19524
19631
  hideGanttDateIndicators(g);
19525
19632
  });
19526
- const labelX = 10 + group.depth * 14;
19633
+ const groupIndent = group.depth <= 2 ? group.depth * 14 : 2 * 14 + (group.depth - 2) * 8;
19634
+ const labelX = 10 + groupIndent;
19527
19635
  labelG.append("text").attr("x", labelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("font-weight", "bold").attr("fill", palette.text).text(toggleIcon + " " + group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : ""));
19528
19636
  const gStart = dateToFractionalYear(group.startDate);
19529
19637
  const gEnd = dateToFractionalYear(group.endDate);
@@ -19541,7 +19649,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19541
19649
  });
19542
19650
  summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", mix(groupColor, palette.bg, 30)).attr("stroke", groupColor).attr("stroke-width", 2);
19543
19651
  if (group.progress !== null && group.progress > 0) {
19544
- summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("rx", 4).attr("fill", groupColor).attr("opacity", 0.5);
19652
+ summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("fill", groupColor).attr("opacity", 0.5);
19653
+ }
19654
+ const summaryLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
19655
+ const summaryPlacement = computeBarLabel(summaryLabel, gx1, barWidth, innerWidth, palette.text);
19656
+ if (summaryPlacement) {
19657
+ summaryG.append("text").attr("x", summaryPlacement.x).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("font-weight", "bold").attr("text-anchor", summaryPlacement.anchor).attr("fill", summaryPlacement.fill).attr("pointer-events", "none").text(summaryPlacement.text);
19545
19658
  }
19546
19659
  groupPositions.set(group.name, { x1: gx1, x2: gx1 + barWidth, y: yOffset + BAR_H / 2 });
19547
19660
  } else {
@@ -19556,7 +19669,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19556
19669
  });
19557
19670
  groupBarG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", groupBarWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", bandFill).attr("stroke", groupColor).attr("stroke-width", 2);
19558
19671
  if (group.progress !== null && group.progress > 0) {
19559
- groupBarG.append("rect").attr("class", "gantt-group-progress").attr("x", gx1).attr("y", yOffset).attr("width", groupBarWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("rx", 4).attr("fill", groupColor).attr("opacity", 0.5);
19672
+ groupBarG.append("rect").attr("class", "gantt-group-progress").attr("x", gx1).attr("y", yOffset).attr("width", groupBarWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("fill", groupColor).attr("opacity", 0.5);
19673
+ }
19674
+ const expandedLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
19675
+ const expandedPlacement = computeBarLabel(expandedLabel, gx1, groupBarWidth, innerWidth, palette.text);
19676
+ if (expandedPlacement) {
19677
+ groupBarG.append("text").attr("x", expandedPlacement.x).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("font-weight", "bold").attr("text-anchor", expandedPlacement.anchor).attr("fill", expandedPlacement.fill).attr("pointer-events", "none").text(expandedPlacement.text);
19560
19678
  }
19561
19679
  }
19562
19680
  }
@@ -19564,33 +19682,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19564
19682
  } else if (row.type === "task") {
19565
19683
  const rt = row.task;
19566
19684
  const task = rt.task;
19567
- const taskLabelX = isTagMode ? 20 : 6 + rt.groupPath.length * 14;
19685
+ const barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
19686
+ const depth = rt.groupPath.length;
19687
+ const indent = depth <= 2 ? depth * 14 : 2 * 14 + (depth - 2) * 8;
19688
+ const taskLabelX = isTagMode ? 20 : 6 + indent;
19568
19689
  const topGroup = rt.groupPath.length > 0 ? rt.groupPath[0] : null;
19569
- const taskLabel = svg.append("text").attr("class", "gantt-task-label").attr("x", taskLabelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("fill", palette.text).attr("data-line-number", String(task.lineNumber)).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").text(task.label).on("click", () => {
19690
+ const taskLabel = svg.append("text").attr("class", "gantt-task-label").attr("x", taskLabelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("fill", palette.text).attr("data-line-number", String(task.lineNumber)).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
19570
19691
  if (onClickItem) onClickItem(task.lineNumber);
19571
19692
  }).on("mouseenter", () => {
19572
- highlightTask(g, svg, task.id);
19693
+ if (rt.isMilestone) {
19694
+ highlightMilestone(g, svg, task.id);
19695
+ } else {
19696
+ highlightTask(g, svg, task.id);
19697
+ }
19573
19698
  }).on("mouseleave", () => {
19574
19699
  resetHighlight(g, svg);
19575
19700
  });
19701
+ appendTaskIcon(taskLabel, task.label, rt.isMilestone, barColor, palette.text);
19576
19702
  for (const [key, value] of Object.entries(rt.effectiveMetadata)) {
19577
19703
  taskLabel.attr(`data-tag-${key}`, value.toLowerCase());
19578
19704
  }
19579
19705
  if (rt.isCriticalPath) {
19580
19706
  taskLabel.attr("data-critical-path", "true");
19581
19707
  }
19582
- let barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
19583
19708
  if (rt.isMilestone) {
19584
19709
  const mx = xScale(dateToFractionalYear(rt.startDate));
19585
19710
  const my = yOffset + BAR_H / 2;
19586
- g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
19711
+ g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
19587
19712
  if (onClickItem) onClickItem(task.lineNumber);
19588
19713
  }).on("mouseenter", () => {
19589
- highlightTaskLabel(svg, task.lineNumber);
19714
+ highlightMilestone(g, svg, task.id);
19590
19715
  showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
19591
19716
  g.append("text").attr("class", "gantt-milestone-hover-label").attr("x", mx - MILESTONE_SIZE - 4).attr("y", my).attr("dy", "0.35em").attr("text-anchor", "end").attr("font-size", "10px").attr("fill", barColor).attr("font-weight", "600").text(task.label);
19592
19717
  }).on("mouseleave", () => {
19593
- resetTaskLabels(svg);
19718
+ resetHighlight(g, svg);
19594
19719
  hideGanttDateIndicators(g);
19595
19720
  g.selectAll(".gantt-milestone-hover-label").remove();
19596
19721
  });
@@ -19657,32 +19782,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19657
19782
  progGrad.append("stop").attr("offset", "100%").attr("stop-color", barColor).attr("stop-opacity", 0);
19658
19783
  progressFill = `url(#${progGradId})`;
19659
19784
  }
19660
- taskG.append("rect").attr("class", "gantt-progress").attr("x", x1).attr("y", yOffset).attr("width", progressWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", progressFill).attr("opacity", 0.5);
19785
+ taskG.append("rect").attr("class", "gantt-progress").attr("x", x1).attr("y", yOffset).attr("width", progressWidth).attr("height", BAR_H).attr("fill", progressFill).attr("opacity", 0.5);
19661
19786
  }
19662
19787
  if (rt.isCriticalPath) {
19663
19788
  taskG.attr("data-critical-path", "true");
19664
19789
  }
19665
- const textWidth = task.label.length * 6.5;
19666
- if (textWidth < barWidth - 8) {
19667
- taskG.append("text").attr("x", x1 + 6).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("fill", palette.text).attr("pointer-events", "none").text(task.label);
19790
+ const labelPlacement = computeBarLabel(task.label, x1, barWidth, innerWidth, palette.text);
19791
+ if (labelPlacement) {
19792
+ taskG.append("text").attr("x", labelPlacement.x).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("text-anchor", labelPlacement.anchor).attr("fill", labelPlacement.fill).attr("pointer-events", "none").text(labelPlacement.text);
19668
19793
  }
19669
19794
  taskPositions.set(task.id, { x1, x2: x1 + barWidth, y: yOffset + BAR_H / 2 });
19670
19795
  }
19671
19796
  yOffset += BAR_H + ROW_GAP;
19672
19797
  }
19673
19798
  }
19674
- if (resolved.options.todayMarker !== "off") {
19675
- let todayDate;
19676
- if (resolved.options.todayMarker === "on") {
19677
- todayDate = /* @__PURE__ */ new Date();
19678
- } else {
19679
- todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
19680
- }
19681
- const todayX = xScale(dateToFractionalYear(todayDate));
19682
- if (todayX >= 0 && todayX <= innerWidth) {
19683
- g.append("line").attr("class", "gantt-today").attr("x1", todayX).attr("y1", 0).attr("x2", todayX).attr("y2", innerHeight + 10).attr("stroke", palette.accent || "#e74c3c").attr("stroke-width", 2).attr("stroke-dasharray", "6 4").attr("opacity", 0.7);
19684
- g.append("text").attr("class", "gantt-today").attr("x", todayX + 4).attr("y", innerHeight + 24).attr("text-anchor", "start").attr("font-size", "10px").attr("fill", palette.accent || "#e74c3c").attr("opacity", 0.7).text("Today");
19685
- }
19799
+ if (todayDate && todayX >= 0 && todayX <= innerWidth) {
19800
+ const todayHoverG = g.append("g").attr("class", "gantt-today-hover").style("cursor", "pointer");
19801
+ todayHoverG.append("rect").attr("x", todayX - 10).attr("y", -6).attr("width", 20).attr("height", innerHeight + 16).attr("fill", "transparent").attr("pointer-events", "all");
19802
+ const todayDateObj = todayDate;
19803
+ todayHoverG.on("mouseenter", () => {
19804
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
19805
+ g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
19806
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
19807
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
19808
+ svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
19809
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
19810
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
19811
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
19812
+ g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
19813
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
19814
+ showGanttDateIndicators(g, xScale, todayDateObj, null, innerHeight, todayColor);
19815
+ }).on("mouseleave", () => {
19816
+ resetHighlight(g, svg);
19817
+ hideGanttDateIndicators(g);
19818
+ });
19686
19819
  }
19687
19820
  if (resolved.options.dependencies) {
19688
19821
  renderDependencyArrows(g, resolved, taskPositions, groupPositions, collapsedGroups, palette, isDark, isTagMode, lanePositions, collapsedLanes, taskLaneMap);
@@ -19799,10 +19932,10 @@ function renderDependencyArrows(g, resolved, taskPositions, groupPositions, coll
19799
19932
  const path = `M ${sx} ${sy} C ${sx + cpOffset} ${sy}, ${tx - cpOffset} ${ty}, ${tx} ${ty}`;
19800
19933
  const arrowColor = mix(palette.text, palette.bg, 50);
19801
19934
  const isCpArrow = rt.isCriticalPath && targetTask.isCriticalPath;
19802
- g.append("path").attr("class", "gantt-dep-arrow").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-critical-path", isCpArrow ? "true" : null).attr("d", path).attr("fill", "none").attr("stroke", arrowColor).attr("stroke-width", 1.5).attr("opacity", 0.5);
19935
+ g.append("path").attr("class", "gantt-dep-arrow").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-line-number", String(dep.lineNumber)).attr("data-critical-path", isCpArrow ? "true" : null).attr("d", path).attr("fill", "none").attr("stroke", arrowColor).attr("stroke-width", 1.5).attr("opacity", 0.5);
19803
19936
  const headSize = 5;
19804
19937
  const angle = 0;
19805
- g.append("polygon").attr("class", "gantt-dep-arrowhead").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-critical-path", isCpArrow ? "true" : null).attr("points", arrowheadPoints(tx, ty, headSize, angle)).attr("fill", arrowColor).attr("opacity", 0.5);
19938
+ g.append("polygon").attr("class", "gantt-dep-arrowhead").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-line-number", String(dep.lineNumber)).attr("data-critical-path", isCpArrow ? "true" : null).attr("points", arrowheadPoints(tx, ty, headSize, angle)).attr("fill", arrowColor).attr("opacity", 0.5);
19806
19939
  }
19807
19940
  }
19808
19941
  }
@@ -19823,7 +19956,9 @@ function applyCriticalPathHighlight(svg, chartG) {
19823
19956
  el.attr("opacity", el.attr("data-critical-path") === "true" ? 1 : FADE_OPACITY);
19824
19957
  });
19825
19958
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
19959
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
19826
19960
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
19961
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
19827
19962
  chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
19828
19963
  chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").each(function() {
19829
19964
  const el = d3Selection10.select(this);
@@ -19835,7 +19970,9 @@ function resetHighlightAll(svg, chartG) {
19835
19970
  chartG.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
19836
19971
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
19837
19972
  svg.selectAll(".gantt-group-label").attr("opacity", 1);
19973
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
19838
19974
  svg.selectAll(".gantt-lane-header").attr("opacity", 1);
19975
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
19839
19976
  chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", 1);
19840
19977
  chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
19841
19978
  }
@@ -19851,7 +19988,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
19851
19988
  }
19852
19989
  return iconG;
19853
19990
  }
19854
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
19991
+ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
19855
19992
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
19856
19993
  let visibleGroups;
19857
19994
  if (activeGroupName) {
@@ -19861,6 +19998,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19861
19998
  } else {
19862
19999
  visibleGroups = tagGroups;
19863
20000
  }
20001
+ const usedValues = /* @__PURE__ */ new Map();
20002
+ if (resolvedTasks) {
20003
+ for (const group of visibleGroups) {
20004
+ const key = group.name.toLowerCase();
20005
+ const used = /* @__PURE__ */ new Set();
20006
+ for (const rt of resolvedTasks) {
20007
+ const val = rt.effectiveMetadata[key];
20008
+ if (val) used.add(val.toLowerCase());
20009
+ }
20010
+ usedValues.set(key, used);
20011
+ }
20012
+ }
20013
+ const filteredEntries = /* @__PURE__ */ new Map();
20014
+ for (const group of visibleGroups) {
20015
+ const key = group.name.toLowerCase();
20016
+ const used = usedValues.get(key);
20017
+ if (used && used.size > 0) {
20018
+ filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
20019
+ } else {
20020
+ filteredEntries.set(key, group.entries);
20021
+ }
20022
+ }
19864
20023
  const groupWidths = [];
19865
20024
  let totalW = 0;
19866
20025
  for (const group of visibleGroups) {
@@ -19871,8 +20030,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19871
20030
  const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
19872
20031
  let groupW = pillW;
19873
20032
  if (isActive) {
20033
+ const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
19874
20034
  let entriesW = 0;
19875
- for (const entry of group.entries) {
20035
+ for (const entry of entries) {
19876
20036
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
19877
20037
  }
19878
20038
  groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
@@ -19889,7 +20049,8 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19889
20049
  if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
19890
20050
  totalW += cpPillW;
19891
20051
  }
19892
- const legendX = chartLeftMargin + (chartInnerWidth - totalW) / 2;
20052
+ const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
20053
+ const legendX = (containerWidth - totalW) / 2;
19893
20054
  const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
19894
20055
  let cursorX = 0;
19895
20056
  for (let i = 0; i < visibleGroups.length; i++) {
@@ -19901,7 +20062,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19901
20062
  const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
19902
20063
  const pillH = isActive ? LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2 : LEGEND_HEIGHT;
19903
20064
  const groupW = groupWidths[i];
19904
- const gEl = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-tag-legend-group").attr("data-tag-group", group.name).style("cursor", "pointer").on("click", () => {
20065
+ const gEl = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-tag-legend-group").attr("data-tag-group", group.name).attr("data-line-number", String(group.lineNumber)).style("cursor", "pointer").on("click", () => {
19905
20066
  if (onToggle) onToggle(group.name);
19906
20067
  });
19907
20068
  if (isActive) {
@@ -19931,10 +20092,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19931
20092
  }
19932
20093
  if (isActive) {
19933
20094
  const tagKey = group.name.toLowerCase();
20095
+ const entries = filteredEntries.get(tagKey) ?? group.entries;
19934
20096
  let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
19935
- for (const entry of group.entries) {
20097
+ for (const entry of entries) {
19936
20098
  const entryValue = entry.value.toLowerCase();
19937
- const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
20099
+ const entryG = gEl.append("g").attr("class", "gantt-legend-entry").attr("data-line-number", String(entry.lineNumber)).style("cursor", "pointer");
19938
20100
  entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
19939
20101
  entryG.append("text").attr("x", ex + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 2).attr("text-anchor", "start").attr("font-size", `${LEGEND_ENTRY_FONT_SIZE}px`).attr("fill", palette.textMuted).text(entry.value);
19940
20102
  entryG.on("mouseenter", () => {
@@ -19970,9 +20132,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19970
20132
  cursorX += groupW + LEGEND_GROUP_GAP;
19971
20133
  }
19972
20134
  if (hasCriticalPath) {
20135
+ const cpLineNum = optionLineNumbers["critical-path"];
19973
20136
  const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
19974
20137
  if (onToggleCriticalPath) onToggleCriticalPath();
19975
20138
  });
20139
+ if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
19976
20140
  cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
19977
20141
  if (criticalPathActive) {
19978
20142
  cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
@@ -19990,7 +20154,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19990
20154
  });
19991
20155
  }
19992
20156
  }
19993
- function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
20157
+ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
19994
20158
  for (let i = 0; i < resolved.eras.length; i++) {
19995
20159
  const era = resolved.eras[i];
19996
20160
  const color = era.color || ERA_COLORS[i % ERA_COLORS.length];
@@ -20001,13 +20165,23 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
20001
20165
  const hoverEraOpacity = 0.16;
20002
20166
  const eraStartDate = parseDateStringToDate(era.startDate);
20003
20167
  const eraEndDate = parseDateStringToDate(era.endDate);
20004
- const eraG = g.append("g").attr("class", "gantt-era-group");
20168
+ const eraG = g.append("g").attr("class", "gantt-era-group").attr("data-line-number", String(era.lineNumber));
20005
20169
  const eraRect = eraG.append("rect").attr("class", "gantt-era").attr("x", sx).attr("y", 0).attr("width", ex - sx).attr("height", innerHeight).attr("fill", color).attr("opacity", baseEraOpacity);
20006
- eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y", 12).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", color).attr("opacity", 0.7).attr("pointer-events", "none").text(era.label);
20170
+ eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y", -24).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", color).attr("opacity", 0.7).style("cursor", "pointer").text(era.label);
20007
20171
  eraG.on("mouseenter", () => {
20172
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20173
+ g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
20174
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20175
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20176
+ svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
20177
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20178
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20179
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20180
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20008
20181
  eraRect.attr("opacity", hoverEraOpacity);
20009
20182
  showGanttDateIndicators(g, xScale, eraStartDate, eraEndDate, innerHeight, color);
20010
20183
  }).on("mouseleave", () => {
20184
+ resetHighlight(g, svg);
20011
20185
  eraRect.attr("opacity", baseEraOpacity);
20012
20186
  hideGanttDateIndicators(g);
20013
20187
  });
@@ -20016,13 +20190,35 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
20016
20190
  const color = marker.color || palette.accent || "#d08770";
20017
20191
  const mx = xScale(parseDateToFractionalYear(marker.date));
20018
20192
  const markerDate = parseDateStringToDate(marker.date);
20019
- const markerG = g.append("g").attr("class", "gantt-marker-group");
20020
- markerG.append("line").attr("class", "gantt-marker").attr("x1", mx).attr("y1", 0).attr("x2", mx).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "6 3").attr("opacity", 0.5);
20021
- markerG.append("polygon").attr("points", diamondPoints(mx, 6, 8)).attr("fill", color).attr("opacity", 0.5);
20022
- markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx + 8).attr("y", 10).attr("font-size", "9px").attr("fill", color).attr("opacity", 0.7).attr("pointer-events", "none").text(marker.label);
20193
+ const diamondSize = 5;
20194
+ const labelY = -24;
20195
+ const diamondY = labelY + 14;
20196
+ const markerG = g.append("g").attr("class", "gantt-marker-group").attr("data-line-number", String(marker.lineNumber)).style("cursor", "pointer");
20197
+ markerG.append("rect").attr("x", mx - 40).attr("y", labelY - 12).attr("width", 80).attr("height", innerHeight - labelY + 12).attr("fill", "transparent").attr("pointer-events", "all");
20198
+ markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx).attr("y", labelY).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-weight", "600").attr("fill", color).text(marker.label);
20199
+ markerG.append("path").attr("d", `M${mx},${diamondY - diamondSize} l${diamondSize},${diamondSize} l-${diamondSize},${diamondSize} l-${diamondSize},-${diamondSize} Z`).attr("fill", color).attr("opacity", 0.9);
20200
+ markerG.append("line").attr("class", "gantt-marker").attr("x1", mx).attr("y1", diamondY + diamondSize).attr("x2", mx).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "6 4").attr("opacity", 0.5);
20201
+ const markerLine = markerG.select(".gantt-marker");
20202
+ const markerDiamond = markerG.select("path");
20023
20203
  markerG.on("mouseenter", () => {
20024
- showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color);
20204
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20205
+ g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
20206
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20207
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20208
+ svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
20209
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20210
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20211
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20212
+ g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
20213
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20214
+ markerG.attr("opacity", 1);
20215
+ markerLine.attr("opacity", 0.8);
20216
+ markerDiamond.attr("opacity", 0);
20217
+ showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color, { skipStartLine: true });
20025
20218
  }).on("mouseleave", () => {
20219
+ resetHighlight(g, svg);
20220
+ markerLine.attr("opacity", 0.5);
20221
+ markerDiamond.attr("opacity", 0.9);
20026
20222
  hideGanttDateIndicators(g);
20027
20223
  });
20028
20224
  }
@@ -20035,11 +20231,7 @@ function parseDateStringToDate(s) {
20035
20231
  return new Date(year, month, day);
20036
20232
  }
20037
20233
  function parseDateToFractionalYear(s) {
20038
- const parts = s.split("-").map((p) => parseInt(p, 10));
20039
- const year = parts[0];
20040
- const month = parts.length >= 2 ? parts[1] : 1;
20041
- const day = parts.length >= 3 ? parts[2] : 1;
20042
- return year + (month - 1) / 12 + (day - 1) / 365;
20234
+ return dateToFractionalYear(parseDateStringToDate(s));
20043
20235
  }
20044
20236
  function highlightDeps(g, svg, taskId, resolved) {
20045
20237
  const related = /* @__PURE__ */ new Set([taskId]);
@@ -20073,6 +20265,7 @@ function highlightDeps(g, svg, taskId, resolved) {
20073
20265
  const isRelated = from && related.has(from) || to && related.has(to);
20074
20266
  el.attr("opacity", isRelated ? 0.5 : FADE_OPACITY);
20075
20267
  });
20268
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20076
20269
  }
20077
20270
  function highlightGroup(g, svg, groupName) {
20078
20271
  g.selectAll(".gantt-task").each(function() {
@@ -20095,8 +20288,14 @@ function highlightGroup(g, svg, groupName) {
20095
20288
  const el = d3Selection10.select(this);
20096
20289
  el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
20097
20290
  });
20291
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").each(function() {
20292
+ const el = d3Selection10.select(this);
20293
+ el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
20294
+ });
20098
20295
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20296
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
20099
20297
  g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
20298
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20100
20299
  }
20101
20300
  function highlightLane(g, svg, tagKey, laneName) {
20102
20301
  const tagAttr = `data-tag-${tagKey}`;
@@ -20121,8 +20320,14 @@ function highlightLane(g, svg, tagKey, laneName) {
20121
20320
  const el = d3Selection10.select(this);
20122
20321
  el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
20123
20322
  });
20323
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").each(function() {
20324
+ const el = d3Selection10.select(this);
20325
+ el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
20326
+ });
20124
20327
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20125
20328
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20329
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
20330
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20126
20331
  }
20127
20332
  function highlightTask(g, svg, taskId) {
20128
20333
  g.selectAll(".gantt-task").each(function() {
@@ -20136,9 +20341,31 @@ function highlightTask(g, svg, taskId) {
20136
20341
  });
20137
20342
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20138
20343
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20344
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
20139
20345
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20346
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
20140
20347
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20141
20348
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20349
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20350
+ }
20351
+ function highlightMilestone(g, svg, taskId) {
20352
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20353
+ g.selectAll(".gantt-milestone").each(function() {
20354
+ const el = d3Selection10.select(this);
20355
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20356
+ });
20357
+ svg.selectAll(".gantt-task-label").each(function() {
20358
+ const el = d3Selection10.select(this);
20359
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20360
+ });
20361
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20362
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20363
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
20364
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20365
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
20366
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20367
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20368
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20142
20369
  }
20143
20370
  function highlightTaskLabel(svg, lineNumber) {
20144
20371
  const ln = String(lineNumber);
@@ -20154,10 +20381,14 @@ function resetHighlight(g, svg) {
20154
20381
  g.selectAll(".gantt-task, .gantt-milestone").attr("opacity", 1);
20155
20382
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
20156
20383
  svg.selectAll(".gantt-group-label").attr("opacity", 1);
20384
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
20157
20385
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
20158
20386
  svg.selectAll(".gantt-lane-header").attr("opacity", 1);
20387
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
20159
20388
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
20160
20389
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
20390
+ g.selectAll(".gantt-marker-group").attr("opacity", 1);
20391
+ g.selectAll(".gantt-era-group").attr("opacity", 1);
20161
20392
  }
20162
20393
  function buildRowList(resolved, collapsedGroups) {
20163
20394
  const rows = [];
@@ -20236,9 +20467,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20236
20467
  for (const entry of tagGroup.entries) {
20237
20468
  const entryKey = entry.value.toLowerCase();
20238
20469
  const tasks = buckets.get(entryKey) ?? [];
20470
+ if (tasks.length === 0) continue;
20239
20471
  tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20240
- const progressValues = tasks.map((t) => t.task.progress).filter((p) => p !== null);
20241
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20472
+ const aggregateProgress = durationWeightedProgress(tasks);
20242
20473
  const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
20243
20474
  const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
20244
20475
  const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
@@ -20260,8 +20491,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20260
20491
  }
20261
20492
  if (unbucketed.length > 0) {
20262
20493
  unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20263
- const progressValues = unbucketed.map((t) => t.task.progress).filter((p) => p !== null);
20264
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20494
+ const aggregateProgress = durationWeightedProgress(unbucketed);
20265
20495
  const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
20266
20496
  const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
20267
20497
  const noLaneName = `No ${tagGroup.name}`;
@@ -20284,6 +20514,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20284
20514
  }
20285
20515
  return rows;
20286
20516
  }
20517
+ function durationWeightedProgress(tasks) {
20518
+ let totalDuration = 0;
20519
+ let totalProgress = 0;
20520
+ let hasProgress = false;
20521
+ for (const rt of tasks) {
20522
+ const dur = rt.endDate.getTime() - rt.startDate.getTime();
20523
+ totalDuration += dur;
20524
+ if (rt.task.progress !== null) {
20525
+ totalProgress += rt.task.progress * dur;
20526
+ hasProgress = true;
20527
+ }
20528
+ }
20529
+ return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
20530
+ }
20287
20531
  function dateToFractionalYear(d) {
20288
20532
  const y = d.getFullYear();
20289
20533
  const startOfYear = new Date(y, 0, 1);
@@ -20298,21 +20542,43 @@ function diamondPoints(cx, cy, size) {
20298
20542
  function formatGanttDate(d) {
20299
20543
  return `${MONTH_ABBR[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
20300
20544
  }
20301
- function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color) {
20545
+ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color, options) {
20302
20546
  g.selectAll(".gantt-scale-tick").attr("opacity", 0.05);
20303
20547
  g.selectAll(".gantt-today").attr("opacity", 0.05);
20548
+ const hg = g.append("g").attr("class", "gantt-hover-date").attr("pointer-events", "none");
20304
20549
  const tickLen = 6;
20305
20550
  const startPos = xScale(dateToFractionalYear(startDate));
20306
20551
  const startLabel = formatGanttDate(startDate);
20307
- g.append("line").attr("class", "gantt-hover-date").attr("x1", startPos).attr("y1", -tickLen).attr("x2", startPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
20308
- g.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", -tickLen - 4).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
20309
- g.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
20552
+ if (!options?.skipStartLine) {
20553
+ hg.append("line").attr("class", "gantt-hover-date").attr("x1", startPos).attr("y1", -tickLen).attr("x2", startPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
20554
+ }
20555
+ hg.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", -tickLen - 4).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
20556
+ hg.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
20310
20557
  if (endDate && endDate.getTime() !== startDate.getTime()) {
20311
20558
  const endPos = xScale(dateToFractionalYear(endDate));
20312
20559
  const endLabel = formatGanttDate(endDate);
20313
- g.append("line").attr("class", "gantt-hover-date").attr("x1", endPos).attr("y1", -tickLen).attr("x2", endPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
20314
- g.append("text").attr("class", "gantt-hover-date").attr("x", endPos).attr("y", -tickLen - 4).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20315
- g.append("text").attr("class", "gantt-hover-date").attr("x", endPos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20560
+ const minLabelGap = 90;
20561
+ const gap = endPos - startPos;
20562
+ let startLabelX = startPos;
20563
+ let endLabelX = endPos;
20564
+ let startAnchor = "middle";
20565
+ let endAnchor = "middle";
20566
+ if (gap < minLabelGap) {
20567
+ const mid = (startPos + endPos) / 2;
20568
+ startLabelX = mid - minLabelGap / 2;
20569
+ endLabelX = mid + minLabelGap / 2;
20570
+ startAnchor = "middle";
20571
+ endAnchor = "middle";
20572
+ }
20573
+ hg.append("line").attr("class", "gantt-hover-date").attr("x1", endPos).attr("y1", -tickLen).attr("x2", endPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
20574
+ hg.selectAll("text.gantt-hover-date").each(function() {
20575
+ const el = d3Selection10.select(this);
20576
+ if (el.text() === startLabel) {
20577
+ el.attr("x", startLabelX).attr("text-anchor", startAnchor);
20578
+ }
20579
+ });
20580
+ hg.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", -tickLen - 4).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20581
+ hg.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", innerHeight + tickLen + 12).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20316
20582
  }
20317
20583
  }
20318
20584
  function hideGanttDateIndicators(g) {
@@ -20357,7 +20623,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
20357
20623
  g.append("text").attr("class", "gantt-scale-tick").attr("x", tick.pos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", textColor).attr("opacity", opacity).text(tick.label);
20358
20624
  }
20359
20625
  }
20360
- var d3Scale, d3Selection10, BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, JS_DAY_TO_WEEKDAY2, ERA_COLORS, FADE_OPACITY, MONTH_ABBR;
20626
+ var d3Scale, d3Selection10, BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, CHAR_W, LABEL_PAD, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, FADE_OPACITY, MONTH_ABBR;
20361
20627
  var init_renderer9 = __esm({
20362
20628
  "src/gantt/renderer.ts"() {
20363
20629
  "use strict";
@@ -20376,6 +20642,12 @@ var init_renderer9 = __esm({
20376
20642
  MIN_LEFT_MARGIN = 120;
20377
20643
  BOTTOM_MARGIN = 40;
20378
20644
  RIGHT_MARGIN = 20;
20645
+ CHAR_W = 6.5;
20646
+ LABEL_PAD = 8;
20647
+ LABEL_GAP = 5;
20648
+ BAND_ACCENT_W = 4;
20649
+ BAND_RADIUS = 4;
20650
+ bandClipCounter = 0;
20379
20651
  JS_DAY_TO_WEEKDAY2 = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
20380
20652
  ERA_COLORS = ["#5e81ac", "#a3be8c", "#ebcb8b", "#d08770", "#b48ead"];
20381
20653
  FADE_OPACITY = 0.1;
@@ -20476,14 +20748,14 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
20476
20748
  nodePositionMap.set(node.id, node);
20477
20749
  }
20478
20750
  const LABEL_CHAR_W = 7;
20479
- const LABEL_PAD = 8;
20751
+ const LABEL_PAD2 = 8;
20480
20752
  const LABEL_H = 16;
20481
20753
  const PERP_OFFSET = 10;
20482
20754
  const labelPositions = [];
20483
20755
  for (let ei = 0; ei < layout.edges.length; ei++) {
20484
20756
  const edge = layout.edges[ei];
20485
20757
  if (!edge.label) continue;
20486
- const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD;
20758
+ const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
20487
20759
  let lx, ly;
20488
20760
  if (edge.source === edge.target) {
20489
20761
  const node = nodePositionMap.get(edge.source);
@@ -22365,7 +22637,7 @@ function parseVisualization(content, palette) {
22365
22637
  continue;
22366
22638
  }
22367
22639
  const markerMatch = line10.match(
22368
- /^marker\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*:\s*(.+?)(?:\s*\(([^)]+)\))?\s*$/
22640
+ /^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
22369
22641
  );
22370
22642
  if (markerMatch) {
22371
22643
  const colorAnnotation = markerMatch[3]?.trim() || null;
@@ -22389,7 +22661,7 @@ function parseVisualization(content, palette) {
22389
22661
  const unit = durationMatch[3];
22390
22662
  const endDate = addDurationToDate(startDate, amount, unit);
22391
22663
  const segments = durationMatch[5].split("|");
22392
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22664
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22393
22665
  result.timelineEvents.push({
22394
22666
  date: startDate,
22395
22667
  endDate,
@@ -22406,7 +22678,7 @@ function parseVisualization(content, palette) {
22406
22678
  );
22407
22679
  if (rangeMatch) {
22408
22680
  const segments = rangeMatch[4].split("|");
22409
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22681
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22410
22682
  result.timelineEvents.push({
22411
22683
  date: rangeMatch[1],
22412
22684
  endDate: rangeMatch[2],
@@ -22423,7 +22695,7 @@ function parseVisualization(content, palette) {
22423
22695
  );
22424
22696
  if (pointMatch) {
22425
22697
  const segments = pointMatch[2].split("|");
22426
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22698
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22427
22699
  result.timelineEvents.push({
22428
22700
  date: pointMatch[1],
22429
22701
  endDate: null,
@@ -24983,12 +25255,12 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
24983
25255
  };
24984
25256
  const LABEL_MAX_FONT = 48;
24985
25257
  const LABEL_MIN_FONT = 14;
24986
- const LABEL_PAD = 40;
25258
+ const LABEL_PAD2 = 40;
24987
25259
  const CHAR_WIDTH_RATIO3 = 0.6;
24988
25260
  const estTextWidth = (text, fontSize) => text.length * fontSize * CHAR_WIDTH_RATIO3;
24989
25261
  const quadrantLabelLayout = (text, qw2, qh2) => {
24990
- const availW = qw2 - LABEL_PAD;
24991
- const availH = qh2 - LABEL_PAD;
25262
+ const availW = qw2 - LABEL_PAD2;
25263
+ const availH = qh2 - LABEL_PAD2;
24992
25264
  const words = text.split(/\s+/);
24993
25265
  if (estTextWidth(text, LABEL_MAX_FONT) <= availW) {
24994
25266
  const fs = Math.min(LABEL_MAX_FONT, availH);
@@ -25375,9 +25647,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
25375
25647
  const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
25376
25648
  const effectivePalette2 = await resolveExportPalette(theme, palette);
25377
25649
  const ganttParsed = parseGantt2(content, effectivePalette2);
25378
- if (ganttParsed.error) return "";
25379
25650
  const resolved = calculateSchedule2(ganttParsed);
25380
- if (resolved.error || resolved.tasks.length === 0) return "";
25651
+ if (resolved.tasks.length === 0) return "";
25381
25652
  const EXPORT_W = 1200;
25382
25653
  const EXPORT_H = 800;
25383
25654
  const container2 = createExportContainer(EXPORT_W, EXPORT_H);