@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.js CHANGED
@@ -1495,24 +1495,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
1495
1495
  }
1496
1496
  return { series, names, nameColors, newIndex };
1497
1497
  }
1498
- function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
1498
+ function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
1499
+ if (segments.length > 2 && warnMultiplePipes) {
1500
+ warnMultiplePipes();
1501
+ }
1499
1502
  const metadata = {};
1500
- for (let j = 1; j < segments.length; j++) {
1501
- for (const part of segments[j].split(",")) {
1502
- const trimmedPart = part.trim();
1503
- if (!trimmedPart) continue;
1504
- const colonIdx = trimmedPart.indexOf(":");
1505
- if (colonIdx > 0) {
1506
- const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1507
- const key = aliasMap.get(rawKey) ?? rawKey;
1508
- const value = trimmedPart.substring(colonIdx + 1).trim();
1509
- metadata[key] = value;
1510
- }
1503
+ const raw = segments.slice(1).join(",");
1504
+ for (const part of raw.split(",")) {
1505
+ const trimmedPart = part.trim();
1506
+ if (!trimmedPart) continue;
1507
+ const colonIdx = trimmedPart.indexOf(":");
1508
+ if (colonIdx > 0) {
1509
+ const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1510
+ const key = aliasMap.get(rawKey) ?? rawKey;
1511
+ const value = trimmedPart.substring(colonIdx + 1).trim();
1512
+ metadata[key] = value;
1511
1513
  }
1512
1514
  }
1513
1515
  return metadata;
1514
1516
  }
1515
- var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
1517
+ var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
1516
1518
  var init_parsing = __esm({
1517
1519
  "src/utils/parsing.ts"() {
1518
1520
  "use strict";
@@ -1521,6 +1523,7 @@ var init_parsing = __esm({
1521
1523
  CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
1522
1524
  TITLE_RE = /^title\s*:\s*(.+)/i;
1523
1525
  OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
1526
+ MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
1524
1527
  }
1525
1528
  });
1526
1529
 
@@ -2021,12 +2024,13 @@ function parseSequenceDgmo(content) {
2021
2024
  const participantGroupMap = /* @__PURE__ */ new Map();
2022
2025
  let currentTagGroup = null;
2023
2026
  const aliasMap = /* @__PURE__ */ new Map();
2024
- const splitPipe = (text) => {
2027
+ const splitPipe = (text, ln) => {
2025
2028
  const idx = text.indexOf("|");
2026
2029
  if (idx < 0) return { core: text };
2027
2030
  const core = text.substring(0, idx).trimEnd();
2028
2031
  const segments = text.substring(idx).split("|");
2029
- const meta = parsePipeMetadata(segments, aliasMap);
2032
+ const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
2033
+ const meta = parsePipeMetadata(segments, aliasMap, warnFn);
2030
2034
  return Object.keys(meta).length > 0 ? { core, meta } : { core };
2031
2035
  };
2032
2036
  const blockStack = [];
@@ -2055,7 +2059,7 @@ function parseSequenceDgmo(content) {
2055
2059
  if (gpipeIdx >= 0) {
2056
2060
  const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
2057
2061
  const segments = groupName.substring(gpipeIdx).split("|");
2058
- const meta = parsePipeMetadata(segments, aliasMap);
2062
+ const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
2059
2063
  if (Object.keys(meta).length > 0) groupMeta = meta;
2060
2064
  const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
2061
2065
  if (colorSuffix) {
@@ -2178,7 +2182,7 @@ function parseSequenceDgmo(content) {
2178
2182
  continue;
2179
2183
  }
2180
2184
  }
2181
- const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
2185
+ const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
2182
2186
  const isAMatch = isACore.match(IS_A_PATTERN);
2183
2187
  if (isAMatch) {
2184
2188
  contentStarted = true;
@@ -2215,7 +2219,7 @@ function parseSequenceDgmo(content) {
2215
2219
  }
2216
2220
  continue;
2217
2221
  }
2218
- const { core: posCore, meta: posMeta } = splitPipe(trimmed);
2222
+ const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
2219
2223
  const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
2220
2224
  if (posOnlyMatch) {
2221
2225
  contentStarted = true;
@@ -2242,7 +2246,7 @@ function parseSequenceDgmo(content) {
2242
2246
  }
2243
2247
  continue;
2244
2248
  }
2245
- const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
2249
+ const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
2246
2250
  const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
2247
2251
  if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
2248
2252
  const id = coloredMatch[1];
@@ -2270,7 +2274,7 @@ function parseSequenceDgmo(content) {
2270
2274
  continue;
2271
2275
  }
2272
2276
  {
2273
- const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
2277
+ const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
2274
2278
  const inGroup = activeGroup && measureIndent(raw) > 0;
2275
2279
  if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
2276
2280
  contentStarted = true;
@@ -2306,7 +2310,7 @@ function parseSequenceDgmo(content) {
2306
2310
  }
2307
2311
  blockStack.pop();
2308
2312
  }
2309
- const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
2313
+ const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
2310
2314
  const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
2311
2315
  if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
2312
2316
  pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
@@ -3757,7 +3761,12 @@ function parseERDiagram(content, palette) {
3757
3761
  table.lineNumber = lineNumber;
3758
3762
  const pipeStr = tableDecl[3]?.trim();
3759
3763
  if (pipeStr) {
3760
- const meta = parsePipeMetadata(["", pipeStr], aliasMap);
3764
+ const pipeSegments = pipeStr.split("|");
3765
+ const meta = parsePipeMetadata(
3766
+ ["", ...pipeSegments],
3767
+ aliasMap,
3768
+ () => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
3769
+ );
3761
3770
  Object.assign(table.metadata, meta);
3762
3771
  }
3763
3772
  currentTable = table;
@@ -5694,13 +5703,13 @@ function parseOrg(content, palette) {
5694
5703
  }
5695
5704
  } else if (metadataMatch && indentStack.length === 0) {
5696
5705
  if (indent === 0) {
5697
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5706
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5698
5707
  attachNode(node, indent, indentStack, result);
5699
5708
  } else {
5700
5709
  pushError(lineNumber, "Metadata has no parent node");
5701
5710
  }
5702
5711
  } else {
5703
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5712
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5704
5713
  attachNode(node, indent, indentStack, result);
5705
5714
  }
5706
5715
  }
@@ -5722,11 +5731,11 @@ function parseOrg(content, palette) {
5722
5731
  }
5723
5732
  return result;
5724
5733
  }
5725
- function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
5734
+ function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
5726
5735
  const segments = trimmed.split("|").map((s) => s.trim());
5727
5736
  let rawLabel = segments[0];
5728
5737
  const { label, color } = extractColor(rawLabel, palette);
5729
- const metadata = parsePipeMetadata(segments, aliasMap);
5738
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
5730
5739
  return {
5731
5740
  id: `node-${counter}`,
5732
5741
  label,
@@ -6268,7 +6277,7 @@ function parseC4(content, palette) {
6268
6277
  }
6269
6278
  const segments = trimmed.split("|").map((s) => s.trim());
6270
6279
  const nodeName = segments[0];
6271
- const metadata = parsePipeMetadata(segments, aliasMap);
6280
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6272
6281
  const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
6273
6282
  const dNode = {
6274
6283
  name: nodeName,
@@ -6421,7 +6430,7 @@ function parseC4(content, palette) {
6421
6430
  }
6422
6431
  namePart = namePart.substring(0, isAMatch.index).trim();
6423
6432
  }
6424
- const metadata = parsePipeMetadata(segments, aliasMap);
6433
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6425
6434
  const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
6426
6435
  const element = {
6427
6436
  name: namePart,
@@ -7208,14 +7217,14 @@ function parseSitemap(content, palette) {
7208
7217
  }
7209
7218
  } else if (metadataMatch && indentStack.length === 0) {
7210
7219
  if (indent === 0) {
7211
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7220
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7212
7221
  attachNode2(node, indent, indentStack, result);
7213
7222
  labelToNode.set(node.label.toLowerCase(), node);
7214
7223
  } else {
7215
7224
  pushError(lineNumber, "Metadata has no parent node");
7216
7225
  }
7217
7226
  } else {
7218
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7227
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7219
7228
  attachNode2(node, indent, indentStack, result);
7220
7229
  labelToNode.set(node.label.toLowerCase(), node);
7221
7230
  }
@@ -7257,11 +7266,11 @@ function parseSitemap(content, palette) {
7257
7266
  }
7258
7267
  return result;
7259
7268
  }
7260
- function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
7269
+ function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
7261
7270
  const segments = trimmed.split("|").map((s) => s.trim());
7262
7271
  const rawLabel = segments[0];
7263
7272
  const { label, color } = extractColor(rawLabel, palette);
7264
- const metadata = parsePipeMetadata(segments, aliasMap);
7273
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
7265
7274
  return {
7266
7275
  id: `node-${counter}`,
7267
7276
  label,
@@ -7955,7 +7964,9 @@ function parseGantt(content, palette) {
7955
7964
  criticalPath: false,
7956
7965
  dependencies: false,
7957
7966
  sort: "default",
7958
- defaultSwimlaneGroup: null
7967
+ defaultSwimlaneGroup: null,
7968
+ optionLineNumbers: {},
7969
+ holidaysLineNumber: null
7959
7970
  },
7960
7971
  diagnostics,
7961
7972
  error: null
@@ -7969,6 +7980,9 @@ function parseGantt(content, palette) {
7969
7980
  const warn = (line10, message) => {
7970
7981
  diagnostics.push(makeDgmoError(line10, message, "warning"));
7971
7982
  };
7983
+ const softError = (line10, message) => {
7984
+ diagnostics.push(makeDgmoError(line10, message, "error"));
7985
+ };
7972
7986
  const aliasMap = /* @__PURE__ */ new Map();
7973
7987
  const blockStack = [];
7974
7988
  const currentContainer = () => {
@@ -8102,10 +8116,10 @@ function parseGantt(content, palette) {
8102
8116
  const targetName = depParts[0].trim();
8103
8117
  let offset;
8104
8118
  if (depParts.length > 1) {
8105
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8119
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8106
8120
  if (meta.lag || meta.lead) {
8107
8121
  const key = meta.lag ? "lag" : "lead";
8108
- return fail(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8122
+ softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8109
8123
  }
8110
8124
  if (meta.offset) {
8111
8125
  const raw = meta.offset;
@@ -8137,6 +8151,7 @@ function parseGantt(content, palette) {
8137
8151
  inHolidaysBlock = true;
8138
8152
  holidaysBlockIndent = indent;
8139
8153
  inHeaderBlock = false;
8154
+ result.options.holidaysLineNumber = lineNumber;
8140
8155
  continue;
8141
8156
  }
8142
8157
  const tagMatch = matchTagBlockHeading(line10);
@@ -8163,7 +8178,8 @@ function parseGantt(content, palette) {
8163
8178
  startDate: eraMatch[1],
8164
8179
  endDate: eraMatch[2],
8165
8180
  label: eraExtracted.label,
8166
- color: eraExtracted.color || null
8181
+ color: eraExtracted.color || null,
8182
+ lineNumber
8167
8183
  });
8168
8184
  inHeaderBlock = false;
8169
8185
  continue;
@@ -8185,6 +8201,7 @@ function parseGantt(content, palette) {
8185
8201
  if (optMatch && isKnownOption(optMatch[1].toLowerCase())) {
8186
8202
  const key = optMatch[1].toLowerCase();
8187
8203
  const value = optMatch[2].trim();
8204
+ result.options.optionLineNumbers[key] = lineNumber;
8188
8205
  switch (key) {
8189
8206
  case "start":
8190
8207
  result.options.start = value;
@@ -8244,16 +8261,18 @@ function parseGantt(content, palette) {
8244
8261
  const groupMatch = line10.match(GROUP_RE2);
8245
8262
  if (groupMatch) {
8246
8263
  if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
8247
- return fail(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8264
+ softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8265
+ continue;
8248
8266
  }
8249
8267
  const afterBrackets = groupMatch[2].trim();
8250
8268
  const segments = afterBrackets ? afterBrackets.split("|") : [];
8251
8269
  let metadata = {};
8252
8270
  let color = null;
8271
+ const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
8253
8272
  if (segments.length > 0 && segments[0].trim()) {
8254
- metadata = parsePipeMetadata(["", ...segments], aliasMap);
8273
+ metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
8255
8274
  } else if (segments.length > 1) {
8256
- metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
8275
+ metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
8257
8276
  }
8258
8277
  const nameExtracted = extractColor(groupMatch[1], palette);
8259
8278
  if (nameExtracted.color) {
@@ -8284,7 +8303,6 @@ function parseGantt(content, palette) {
8284
8303
  const uncertain = !!timelineDurMatch[4];
8285
8304
  const labelRaw = timelineDurMatch[5];
8286
8305
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
8287
- if (result.error) return result;
8288
8306
  const taskNode = { kind: "task", ...task };
8289
8307
  currentContainer().push(taskNode);
8290
8308
  lastTaskNode = taskNode;
@@ -8298,7 +8316,6 @@ function parseGantt(content, palette) {
8298
8316
  const uncertain = !!durMatch[3];
8299
8317
  const labelRaw = durMatch[4];
8300
8318
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
8301
- if (result.error) return result;
8302
8319
  const taskNode = { kind: "task", ...task };
8303
8320
  currentContainer().push(taskNode);
8304
8321
  lastTaskNode = taskNode;
@@ -8315,7 +8332,6 @@ function parseGantt(content, palette) {
8315
8332
  lineNumber,
8316
8333
  explicitDateMatch[1]
8317
8334
  );
8318
- if (result.error) return result;
8319
8335
  const taskNode = { kind: "task", ...task };
8320
8336
  currentContainer().push(taskNode);
8321
8337
  lastTaskNode = taskNode;
@@ -8325,13 +8341,14 @@ function parseGantt(content, palette) {
8325
8341
  const depMatch = line10.match(DEPENDENCY_RE);
8326
8342
  if (depMatch) {
8327
8343
  if (!lastTaskNode) {
8328
- return fail(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8344
+ softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8345
+ continue;
8329
8346
  }
8330
8347
  const depParts = depMatch[1].split("|");
8331
8348
  const targetName = depParts[0].trim();
8332
8349
  let offset;
8333
8350
  if (depParts.length > 1) {
8334
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8351
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8335
8352
  if (meta.lag || meta.lead) {
8336
8353
  const key = meta.lag ? "lag" : "lead";
8337
8354
  warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
@@ -8351,7 +8368,8 @@ function parseGantt(content, palette) {
8351
8368
  lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
8352
8369
  continue;
8353
8370
  }
8354
- return fail(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8371
+ softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8372
+ continue;
8355
8373
  }
8356
8374
  if (currentTagGroup) {
8357
8375
  result.tagGroups.push(currentTagGroup);
@@ -8365,16 +8383,16 @@ function parseGantt(content, palette) {
8365
8383
  const segments = labelRaw.split("|");
8366
8384
  const label = segments[0].trim();
8367
8385
  if (label.toLowerCase() === "parallel") {
8368
- fail(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8386
+ softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8369
8387
  }
8370
- const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
8388
+ const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
8371
8389
  let progress = null;
8372
8390
  if (metadata.progress) {
8373
8391
  progress = parseFloat(metadata.progress);
8374
8392
  delete metadata.progress;
8375
8393
  }
8376
- for (let j = 1; j < segments.length; j++) {
8377
- const seg = segments[j].trim();
8394
+ for (const part of segments.slice(1).join(",").split(",")) {
8395
+ const seg = part.trim();
8378
8396
  const progressMatch = seg.match(/^(\d+)%$/);
8379
8397
  if (progressMatch) {
8380
8398
  progress = parseInt(progressMatch[1], 10);
@@ -8382,7 +8400,7 @@ function parseGantt(content, palette) {
8382
8400
  }
8383
8401
  if (metadata.lag || metadata.lead) {
8384
8402
  const key = metadata.lag ? "lag" : "lead";
8385
- fail(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8403
+ softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8386
8404
  }
8387
8405
  let taskOffset;
8388
8406
  if (metadata.offset) {
@@ -8469,7 +8487,7 @@ var init_parser10 = __esm({
8469
8487
  DEPENDENCY_RE = /^->\s*(.+)$/;
8470
8488
  COMMENT_RE = /^\/\//;
8471
8489
  ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8472
- MARKER_RE = /^marker\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8490
+ MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
8473
8491
  HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8474
8492
  HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8475
8493
  WORKWEEK_RE = /^workweek:\s*(.+)$/i;
@@ -16096,7 +16114,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
16096
16114
  }
16097
16115
  const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
16098
16116
  const LABEL_CHAR_W = 7;
16099
- const LABEL_PAD = 8;
16117
+ const LABEL_PAD2 = 8;
16100
16118
  const LABEL_H = 16;
16101
16119
  const PERP_OFFSET = 10;
16102
16120
  const labelPositions = [];
@@ -16105,7 +16123,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
16105
16123
  if (!edge.label || edge.points.length < 2) continue;
16106
16124
  const midIdx = Math.floor(edge.points.length / 2);
16107
16125
  const midPt = edge.points[midIdx];
16108
- const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD;
16126
+ const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
16109
16127
  const prev = edge.points[Math.max(0, midIdx - 1)];
16110
16128
  const next = edge.points[Math.min(edge.points.length - 1, midIdx + 1)];
16111
16129
  const dx = next.x - prev.x;
@@ -18882,11 +18900,8 @@ function calculateSchedule(parsed) {
18882
18900
  for (const dep of task.dependencies) {
18883
18901
  const resolved = resolveTaskName(dep.targetName, allTasks);
18884
18902
  if (isResolverError(resolved)) {
18885
- if (resolved.kind === "ambiguous") {
18886
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18887
- } else {
18888
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18889
- }
18903
+ warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18904
+ continue;
18890
18905
  }
18891
18906
  const targetNode = taskMap.get(resolved.task.id);
18892
18907
  if (targetNode) {
@@ -18901,14 +18916,26 @@ function calculateSchedule(parsed) {
18901
18916
  }
18902
18917
  }
18903
18918
  }
18904
- const sortedIds = topologicalSort(taskMap);
18919
+ let sortedIds = topologicalSort(taskMap);
18905
18920
  if (!sortedIds) {
18906
18921
  const cycle = findCycle(taskMap);
18907
18922
  const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
18908
- return fail(
18923
+ warn(
18909
18924
  taskMap.get(cycle[0]).task.lineNumber,
18910
- `Circular dependency detected: ${cycleStr}`
18925
+ `Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
18911
18926
  );
18927
+ breakCycle(cycle, taskMap, depOffsetMap);
18928
+ sortedIds = topologicalSort(taskMap);
18929
+ let safety = 10;
18930
+ while (!sortedIds && safety-- > 0) {
18931
+ const nextCycle = findCycle(taskMap);
18932
+ if (nextCycle.length === 0) break;
18933
+ breakCycle(nextCycle, taskMap, depOffsetMap);
18934
+ sortedIds = topologicalSort(taskMap);
18935
+ }
18936
+ if (!sortedIds) {
18937
+ sortedIds = [...taskMap.keys()];
18938
+ }
18912
18939
  }
18913
18940
  for (const taskId of sortedIds) {
18914
18941
  const node = taskMap.get(taskId);
@@ -19192,6 +19219,19 @@ function findCycle(taskMap) {
19192
19219
  return null;
19193
19220
  }
19194
19221
  }
19222
+ function breakCycle(cycle, taskMap, depOffsetMap) {
19223
+ if (cycle.length < 3) return;
19224
+ const fromId = cycle[cycle.length - 2];
19225
+ const toId = cycle[0];
19226
+ const toNode = taskMap.get(toId);
19227
+ if (toNode) {
19228
+ const idx = toNode.predecessors.indexOf(fromId);
19229
+ if (idx !== -1) {
19230
+ toNode.predecessors.splice(idx, 1);
19231
+ depOffsetMap.delete(`${fromId}->${toId}`);
19232
+ }
19233
+ }
19234
+ }
19195
19235
  function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
19196
19236
  if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
19197
19237
  const latestEnd = /* @__PURE__ */ new Map();
@@ -19280,10 +19320,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
19280
19320
  if (!resolved?.startDate || !resolved?.endDate) continue;
19281
19321
  if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
19282
19322
  if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
19323
+ const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19324
+ totalDuration += dur;
19283
19325
  if (task.progress !== null) {
19284
- const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19285
19326
  totalProgress += task.progress * dur;
19286
- totalDuration += dur;
19287
19327
  hasProgress = true;
19288
19328
  }
19289
19329
  }
@@ -19326,9 +19366,47 @@ __export(renderer_exports9, {
19326
19366
  });
19327
19367
  import * as d3Scale from "d3-scale";
19328
19368
  import * as d3Selection10 from "d3-selection";
19369
+ function computeBarLabel(label, x1, barWidth, innerWidth, textColor) {
19370
+ const textWidth = label.length * CHAR_W;
19371
+ const x2 = x1 + barWidth;
19372
+ if (textWidth < barWidth - LABEL_PAD) {
19373
+ return { x: x1 + 6, anchor: "start", fill: textColor, text: label };
19374
+ }
19375
+ if (x2 + LABEL_GAP + textWidth <= innerWidth) {
19376
+ return { x: x2 + LABEL_GAP, anchor: "start", fill: textColor, text: label };
19377
+ }
19378
+ if (x1 - LABEL_GAP - textWidth >= 0) {
19379
+ return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label };
19380
+ }
19381
+ const availWidth = x1 - LABEL_GAP;
19382
+ if (availWidth > CHAR_W * 3) {
19383
+ const maxChars = Math.floor(availWidth / CHAR_W) - 1;
19384
+ return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label.slice(0, maxChars) + "\u2026" };
19385
+ }
19386
+ return null;
19387
+ }
19388
+ function renderLabelBand(svg, y, leftMargin, color, palette, cssPrefix, dataAttr) {
19389
+ const bandX = 5;
19390
+ const bandW = leftMargin - 7;
19391
+ const bandY = y - BAR_H / 2;
19392
+ const clipId = `gantt-band-clip-${bandClipCounter++}`;
19393
+ 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);
19394
+ 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");
19395
+ 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");
19396
+ if (dataAttr) {
19397
+ tint2.attr(dataAttr.key, dataAttr.value);
19398
+ accent.attr(dataAttr.key, dataAttr.value);
19399
+ }
19400
+ }
19401
+ function appendTaskIcon(textEl, label, isMilestone, iconColor, textColor) {
19402
+ const icon = isMilestone ? "\u25C6" : "\u25CF";
19403
+ textEl.append("tspan").attr("fill", iconColor).text(icon);
19404
+ textEl.append("tspan").attr("fill", textColor).text(" " + label);
19405
+ }
19329
19406
  function renderGantt(container, resolved, palette, isDark, options, exportDims) {
19330
19407
  container.innerHTML = "";
19331
- if (resolved.error || resolved.tasks.length === 0) return;
19408
+ bandClipCounter = 0;
19409
+ if (resolved.tasks.length === 0) return;
19332
19410
  const onClickItem = options?.onClickItem;
19333
19411
  const collapsedGroups = options?.collapsedGroups;
19334
19412
  const onToggleGroup = options?.onToggleGroup;
@@ -19346,10 +19424,13 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19346
19424
  const isTagMode = tagRows !== null;
19347
19425
  const allLabels = isTagMode ? [
19348
19426
  ...rows.filter((r) => r.type === "lane-header").map((r) => r.laneName),
19349
- ...rows.filter((r) => r.type === "task").map((r) => r.task.task.label)
19427
+ ...rows.filter((r) => r.type === "task").map((r) => "\u25CF " + r.task.task.label)
19350
19428
  ] : [
19351
- ...resolved.tasks.map((t) => t.task.label),
19352
- ...resolved.groups.map((g2) => " ".repeat(g2.depth) + g2.name)
19429
+ ...resolved.tasks.map((t) => "\u25CF " + t.task.label),
19430
+ ...resolved.groups.map((g2) => {
19431
+ const px = g2.depth <= 2 ? g2.depth * 14 : 2 * 14 + (g2.depth - 2) * 8;
19432
+ return " ".repeat(Math.ceil(px / 7)) + g2.name;
19433
+ })
19353
19434
  ];
19354
19435
  const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
19355
19436
  const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
@@ -19358,9 +19439,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19358
19439
  const titleHeight = title ? 50 : 20;
19359
19440
  const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
19360
19441
  const topDateLabelReserve = 22;
19361
- const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve;
19442
+ const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
19443
+ const markerLabelReserve = hasOverheadLabels ? 18 : 0;
19444
+ const CONTENT_TOP_PAD = 16;
19445
+ const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve;
19362
19446
  const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
19363
- const innerHeight = contentH;
19447
+ const innerHeight = CONTENT_TOP_PAD + contentH;
19364
19448
  const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
19365
19449
  const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
19366
19450
  const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
@@ -19386,6 +19470,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19386
19470
  isDark,
19387
19471
  hasCriticalPath,
19388
19472
  criticalPathActive,
19473
+ resolved.options.optionLineNumbers,
19389
19474
  (groupName) => {
19390
19475
  currentActiveGroup = currentActiveGroup?.toLowerCase() === groupName.toLowerCase() ? null : groupName;
19391
19476
  if (onActiveGroupChange) onActiveGroupChange(currentActiveGroup);
@@ -19398,7 +19483,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19398
19483
  },
19399
19484
  currentSwimlaneGroup,
19400
19485
  onSwimlaneChange,
19401
- viewMode
19486
+ viewMode,
19487
+ resolved.tasks
19402
19488
  );
19403
19489
  }
19404
19490
  }
@@ -19423,7 +19509,25 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19423
19509
  renderTimeScaleHorizontal(g, xScale, innerWidth, innerHeight, palette.text);
19424
19510
  renderWeekendBands(g, resolved, xScale, innerHeight, palette, isDark);
19425
19511
  renderHolidayBands(g, svg, resolved, xScale, innerHeight, palette, isDark, marginTop - 4, leftMargin, onClickItem);
19426
- renderErasAndMarkers(g, resolved, xScale, innerHeight, palette);
19512
+ renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette);
19513
+ let todayDate = null;
19514
+ let todayX = -1;
19515
+ const todayColor = palette.accent || "#e74c3c";
19516
+ const todayMarkerLineNum = resolved.options.optionLineNumbers["today-marker"];
19517
+ if (resolved.options.todayMarker !== "off") {
19518
+ if (resolved.options.todayMarker === "on") {
19519
+ todayDate = /* @__PURE__ */ new Date();
19520
+ } else {
19521
+ todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
19522
+ }
19523
+ todayX = xScale(dateToFractionalYear(todayDate));
19524
+ if (todayX >= 0 && todayX <= innerWidth) {
19525
+ 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");
19526
+ if (todayMarkerLineNum) todayLine.attr("data-line-number", String(todayMarkerLineNum));
19527
+ 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");
19528
+ if (todayMarkerLineNum) todayLabel.attr("data-line-number", String(todayMarkerLineNum));
19529
+ }
19530
+ }
19427
19531
  const taskPositions = /* @__PURE__ */ new Map();
19428
19532
  const groupPositions = /* @__PURE__ */ new Map();
19429
19533
  const lanePositions = /* @__PURE__ */ new Map();
@@ -19444,7 +19548,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19444
19548
  }
19445
19549
  }
19446
19550
  }
19447
- let yOffset = 0;
19551
+ let yOffset = CONTENT_TOP_PAD;
19448
19552
  for (const row of rows) {
19449
19553
  if (row.type === "lane-header") {
19450
19554
  const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
@@ -19459,6 +19563,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19459
19563
  laneBarWidth = Math.max(lx2 - lx1, 2);
19460
19564
  }
19461
19565
  lanePositions.set(row.laneName, { x1: lx1, x2: lx1 + laneBarWidth, y: yOffset + BAR_H / 2 });
19566
+ renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, laneColor, palette, "lane", { key: "data-lane", value: row.laneName });
19462
19567
  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", () => {
19463
19568
  if (onToggleLane) onToggleLane(row.laneName);
19464
19569
  }).on("mouseenter", () => {
@@ -19484,7 +19589,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19484
19589
  });
19485
19590
  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);
19486
19591
  if (row.aggregateProgress !== null && row.aggregateProgress > 0) {
19487
- 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");
19592
+ 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");
19488
19593
  }
19489
19594
  }
19490
19595
  yOffset += BAR_H + ROW_GAP;
@@ -19493,7 +19598,9 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19493
19598
  const isCollapsed = collapsedGroups?.has(group.name) ?? false;
19494
19599
  const indent = " ".repeat(group.depth);
19495
19600
  const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
19496
- const groupColor = group.color || palette.textMuted;
19601
+ const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
19602
+ const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
19603
+ renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, groupColor, palette, "group", { key: "data-group", value: group.name });
19497
19604
  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", () => {
19498
19605
  if (onToggleGroup) onToggleGroup(group.name);
19499
19606
  }).on("mouseenter", () => {
@@ -19503,7 +19610,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19503
19610
  resetHighlight(g, svg);
19504
19611
  hideGanttDateIndicators(g);
19505
19612
  });
19506
- const labelX = 10 + group.depth * 14;
19613
+ const groupIndent = group.depth <= 2 ? group.depth * 14 : 2 * 14 + (group.depth - 2) * 8;
19614
+ const labelX = 10 + groupIndent;
19507
19615
  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)}%` : ""));
19508
19616
  const gStart = dateToFractionalYear(group.startDate);
19509
19617
  const gEnd = dateToFractionalYear(group.endDate);
@@ -19521,7 +19629,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19521
19629
  });
19522
19630
  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);
19523
19631
  if (group.progress !== null && group.progress > 0) {
19524
- 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);
19632
+ 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);
19633
+ }
19634
+ const summaryLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
19635
+ const summaryPlacement = computeBarLabel(summaryLabel, gx1, barWidth, innerWidth, palette.text);
19636
+ if (summaryPlacement) {
19637
+ 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);
19525
19638
  }
19526
19639
  groupPositions.set(group.name, { x1: gx1, x2: gx1 + barWidth, y: yOffset + BAR_H / 2 });
19527
19640
  } else {
@@ -19536,7 +19649,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19536
19649
  });
19537
19650
  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);
19538
19651
  if (group.progress !== null && group.progress > 0) {
19539
- 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);
19652
+ 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);
19653
+ }
19654
+ const expandedLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
19655
+ const expandedPlacement = computeBarLabel(expandedLabel, gx1, groupBarWidth, innerWidth, palette.text);
19656
+ if (expandedPlacement) {
19657
+ 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);
19540
19658
  }
19541
19659
  }
19542
19660
  }
@@ -19544,33 +19662,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19544
19662
  } else if (row.type === "task") {
19545
19663
  const rt = row.task;
19546
19664
  const task = rt.task;
19547
- const taskLabelX = isTagMode ? 20 : 6 + rt.groupPath.length * 14;
19665
+ const barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
19666
+ const depth = rt.groupPath.length;
19667
+ const indent = depth <= 2 ? depth * 14 : 2 * 14 + (depth - 2) * 8;
19668
+ const taskLabelX = isTagMode ? 20 : 6 + indent;
19548
19669
  const topGroup = rt.groupPath.length > 0 ? rt.groupPath[0] : null;
19549
- 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", () => {
19670
+ 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", () => {
19550
19671
  if (onClickItem) onClickItem(task.lineNumber);
19551
19672
  }).on("mouseenter", () => {
19552
- highlightTask(g, svg, task.id);
19673
+ if (rt.isMilestone) {
19674
+ highlightMilestone(g, svg, task.id);
19675
+ } else {
19676
+ highlightTask(g, svg, task.id);
19677
+ }
19553
19678
  }).on("mouseleave", () => {
19554
19679
  resetHighlight(g, svg);
19555
19680
  });
19681
+ appendTaskIcon(taskLabel, task.label, rt.isMilestone, barColor, palette.text);
19556
19682
  for (const [key, value] of Object.entries(rt.effectiveMetadata)) {
19557
19683
  taskLabel.attr(`data-tag-${key}`, value.toLowerCase());
19558
19684
  }
19559
19685
  if (rt.isCriticalPath) {
19560
19686
  taskLabel.attr("data-critical-path", "true");
19561
19687
  }
19562
- let barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
19563
19688
  if (rt.isMilestone) {
19564
19689
  const mx = xScale(dateToFractionalYear(rt.startDate));
19565
19690
  const my = yOffset + BAR_H / 2;
19566
- 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", () => {
19691
+ 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", () => {
19567
19692
  if (onClickItem) onClickItem(task.lineNumber);
19568
19693
  }).on("mouseenter", () => {
19569
- highlightTaskLabel(svg, task.lineNumber);
19694
+ highlightMilestone(g, svg, task.id);
19570
19695
  showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
19571
19696
  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);
19572
19697
  }).on("mouseleave", () => {
19573
- resetTaskLabels(svg);
19698
+ resetHighlight(g, svg);
19574
19699
  hideGanttDateIndicators(g);
19575
19700
  g.selectAll(".gantt-milestone-hover-label").remove();
19576
19701
  });
@@ -19637,32 +19762,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19637
19762
  progGrad.append("stop").attr("offset", "100%").attr("stop-color", barColor).attr("stop-opacity", 0);
19638
19763
  progressFill = `url(#${progGradId})`;
19639
19764
  }
19640
- 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);
19765
+ 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);
19641
19766
  }
19642
19767
  if (rt.isCriticalPath) {
19643
19768
  taskG.attr("data-critical-path", "true");
19644
19769
  }
19645
- const textWidth = task.label.length * 6.5;
19646
- if (textWidth < barWidth - 8) {
19647
- 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);
19770
+ const labelPlacement = computeBarLabel(task.label, x1, barWidth, innerWidth, palette.text);
19771
+ if (labelPlacement) {
19772
+ 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);
19648
19773
  }
19649
19774
  taskPositions.set(task.id, { x1, x2: x1 + barWidth, y: yOffset + BAR_H / 2 });
19650
19775
  }
19651
19776
  yOffset += BAR_H + ROW_GAP;
19652
19777
  }
19653
19778
  }
19654
- if (resolved.options.todayMarker !== "off") {
19655
- let todayDate;
19656
- if (resolved.options.todayMarker === "on") {
19657
- todayDate = /* @__PURE__ */ new Date();
19658
- } else {
19659
- todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
19660
- }
19661
- const todayX = xScale(dateToFractionalYear(todayDate));
19662
- if (todayX >= 0 && todayX <= innerWidth) {
19663
- 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);
19664
- 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");
19665
- }
19779
+ if (todayDate && todayX >= 0 && todayX <= innerWidth) {
19780
+ const todayHoverG = g.append("g").attr("class", "gantt-today-hover").style("cursor", "pointer");
19781
+ todayHoverG.append("rect").attr("x", todayX - 10).attr("y", -6).attr("width", 20).attr("height", innerHeight + 16).attr("fill", "transparent").attr("pointer-events", "all");
19782
+ const todayDateObj = todayDate;
19783
+ todayHoverG.on("mouseenter", () => {
19784
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
19785
+ g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
19786
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
19787
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
19788
+ svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
19789
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
19790
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
19791
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
19792
+ g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
19793
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
19794
+ showGanttDateIndicators(g, xScale, todayDateObj, null, innerHeight, todayColor);
19795
+ }).on("mouseleave", () => {
19796
+ resetHighlight(g, svg);
19797
+ hideGanttDateIndicators(g);
19798
+ });
19666
19799
  }
19667
19800
  if (resolved.options.dependencies) {
19668
19801
  renderDependencyArrows(g, resolved, taskPositions, groupPositions, collapsedGroups, palette, isDark, isTagMode, lanePositions, collapsedLanes, taskLaneMap);
@@ -19779,10 +19912,10 @@ function renderDependencyArrows(g, resolved, taskPositions, groupPositions, coll
19779
19912
  const path = `M ${sx} ${sy} C ${sx + cpOffset} ${sy}, ${tx - cpOffset} ${ty}, ${tx} ${ty}`;
19780
19913
  const arrowColor = mix(palette.text, palette.bg, 50);
19781
19914
  const isCpArrow = rt.isCriticalPath && targetTask.isCriticalPath;
19782
- 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);
19915
+ 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);
19783
19916
  const headSize = 5;
19784
19917
  const angle = 0;
19785
- 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);
19918
+ 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);
19786
19919
  }
19787
19920
  }
19788
19921
  }
@@ -19803,7 +19936,9 @@ function applyCriticalPathHighlight(svg, chartG) {
19803
19936
  el.attr("opacity", el.attr("data-critical-path") === "true" ? 1 : FADE_OPACITY);
19804
19937
  });
19805
19938
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
19939
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
19806
19940
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
19941
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
19807
19942
  chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
19808
19943
  chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").each(function() {
19809
19944
  const el = d3Selection10.select(this);
@@ -19815,7 +19950,9 @@ function resetHighlightAll(svg, chartG) {
19815
19950
  chartG.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
19816
19951
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
19817
19952
  svg.selectAll(".gantt-group-label").attr("opacity", 1);
19953
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
19818
19954
  svg.selectAll(".gantt-lane-header").attr("opacity", 1);
19955
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
19819
19956
  chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", 1);
19820
19957
  chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
19821
19958
  }
@@ -19831,7 +19968,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
19831
19968
  }
19832
19969
  return iconG;
19833
19970
  }
19834
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
19971
+ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
19835
19972
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
19836
19973
  let visibleGroups;
19837
19974
  if (activeGroupName) {
@@ -19841,6 +19978,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19841
19978
  } else {
19842
19979
  visibleGroups = tagGroups;
19843
19980
  }
19981
+ const usedValues = /* @__PURE__ */ new Map();
19982
+ if (resolvedTasks) {
19983
+ for (const group of visibleGroups) {
19984
+ const key = group.name.toLowerCase();
19985
+ const used = /* @__PURE__ */ new Set();
19986
+ for (const rt of resolvedTasks) {
19987
+ const val = rt.effectiveMetadata[key];
19988
+ if (val) used.add(val.toLowerCase());
19989
+ }
19990
+ usedValues.set(key, used);
19991
+ }
19992
+ }
19993
+ const filteredEntries = /* @__PURE__ */ new Map();
19994
+ for (const group of visibleGroups) {
19995
+ const key = group.name.toLowerCase();
19996
+ const used = usedValues.get(key);
19997
+ if (used && used.size > 0) {
19998
+ filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
19999
+ } else {
20000
+ filteredEntries.set(key, group.entries);
20001
+ }
20002
+ }
19844
20003
  const groupWidths = [];
19845
20004
  let totalW = 0;
19846
20005
  for (const group of visibleGroups) {
@@ -19851,8 +20010,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19851
20010
  const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
19852
20011
  let groupW = pillW;
19853
20012
  if (isActive) {
20013
+ const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
19854
20014
  let entriesW = 0;
19855
- for (const entry of group.entries) {
20015
+ for (const entry of entries) {
19856
20016
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
19857
20017
  }
19858
20018
  groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
@@ -19869,7 +20029,8 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19869
20029
  if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
19870
20030
  totalW += cpPillW;
19871
20031
  }
19872
- const legendX = chartLeftMargin + (chartInnerWidth - totalW) / 2;
20032
+ const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
20033
+ const legendX = (containerWidth - totalW) / 2;
19873
20034
  const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
19874
20035
  let cursorX = 0;
19875
20036
  for (let i = 0; i < visibleGroups.length; i++) {
@@ -19881,7 +20042,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19881
20042
  const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
19882
20043
  const pillH = isActive ? LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2 : LEGEND_HEIGHT;
19883
20044
  const groupW = groupWidths[i];
19884
- 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", () => {
20045
+ 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", () => {
19885
20046
  if (onToggle) onToggle(group.name);
19886
20047
  });
19887
20048
  if (isActive) {
@@ -19911,10 +20072,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19911
20072
  }
19912
20073
  if (isActive) {
19913
20074
  const tagKey = group.name.toLowerCase();
20075
+ const entries = filteredEntries.get(tagKey) ?? group.entries;
19914
20076
  let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
19915
- for (const entry of group.entries) {
20077
+ for (const entry of entries) {
19916
20078
  const entryValue = entry.value.toLowerCase();
19917
- const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
20079
+ const entryG = gEl.append("g").attr("class", "gantt-legend-entry").attr("data-line-number", String(entry.lineNumber)).style("cursor", "pointer");
19918
20080
  entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
19919
20081
  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);
19920
20082
  entryG.on("mouseenter", () => {
@@ -19950,9 +20112,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19950
20112
  cursorX += groupW + LEGEND_GROUP_GAP;
19951
20113
  }
19952
20114
  if (hasCriticalPath) {
20115
+ const cpLineNum = optionLineNumbers["critical-path"];
19953
20116
  const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
19954
20117
  if (onToggleCriticalPath) onToggleCriticalPath();
19955
20118
  });
20119
+ if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
19956
20120
  cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
19957
20121
  if (criticalPathActive) {
19958
20122
  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);
@@ -19970,7 +20134,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19970
20134
  });
19971
20135
  }
19972
20136
  }
19973
- function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
20137
+ function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
19974
20138
  for (let i = 0; i < resolved.eras.length; i++) {
19975
20139
  const era = resolved.eras[i];
19976
20140
  const color = era.color || ERA_COLORS[i % ERA_COLORS.length];
@@ -19981,13 +20145,23 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
19981
20145
  const hoverEraOpacity = 0.16;
19982
20146
  const eraStartDate = parseDateStringToDate(era.startDate);
19983
20147
  const eraEndDate = parseDateStringToDate(era.endDate);
19984
- const eraG = g.append("g").attr("class", "gantt-era-group");
20148
+ const eraG = g.append("g").attr("class", "gantt-era-group").attr("data-line-number", String(era.lineNumber));
19985
20149
  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);
19986
- 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);
20150
+ 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);
19987
20151
  eraG.on("mouseenter", () => {
20152
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20153
+ g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
20154
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20155
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20156
+ svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
20157
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20158
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20159
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20160
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
19988
20161
  eraRect.attr("opacity", hoverEraOpacity);
19989
20162
  showGanttDateIndicators(g, xScale, eraStartDate, eraEndDate, innerHeight, color);
19990
20163
  }).on("mouseleave", () => {
20164
+ resetHighlight(g, svg);
19991
20165
  eraRect.attr("opacity", baseEraOpacity);
19992
20166
  hideGanttDateIndicators(g);
19993
20167
  });
@@ -19996,13 +20170,35 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
19996
20170
  const color = marker.color || palette.accent || "#d08770";
19997
20171
  const mx = xScale(parseDateToFractionalYear(marker.date));
19998
20172
  const markerDate = parseDateStringToDate(marker.date);
19999
- const markerG = g.append("g").attr("class", "gantt-marker-group");
20000
- 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);
20001
- markerG.append("polygon").attr("points", diamondPoints(mx, 6, 8)).attr("fill", color).attr("opacity", 0.5);
20002
- 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);
20173
+ const diamondSize = 5;
20174
+ const labelY = -24;
20175
+ const diamondY = labelY + 14;
20176
+ const markerG = g.append("g").attr("class", "gantt-marker-group").attr("data-line-number", String(marker.lineNumber)).style("cursor", "pointer");
20177
+ 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");
20178
+ 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);
20179
+ 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);
20180
+ 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);
20181
+ const markerLine = markerG.select(".gantt-marker");
20182
+ const markerDiamond = markerG.select("path");
20003
20183
  markerG.on("mouseenter", () => {
20004
- showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color);
20184
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20185
+ g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
20186
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20187
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20188
+ svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
20189
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20190
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20191
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20192
+ g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
20193
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20194
+ markerG.attr("opacity", 1);
20195
+ markerLine.attr("opacity", 0.8);
20196
+ markerDiamond.attr("opacity", 0);
20197
+ showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color, { skipStartLine: true });
20005
20198
  }).on("mouseleave", () => {
20199
+ resetHighlight(g, svg);
20200
+ markerLine.attr("opacity", 0.5);
20201
+ markerDiamond.attr("opacity", 0.9);
20006
20202
  hideGanttDateIndicators(g);
20007
20203
  });
20008
20204
  }
@@ -20015,11 +20211,7 @@ function parseDateStringToDate(s) {
20015
20211
  return new Date(year, month, day);
20016
20212
  }
20017
20213
  function parseDateToFractionalYear(s) {
20018
- const parts = s.split("-").map((p) => parseInt(p, 10));
20019
- const year = parts[0];
20020
- const month = parts.length >= 2 ? parts[1] : 1;
20021
- const day = parts.length >= 3 ? parts[2] : 1;
20022
- return year + (month - 1) / 12 + (day - 1) / 365;
20214
+ return dateToFractionalYear(parseDateStringToDate(s));
20023
20215
  }
20024
20216
  function highlightDeps(g, svg, taskId, resolved) {
20025
20217
  const related = /* @__PURE__ */ new Set([taskId]);
@@ -20053,6 +20245,7 @@ function highlightDeps(g, svg, taskId, resolved) {
20053
20245
  const isRelated = from && related.has(from) || to && related.has(to);
20054
20246
  el.attr("opacity", isRelated ? 0.5 : FADE_OPACITY);
20055
20247
  });
20248
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20056
20249
  }
20057
20250
  function highlightGroup(g, svg, groupName) {
20058
20251
  g.selectAll(".gantt-task").each(function() {
@@ -20075,8 +20268,14 @@ function highlightGroup(g, svg, groupName) {
20075
20268
  const el = d3Selection10.select(this);
20076
20269
  el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
20077
20270
  });
20271
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").each(function() {
20272
+ const el = d3Selection10.select(this);
20273
+ el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
20274
+ });
20078
20275
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20276
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
20079
20277
  g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
20278
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20080
20279
  }
20081
20280
  function highlightLane(g, svg, tagKey, laneName) {
20082
20281
  const tagAttr = `data-tag-${tagKey}`;
@@ -20101,8 +20300,14 @@ function highlightLane(g, svg, tagKey, laneName) {
20101
20300
  const el = d3Selection10.select(this);
20102
20301
  el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
20103
20302
  });
20303
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").each(function() {
20304
+ const el = d3Selection10.select(this);
20305
+ el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
20306
+ });
20104
20307
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20105
20308
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20309
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
20310
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20106
20311
  }
20107
20312
  function highlightTask(g, svg, taskId) {
20108
20313
  g.selectAll(".gantt-task").each(function() {
@@ -20116,9 +20321,31 @@ function highlightTask(g, svg, taskId) {
20116
20321
  });
20117
20322
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20118
20323
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20324
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
20119
20325
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20326
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
20120
20327
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20121
20328
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20329
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20330
+ }
20331
+ function highlightMilestone(g, svg, taskId) {
20332
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20333
+ g.selectAll(".gantt-milestone").each(function() {
20334
+ const el = d3Selection10.select(this);
20335
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20336
+ });
20337
+ svg.selectAll(".gantt-task-label").each(function() {
20338
+ const el = d3Selection10.select(this);
20339
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20340
+ });
20341
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20342
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20343
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
20344
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20345
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
20346
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20347
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20348
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20122
20349
  }
20123
20350
  function highlightTaskLabel(svg, lineNumber) {
20124
20351
  const ln = String(lineNumber);
@@ -20134,10 +20361,14 @@ function resetHighlight(g, svg) {
20134
20361
  g.selectAll(".gantt-task, .gantt-milestone").attr("opacity", 1);
20135
20362
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
20136
20363
  svg.selectAll(".gantt-group-label").attr("opacity", 1);
20364
+ svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
20137
20365
  svg.selectAll(".gantt-task-label").attr("opacity", 1);
20138
20366
  svg.selectAll(".gantt-lane-header").attr("opacity", 1);
20367
+ svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
20139
20368
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
20140
20369
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
20370
+ g.selectAll(".gantt-marker-group").attr("opacity", 1);
20371
+ g.selectAll(".gantt-era-group").attr("opacity", 1);
20141
20372
  }
20142
20373
  function buildRowList(resolved, collapsedGroups) {
20143
20374
  const rows = [];
@@ -20216,9 +20447,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20216
20447
  for (const entry of tagGroup.entries) {
20217
20448
  const entryKey = entry.value.toLowerCase();
20218
20449
  const tasks = buckets.get(entryKey) ?? [];
20450
+ if (tasks.length === 0) continue;
20219
20451
  tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20220
- const progressValues = tasks.map((t) => t.task.progress).filter((p) => p !== null);
20221
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20452
+ const aggregateProgress = durationWeightedProgress(tasks);
20222
20453
  const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
20223
20454
  const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
20224
20455
  const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
@@ -20240,8 +20471,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20240
20471
  }
20241
20472
  if (unbucketed.length > 0) {
20242
20473
  unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20243
- const progressValues = unbucketed.map((t) => t.task.progress).filter((p) => p !== null);
20244
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20474
+ const aggregateProgress = durationWeightedProgress(unbucketed);
20245
20475
  const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
20246
20476
  const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
20247
20477
  const noLaneName = `No ${tagGroup.name}`;
@@ -20264,6 +20494,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20264
20494
  }
20265
20495
  return rows;
20266
20496
  }
20497
+ function durationWeightedProgress(tasks) {
20498
+ let totalDuration = 0;
20499
+ let totalProgress = 0;
20500
+ let hasProgress = false;
20501
+ for (const rt of tasks) {
20502
+ const dur = rt.endDate.getTime() - rt.startDate.getTime();
20503
+ totalDuration += dur;
20504
+ if (rt.task.progress !== null) {
20505
+ totalProgress += rt.task.progress * dur;
20506
+ hasProgress = true;
20507
+ }
20508
+ }
20509
+ return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
20510
+ }
20267
20511
  function dateToFractionalYear(d) {
20268
20512
  const y = d.getFullYear();
20269
20513
  const startOfYear = new Date(y, 0, 1);
@@ -20278,21 +20522,43 @@ function diamondPoints(cx, cy, size) {
20278
20522
  function formatGanttDate(d) {
20279
20523
  return `${MONTH_ABBR[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
20280
20524
  }
20281
- function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color) {
20525
+ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color, options) {
20282
20526
  g.selectAll(".gantt-scale-tick").attr("opacity", 0.05);
20283
20527
  g.selectAll(".gantt-today").attr("opacity", 0.05);
20528
+ const hg = g.append("g").attr("class", "gantt-hover-date").attr("pointer-events", "none");
20284
20529
  const tickLen = 6;
20285
20530
  const startPos = xScale(dateToFractionalYear(startDate));
20286
20531
  const startLabel = formatGanttDate(startDate);
20287
- 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);
20288
- 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);
20289
- 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);
20532
+ if (!options?.skipStartLine) {
20533
+ 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);
20534
+ }
20535
+ 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);
20536
+ 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);
20290
20537
  if (endDate && endDate.getTime() !== startDate.getTime()) {
20291
20538
  const endPos = xScale(dateToFractionalYear(endDate));
20292
20539
  const endLabel = formatGanttDate(endDate);
20293
- 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);
20294
- 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);
20295
- 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);
20540
+ const minLabelGap = 90;
20541
+ const gap = endPos - startPos;
20542
+ let startLabelX = startPos;
20543
+ let endLabelX = endPos;
20544
+ let startAnchor = "middle";
20545
+ let endAnchor = "middle";
20546
+ if (gap < minLabelGap) {
20547
+ const mid = (startPos + endPos) / 2;
20548
+ startLabelX = mid - minLabelGap / 2;
20549
+ endLabelX = mid + minLabelGap / 2;
20550
+ startAnchor = "middle";
20551
+ endAnchor = "middle";
20552
+ }
20553
+ 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);
20554
+ hg.selectAll("text.gantt-hover-date").each(function() {
20555
+ const el = d3Selection10.select(this);
20556
+ if (el.text() === startLabel) {
20557
+ el.attr("x", startLabelX).attr("text-anchor", startAnchor);
20558
+ }
20559
+ });
20560
+ 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);
20561
+ 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);
20296
20562
  }
20297
20563
  }
20298
20564
  function hideGanttDateIndicators(g) {
@@ -20337,7 +20603,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
20337
20603
  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);
20338
20604
  }
20339
20605
  }
20340
- var 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;
20606
+ var 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;
20341
20607
  var init_renderer9 = __esm({
20342
20608
  "src/gantt/renderer.ts"() {
20343
20609
  "use strict";
@@ -20354,6 +20620,12 @@ var init_renderer9 = __esm({
20354
20620
  MIN_LEFT_MARGIN = 120;
20355
20621
  BOTTOM_MARGIN = 40;
20356
20622
  RIGHT_MARGIN = 20;
20623
+ CHAR_W = 6.5;
20624
+ LABEL_PAD = 8;
20625
+ LABEL_GAP = 5;
20626
+ BAND_ACCENT_W = 4;
20627
+ BAND_RADIUS = 4;
20628
+ bandClipCounter = 0;
20357
20629
  JS_DAY_TO_WEEKDAY2 = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
20358
20630
  ERA_COLORS = ["#5e81ac", "#a3be8c", "#ebcb8b", "#d08770", "#b48ead"];
20359
20631
  FADE_OPACITY = 0.1;
@@ -20456,14 +20728,14 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
20456
20728
  nodePositionMap.set(node.id, node);
20457
20729
  }
20458
20730
  const LABEL_CHAR_W = 7;
20459
- const LABEL_PAD = 8;
20731
+ const LABEL_PAD2 = 8;
20460
20732
  const LABEL_H = 16;
20461
20733
  const PERP_OFFSET = 10;
20462
20734
  const labelPositions = [];
20463
20735
  for (let ei = 0; ei < layout.edges.length; ei++) {
20464
20736
  const edge = layout.edges[ei];
20465
20737
  if (!edge.label) continue;
20466
- const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD;
20738
+ const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
20467
20739
  let lx, ly;
20468
20740
  if (edge.source === edge.target) {
20469
20741
  const node = nodePositionMap.get(edge.source);
@@ -22348,7 +22620,7 @@ function parseVisualization(content, palette) {
22348
22620
  continue;
22349
22621
  }
22350
22622
  const markerMatch = line10.match(
22351
- /^marker\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*:\s*(.+?)(?:\s*\(([^)]+)\))?\s*$/
22623
+ /^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
22352
22624
  );
22353
22625
  if (markerMatch) {
22354
22626
  const colorAnnotation = markerMatch[3]?.trim() || null;
@@ -22372,7 +22644,7 @@ function parseVisualization(content, palette) {
22372
22644
  const unit = durationMatch[3];
22373
22645
  const endDate = addDurationToDate(startDate, amount, unit);
22374
22646
  const segments = durationMatch[5].split("|");
22375
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22647
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22376
22648
  result.timelineEvents.push({
22377
22649
  date: startDate,
22378
22650
  endDate,
@@ -22389,7 +22661,7 @@ function parseVisualization(content, palette) {
22389
22661
  );
22390
22662
  if (rangeMatch) {
22391
22663
  const segments = rangeMatch[4].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: rangeMatch[1],
22395
22667
  endDate: rangeMatch[2],
@@ -22406,7 +22678,7 @@ function parseVisualization(content, palette) {
22406
22678
  );
22407
22679
  if (pointMatch) {
22408
22680
  const segments = pointMatch[2].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: pointMatch[1],
22412
22684
  endDate: null,
@@ -24966,12 +25238,12 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
24966
25238
  };
24967
25239
  const LABEL_MAX_FONT = 48;
24968
25240
  const LABEL_MIN_FONT = 14;
24969
- const LABEL_PAD = 40;
25241
+ const LABEL_PAD2 = 40;
24970
25242
  const CHAR_WIDTH_RATIO3 = 0.6;
24971
25243
  const estTextWidth = (text, fontSize) => text.length * fontSize * CHAR_WIDTH_RATIO3;
24972
25244
  const quadrantLabelLayout = (text, qw2, qh2) => {
24973
- const availW = qw2 - LABEL_PAD;
24974
- const availH = qh2 - LABEL_PAD;
25245
+ const availW = qw2 - LABEL_PAD2;
25246
+ const availH = qh2 - LABEL_PAD2;
24975
25247
  const words = text.split(/\s+/);
24976
25248
  if (estTextWidth(text, LABEL_MAX_FONT) <= availW) {
24977
25249
  const fs = Math.min(LABEL_MAX_FONT, availH);
@@ -25358,9 +25630,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
25358
25630
  const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
25359
25631
  const effectivePalette2 = await resolveExportPalette(theme, palette);
25360
25632
  const ganttParsed = parseGantt2(content, effectivePalette2);
25361
- if (ganttParsed.error) return "";
25362
25633
  const resolved = calculateSchedule2(ganttParsed);
25363
- if (resolved.error || resolved.tasks.length === 0) return "";
25634
+ if (resolved.tasks.length === 0) return "";
25364
25635
  const EXPORT_W = 1200;
25365
25636
  const EXPORT_H = 800;
25366
25637
  const container2 = createExportContainer(EXPORT_W, EXPORT_H);