@diagrammo/dgmo 0.7.1 → 0.7.3

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.
@@ -93,6 +93,7 @@ export interface GanttEra {
93
93
  endDate: string;
94
94
  label: string;
95
95
  color: string | null;
96
+ lineNumber: number;
96
97
  }
97
98
 
98
99
  export interface GanttMarker {
@@ -114,6 +115,9 @@ export interface GanttOptions {
114
115
  dependencies: boolean;
115
116
  sort: 'default' | 'tag';
116
117
  defaultSwimlaneGroup: string | null; // tag group name from `sort: tag:Team`
118
+ /** Line numbers for option/block keywords — maps key to source line */
119
+ optionLineNumbers: Record<string, number>;
120
+ holidaysLineNumber: number | null;
117
121
  }
118
122
 
119
123
  // ── Parsed Result ───────────────────────────────────────────
@@ -674,6 +674,11 @@ export function renderInitiativeStatus(
674
674
  .attr('data-legend-group', lg.key)
675
675
  .style('cursor', 'pointer');
676
676
 
677
+ // Mark inactive pills so exports can hide them
678
+ if (!isActive) {
679
+ gEl.attr('data-export-ignore', 'true');
680
+ }
681
+
677
682
  if (isActive) {
678
683
  // Outer capsule background
679
684
  gEl.append('rect')
@@ -724,18 +729,33 @@ export function renderInitiativeStatus(
724
729
  // Determine which values are hidden for this group
725
730
  const hiddenSet = !lg.isStatus ? hiddenTagValues?.get(lg.key) : undefined;
726
731
 
727
- let entryX = pillXOff + pillW + 4;
732
+ // Render each entry in its own <g> with local coordinates,
733
+ // positioned via transform so we can reflow after measuring.
734
+ const entryStartX = pillXOff + pillW + 4;
735
+ const entryData: { g: d3Selection.Selection<SVGGElement, unknown, null, undefined>; textEl: SVGTextElement; estimatedW: number }[] = [];
736
+ let estimatedX = entryStartX;
737
+
728
738
  for (const entry of lg.entries) {
729
739
  const isHidden = hiddenSet?.has(entry.value) ?? false;
740
+ const estimatedTextW = entry.label.length * LEGEND_ENTRY_FONT_W;
730
741
 
731
742
  const entryG = gEl.append('g')
732
743
  .attr('data-legend-entry', entry.value)
733
- .style('cursor', !lg.isStatus ? 'pointer' : 'default');
744
+ .attr('transform', `translate(${estimatedX}, 0)`)
745
+ .style('cursor', 'pointer');
746
+
747
+ // Transparent hit-area rect
748
+ const entryW = LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + estimatedTextW + LEGEND_ENTRY_TRAIL;
749
+ entryG.append('rect')
750
+ .attr('x', -2)
751
+ .attr('y', 0)
752
+ .attr('width', entryW + 4)
753
+ .attr('height', LEGEND_HEIGHT)
754
+ .attr('fill', 'transparent');
734
755
 
735
756
  if (isHidden) {
736
- // Hidden: hollow ring + dimmed text (strikethrough-like)
737
757
  entryG.append('circle')
738
- .attr('cx', entryX + LEGEND_DOT_R)
758
+ .attr('cx', LEGEND_DOT_R)
739
759
  .attr('cy', LEGEND_HEIGHT / 2)
740
760
  .attr('r', LEGEND_DOT_R)
741
761
  .attr('fill', 'none')
@@ -743,16 +763,15 @@ export function renderInitiativeStatus(
743
763
  .attr('stroke-width', 1.2)
744
764
  .attr('opacity', 0.5);
745
765
  } else {
746
- // Visible: solid dot
747
766
  entryG.append('circle')
748
- .attr('cx', entryX + LEGEND_DOT_R)
767
+ .attr('cx', LEGEND_DOT_R)
749
768
  .attr('cy', LEGEND_HEIGHT / 2)
750
769
  .attr('r', LEGEND_DOT_R)
751
770
  .attr('fill', entry.color);
752
771
  }
753
772
 
754
- entryG.append('text')
755
- .attr('x', entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP)
773
+ const textEl = entryG.append('text')
774
+ .attr('x', LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP)
756
775
  .attr('y', LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
757
776
  .attr('font-size', LEGEND_ENTRY_FONT_SIZE)
758
777
  .attr('fill', palette.textMuted)
@@ -761,7 +780,20 @@ export function renderInitiativeStatus(
761
780
  .attr('text-decoration', isHidden ? 'line-through' : 'none')
762
781
  .text(entry.label);
763
782
 
764
- entryX += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.label.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
783
+ entryData.push({ g: entryG, textEl: textEl.node()!, estimatedW: estimatedTextW });
784
+ estimatedX += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + estimatedTextW + LEGEND_ENTRY_TRAIL;
785
+ }
786
+
787
+ // Reflow using measured text widths for even spacing
788
+ let reflowX = entryStartX;
789
+ for (const ed of entryData) {
790
+ const measuredW = ed.textEl.getComputedTextLength?.() ?? 0;
791
+ const textW = measuredW > 0 ? measuredW : ed.estimatedW;
792
+ ed.g.attr('transform', `translate(${reflowX}, 0)`);
793
+ // Update hit-area rect width to match actual width
794
+ const actualEntryW = LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + textW + LEGEND_ENTRY_TRAIL;
795
+ ed.g.select('rect').attr('width', actualEntryW + 4);
796
+ reflowX += actualEntryW;
765
797
  }
766
798
  }
767
799