@zenuml/core 4.2.1 → 4.3.0

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.
@@ -9075,7 +9075,7 @@ ToCollector.enterRet = function (ctx) {
9075
9075
  returnTo && participants.Add(returnTo);
9076
9076
  };
9077
9077
 
9078
- const walker$2 = antlr4.tree.ParseTreeWalker.DEFAULT;
9078
+ const walker$1 = antlr4.tree.ParseTreeWalker.DEFAULT;
9079
9079
 
9080
9080
  // The walk is a pure function of the (immutable) parse tree and consumers only
9081
9081
  // read the returned Participants, so results are cached per context node.
@@ -9086,80 +9086,11 @@ ToCollector.getParticipants = function (context) {
9086
9086
  const cached = participantsCache.get(context);
9087
9087
  if (cached) return cached;
9088
9088
  participants = new Participants$1();
9089
- walker$2.walk(this, context);
9089
+ walker$1.walk(this, context);
9090
9090
  participantsCache.set(context, participants);
9091
9091
  return participants;
9092
9092
  };
9093
9093
 
9094
- const walker$1 = antlr4.tree.ParseTreeWalker.DEFAULT;
9095
-
9096
- let ChildFragmentDetector = new sequenceParserListener();
9097
-
9098
- let cursor = 0;
9099
- let max = 0;
9100
-
9101
- ChildFragmentDetector.enterTcf = function () {
9102
- cursor++;
9103
- };
9104
- ChildFragmentDetector.enterOpt = function () {
9105
- cursor++;
9106
- };
9107
- ChildFragmentDetector.enterPar = function () {
9108
- cursor++;
9109
- };
9110
- ChildFragmentDetector.enterAlt = function () {
9111
- cursor++;
9112
- };
9113
- ChildFragmentDetector.enterLoop = function () {
9114
- cursor++;
9115
- };
9116
- ChildFragmentDetector.enterSection = function () {
9117
- cursor++;
9118
- };
9119
- ChildFragmentDetector.enterCritical = function () {
9120
- cursor++;
9121
- };
9122
-
9123
- ChildFragmentDetector.exitTcf = function () {
9124
- max = Math.max(max, cursor);
9125
- cursor--;
9126
- };
9127
- ChildFragmentDetector.exitOpt = function () {
9128
- max = Math.max(max, cursor);
9129
- cursor--;
9130
- };
9131
- ChildFragmentDetector.exitPar = function () {
9132
- max = Math.max(max, cursor);
9133
- cursor--;
9134
- };
9135
- ChildFragmentDetector.exitAlt = function () {
9136
- max = Math.max(max, cursor);
9137
- cursor--;
9138
- };
9139
- ChildFragmentDetector.exitLoop = function () {
9140
- max = Math.max(max, cursor);
9141
- cursor--;
9142
- };
9143
- ChildFragmentDetector.exitSection = function () {
9144
- max = Math.max(max, cursor);
9145
- cursor--;
9146
- };
9147
- ChildFragmentDetector.exitCritical = function () {
9148
- max = Math.max(max, cursor);
9149
- cursor--;
9150
- };
9151
-
9152
- ChildFragmentDetector.depth = function (me) {
9153
- return function (context) {
9154
- cursor = 0;
9155
- max = 0;
9156
- context.children.map(function (child) {
9157
- walker$1.walk(me, child);
9158
- });
9159
- return max;
9160
- };
9161
- };
9162
-
9163
9094
  const seqParser$9 = sequenceParser;
9164
9095
  const TitleContext = seqParser$9.TitleContext;
9165
9096
  TitleContext.prototype.content = function () {
@@ -9227,10 +9158,6 @@ CreationContext$6.prototype.Constructor = function () {
9227
9158
  return this.creationBody()?.construct()?.getFormattedText();
9228
9159
  };
9229
9160
 
9230
- CreationContext$6.prototype.To = function () {
9231
- return this.Constructor();
9232
- };
9233
-
9234
9161
  CreationContext$6.prototype.Owner = function () {
9235
9162
  if (!this.Constructor()) {
9236
9163
  return "Missing Constructor";
@@ -9245,10 +9172,6 @@ MessageContext$6.prototype.To = function () {
9245
9172
  return toCtx?.name?.()?.getFormattedText() || toCtx?.getFormattedText();
9246
9173
  };
9247
9174
 
9248
- MessageContext$6.prototype.Owner = function () {
9249
- return this.To() || getOwnerFromAncestor(this.parentCtx);
9250
- };
9251
-
9252
9175
  function getOwnerFromAncestor(ctx) {
9253
9176
  while (ctx) {
9254
9177
  if (ctx instanceof CreationContext$6 || ctx instanceof MessageContext$6) {
@@ -9259,32 +9182,39 @@ function getOwnerFromAncestor(ctx) {
9259
9182
  return undefined;
9260
9183
  }
9261
9184
 
9262
- AsyncMessageContext$2.prototype.To = function () {
9263
- const toCtx = this.to();
9264
- return toCtx?.name?.()?.getFormattedText() || toCtx?.getFormattedText();
9265
- };
9266
-
9267
- AsyncMessageContext$2.prototype.Owner = function () {
9185
+ // MessageContext, AsyncMessageContext, ReturnAsyncMessageContext and
9186
+ // RetContext all resolve Owner the same way: prefer their own To(), else
9187
+ // look up the nearest CreationContext/MessageContext ancestor. Assigning
9188
+ // the same function to each prototype keeps the body single-sourced while
9189
+ // each context class still gets its own Owner property.
9190
+ function ownerFromToOrAncestor() {
9268
9191
  return this.To() || getOwnerFromAncestor(this.parentCtx);
9269
- };
9192
+ }
9193
+
9194
+ [
9195
+ MessageContext$6,
9196
+ AsyncMessageContext$2,
9197
+ ReturnAsyncMessageContext$2,
9198
+ RetMessageContext,
9199
+ ].forEach((ContextClass) => {
9200
+ ContextClass.prototype.Owner = ownerFromToOrAncestor;
9201
+ });
9270
9202
 
9271
- ReturnAsyncMessageContext$2.prototype.To = function () {
9203
+ // AsyncMessageContext and ReturnAsyncMessageContext resolve To() identically
9204
+ // (unlike MessageContext, which reaches its `to` node through
9205
+ // messageBody()?.fromTo(), and RetContext, which delegates to ReturnTo()).
9206
+ function toFromDirectTo() {
9272
9207
  const toCtx = this.to();
9273
9208
  return toCtx?.name?.()?.getFormattedText() || toCtx?.getFormattedText();
9274
- };
9209
+ }
9275
9210
 
9276
- ReturnAsyncMessageContext$2.prototype.Owner = function () {
9277
- return this.To() || getOwnerFromAncestor(this.parentCtx);
9278
- };
9211
+ AsyncMessageContext$2.prototype.To = toFromDirectTo;
9212
+ ReturnAsyncMessageContext$2.prototype.To = toFromDirectTo;
9279
9213
 
9280
9214
  RetMessageContext.prototype.To = function () {
9281
9215
  return this.ReturnTo();
9282
9216
  };
9283
9217
 
9284
- RetMessageContext.prototype.Owner = function () {
9285
- return this.To() || getOwnerFromAncestor(this.parentCtx);
9286
- };
9287
-
9288
9218
  const ProgContext$2 = sequenceParser.ProgContext;
9289
9219
 
9290
9220
  /**
@@ -9397,11 +9327,7 @@ const MessageContext$4 = sequenceParser.MessageContext;
9397
9327
  const CreationContext$4 = sequenceParser.CreationContext;
9398
9328
 
9399
9329
  ParserRuleContext$1.prototype.Origin = function () {
9400
- let ctx = this.parentCtx;
9401
- while (ctx && !(ctx instanceof StatContext$2 || ctx instanceof ProgContext)) {
9402
- ctx = this.parentCtx;
9403
- }
9404
- return ctx.Origin();
9330
+ return this.ClosestAncestorStat()?.Origin();
9405
9331
  };
9406
9332
  // Origin is essentially the 'from' of a message.
9407
9333
  // For example, in `S -> A.m1 {B.m2 {C.m3}}`,
@@ -9482,9 +9408,6 @@ CreationContext$3.prototype.SignatureText = function() {
9482
9408
  const text = this.ParametersText() || "create";
9483
9409
  return `«${text}»`;
9484
9410
  };
9485
- CreationContext$3.prototype.isParamValid = function() {
9486
- return (this.creationBody().parameters()?.parameter()?.length ?? 0) > 0;
9487
- };
9488
9411
  RetContext$1.prototype.SignatureText = function() {
9489
9412
  return this.asyncMessage()?.content()?.getFormattedText() ?? this.returnAsyncMessage()?.content()?.getFormattedText() ?? this.expr()?.getFormattedText() ?? "";
9490
9413
  };
@@ -9575,27 +9498,22 @@ CreationContext.prototype.From = function() {
9575
9498
  }
9576
9499
  return void 0;
9577
9500
  };
9501
+ function fromProvidedOrOrigin() {
9502
+ return this.ProvidedFrom() || this.ClosestAncestorStat()?.Origin();
9503
+ }
9578
9504
  MessageContext.prototype.ProvidedFrom = function() {
9579
9505
  const fromCtx = this.messageBody()?.fromTo?.()?.from?.();
9580
9506
  return fromCtx?.name?.()?.getFormattedText?.() || fromCtx?.getFormattedText?.();
9581
9507
  };
9582
- MessageContext.prototype.From = function() {
9583
- return this.ProvidedFrom() || this.ClosestAncestorStat()?.Origin();
9584
- };
9585
- AsyncMessageContext.prototype.ProvidedFrom = function() {
9508
+ MessageContext.prototype.From = fromProvidedOrOrigin;
9509
+ function providedFromDirectFrom() {
9586
9510
  const fromCtx = this.from();
9587
9511
  return fromCtx?.name?.()?.getFormattedText?.() || fromCtx?.getFormattedText?.();
9588
- };
9589
- AsyncMessageContext.prototype.From = function() {
9590
- return this.ProvidedFrom() || this.ClosestAncestorStat()?.Origin();
9591
- };
9592
- ReturnAsyncMessageContext.prototype.ProvidedFrom = function() {
9593
- const fromCtx = this.from();
9594
- return fromCtx?.name?.()?.getFormattedText?.() || fromCtx?.getFormattedText?.();
9595
- };
9596
- ReturnAsyncMessageContext.prototype.From = function() {
9597
- return this.ProvidedFrom() || this.ClosestAncestorStat()?.Origin();
9598
- };
9512
+ }
9513
+ AsyncMessageContext.prototype.ProvidedFrom = providedFromDirectFrom;
9514
+ AsyncMessageContext.prototype.From = fromProvidedOrOrigin;
9515
+ ReturnAsyncMessageContext.prototype.ProvidedFrom = providedFromDirectFrom;
9516
+ ReturnAsyncMessageContext.prototype.From = fromProvidedOrOrigin;
9599
9517
  RetContext.prototype.From = function() {
9600
9518
  return this.asyncMessage()?.From?.() || this.returnAsyncMessage()?.From?.() || this.ClosestAncestorStat()?.Origin();
9601
9519
  };
@@ -9616,14 +9534,6 @@ ParserRuleContext.prototype.ClosestAncestorStat = function() {
9616
9534
  }
9617
9535
  return void 0;
9618
9536
  };
9619
- ParserRuleContext.prototype.ClosestAncestorBlock = function() {
9620
- const parentCtx = this.ClosestAncestorStat()?.parentCtx;
9621
- if (parentCtx instanceof seqParser.BlockContext) {
9622
- return parentCtx;
9623
- }
9624
- console.warn("Cannot find closest ancestor block for context:", this);
9625
- return void 0;
9626
- };
9627
9537
 
9628
9538
  antlr4.ParserRuleContext.prototype.getAncestors = function(predicate) {
9629
9539
  const ancestors = [];
@@ -9642,11 +9552,9 @@ antlr4.ParserRuleContext.prototype.getAncestors = function(predicate) {
9642
9552
 
9643
9553
  const formatText = (text) => text.replace(/[\n\r]/g, " ").replace(/\s+/g, " ").replace(/\s*([,;.])\s*/g, "$1").replace(/\s*(\()\s*|\s*(\))/g, "$1$2").replace(/\s+$/g, "").replace(/^"(.*)"$/, "$1");
9644
9554
 
9645
- const errors = [];
9646
9555
  const errorDetails = [];
9647
9556
  class SeqErrorListener extends antlr4.error.ErrorListener {
9648
9557
  syntaxError(recognizer, offendingSymbol, line, column, msg) {
9649
- errors.push(`${offendingSymbol} line ${line}, col ${column}: ${msg}`);
9650
9558
  errorDetails.push({
9651
9559
  line,
9652
9560
  column,
@@ -9656,7 +9564,7 @@ class SeqErrorListener extends antlr4.error.ErrorListener {
9656
9564
  }
9657
9565
 
9658
9566
  // Reentrant error listener: collects syntax errors into its OWN instance array
9659
- // instead of the shared module-level `errors`/`errorDetails`. This is what lets
9567
+ // instead of the shared module-level `errorDetails`. This is what lets
9660
9568
  // validate()/parse() below run repeatedly or concurrently (e.g. server-side)
9661
9569
  // without the clear-before/clear-after dance the legacy globals require.
9662
9570
  class CollectingErrorListener extends antlr4.error.ErrorListener {
@@ -9716,7 +9624,7 @@ function rootContext(code) {
9716
9624
 
9717
9625
  const parser = createParser(code);
9718
9626
  parser.addErrorListener(new SeqErrorListener());
9719
- return parser._syntaxErrors ? null : parser.prog();
9627
+ return parser.prog();
9720
9628
  }
9721
9629
 
9722
9630
  antlr4.ParserRuleContext.prototype.getFormattedText = function () {
@@ -9747,7 +9655,7 @@ antlr4.ParserRuleContext.prototype.getComment = function () {
9747
9655
  /**
9748
9656
  * Parse ZenUML DSL with a per-call (reentrant) error listener and return both
9749
9657
  * the error-recovered parse tree and a structured error list. Safe to call
9750
- * repeatedly/concurrently — it does not touch the module-level Errors/ErrorDetails.
9658
+ * repeatedly/concurrently — it does not touch the module-level ErrorDetails.
9751
9659
  *
9752
9660
  * @param {string} code ZenUML DSL source
9753
9661
  * @returns {{ rootContext: object, pass: boolean, errorDetails: { line: number, column: number, msg: string }[] }}
@@ -9801,15 +9709,7 @@ const Parser = {
9801
9709
  },
9802
9710
  parse,
9803
9711
  validate,
9804
- Errors: errors,
9805
9712
  ErrorDetails: errorDetails,
9806
- /**
9807
- * @return {number} how many levels of embedded fragments
9808
- */
9809
- Depth: function (ctx) {
9810
- const childFragmentDetector = ChildFragmentDetector;
9811
- return childFragmentDetector.depth(childFragmentDetector)(ctx);
9812
- },
9813
9713
  };
9814
9714
 
9815
9715
  const FRAGMENT_PADDING_X = 10;
@@ -9956,39 +9856,6 @@ function OrderedParticipants(rootContext) {
9956
9856
  return result;
9957
9857
  }
9958
9858
 
9959
- const LEVELS = ["log", "trace", "debug", "info", "warn", "error"];
9960
- const LEVEL_WEIGHT = {
9961
- trace: 10,
9962
- debug: 20,
9963
- log: 30,
9964
- info: 30,
9965
- warn: 40,
9966
- error: 50
9967
- };
9968
- const THRESHOLD = LEVEL_WEIGHT.warn;
9969
- function consoleFn(level) {
9970
- const fn = console[level];
9971
- return typeof fn === "function" ? fn.bind(console) : console.log.bind(console);
9972
- }
9973
- function createLogger(prefix) {
9974
- const logger = {};
9975
- for (const level of LEVELS) {
9976
- const fn = consoleFn(level);
9977
- logger[level] = (...args) => {
9978
- if (LEVEL_WEIGHT[level] >= THRESHOLD) {
9979
- if (prefix) {
9980
- fn(prefix[0], prefix[1], ...args);
9981
- } else {
9982
- fn(...args);
9983
- }
9984
- }
9985
- };
9986
- }
9987
- logger.child = (opts) => createLogger(["%c" + (opts.name || ""), "color: #00f"]);
9988
- return logger;
9989
- }
9990
- const rootLogger = createLogger();
9991
-
9992
9859
  function Dual(p, v) {
9993
9860
  return { position: p, velocity: v };
9994
9861
  }
@@ -10129,7 +9996,6 @@ class Coordinates {
10129
9996
  }
10130
9997
  const leftGap = this.getParticipantGap(this.participantModels[0]);
10131
9998
  const position = leftGap + this.positionsCache[pIndex];
10132
- rootLogger.debug(`Position of ${participantName} is ${position}`);
10133
9999
  return position;
10134
10000
  }
10135
10001
  walkThrough() {
@@ -10176,17 +10042,11 @@ class Coordinates {
10176
10042
  }
10177
10043
  const leftIndex = Math.min(indexFrom, indexTo);
10178
10044
  const rightIndex = Math.max(indexFrom, indexTo);
10179
- try {
10180
- const messageWidth = this.getMessageWidth(message);
10181
- this.m[leftIndex][rightIndex] = Math.max(
10182
- messageWidth + ARROW_HEAD_WIDTH + OCCURRENCE_WIDTH,
10183
- this.m[leftIndex][rightIndex]
10184
- );
10185
- } catch {
10186
- console.warn(
10187
- `Could not set message gap between ${from} and ${message.to}`
10188
- );
10189
- }
10045
+ const messageWidth = this.getMessageWidth(message);
10046
+ this.m[leftIndex][rightIndex] = Math.max(
10047
+ messageWidth + ARROW_HEAD_WIDTH + OCCURRENCE_WIDTH,
10048
+ this.m[leftIndex][rightIndex]
10049
+ );
10190
10050
  }
10191
10051
  }
10192
10052
  withParticipantGaps(participantModels) {
@@ -10216,9 +10076,6 @@ class Coordinates {
10216
10076
  );
10217
10077
  const participantWidth = Math.max(labelWidth + iconWidth + emojiWidth, MIN_PARTICIPANT_WIDTH) + MARGIN;
10218
10078
  this.participantWidthCache.set(participant.name, participantWidth);
10219
- rootLogger.debug(
10220
- `Width of ${participant.name} is ${participantWidth}; labelWidth: ${labelWidth}`
10221
- );
10222
10079
  return participantWidth;
10223
10080
  }
10224
10081
  }
@@ -10247,6 +10104,23 @@ class NodeVM {
10247
10104
  }
10248
10105
  }
10249
10106
 
10107
+ const SPACING_UNIT = 4;
10108
+ const tw = (value) => value * SPACING_UNIT;
10109
+ const DEFAULT_LAYOUT_METRICS = {
10110
+ statementMarginY: tw(4),
10111
+ // .statement-container .my-4
10112
+ fragmentHeaderHeight: 25,
10113
+ // .fragment .leading-4 => line-height: 1rem
10114
+ fragmentPaddingBottom: 10
10115
+ // .zenuml .fragment =>padding-bottom: 10px
10116
+ };
10117
+ const MESSAGE_HEIGHT = 16;
10118
+ const SELF_MESSAGE_HEIGHT = 30;
10119
+ const ASYNC_SELF_MESSAGE_HEIGHT = 44;
10120
+ const CREATION_MESSAGE_HEIGHT = 40;
10121
+ const COMMENT_LINE_HEIGHT = tw(5);
10122
+ const FRAGMENT_BORDER_WIDTH = 1;
10123
+
10250
10124
  function getLocalParticipantNames(ctx) {
10251
10125
  if (!ctx) return [];
10252
10126
  return [ctx.Origin() || _STARTER_, ...Participants(ctx).Names()];
@@ -10677,7 +10551,7 @@ class MarkdownMeasurer {
10677
10551
  return 0;
10678
10552
  }
10679
10553
  const lines = _text.trim().split("\n");
10680
- return lines.length * 20;
10554
+ return lines.length * COMMENT_LINE_HEIGHT;
10681
10555
  }
10682
10556
  }
10683
10557
 
@@ -10689,57 +10563,12 @@ class StatementVM extends NodeVM {
10689
10563
  if (!context?.getComment || !context.getComment()) return 0;
10690
10564
  return new MarkdownMeasurer().measure(context.getComment());
10691
10565
  }
10692
- resolveFragmentOrigin(fallbackOrigin) {
10693
- try {
10694
- const participants = getLocalParticipantNames(this.context) || [];
10695
- return participants[0] || fallbackOrigin;
10696
- } catch (error) {
10697
- console.warn("Failed to resolve fragment origin", error);
10698
- return fallbackOrigin;
10699
- }
10700
- }
10701
10566
  findLeftParticipant(ctx, fallbackOrigin) {
10702
10567
  if (!ctx) return void 0;
10703
10568
  const local = getLocalParticipantNames(ctx) || [];
10704
10569
  const ordered = this.runtime.participants;
10705
10570
  return ordered.find((name) => local.includes(name)) || local[0] || fallbackOrigin || void 0;
10706
10571
  }
10707
- measureOccurrence(context, top, participant, minHeight = 24, contentInset = 1) {
10708
- const block = context?.braceBlock?.()?.block?.();
10709
- if (!block) {
10710
- return minHeight;
10711
- }
10712
- const offset = this.metrics.statementMarginY - contentInset;
10713
- const blockStart = top - offset;
10714
- const blockEnd = this.layoutBlock(
10715
- block,
10716
- participant || this.runtime.originParticipant,
10717
- blockStart
10718
- );
10719
- const height = blockEnd - blockStart - offset;
10720
- return Math.max(minHeight, height);
10721
- }
10722
- isRootLevelStatement(statCtx) {
10723
- const block = statCtx?.parentCtx;
10724
- return block === this.runtime.rootBlock;
10725
- }
10726
- isFirstStatement(statCtx) {
10727
- const block = statCtx?.parentCtx;
10728
- const statements = block?.stat?.() || [];
10729
- return statements.length > 0 && statements[0] === statCtx;
10730
- }
10731
- altHasMultipleBranches(ctx) {
10732
- if (typeof ctx?.alt !== "function") {
10733
- return false;
10734
- }
10735
- const altContext = ctx.alt();
10736
- if (!altContext) {
10737
- return false;
10738
- }
10739
- const elseIfBlocks = altContext?.elseIfBlock?.() || [];
10740
- const hasElse = Boolean(altContext?.elseBlock?.());
10741
- return elseIfBlocks.length > 0 || hasElse;
10742
- }
10743
10572
  }
10744
10573
 
10745
10574
  class AsyncMessageStatementVM extends StatementVM {
@@ -10754,7 +10583,7 @@ class AsyncMessageStatementVM extends StatementVM {
10754
10583
  const source = asyncContext?.From?.() || asyncContext?.ProvidedFrom?.() || asyncContext?.Origin?.() || origin;
10755
10584
  const target = asyncContext?.Owner?.() || asyncContext?.to?.()?.getFormattedText?.() || source;
10756
10585
  const isSelf = source === target;
10757
- const messageHeight = isSelf ? 44 : 16;
10586
+ const messageHeight = isSelf ? ASYNC_SELF_MESSAGE_HEIGHT : MESSAGE_HEIGHT;
10758
10587
  return {
10759
10588
  top,
10760
10589
  height: commentHeight + messageHeight,
@@ -10763,7 +10592,6 @@ class AsyncMessageStatementVM extends StatementVM {
10763
10592
  }
10764
10593
  }
10765
10594
 
10766
- const CREATION_MESSAGE_HEIGHT = 40;
10767
10595
  class CreationStatementVM extends StatementVM {
10768
10596
  constructor(statement, creation, runtime) {
10769
10597
  super(statement, runtime);
@@ -10787,7 +10615,6 @@ class CreationStatementVM extends StatementVM {
10787
10615
  }
10788
10616
  const assignment = this.creation?.Assignment?.();
10789
10617
  if (assignment) {
10790
- rootLogger.info(`creation::assignment::${participant}`);
10791
10618
  cursor += 12;
10792
10619
  }
10793
10620
  const height = cursor - top;
@@ -10813,26 +10640,7 @@ class EmptyStatementVM extends StatementVM {
10813
10640
  }
10814
10641
  }
10815
10642
 
10816
- class FragmentVM extends StatementVM {
10817
- beginFragment(context, top) {
10818
- const commentHeight = this.measureComment(context);
10819
- const headerHeight = this.metrics.fragmentHeaderHeight;
10820
- return {
10821
- cursor: top + commentHeight + headerHeight,
10822
- commentHeight,
10823
- headerHeight
10824
- };
10825
- }
10826
- finalizeFragment(top, cursor) {
10827
- cursor += this.metrics.fragmentPaddingBottom;
10828
- return {
10829
- top,
10830
- height: cursor - top
10831
- };
10832
- }
10833
- }
10834
-
10835
- class FragmentAltVM extends FragmentVM {
10643
+ class FragmentAltVM extends StatementVM {
10836
10644
  constructor(statement, alt, runtime) {
10837
10645
  super(statement, runtime);
10838
10646
  this.alt = alt;
@@ -10877,7 +10685,7 @@ class FragmentAltVM extends FragmentVM {
10877
10685
  }
10878
10686
  }
10879
10687
 
10880
- class FragmentRefVM extends FragmentVM {
10688
+ class FragmentRefVM extends StatementVM {
10881
10689
  kind = "ref";
10882
10690
  measure(top) {
10883
10691
  const context = this.context?.ref?.() || this.context;
@@ -10888,7 +10696,7 @@ class FragmentRefVM extends FragmentVM {
10888
10696
  }
10889
10697
  }
10890
10698
 
10891
- class FragmentSingleBlockVM extends FragmentVM {
10699
+ class FragmentSingleBlockVM extends StatementVM {
10892
10700
  constructor(statement, fragment, runtime, kind) {
10893
10701
  super(statement, runtime);
10894
10702
  this.fragment = fragment;
@@ -10896,7 +10704,7 @@ class FragmentSingleBlockVM extends FragmentVM {
10896
10704
  }
10897
10705
  measure(top, origin) {
10898
10706
  const commentHeight = this.measureComment(this.fragment);
10899
- let cursor = top + 1 + this.metrics.fragmentHeaderHeight + commentHeight;
10707
+ let cursor = top + FRAGMENT_BORDER_WIDTH + this.metrics.fragmentHeaderHeight + commentHeight;
10900
10708
  const hasCondition = Boolean(this.fragment?.parExpr?.()?.condition?.());
10901
10709
  if (hasCondition) cursor += 20;
10902
10710
  const block = this.fragment?.braceBlock?.()?.block?.();
@@ -10904,7 +10712,7 @@ class FragmentSingleBlockVM extends FragmentVM {
10904
10712
  const fragmentOrigin = this.findLeftParticipant(this.fragment, origin) || origin;
10905
10713
  cursor = this.layoutBlock(block, fragmentOrigin, cursor, this.kind);
10906
10714
  }
10907
- cursor += this.metrics.fragmentPaddingBottom + 1;
10715
+ cursor += this.metrics.fragmentPaddingBottom + FRAGMENT_BORDER_WIDTH;
10908
10716
  return {
10909
10717
  top,
10910
10718
  height: cursor - top,
@@ -10913,7 +10721,7 @@ class FragmentSingleBlockVM extends FragmentVM {
10913
10721
  }
10914
10722
  }
10915
10723
 
10916
- class FragmentTryCatchVM extends FragmentVM {
10724
+ class FragmentTryCatchVM extends StatementVM {
10917
10725
  constructor(statement, tcf, runtime) {
10918
10726
  super(statement, runtime);
10919
10727
  this.tcf = tcf;
@@ -10992,6 +10800,14 @@ class ReturnStatementVM extends StatementVM {
10992
10800
  }
10993
10801
  }
10994
10802
 
10803
+ const SINGLE_BLOCK_FRAGMENT_KINDS = [
10804
+ "loop",
10805
+ "opt",
10806
+ "par",
10807
+ "section",
10808
+ "critical"
10809
+ ];
10810
+
10995
10811
  class SyncMessageStatementVM extends StatementVM {
10996
10812
  constructor(statement, message, runtime) {
10997
10813
  super(statement, runtime);
@@ -11006,7 +10822,7 @@ class SyncMessageStatementVM extends StatementVM {
11006
10822
  const block = this.message.braceBlock?.()?.block?.();
11007
10823
  const assignee = this.message.Assignment?.();
11008
10824
  const isSelf = source === target;
11009
- const messageHeight = isSelf ? 30 : 16;
10825
+ const messageHeight = isSelf ? SELF_MESSAGE_HEIGHT : MESSAGE_HEIGHT;
11010
10826
  cursor += messageHeight;
11011
10827
  if (block) {
11012
10828
  const fragmentOrigin = this.findLeftParticipant(this.message, origin) || origin;
@@ -11042,7 +10858,7 @@ const createStatementVM = (statement, runtime) => {
11042
10858
  if (statement.divider?.()) {
11043
10859
  return new DividerStatementVM(statement, runtime);
11044
10860
  }
11045
- for (const kind of ["loop", "opt", "par", "section", "critical"]) {
10861
+ for (const kind of SINGLE_BLOCK_FRAGMENT_KINDS) {
11046
10862
  const fragment = statement[kind]?.();
11047
10863
  if (fragment) {
11048
10864
  return new FragmentSingleBlockVM(statement, fragment, runtime, kind);
@@ -11083,17 +10899,6 @@ class BlockVM extends NodeVM {
11083
10899
  }
11084
10900
  }
11085
10901
 
11086
- const SPACING_UNIT = 4;
11087
- const tw = (value) => value * SPACING_UNIT;
11088
- const DEFAULT_LAYOUT_METRICS = {
11089
- statementMarginY: tw(4),
11090
- // .statement-container .my-4
11091
- fragmentHeaderHeight: 25,
11092
- // .fragment .leading-4 => line-height: 1rem
11093
- fragmentPaddingBottom: 10
11094
- // .zenuml .fragment =>padding-bottom: 10px
11095
- };
11096
-
11097
10902
  class VerticalCoordinates {
11098
10903
  statementMap = /* @__PURE__ */ new Map();
11099
10904
  creationTops = /* @__PURE__ */ new Map();
@@ -11105,16 +10910,13 @@ class VerticalCoordinates {
11105
10910
  const originParticipant = messages.length === 0 ? _STARTER_ : messages[0].from || _STARTER_;
11106
10911
  const runtime = {
11107
10912
  metrics: DEFAULT_LAYOUT_METRICS,
11108
- rootBlock,
11109
10913
  participants,
11110
- originParticipant,
11111
10914
  recordCoordinate: (statement, coordinate) => {
11112
10915
  const key = createStatementKey(statement);
11113
10916
  this.statementMap.set(key, coordinate);
11114
10917
  },
11115
10918
  updateCreationTop: (participant, top) => {
11116
10919
  const paddingTop = top - 8;
11117
- rootLogger.info(`[VerticalCoordinates] updateCreationTop participant="${participant}" raw=${top} paddingTop=${paddingTop}`);
11118
10920
  this.creationTops.set(participant, paddingTop);
11119
10921
  }
11120
10922
  };
@@ -11127,19 +10929,13 @@ class VerticalCoordinates {
11127
10929
  getStatementCoordinate(key) {
11128
10930
  return this.statementMap.get(key);
11129
10931
  }
11130
- getStatementCoordinateFor(statement) {
11131
- return this.statementMap.get(createStatementKey(statement));
11132
- }
11133
- entries() {
11134
- return Array.from(this.statementMap.entries());
11135
- }
11136
10932
  getTotalHeight() {
11137
10933
  return this.totalHeight;
11138
10934
  }
11139
10935
  }
11140
10936
 
11141
10937
  let dic = {};
11142
- const persistDic = {};
10938
+ let persistDic = {};
11143
10939
  const getCache = (key) => {
11144
10940
  if (key != null) {
11145
10941
  const cacheValue = dic[key] ?? persistDic[key];
@@ -11153,6 +10949,10 @@ const setCache = (key, value, persist = false) => {
11153
10949
  persistDic[key] = value;
11154
10950
  }
11155
10951
  };
10952
+ const clearPersistentCache = () => {
10953
+ persistDic = {};
10954
+ dic = {};
10955
+ };
11156
10956
 
11157
10957
  const FONT_FAMILY = "Helvetica, Verdana, serif";
11158
10958
  const FONT_SIZE_PARTICIPANT = "16px";
@@ -11162,28 +10962,25 @@ function getFontSpec() {
11162
10962
  }
11163
10963
  let canvasCtx = null;
11164
10964
  function setCanvasContext(ctx) {
10965
+ if (ctx === canvasCtx) return;
11165
10966
  canvasCtx = ctx;
10967
+ clearPersistentCache();
11166
10968
  }
11167
10969
  function getCanvasContext() {
11168
10970
  if (canvasCtx) return canvasCtx;
11169
10971
  try {
11170
10972
  if (typeof OffscreenCanvas !== "undefined") {
11171
10973
  canvasCtx = new OffscreenCanvas(1, 1).getContext("2d");
11172
- rootLogger.debug("[ZenUML] WidthProviderOnCanvas: using OffscreenCanvas");
11173
10974
  } else if (typeof document !== "undefined") {
11174
10975
  canvasCtx = document.createElement("canvas").getContext("2d");
11175
- rootLogger.debug("[ZenUML] WidthProviderOnCanvas: using <canvas> element");
11176
- } else {
11177
- rootLogger.debug("[ZenUML] WidthProviderOnCanvas: no canvas available, using character estimate fallback");
11178
10976
  }
11179
10977
  } catch {
11180
- rootLogger.debug("[ZenUML] WidthProviderOnCanvas: canvas creation failed, using character estimate fallback");
11181
10978
  }
11182
10979
  return canvasCtx;
11183
10980
  }
11184
10981
  function WidthProviderOnCanvas(text, type) {
11185
10982
  const measured = text.trim();
11186
- const cacheKey = `WidthProviderOnCanvas_${measured}_${type}`;
10983
+ const cacheKey = `WidthProviderOnCanvas_${getFontSpec()}_${measured}_${type}`;
11187
10984
  const cacheValue = getCache(cacheKey);
11188
10985
  if (cacheValue != null) {
11189
10986
  return cacheValue;
@@ -11191,7 +10988,7 @@ function WidthProviderOnCanvas(text, type) {
11191
10988
  const ctx = getCanvasContext();
11192
10989
  if (!ctx) {
11193
10990
  const width2 = Math.ceil(measured.length * 16 * 0.6);
11194
- setCache(cacheKey, width2, true);
10991
+ setCache(cacheKey, width2);
11195
10992
  return width2;
11196
10993
  }
11197
10994
  ctx.font = getFontSpec();
@@ -11199,7 +10996,10 @@ function WidthProviderOnCanvas(text, type) {
11199
10996
  setCache(cacheKey, width, true);
11200
10997
  return width;
11201
10998
  }
11202
- const EMOJI_PATTERN = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE00}-\u{FE0F}\u{200D}]/u;
10999
+ const EMOJI_PATTERN = (
11000
+ // eslint-disable-next-line no-misleading-character-class
11001
+ /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{FE00}-\u{FE0F}\u{200D}]/u
11002
+ );
11203
11003
  function measureWithSvg(text, fontSize) {
11204
11004
  if (typeof document === "undefined") return null;
11205
11005
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
@@ -11218,7 +11018,8 @@ function measureTextWithFont(text, fontSize) {
11218
11018
  const measured = text.trim();
11219
11019
  if (!measured) return 0;
11220
11020
  const hasEmoji = EMOJI_PATTERN.test(measured);
11221
- const cacheKey = hasEmoji ? `measureTextWithFont_svg_${fontSize}_${measured}` : `measureTextWithFont_${fontSize}_${measured}`;
11021
+ const font = `${fontSize} ${FONT_FAMILY}`;
11022
+ const cacheKey = hasEmoji ? `measureTextWithFont_svg_${font}_${measured}` : `measureTextWithFont_${font}_${measured}`;
11222
11023
  const cacheValue = getCache(cacheKey);
11223
11024
  if (cacheValue != null) {
11224
11025
  return cacheValue;
@@ -11234,10 +11035,10 @@ function measureTextWithFont(text, fontSize) {
11234
11035
  if (!ctx) {
11235
11036
  const px = Number.parseFloat(fontSize) || 14;
11236
11037
  const width2 = Math.ceil(measured.length * px * 0.6);
11237
- setCache(cacheKey, width2, true);
11038
+ setCache(cacheKey, width2);
11238
11039
  return width2;
11239
11040
  }
11240
- ctx.font = `${fontSize} ${FONT_FAMILY}`;
11041
+ ctx.font = font;
11241
11042
  const width = ctx.measureText(measured).width;
11242
11043
  setCache(cacheKey, width, true);
11243
11044
  return width;
@@ -11358,22 +11159,42 @@ function FrameBorder(frame) {
11358
11159
  return { left: 0, right: 0 };
11359
11160
  }
11360
11161
  return {
11361
- type: frame.type,
11362
11162
  left: FRAGMENT_PADDING_X * longestPath(frame, "LEFT" /* LEFT */),
11363
11163
  right: FRAGMENT_PADDING_X * longestPath(frame, "RIGHT" /* RIGHT */)
11364
11164
  };
11365
11165
  }
11366
11166
 
11167
+ function TotalWidth(ctx, coordinates) {
11168
+ const allParticipants = coordinates.orderedParticipantNames();
11169
+ if (!ctx || allParticipants.length === 0) return 0;
11170
+ const localParticipants = getLocalParticipantNames(ctx);
11171
+ const leftParticipant = allParticipants.find((p) => localParticipants.includes(p)) || "";
11172
+ const rightParticipant = allParticipants.slice().reverse().find((p) => localParticipants.includes(p)) || "";
11173
+ const frameBuilder = new FrameBuilder(allParticipants);
11174
+ const frame = frameBuilder.getFrame(ctx);
11175
+ const border = FrameBorder(frame);
11176
+ const extraWidth = extraWidthDueToSelfMessage(
11177
+ ctx,
11178
+ rightParticipant,
11179
+ coordinates
11180
+ );
11181
+ const participantWidth = coordinates.distance(leftParticipant, rightParticipant) + coordinates.half(leftParticipant) + coordinates.half(rightParticipant);
11182
+ return Math.max(participantWidth, FRAGMENT_MIN_WIDTH) + border.left + border.right + extraWidth;
11183
+ }
11184
+ function extraWidthDueToSelfMessage(ctx, rightParticipant, coordinates) {
11185
+ const allMessages = AllMessages(ctx);
11186
+ const widths = allMessages.filter((m) => m.from === m.to).map(
11187
+ (m) => coordinates.getMessageWidth(m) - coordinates.distance(m.from || _STARTER_, rightParticipant) - coordinates.half(rightParticipant)
11188
+ );
11189
+ return Math.max.apply(null, [0, ...widths]);
11190
+ }
11191
+
11367
11192
  const PARTICIPANT_TOP_SPACE = PARTICIPANT_TOP_SPACE_FOR_GROUP + 8;
11368
11193
  const PARTICIPANT_BOX_PADDING = 16;
11369
- const PARTICIPANT_BOX_PADDING_ASSIGNEE = 16;
11370
11194
  const PARTICIPANT_ICON_ROW_WIDTH = 28;
11371
11195
  const PARTICIPANT_EMOJI_WIDTH = 20;
11372
11196
  const PARTICIPANT_VISUAL_HEIGHT = 40;
11373
11197
  const PARTICIPANT_MAX_WIDTH = 250;
11374
- function snapX$1(x) {
11375
- return x;
11376
- }
11377
11198
  function cssToSvgStyle(css) {
11378
11199
  const result = {};
11379
11200
  let hasKeys = false;
@@ -11392,24 +11213,32 @@ function cssToSvgStyle(css) {
11392
11213
 
11393
11214
  function buildParticipants(models, coordinates, verticalCoordinates, measureText) {
11394
11215
  return models.map((m) => {
11395
- const centerX = snapX$1(coordinates.getPosition(m.name));
11216
+ const centerX = coordinates.getPosition(m.name);
11396
11217
  const halfWidth = coordinates.half(m.name);
11397
- const isAssignee = m.name.includes(":") && m.getDisplayName() === m.name;
11398
11218
  let width;
11399
11219
  let measuredTextWidth;
11400
11220
  let measuredStereotypeWidth;
11401
11221
  if (measureText && m.name !== _STARTER_) {
11402
11222
  const textWidth = measureSvgParticipantLabelWidth(m.getDisplayName());
11403
11223
  measuredTextWidth = textWidth;
11404
- const padding = isAssignee ? PARTICIPANT_BOX_PADDING_ASSIGNEE : PARTICIPANT_BOX_PADDING;
11224
+ const padding = PARTICIPANT_BOX_PADDING;
11405
11225
  const iconWidth = m.hasIcon() ? PARTICIPANT_ICON_ROW_WIDTH : 0;
11406
11226
  const emojiWidth = m.emoji ? PARTICIPANT_EMOJI_WIDTH : 0;
11407
11227
  const STEREOTYPE_BOX_OVERHEAD = 8;
11408
11228
  if (m.stereotype) {
11409
- measuredStereotypeWidth = measureSvgParticipantLabelWidth(`«${m.stereotype}»`);
11229
+ measuredStereotypeWidth = measureSvgParticipantLabelWidth(
11230
+ `«${m.stereotype}»`
11231
+ );
11410
11232
  }
11411
11233
  const stereotypeBoxWidth = measuredStereotypeWidth != null ? measuredStereotypeWidth + STEREOTYPE_BOX_OVERHEAD : 0;
11412
- width = Math.min(Math.max(textWidth + padding + iconWidth + emojiWidth, stereotypeBoxWidth, MIN_PARTICIPANT_WIDTH), PARTICIPANT_MAX_WIDTH);
11234
+ width = Math.min(
11235
+ Math.max(
11236
+ textWidth + padding + iconWidth + emojiWidth,
11237
+ stereotypeBoxWidth,
11238
+ MIN_PARTICIPANT_WIDTH
11239
+ ),
11240
+ PARTICIPANT_MAX_WIDTH
11241
+ );
11413
11242
  } else {
11414
11243
  width = Math.min(halfWidth * 2 - MARGIN, PARTICIPANT_MAX_WIDTH);
11415
11244
  }
@@ -11488,39 +11317,49 @@ function buildGroups(participants, diagramHeight) {
11488
11317
  return groups;
11489
11318
  }
11490
11319
 
11491
- function snapX(x) {
11492
- return x;
11320
+ class Anchor2 {
11321
+ constructor(position, layers) {
11322
+ this.position = position;
11323
+ this.layers = layers;
11324
+ }
11325
+ /**
11326
+ * centerToEdge is used for translateX calculations.
11327
+ */
11328
+ centerToEdge(other) {
11329
+ return other.rightEdgeOfRightWall() - this.centerOfRightWall();
11330
+ }
11331
+ centerToCenter(other) {
11332
+ return other.centerOfRightWall() - this.centerOfRightWall();
11333
+ }
11334
+ /**
11335
+ * edgeOffset is used for interactionWidth calculations.
11336
+ */
11337
+ edgeOffset(other) {
11338
+ const isRightToLeft = other.position < this.position;
11339
+ const rightEdgeOfLeftAnchor = isRightToLeft ? other.rightEdgeOfRightWall() : this.rightEdgeOfRightWall();
11340
+ const leftEdgeOfRightAnchor = isRightToLeft ? this.leftEdgeOfRightWall() : other.leftEdgeOfRightWall();
11341
+ const distance = leftEdgeOfRightAnchor - rightEdgeOfLeftAnchor - LIFELINE_WIDTH;
11342
+ return isRightToLeft ? distance * -1 : distance;
11343
+ }
11344
+ centerOfRightWall() {
11345
+ return this.layers <= 1 ? this.position : this.position + OCCURRENCE_BAR_SIDE_WIDTH * (this.layers - 1);
11346
+ }
11347
+ rightEdgeOfRightWall() {
11348
+ return this.position + OCCURRENCE_BAR_SIDE_WIDTH * this.layers;
11349
+ }
11350
+ leftEdgeOfRightWall() {
11351
+ return this.layers === 0 ? this.position : this.centerOfRightWall() - OCCURRENCE_BAR_SIDE_WIDTH;
11352
+ }
11493
11353
  }
11354
+
11494
11355
  const COMMENT_FONT_ASCENT = 15;
11495
11356
  function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, allParticipants, _measureText, commentHeight = 0, commentText, commentStyle, commentYBase) {
11496
11357
  const statNode = info.statNode;
11497
- if (!statNode) return null;
11498
11358
  const fragmentCtx = findFragmentContext(statNode);
11499
- if (!fragmentCtx) {
11500
- return {
11501
- fragment: {
11502
- kind: info.fragmentKind,
11503
- label: info.fragmentLabel || "",
11504
- labelWidth: info.fragmentLabel ? measureSvgFragmentLabelWidth(resolveEmojiInText(info.fragmentLabel)) : void 0,
11505
- x: 0,
11506
- y: coord.top,
11507
- width: coordinates.getWidth(),
11508
- height: coord.height,
11509
- headerY: coord.top + 1 + commentHeight,
11510
- sections: [],
11511
- number: info.number,
11512
- depth: info.depth
11513
- },
11514
- comment: commentText && commentYBase != null ? {
11515
- x: 1,
11516
- y: commentYBase + 1 + COMMENT_FONT_ASCENT,
11517
- text: commentText,
11518
- style: commentStyle,
11519
- fragmentComment: true
11520
- } : void 0
11521
- };
11522
- }
11523
- const localNames = getDeepParticipantNames(fragmentCtx, info.fragmentSections);
11359
+ const localNames = getDeepParticipantNames(
11360
+ fragmentCtx,
11361
+ info.fragmentSections
11362
+ );
11524
11363
  const leftParticipant = allParticipants.find((p) => localNames.includes(p)) || "";
11525
11364
  const rightParticipant = allParticipants.slice().reverse().find((p) => localNames.includes(p)) || "";
11526
11365
  const frameBuilder = new FrameBuilder(allParticipants);
@@ -11529,14 +11368,8 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11529
11368
  let fragWidth;
11530
11369
  let fragX;
11531
11370
  if (leftParticipant && rightParticipant) {
11532
- const participantWidth = coordinates.distance(leftParticipant, rightParticipant) + coordinates.half(leftParticipant) + coordinates.half(rightParticipant);
11533
- const selfMessages = AllMessages(statNode).filter((m) => m.from === m.to);
11534
- const extraWidths = selfMessages.map(
11535
- (m) => coordinates.getMessageWidth(m) - coordinates.distance(m.from || _STARTER_, rightParticipant) - coordinates.half(rightParticipant)
11536
- );
11537
- const extraWidth = Math.max(0, ...extraWidths);
11538
- fragWidth = Math.max(participantWidth, FRAGMENT_MIN_WIDTH) + fragBorder.left + fragBorder.right + extraWidth;
11539
- fragX = snapX(coordinates.getPosition(leftParticipant)) - coordinates.half(leftParticipant);
11371
+ fragWidth = TotalWidth(statNode, coordinates);
11372
+ fragX = coordinates.getPosition(leftParticipant) - coordinates.half(leftParticipant);
11540
11373
  } else {
11541
11374
  fragWidth = Math.max(FRAGMENT_MIN_WIDTH, coordinates.getWidth());
11542
11375
  fragX = 0;
@@ -11551,7 +11384,7 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11551
11384
  if (sectionBlock) {
11552
11385
  const innerStats = sectionBlock.stat?.() || [];
11553
11386
  if (innerStats.length > 0) {
11554
- const firstStatKey = createStatementKeyFromStat(innerStats[0]);
11387
+ const firstStatKey = createStatementKey(innerStats[0]);
11555
11388
  if (firstStatKey) {
11556
11389
  const innerCoord = verticalCoordinates.getStatementCoordinate(firstStatKey);
11557
11390
  if (innerCoord) {
@@ -11568,7 +11401,9 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11568
11401
  height: sectionHeight,
11569
11402
  labelWidth: section.label ? measureSvgFragmentLabelWidth(resolveEmojiInText(section.label)) : void 0,
11570
11403
  innerLabel: /^\[\s*.*\s*\]$/.test(section.label) ? section.label.slice(1, -1).trim() : void 0,
11571
- innerLabelWidth: /^\[\s*.*\s*\]$/.test(section.label) ? measureSvgFragmentLabelWidth(resolveEmojiInText(section.label.slice(1, -1).trim())) : void 0,
11404
+ innerLabelWidth: /^\[\s*.*\s*\]$/.test(section.label) ? measureSvgFragmentLabelWidth(
11405
+ resolveEmojiInText(section.label.slice(1, -1).trim())
11406
+ ) : void 0,
11572
11407
  keyword: (() => {
11573
11408
  const spaceIdx = section.label.indexOf(" ");
11574
11409
  if (spaceIdx > 0 && !section.label.startsWith("finally") && !section.label.startsWith("[")) {
@@ -11579,7 +11414,9 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11579
11414
  keywordWidth: (() => {
11580
11415
  const spaceIdx = section.label.indexOf(" ");
11581
11416
  if (spaceIdx > 0 && !section.label.startsWith("finally") && !section.label.startsWith("[")) {
11582
- return measureSvgFragmentLabelWidth(section.label.substring(0, spaceIdx));
11417
+ return measureSvgFragmentLabelWidth(
11418
+ section.label.substring(0, spaceIdx)
11419
+ );
11583
11420
  }
11584
11421
  return void 0;
11585
11422
  })(),
@@ -11593,7 +11430,9 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11593
11430
  detailWidth: (() => {
11594
11431
  const spaceIdx = section.label.indexOf(" ");
11595
11432
  if (spaceIdx > 0 && !section.label.startsWith("finally") && !section.label.startsWith("[")) {
11596
- return measureSvgFragmentLabelWidth(section.label.substring(spaceIdx + 1));
11433
+ return measureSvgFragmentLabelWidth(
11434
+ section.label.substring(spaceIdx + 1)
11435
+ );
11597
11436
  }
11598
11437
  return void 0;
11599
11438
  })()
@@ -11606,7 +11445,7 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11606
11445
  if (innerStats.length > 1) {
11607
11446
  sections.push({ label: "", y: coord.top, height: 0 });
11608
11447
  for (let i = 1; i < innerStats.length; i++) {
11609
- const statKey = createStatementKeyFromStat(innerStats[i]);
11448
+ const statKey = createStatementKey(innerStats[i]);
11610
11449
  if (!statKey) continue;
11611
11450
  const innerCoord = verticalCoordinates.getStatementCoordinate(statKey);
11612
11451
  if (!innerCoord) continue;
@@ -11639,7 +11478,9 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11639
11478
  y: coord.top,
11640
11479
  width: fragWidth,
11641
11480
  height: coord.height,
11642
- headerY: coord.top + 1 + commentHeight,
11481
+ // The header sits just inside the fragment's 1px top border, below any
11482
+ // inline comment — the same offset FragmentSingleBlockVM.measure() applies.
11483
+ headerY: coord.top + FRAGMENT_BORDER_WIDTH + commentHeight,
11643
11484
  sections,
11644
11485
  number: info.number,
11645
11486
  depth: info.depth
@@ -11648,7 +11489,7 @@ function buildFragmentGeometry(info, coord, coordinates, verticalCoordinates, al
11648
11489
  };
11649
11490
  }
11650
11491
  function findFragmentContext(stat) {
11651
- for (const kind of ["loop", "opt", "par", "critical", "section"]) {
11492
+ for (const kind of SINGLE_BLOCK_FRAGMENT_KINDS) {
11652
11493
  const frag = stat[kind]?.();
11653
11494
  if (frag) return frag;
11654
11495
  }
@@ -11660,10 +11501,6 @@ function findFragmentContext(stat) {
11660
11501
  if (ref) return ref;
11661
11502
  return null;
11662
11503
  }
11663
- function createStatementKeyFromStat(statement) {
11664
- if (!statement?.start || !statement?.stop) return "";
11665
- return `${statement.start.start}-${statement.stop.stop}`;
11666
- }
11667
11504
  function getDeepParticipantNames(fragmentCtx, sections) {
11668
11505
  const names = new Set(getLocalParticipantNames(fragmentCtx));
11669
11506
  if (sections) {
@@ -11701,11 +11538,37 @@ function walkBlock(block, currentOrigin, activeOccurrences, parentNumber, depth,
11701
11538
  const to = message.Owner?.() || _STARTER_;
11702
11539
  const label = normalizeLabel(message.SignatureText?.() || "");
11703
11540
  const nestedBlock = message.braceBlock?.()?.block?.();
11704
- results.push({ key, kind: "sync", from, to, label, isSelf: from === to, hasBlock: !!nestedBlock, comment, statNode: stat, senderOccurrenceDepth: activeOccurrences.get(from) || 0, targetHasOccurrence: activeOccurrences.has(to), targetOccurrenceDepth: activeOccurrences.get(to) || 0, number, depth, parentBlockKind });
11541
+ results.push({
11542
+ key,
11543
+ kind: "sync",
11544
+ from,
11545
+ to,
11546
+ label,
11547
+ isSelf: from === to,
11548
+ hasBlock: !!nestedBlock,
11549
+ comment,
11550
+ statNode: stat,
11551
+ senderOccurrenceDepth: activeOccurrences.get(from) || 0,
11552
+ targetHasOccurrence: activeOccurrences.has(to),
11553
+ targetOccurrenceDepth: activeOccurrences.get(to) || 0,
11554
+ number,
11555
+ depth,
11556
+ parentBlockKind
11557
+ });
11705
11558
  if (nestedBlock) {
11706
11559
  const innerOccs = new Map(activeOccurrences);
11707
11560
  innerOccs.set(to, (innerOccs.get(to) || 0) + 1);
11708
- results.push(...walkBlock(nestedBlock, to, innerOccs, number, depth + 1, "sync", 0));
11561
+ results.push(
11562
+ ...walkBlock(
11563
+ nestedBlock,
11564
+ to,
11565
+ innerOccs,
11566
+ number,
11567
+ depth + 1,
11568
+ "sync",
11569
+ 0
11570
+ )
11571
+ );
11709
11572
  }
11710
11573
  continue;
11711
11574
  }
@@ -11713,8 +11576,23 @@ function walkBlock(block, currentOrigin, activeOccurrences, parentNumber, depth,
11713
11576
  if (asyncMsg) {
11714
11577
  const from = asyncMsg.From?.() || asyncMsg.ProvidedFrom?.() || asyncMsg.Origin?.() || currentOrigin;
11715
11578
  const to = asyncMsg.Owner?.() || asyncMsg.to?.()?.getFormattedText?.() || from;
11716
- const label = normalizeLabel(asyncMsg.content?.()?.getText?.() || asyncMsg.SignatureText?.() || "");
11717
- results.push({ key, kind: "async", from, to, label, isSelf: from === to, hasBlock: false, comment, senderOccurrenceDepth: activeOccurrences.get(from) || 0, targetHasOccurrence: activeOccurrences.has(to), number, depth });
11579
+ const label = normalizeLabel(
11580
+ asyncMsg.content?.()?.getText?.() || asyncMsg.SignatureText?.() || ""
11581
+ );
11582
+ results.push({
11583
+ key,
11584
+ kind: "async",
11585
+ from,
11586
+ to,
11587
+ label,
11588
+ isSelf: from === to,
11589
+ hasBlock: false,
11590
+ comment,
11591
+ senderOccurrenceDepth: activeOccurrences.get(from) || 0,
11592
+ targetHasOccurrence: activeOccurrences.has(to),
11593
+ number,
11594
+ depth
11595
+ });
11718
11596
  continue;
11719
11597
  }
11720
11598
  const creation = stat.creation?.();
@@ -11723,11 +11601,36 @@ function walkBlock(block, currentOrigin, activeOccurrences, parentNumber, depth,
11723
11601
  const to = creation.Owner?.() || "";
11724
11602
  const label = normalizeLabel(creation.SignatureText?.() || "«create»");
11725
11603
  const creationBlock = creation.braceBlock?.()?.block?.();
11726
- results.push({ key, kind: "creation", from, to, label, isSelf: false, hasBlock: !!creationBlock, comment, statNode: stat, senderOccurrenceDepth: activeOccurrences.get(from) || 0, targetHasOccurrence: activeOccurrences.has(to), number, depth, parentBlockKind });
11604
+ results.push({
11605
+ key,
11606
+ kind: "creation",
11607
+ from,
11608
+ to,
11609
+ label,
11610
+ isSelf: false,
11611
+ hasBlock: !!creationBlock,
11612
+ comment,
11613
+ statNode: stat,
11614
+ senderOccurrenceDepth: activeOccurrences.get(from) || 0,
11615
+ targetHasOccurrence: activeOccurrences.has(to),
11616
+ number,
11617
+ depth,
11618
+ parentBlockKind
11619
+ });
11727
11620
  if (creationBlock) {
11728
11621
  const innerOccs = new Map(activeOccurrences);
11729
11622
  innerOccs.set(to, (innerOccs.get(to) || 0) + 1);
11730
- results.push(...walkBlock(creationBlock, to || currentOrigin, innerOccs, number, depth + 1, "creation", 0));
11623
+ results.push(
11624
+ ...walkBlock(
11625
+ creationBlock,
11626
+ to || currentOrigin,
11627
+ innerOccs,
11628
+ number,
11629
+ depth + 1,
11630
+ "creation",
11631
+ 0
11632
+ )
11633
+ );
11731
11634
  }
11732
11635
  continue;
11733
11636
  }
@@ -11737,13 +11640,40 @@ function walkBlock(block, currentOrigin, activeOccurrences, parentNumber, depth,
11737
11640
  const asyncMessage = ret?.asyncMessage?.();
11738
11641
  const from = asyncMessage?.From?.() || ret?.From?.() || currentOrigin;
11739
11642
  const to = asyncMessage?.to?.()?.getFormattedText?.() || ret?.ReturnTo?.() || _STARTER_;
11740
- results.push({ key, kind: "return", from, to, label, isSelf: from === to, hasBlock: false, comment, senderOccurrenceDepth: activeOccurrences.get(from) || 0, targetOccurrenceDepth: activeOccurrences.get(to) || 0, number, depth, parentBlockKind });
11643
+ results.push({
11644
+ key,
11645
+ kind: "return",
11646
+ from,
11647
+ to,
11648
+ label,
11649
+ isSelf: from === to,
11650
+ hasBlock: false,
11651
+ comment,
11652
+ senderOccurrenceDepth: activeOccurrences.get(from) || 0,
11653
+ targetOccurrenceDepth: activeOccurrences.get(to) || 0,
11654
+ number,
11655
+ depth,
11656
+ parentBlockKind
11657
+ });
11741
11658
  continue;
11742
11659
  }
11743
11660
  const divider = stat.divider?.();
11744
11661
  if (divider) {
11745
- const label = normalizeLabel(divider.getFormattedText?.() || divider.getText?.() || "");
11746
- results.push({ key, kind: "divider", from: "", to: "", label, isSelf: false, hasBlock: false, senderOccurrenceDepth: 0, number, depth });
11662
+ const label = normalizeLabel(
11663
+ divider.getFormattedText?.() || divider.getText?.() || ""
11664
+ );
11665
+ results.push({
11666
+ key,
11667
+ kind: "divider",
11668
+ from: "",
11669
+ to: "",
11670
+ label,
11671
+ isSelf: false,
11672
+ hasBlock: false,
11673
+ senderOccurrenceDepth: 0,
11674
+ number,
11675
+ depth
11676
+ });
11747
11677
  continue;
11748
11678
  }
11749
11679
  const fragmentInfo = extractFragmentInfo(stat);
@@ -11765,12 +11695,19 @@ function walkBlock(block, currentOrigin, activeOccurrences, parentNumber, depth,
11765
11695
  number,
11766
11696
  depth
11767
11697
  });
11768
- walkFragmentBlocks(stat, currentOrigin, results, activeOccurrences, number, depth);
11698
+ walkFragmentBlocks(
11699
+ stat,
11700
+ currentOrigin,
11701
+ results,
11702
+ activeOccurrences,
11703
+ number,
11704
+ depth
11705
+ );
11769
11706
  }
11770
11707
  return results;
11771
11708
  }
11772
11709
  function extractFragmentInfo(stat) {
11773
- for (const kind of ["loop", "opt", "par", "critical", "section"]) {
11710
+ for (const kind of SINGLE_BLOCK_FRAGMENT_KINDS) {
11774
11711
  const frag = stat[kind]?.();
11775
11712
  if (frag) {
11776
11713
  const condition = frag.parExpr?.()?.condition?.();
@@ -11787,16 +11724,25 @@ function extractFragmentInfo(stat) {
11787
11724
  const sections = [];
11788
11725
  const ifBlock = alt.ifBlock?.();
11789
11726
  if (ifBlock) {
11790
- sections.push({ label: "Alt", blockNode: ifBlock.braceBlock?.()?.block?.() });
11727
+ sections.push({
11728
+ label: "Alt",
11729
+ blockNode: ifBlock.braceBlock?.()?.block?.()
11730
+ });
11791
11731
  }
11792
11732
  for (const elseIf of alt.elseIfBlock?.() || []) {
11793
11733
  const condition = elseIf.parExpr?.()?.condition?.();
11794
11734
  const label = condition?.getFormattedText?.() || "";
11795
- sections.push({ label: `[ ${label} ]`, blockNode: elseIf.braceBlock?.()?.block?.() });
11735
+ sections.push({
11736
+ label: `[ ${label} ]`,
11737
+ blockNode: elseIf.braceBlock?.()?.block?.()
11738
+ });
11796
11739
  }
11797
11740
  const elseBlock = alt.elseBlock?.();
11798
11741
  if (elseBlock) {
11799
- sections.push({ label: "[else]", blockNode: elseBlock.braceBlock?.()?.block?.() });
11742
+ sections.push({
11743
+ label: "[else]",
11744
+ blockNode: elseBlock.braceBlock?.()?.block?.()
11745
+ });
11800
11746
  }
11801
11747
  const ifCondition = alt.ifBlock?.()?.parExpr?.()?.condition?.();
11802
11748
  const firstLabel = ifCondition?.getFormattedText?.() || "";
@@ -11807,15 +11753,24 @@ function extractFragmentInfo(stat) {
11807
11753
  const sections = [];
11808
11754
  const tryBlock = tcf.tryBlock?.();
11809
11755
  if (tryBlock) {
11810
- sections.push({ label: "Try", blockNode: tryBlock.braceBlock?.()?.block?.() });
11756
+ sections.push({
11757
+ label: "Try",
11758
+ blockNode: tryBlock.braceBlock?.()?.block?.()
11759
+ });
11811
11760
  }
11812
11761
  for (const catchBlock of tcf.catchBlock?.() || []) {
11813
11762
  const exception = catchBlock.invocation?.()?.parameters?.()?.getFormattedText?.() || "";
11814
- sections.push({ label: `catch ${exception}`, blockNode: catchBlock.braceBlock?.()?.block?.() });
11763
+ sections.push({
11764
+ label: `catch ${exception}`,
11765
+ blockNode: catchBlock.braceBlock?.()?.block?.()
11766
+ });
11815
11767
  }
11816
11768
  const finallyBlock = tcf.finallyBlock?.();
11817
11769
  if (finallyBlock) {
11818
- sections.push({ label: "finally", blockNode: finallyBlock.braceBlock?.()?.block?.() });
11770
+ sections.push({
11771
+ label: "finally",
11772
+ blockNode: finallyBlock.braceBlock?.()?.block?.()
11773
+ });
11819
11774
  }
11820
11775
  return { fragmentKind: "tcf", label: "", sections };
11821
11776
  }
@@ -11827,14 +11782,27 @@ function extractFragmentInfo(stat) {
11827
11782
  return null;
11828
11783
  }
11829
11784
  function blockLength(block) {
11830
- return (block?.stat?.() || []).filter((stat) => !!createStatementKey(stat)).length;
11785
+ return (block?.stat?.() || []).filter(
11786
+ (stat) => !!createStatementKey(stat)
11787
+ ).length;
11831
11788
  }
11832
11789
  function walkFragmentBlocks(stat, origin, results, activeOccurrences, parentNumber, depth) {
11833
- for (const kind of ["loop", "opt", "par", "critical", "section"]) {
11790
+ for (const kind of SINGLE_BLOCK_FRAGMENT_KINDS) {
11834
11791
  const frag = stat[kind]?.();
11835
11792
  if (frag) {
11836
11793
  const block = frag.braceBlock?.()?.block?.();
11837
- if (block) results.push(...walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, 0));
11794
+ if (block)
11795
+ results.push(
11796
+ ...walkBlock(
11797
+ block,
11798
+ origin,
11799
+ activeOccurrences,
11800
+ parentNumber,
11801
+ depth + 1,
11802
+ null,
11803
+ 0
11804
+ )
11805
+ );
11838
11806
  return;
11839
11807
  }
11840
11808
  }
@@ -11845,14 +11813,32 @@ function walkFragmentBlocks(stat, origin, results, activeOccurrences, parentNumb
11845
11813
  if (ifBlock) {
11846
11814
  const block = ifBlock.braceBlock?.()?.block?.();
11847
11815
  if (block) {
11848
- results.push(...walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, sectionOffset));
11816
+ results.push(
11817
+ ...walkBlock(
11818
+ block,
11819
+ origin,
11820
+ activeOccurrences,
11821
+ parentNumber,
11822
+ depth + 1,
11823
+ null,
11824
+ sectionOffset
11825
+ )
11826
+ );
11849
11827
  sectionOffset += blockLength(block);
11850
11828
  }
11851
11829
  }
11852
11830
  for (const elseIf of alt.elseIfBlock?.() || []) {
11853
11831
  const block = elseIf.braceBlock?.()?.block?.();
11854
11832
  if (block) {
11855
- const sectionStmts = walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, sectionOffset);
11833
+ const sectionStmts = walkBlock(
11834
+ block,
11835
+ origin,
11836
+ activeOccurrences,
11837
+ parentNumber,
11838
+ depth + 1,
11839
+ null,
11840
+ sectionOffset
11841
+ );
11856
11842
  if (sectionStmts.length > 0) sectionStmts[0].sectionReset = true;
11857
11843
  results.push(...sectionStmts);
11858
11844
  sectionOffset += blockLength(block);
@@ -11862,7 +11848,15 @@ function walkFragmentBlocks(stat, origin, results, activeOccurrences, parentNumb
11862
11848
  if (elseBlock) {
11863
11849
  const block = elseBlock.braceBlock?.()?.block?.();
11864
11850
  if (block) {
11865
- const sectionStmts = walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, sectionOffset);
11851
+ const sectionStmts = walkBlock(
11852
+ block,
11853
+ origin,
11854
+ activeOccurrences,
11855
+ parentNumber,
11856
+ depth + 1,
11857
+ null,
11858
+ sectionOffset
11859
+ );
11866
11860
  if (sectionStmts.length > 0) sectionStmts[0].sectionReset = true;
11867
11861
  results.push(...sectionStmts);
11868
11862
  }
@@ -11876,14 +11870,32 @@ function walkFragmentBlocks(stat, origin, results, activeOccurrences, parentNumb
11876
11870
  if (tryBlock) {
11877
11871
  const block = tryBlock.braceBlock?.()?.block?.();
11878
11872
  if (block) {
11879
- results.push(...walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, sectionOffset));
11873
+ results.push(
11874
+ ...walkBlock(
11875
+ block,
11876
+ origin,
11877
+ activeOccurrences,
11878
+ parentNumber,
11879
+ depth + 1,
11880
+ null,
11881
+ sectionOffset
11882
+ )
11883
+ );
11880
11884
  sectionOffset += blockLength(block);
11881
11885
  }
11882
11886
  }
11883
11887
  for (const catchBlock of tcf.catchBlock?.() || []) {
11884
11888
  const block = catchBlock.braceBlock?.()?.block?.();
11885
11889
  if (block) {
11886
- const sectionStmts = walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, sectionOffset);
11890
+ const sectionStmts = walkBlock(
11891
+ block,
11892
+ origin,
11893
+ activeOccurrences,
11894
+ parentNumber,
11895
+ depth + 1,
11896
+ null,
11897
+ sectionOffset
11898
+ );
11887
11899
  if (sectionStmts.length > 0) sectionStmts[0].sectionReset = true;
11888
11900
  results.push(...sectionStmts);
11889
11901
  sectionOffset += blockLength(block);
@@ -11893,7 +11905,15 @@ function walkFragmentBlocks(stat, origin, results, activeOccurrences, parentNumb
11893
11905
  if (finallyBlock) {
11894
11906
  const block = finallyBlock.braceBlock?.()?.block?.();
11895
11907
  if (block) {
11896
- const sectionStmts = walkBlock(block, origin, activeOccurrences, parentNumber, depth + 1, null, sectionOffset);
11908
+ const sectionStmts = walkBlock(
11909
+ block,
11910
+ origin,
11911
+ activeOccurrences,
11912
+ parentNumber,
11913
+ depth + 1,
11914
+ null,
11915
+ sectionOffset
11916
+ );
11897
11917
  if (sectionStmts.length > 0) sectionStmts[0].sectionReset = true;
11898
11918
  results.push(...sectionStmts);
11899
11919
  }
@@ -11925,10 +11945,6 @@ function computeReturnDebt(statements, verticalCoordinates, returnHeight) {
11925
11945
  if (ownerKey && occInnerDebt > 0) {
11926
11946
  result.set(`inner:${ownerKey}`, occInnerDebt);
11927
11947
  }
11928
- const hasMixed = hasNonReturnChild[maxDepth] || false;
11929
- if (ownerKey && hasMixed) {
11930
- result.set(`mixed:${ownerKey}`, 1);
11931
- }
11932
11948
  const hasAssign = blockHasAssignment[maxDepth] || false;
11933
11949
  debtByDepth.pop();
11934
11950
  directDebtByDepth.pop();
@@ -12012,10 +12028,6 @@ function computeReturnDebt(statements, verticalCoordinates, returnHeight) {
12012
12028
  if (ownerKey && occInnerDebtEnd > 0) {
12013
12029
  result.set(`inner:${ownerKey}`, occInnerDebtEnd);
12014
12030
  }
12015
- const hasMixedEnd = hasNonReturnChild[maxDepth] || false;
12016
- if (ownerKey && hasMixedEnd) {
12017
- result.set(`mixed:${ownerKey}`, 1);
12018
- }
12019
12031
  const hasAssignEnd = blockHasAssignment[maxDepth] || false;
12020
12032
  debtByDepth.pop();
12021
12033
  directDebtByDepth.pop();
@@ -12031,7 +12043,6 @@ function computeReturnDebt(statements, verticalCoordinates, returnHeight) {
12031
12043
  }
12032
12044
  }
12033
12045
  }
12034
- result.set("__totalDebt__", debtByDepth[0] || 0);
12035
12046
  return result;
12036
12047
  }
12037
12048
 
@@ -12059,7 +12070,11 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12059
12070
  }
12060
12071
  }
12061
12072
  const RETURN_VISUAL_HEIGHT = 16;
12062
- const adjustMap = computeReturnDebt(statements, verticalCoordinates, RETURN_VISUAL_HEIGHT);
12073
+ const adjustMap = computeReturnDebt(
12074
+ statements,
12075
+ verticalCoordinates,
12076
+ RETURN_VISUAL_HEIGHT
12077
+ );
12063
12078
  for (const info of statements) {
12064
12079
  const coord = verticalCoordinates.getStatementCoordinate(info.key);
12065
12080
  if (!coord) continue;
@@ -12075,7 +12090,7 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12075
12090
  const tX = coordinates.getPosition(info.to);
12076
12091
  if (fX > tX) commentParticipant = info.to;
12077
12092
  }
12078
- const commentX = commentParticipant ? snapX$1(coordinates.getPosition(commentParticipant)) : 10;
12093
+ const commentX = commentParticipant ? coordinates.getPosition(commentParticipant) : 10;
12079
12094
  const COMMENT_FONT_ASCENT = 15;
12080
12095
  const commentXOffset = info.senderOccurrenceDepth >= 1 ? OCCURRENCE_BAR_SIDE_WIDTH + 1 : 1;
12081
12096
  const codeSpanPadding = commentObj.text.trimStart().startsWith("`") ? 2 : 0;
@@ -12088,30 +12103,31 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12088
12103
  }
12089
12104
  const messageStyle = commentObj ? cssToSvgStyle(commentObj.messageStyle) : void 0;
12090
12105
  if (info.kind === "sync" || info.kind === "async") {
12091
- let fromX = snapX$1(coordinates.getPosition(info.from));
12092
- const toX = snapX$1(coordinates.getPosition(info.to));
12093
- const messageHeight = info.isSelf ? 30 : 16;
12094
- const msgCommentHeight = commentObj?.text ? (info.comment?.trim().split("\n").length || 0) * 20 : 0;
12106
+ let fromX = coordinates.getPosition(info.from);
12107
+ const toX = coordinates.getPosition(info.to);
12108
+ const isAsync = info.kind === "async";
12109
+ const messageHeight = !info.isSelf ? MESSAGE_HEIGHT : isAsync ? ASYNC_SELF_MESSAGE_HEIGHT : SELF_MESSAGE_HEIGHT;
12110
+ const msgCommentHeight = commentObj?.text ? (info.comment?.trim().split("\n").length || 0) * COMMENT_LINE_HEIGHT : 0;
12095
12111
  const messageY = coord.top + adjust + msgCommentHeight + messageHeight - 0.5;
12096
12112
  if (info.senderOccurrenceDepth >= 1 && !info.isSelf) {
12097
- const depth = info.senderOccurrenceDepth;
12098
- const occOffset = depth * OCCURRENCE_BAR_SIDE_WIDTH;
12113
+ const senderAnchor = new Anchor2(fromX, info.senderOccurrenceDepth);
12099
12114
  const isLTR = fromX < toX;
12100
- fromX = isLTR ? fromX + occOffset : fromX - OCCURRENCE_BAR_SIDE_WIDTH + (depth - 1) * OCCURRENCE_BAR_SIDE_WIDTH;
12115
+ fromX = isLTR ? senderAnchor.rightEdgeOfRightWall() : senderAnchor.leftEdgeOfRightWall();
12101
12116
  }
12102
12117
  const targetDepth = info.targetOccurrenceDepth || (info.targetHasOccurrence ? 1 : 0);
12103
12118
  const nestingOffset = targetDepth * OCCURRENCE_BAR_SIDE_WIDTH;
12104
12119
  if (info.isSelf) {
12105
- const isAsync = info.kind === "async";
12106
12120
  const selfYOffset = 0;
12107
12121
  const selfWidth = isAsync ? 28 : OCCURRENCE_WIDTH;
12108
- const selfHeight = isAsync ? 44 : messageHeight;
12109
- const selfX = info.senderOccurrenceDepth >= 1 ? fromX + OCCURRENCE_BAR_SIDE_WIDTH + (info.senderOccurrenceDepth - 1) * OCCURRENCE_BAR_SIDE_WIDTH : fromX;
12122
+ const selfX = new Anchor2(
12123
+ fromX,
12124
+ info.senderOccurrenceDepth
12125
+ ).rightEdgeOfRightWall();
12110
12126
  selfCalls.push({
12111
12127
  x: selfX,
12112
12128
  y: coord.top + selfYOffset + msgCommentHeight,
12113
12129
  width: selfWidth,
12114
- height: selfHeight,
12130
+ height: messageHeight,
12115
12131
  label: info.label,
12116
12132
  arrowStyle: isAsync ? "open" : "solid",
12117
12133
  number: info.number,
@@ -12183,16 +12199,15 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12183
12199
  continue;
12184
12200
  }
12185
12201
  if (info.kind === "creation") {
12186
- const CREATION_MSG_HEIGHT = 40;
12187
- let fromX = snapX$1(coordinates.getPosition(info.from));
12188
- const toX = snapX$1(coordinates.getPosition(info.to));
12202
+ let fromX = coordinates.getPosition(info.from);
12203
+ const toX = coordinates.getPosition(info.to);
12189
12204
  if (info.senderOccurrenceDepth >= 1) {
12190
12205
  const occOffset = OCCURRENCE_BAR_SIDE_WIDTH + (info.senderOccurrenceDepth - 1) * OCCURRENCE_BAR_SIDE_WIDTH;
12191
12206
  const isLTR = fromX < toX;
12192
12207
  fromX = isLTR ? fromX + occOffset : fromX - occOffset;
12193
12208
  }
12194
12209
  const targetParticipant = participants.find((p) => p.name === info.to);
12195
- const messageY = targetParticipant ? targetParticipant.y + PARTICIPANT_VISUAL_HEIGHT / 2 : coord.top + CREATION_MSG_HEIGHT / 2;
12210
+ const messageY = targetParticipant ? targetParticipant.y + PARTICIPANT_VISUAL_HEIGHT / 2 : coord.top + CREATION_MESSAGE_HEIGHT / 2;
12196
12211
  if (targetParticipant) {
12197
12212
  creations.push({
12198
12213
  participant: targetParticipant,
@@ -12211,7 +12226,7 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12211
12226
  });
12212
12227
  }
12213
12228
  const occX = toX - OCCURRENCE_BAR_SIDE_WIDTH;
12214
- const occY = targetParticipant ? targetParticipant.y + PARTICIPANT_VISUAL_HEIGHT - 2 : coord.top + CREATION_MSG_HEIGHT - 2;
12229
+ const occY = targetParticipant ? targetParticipant.y + PARTICIPANT_VISUAL_HEIGHT - 2 : coord.top + CREATION_MESSAGE_HEIGHT - 2;
12215
12230
  const occHeight = Math.max(
12216
12231
  coord.top + coord.height - occY,
12217
12232
  OCCURRENCE_EMPTY_HEIGHT
@@ -12228,7 +12243,7 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12228
12243
  const creationAssign = creationCtx?.Assignment?.();
12229
12244
  if (creationAssign?.assignee) {
12230
12245
  const occBottom = occY + occHeight - 2;
12231
- const rawFromX = snapX$1(coordinates.getPosition(info.from));
12246
+ const rawFromX = coordinates.getPosition(info.from);
12232
12247
  const isLTR = rawFromX < toX;
12233
12248
  const occHalf = OCCURRENCE_BAR_SIDE_WIDTH;
12234
12249
  const senderNest = Math.max(info.senderOccurrenceDepth - 1, 0) * OCCURRENCE_BAR_SIDE_WIDTH;
@@ -12250,7 +12265,7 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12250
12265
  }
12251
12266
  if (info.kind === "fragment" && info.fragmentKind) {
12252
12267
  const adjustedCoord = { top: coord.top + adjust, height: coord.height };
12253
- const fragmentCommentHeight = commentObj?.text ? (info.comment?.trim().split("\n").length || 0) * 20 : 0;
12268
+ const fragmentCommentHeight = commentObj?.text ? (info.comment?.trim().split("\n").length || 0) * COMMENT_LINE_HEIGHT : 0;
12254
12269
  const fragmentResult = buildFragmentGeometry(
12255
12270
  info,
12256
12271
  adjustedCoord,
@@ -12272,26 +12287,14 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12272
12287
  continue;
12273
12288
  }
12274
12289
  if (info.kind === "return") {
12275
- const rawFromX = snapX$1(coordinates.getPosition(info.from));
12276
- const rawToX = snapX$1(coordinates.getPosition(info.to));
12290
+ const rawFromX = coordinates.getPosition(info.from);
12291
+ const rawToX = coordinates.getPosition(info.to);
12277
12292
  const isReverse = rawToX < rawFromX;
12278
- const fromLayers = info.senderOccurrenceDepth;
12279
- let fromX;
12280
- if (isReverse) {
12281
- fromX = fromLayers === 0 ? rawFromX : (fromLayers <= 1 ? rawFromX : rawFromX + OCCURRENCE_BAR_SIDE_WIDTH * (fromLayers - 1)) - OCCURRENCE_BAR_SIDE_WIDTH;
12282
- } else {
12283
- fromX = rawFromX + OCCURRENCE_BAR_SIDE_WIDTH * fromLayers + 1;
12284
- }
12285
- const toLayers = info.targetOccurrenceDepth || 0;
12286
- let toX;
12287
- if (isReverse) {
12288
- toX = rawToX + OCCURRENCE_BAR_SIDE_WIDTH * toLayers;
12289
- } else {
12290
- toX = toLayers === 0 ? rawToX : (toLayers <= 1 ? rawToX : rawToX + OCCURRENCE_BAR_SIDE_WIDTH * (toLayers - 1)) - OCCURRENCE_BAR_SIDE_WIDTH;
12291
- }
12292
- if (isReverse) {
12293
- toX += LIFELINE_WIDTH;
12294
- }
12293
+ const fromAnchor = new Anchor2(rawFromX, info.senderOccurrenceDepth);
12294
+ const toAnchor = new Anchor2(rawToX, info.targetOccurrenceDepth || 0);
12295
+ const RETURN_STROKE_OFFSET = 1;
12296
+ const fromX = isReverse ? fromAnchor.leftEdgeOfRightWall() : fromAnchor.rightEdgeOfRightWall() + RETURN_STROKE_OFFSET;
12297
+ const toX = isReverse ? toAnchor.rightEdgeOfRightWall() + LIFELINE_WIDTH : toAnchor.leftEdgeOfRightWall();
12295
12298
  const returnOffset = coord.height === 0 ? 16.5 : 15.5;
12296
12299
  const returnY = coord.top + adjust + returnOffset;
12297
12300
  returns.push({
@@ -12313,7 +12316,9 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12313
12316
  y: coord.top + adjust + coord.height / 2,
12314
12317
  width: diagramWidth,
12315
12318
  label: info.label,
12316
- labelWidth: measureSvgFragmentLabelWidth(resolveEmojiInText(cleanLabel))
12319
+ labelWidth: measureSvgFragmentLabelWidth(
12320
+ resolveEmojiInText(cleanLabel)
12321
+ )
12317
12322
  });
12318
12323
  continue;
12319
12324
  }
@@ -12336,11 +12341,23 @@ function buildMessages(rootContext, coordinates, verticalCoordinates, participan
12336
12341
  }
12337
12342
  }
12338
12343
  if (depth > 0) {
12339
- occurrences[i] = { ...inner, x: inner.x + depth * OCCURRENCE_BAR_SIDE_WIDTH };
12344
+ occurrences[i] = {
12345
+ ...inner,
12346
+ x: inner.x + depth * OCCURRENCE_BAR_SIDE_WIDTH
12347
+ };
12340
12348
  }
12341
12349
  }
12342
- const totalReturnDebt = adjustMap.get("__totalDebt__") || 0;
12343
- return { messages, selfCalls, occurrences, creations, fragments, returns, dividers, comments, totalReturnDebt, maxReturnBottom };
12350
+ return {
12351
+ messages,
12352
+ selfCalls,
12353
+ occurrences,
12354
+ creations,
12355
+ fragments,
12356
+ returns,
12357
+ dividers,
12358
+ comments,
12359
+ maxReturnBottom
12360
+ };
12344
12361
  }
12345
12362
 
12346
12363
  function buildGeometry(input) {
@@ -12384,17 +12401,7 @@ function buildGeometry(input) {
12384
12401
  participants,
12385
12402
  lifelineBottom
12386
12403
  );
12387
- const localParticipants = getLocalParticipantNames(rootContext);
12388
- const orderedNames = coordinates.orderedParticipantNames();
12389
- const leftParticipant = orderedNames.find((p) => localParticipants.includes(p)) || "";
12390
- const rightParticipant = orderedNames.slice().reverse().find((p) => localParticipants.includes(p)) || "";
12391
- const participantWidth = coordinates.distance(leftParticipant, rightParticipant) + coordinates.half(leftParticipant) + coordinates.half(rightParticipant);
12392
- const selfMessages = AllMessages(rootContext).filter((m) => m.from === m.to);
12393
- const extraWidths = selfMessages.map(
12394
- (m) => coordinates.getMessageWidth(m) - coordinates.distance(m.from || _STARTER_, rightParticipant) - coordinates.half(rightParticipant)
12395
- );
12396
- const extraWidthDueToSelfMessage = Math.max(0, ...extraWidths);
12397
- let diagramWidth = Math.max(participantWidth, FRAGMENT_MIN_WIDTH) + extraWidthDueToSelfMessage;
12404
+ let diagramWidth = TotalWidth(rootContext, coordinates) - frameBorder.left - frameBorder.right;
12398
12405
  if (measureText) {
12399
12406
  const LABEL_PAD = 10;
12400
12407
  for (const msg of messages) {
@@ -12732,7 +12739,7 @@ function renderOccurrence(o) {
12732
12739
  return `<rect x="${rx}" y="${o.y + strokeHalf}" width="${o.width - strokeHalf * 2}" height="${o.height - strokeHalf * 2}" rx="1" class="occurrence" data-participant="${esc(o.participantName)}"/>`;
12733
12740
  }
12734
12741
 
12735
- const HEADER_HEIGHT = 25;
12742
+ const HEADER_HEIGHT = DEFAULT_LAYOUT_METRICS.fragmentHeaderHeight;
12736
12743
  const BRACKET_WIDTH = 3.89;
12737
12744
  const TEXT_PAD_X = 4;
12738
12745
  const STROKE_WIDTH = 1;
@@ -13093,47 +13100,153 @@ function renderGroup(g) {
13093
13100
  </g>`;
13094
13101
  }
13095
13102
 
13096
- const FRAME_HEADER_HEIGHT = 28;
13097
- const FRAME_BORDER_RADIUS = 4;
13098
- const DEFAULT_THEME_STYLES = `
13099
- .frame-border-outer { fill: #666; }
13100
- .frame-border-inner { fill: #ffffff; }
13101
- .frame-header-bg { fill: #ffffff; }
13102
- .frame-header-line { stroke: #666; stroke-width: 1; shape-rendering: crispEdges; }
13103
- .frame-title { font-family: Helvetica, Verdana, serif; font-size: 16px; font-weight: 600; fill: #222; }
13104
- .participant-box { fill: #ffffff; stroke: #666; stroke-width: 2; }
13105
- .participant-label { font-family: Helvetica, Verdana, serif; font-size: 16px; fill: #222; }
13106
- .participant-icon { color: #222; }
13107
- .participant-icon [fill="currentColor"]:not([stroke]) { stroke: #666; stroke-width: 1; }
13108
- .lifeline { stroke: #666; stroke-width: 1; }
13109
- .message-line { stroke: #000; stroke-width: 2; shape-rendering: crispEdges; }
13110
- .message-label { font-family: Helvetica, Verdana, serif; font-size: 14px; fill: #222; }
13111
- .arrow-head { fill: #000; stroke: #000; stroke-width: 2; }
13103
+ const HELVETICA = "Helvetica, Verdana, serif";
13104
+ const DEFAULT_PALETTE = {
13105
+ frameBorder: "#666",
13106
+ frameBg: "#ffffff",
13107
+ titleText: "#222",
13108
+ participantBg: "#ffffff",
13109
+ participantBorder: "#666",
13110
+ participantText: "#222",
13111
+ messageArrow: "#000",
13112
+ messageText: "#222",
13113
+ occurrenceBg: "#dedede",
13114
+ occurrenceBorder: "#666",
13115
+ fragmentBorder: "#666",
13116
+ fragmentHeaderBg: "#dedede",
13117
+ fragmentHeaderOpacity: "0.498",
13118
+ fragmentText: "#000",
13119
+ fragmentSeparator: "#e5e7eb",
13120
+ commentText: "#333",
13121
+ fontFamily: HELVETICA
13122
+ };
13123
+ const THEME_PALETTES = {
13124
+ "theme-default": DEFAULT_PALETTE,
13125
+ // `.theme-mermaid` sets only base/text/border/occurrence, and hides the
13126
+ // footer — chrome the SVG output does not draw. Every slot it leaves unset
13127
+ // matches theme-default, so the two render identically.
13128
+ "theme-mermaid": DEFAULT_PALETTE,
13129
+ // src/assets/tailwind.css `.theme-clean-light`.
13130
+ "theme-clean-light": {
13131
+ frameBorder: "#e5e9f0",
13132
+ frameBg: "#ffffff",
13133
+ titleText: "#353748",
13134
+ participantBg: "#edf4fe",
13135
+ participantBorder: "#368eff",
13136
+ participantText: "#368eff",
13137
+ messageArrow: "#368eff",
13138
+ messageText: "#368eff",
13139
+ occurrenceBg: "#ffffff",
13140
+ occurrenceBorder: "#368eff",
13141
+ fragmentBorder: "#c8c9c9",
13142
+ fragmentHeaderBg: "#edf4fe",
13143
+ fragmentHeaderOpacity: "1",
13144
+ fragmentText: "#368eff",
13145
+ fragmentSeparator: "#e5e9f0",
13146
+ commentText: "#353748",
13147
+ fontFamily: HELVETICA
13148
+ },
13149
+ // src/assets/tailwind.css `.theme-clean-dark`.
13150
+ "theme-clean-dark": {
13151
+ frameBorder: "#cecfd2",
13152
+ frameBg: "#111628",
13153
+ titleText: "#cecfd2",
13154
+ participantBg: "#5964f2",
13155
+ participantBorder: "#cecfd2",
13156
+ participantText: "#536fff",
13157
+ messageArrow: "#536fff",
13158
+ messageText: "#cecfd2",
13159
+ occurrenceBg: "#5964f2",
13160
+ occurrenceBorder: "#cecfd2",
13161
+ fragmentBorder: "#cecfd2",
13162
+ fragmentHeaderBg: "#5964f2",
13163
+ fragmentHeaderOpacity: "1",
13164
+ fragmentText: "#cecfd2",
13165
+ fragmentSeparator: "#cecfd2",
13166
+ commentText: "#cecfd2",
13167
+ fontFamily: HELVETICA
13168
+ },
13169
+ // src/assets/tailwind.css `.theme-neon`. The DOM theme also swaps in the
13170
+ // MS Sans Serif webfont (`.zenuml .theme-neon`); the SVG names it first and
13171
+ // keeps the Helvetica stack behind it, since a consumer embedding the SVG
13172
+ // will not have that @font-face rule.
13173
+ "theme-neon": {
13174
+ frameBorder: "#60ff33",
13175
+ frameBg: "#000000",
13176
+ titleText: "#8ffc5b",
13177
+ participantBg: "#000000",
13178
+ participantBorder: "#8ffc5b",
13179
+ participantText: "#8ffc5b",
13180
+ messageArrow: "#8ffc5b",
13181
+ messageText: "#8ffc5b",
13182
+ occurrenceBg: "#8ffc5b",
13183
+ occurrenceBorder: "#8ffc5b",
13184
+ fragmentBorder: "#60ff33",
13185
+ fragmentHeaderBg: "#000000",
13186
+ fragmentHeaderOpacity: "1",
13187
+ fragmentText: "#8ffc5b",
13188
+ fragmentSeparator: "#60ff33",
13189
+ commentText: "#8ffc5b",
13190
+ fontFamily: `"MS Sans Serif", ${HELVETICA}`
13191
+ }
13192
+ };
13193
+ function resolvePalette(theme) {
13194
+ if (theme && theme in THEME_PALETTES) {
13195
+ return THEME_PALETTES[theme];
13196
+ }
13197
+ return DEFAULT_PALETTE;
13198
+ }
13199
+ function buildThemeStyles(p) {
13200
+ return `
13201
+ .frame-border-outer { fill: ${p.frameBorder}; }
13202
+ .frame-border-inner { fill: ${p.frameBg}; }
13203
+ .frame-header-bg { fill: ${p.frameBg}; }
13204
+ .frame-header-line { stroke: ${p.frameBorder}; stroke-width: 1; shape-rendering: crispEdges; }
13205
+ .frame-title { font-family: ${p.fontFamily}; font-size: 16px; font-weight: 600; fill: ${p.titleText}; }
13206
+ .participant-box { fill: ${p.participantBg}; stroke: ${p.participantBorder}; stroke-width: 2; }
13207
+ .participant-label { font-family: ${p.fontFamily}; font-size: 16px; fill: ${p.participantText}; }
13208
+ .participant-icon { color: ${p.participantText}; }
13209
+ .participant-icon [fill="currentColor"]:not([stroke]) { stroke: ${p.participantBorder}; stroke-width: 1; }
13210
+ .lifeline { stroke: ${p.participantBorder}; stroke-width: 1; }
13211
+ .message-line { stroke: ${p.messageArrow}; stroke-width: 2; shape-rendering: crispEdges; }
13212
+ .message-label { font-family: ${p.fontFamily}; font-size: 14px; fill: ${p.messageText}; }
13213
+ .arrow-head { fill: ${p.messageArrow}; stroke: ${p.messageArrow}; stroke-width: 2; }
13112
13214
  .arrow-open { fill: none; }
13113
- .occurrence { fill: #dedede; stroke: #666; stroke-width: 2; shape-rendering: crispEdges; }
13114
- .fragment-border { fill: none; stroke: #666; stroke-width: 1; shape-rendering: crispEdges; }
13115
- .fragment-header { fill: #dedede; fill-opacity: 0.498; stroke: none; shape-rendering: crispEdges; }
13116
- .fragment-label { font-family: Helvetica, Verdana, serif; font-size: 14px; font-weight: 600; fill: #000; }
13117
- .fragment-condition { font-family: Helvetica, Verdana, serif; font-size: 14px; fill: #000; }
13118
- .fragment-separator { stroke: #e5e7eb; stroke-width: 1; shape-rendering: crispEdges; }
13119
- .fragment-section-label { font-family: Helvetica, Verdana, serif; font-size: 14px; fill: #000; }
13120
- .return-line { stroke: #000; stroke-width: 2; stroke-dasharray: 6,4; shape-rendering: crispEdges; }
13121
- .return-arrow { stroke: #000; stroke-width: 2; fill: none; }
13122
- .return-label { font-family: Helvetica, Verdana, serif; font-size: 14px; fill: #222; }
13123
- .return-icon { fill: #222; }
13215
+ .occurrence { fill: ${p.occurrenceBg}; stroke: ${p.occurrenceBorder}; stroke-width: 2; shape-rendering: crispEdges; }
13216
+ .fragment-border { fill: none; stroke: ${p.fragmentBorder}; stroke-width: 1; shape-rendering: crispEdges; }
13217
+ .fragment-header { fill: ${p.fragmentHeaderBg}; fill-opacity: ${p.fragmentHeaderOpacity}; stroke: none; shape-rendering: crispEdges; }
13218
+ .fragment-label { font-family: ${p.fontFamily}; font-size: 14px; font-weight: 600; fill: ${p.fragmentText}; }
13219
+ .fragment-condition { font-family: ${p.fontFamily}; font-size: 14px; fill: ${p.fragmentText}; }
13220
+ .fragment-separator { stroke: ${p.fragmentSeparator}; stroke-width: 1; shape-rendering: crispEdges; }
13221
+ .fragment-section-label { font-family: ${p.fontFamily}; font-size: 14px; fill: ${p.fragmentText}; }
13222
+ .return-line { stroke: ${p.messageArrow}; stroke-width: 2; stroke-dasharray: 6,4; shape-rendering: crispEdges; }
13223
+ .return-arrow { stroke: ${p.messageArrow}; stroke-width: 2; fill: none; }
13224
+ .return-label { font-family: ${p.fontFamily}; font-size: 14px; fill: ${p.messageText}; }
13225
+ .return-icon { fill: ${p.messageText}; }
13124
13226
  .divider-line { stroke: #aaaa33; stroke-width: 1; }
13125
13227
  .divider-bg { fill: #fff5ad; stroke: #aaaa33; stroke-width: 1; }
13126
- .divider-label { font-family: Helvetica, Verdana, serif; font-size: 14px; fill: #333; }
13127
- .comment-text { font-family: Helvetica, Verdana, serif; font-size: 14px; fill: #333; opacity: 0.5; }
13128
- .seq-number { font-family: Helvetica, Verdana, serif; font-size: 12px; font-weight: 100; fill: #6b7280; }
13129
- .group-outline { fill: none; stroke: #666; }
13130
- .group-title-bg { fill: #ffffff; stroke: none; }
13131
- .group-title-text { font-family: Helvetica, Verdana, serif; font-size: 13px; font-weight: 400; fill: #222; }
13228
+ .divider-label { font-family: ${p.fontFamily}; font-size: 14px; fill: #333; }
13229
+ .comment-text { font-family: ${p.fontFamily}; font-size: 14px; fill: ${p.commentText}; opacity: 0.5; }
13230
+ .seq-number { font-family: ${p.fontFamily}; font-size: 12px; font-weight: 100; fill: #6b7280; }
13231
+ .group-outline { fill: none; stroke: ${p.participantBorder}; }
13232
+ .group-title-bg { fill: ${p.frameBg}; stroke: none; }
13233
+ .group-title-text { font-family: ${p.fontFamily}; font-size: 13px; font-weight: 400; fill: ${p.participantText}; }
13132
13234
  `;
13235
+ }
13236
+
13237
+ const FRAME_HEADER_HEIGHT = 28;
13238
+ const FRAME_BORDER_RADIUS = 4;
13133
13239
  function renderToSvg(code, options) {
13134
13240
  const rootContext = RootContext(code);
13135
13241
  if (!rootContext) {
13136
- return { svg: "<svg></svg>", innerSvg: "", width: 0, height: 0, viewBox: "0 0 0 0", geometry: void 0 };
13242
+ return {
13243
+ svg: "<svg></svg>",
13244
+ innerSvg: "",
13245
+ width: 0,
13246
+ height: 0,
13247
+ viewBox: "0 0 0 0",
13248
+ geometry: void 0
13249
+ };
13137
13250
  }
13138
13251
  const coordinates = new Coordinates(rootContext, WidthProviderOnCanvas);
13139
13252
  const verticalCoordinates = new VerticalCoordinates(rootContext);
@@ -13147,7 +13260,7 @@ function renderToSvg(code, options) {
13147
13260
  title,
13148
13261
  measureText: WidthProviderOnCanvas
13149
13262
  });
13150
- return { ...composeSvg(geometry), geometry };
13263
+ return { ...composeSvg(geometry, options), geometry };
13151
13264
  }
13152
13265
  function composeSvg(g, options) {
13153
13266
  const padding = 10;
@@ -13206,10 +13319,10 @@ function composeSvg(g, options) {
13206
13319
  const headerLineY = headerH + contentPaddingTop;
13207
13320
  const headerLineDrawY = headerLineY - 0.5;
13208
13321
  const headerLineSvg = `<line class="frame-header-line" x1="1" y1="${headerLineDrawY}" x2="${viewWidth - 1}" y2="${headerLineDrawY}"/>`;
13209
- const titleSvg = g.title ? `<text x="5" y="${headerLineDrawY / 2}" dominant-baseline="central" class="frame-title">${escXml(g.title)}</text>` : "";
13322
+ const titleSvg = g.title ? `<text x="5" y="${headerLineDrawY / 2}" dominant-baseline="central" class="frame-title">${esc(g.title)}</text>` : "";
13210
13323
  const viewBox = `0 0 ${viewWidth} ${viewHeight}`;
13211
13324
  const defs = `<defs>
13212
- <style>${DEFAULT_THEME_STYLES}</style>
13325
+ <style>${buildThemeStyles(resolvePalette(options?.theme))}</style>
13213
13326
  </defs>`;
13214
13327
  const frame = `${frameSvg}
13215
13328
  ${headerLineSvg}
@@ -13225,9 +13338,6 @@ ${innerSvg}
13225
13338
  </svg>`;
13226
13339
  return { svg, innerSvg, width: viewWidth, height: viewHeight, viewBox };
13227
13340
  }
13228
- function escXml(s) {
13229
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13230
- }
13231
13341
 
13232
13342
  function readVersion() {
13233
13343
  const pkgPath = resolve(import.meta.dir, "../../package.json");
@@ -13249,7 +13359,8 @@ Rendering:
13249
13359
  -o, --output <file> Output file (use "-" for stdout; default: <input>.svg)
13250
13360
  -e, --outputFormat <format> Output format: "svg" (default) or "png"
13251
13361
  -s, --scale <factor> Pixel scale factor for PNG (default: 2; ignored for SVG)
13252
- -t, --theme <name> Theme name passed to renderer (e.g. "theme-default")
13362
+ -t, --theme <name> Theme: default, mermaid, clean-light, clean-dark,
13363
+ neon (prefix "theme-"; unknown names use default)
13253
13364
  -c, --configFile <file> JSON config file with { theme, scale, outputFormat }
13254
13365
  --md Markdown mode: render zenuml code blocks and produce output Markdown
13255
13366
 
@@ -13610,7 +13721,6 @@ async function checkOne(input) {
13610
13721
  errors: [{ line: 0, column: 0, msg: err.message }]
13611
13722
  };
13612
13723
  }
13613
- Parser.Errors.length = 0;
13614
13724
  Parser.ErrorDetails.length = 0;
13615
13725
  Parser.RootContext(code);
13616
13726
  const errors = Parser.ErrorDetails.map((e) => ({
@@ -13776,7 +13886,6 @@ async function main() {
13776
13886
  `);
13777
13887
  process.exit(1);
13778
13888
  }
13779
- Parser.Errors.length = 0;
13780
13889
  Parser.ErrorDetails.length = 0;
13781
13890
  const tree = Parser.RootContext(code);
13782
13891
  if (Parser.ErrorDetails.length > 0) {