@wire-dsl/engine 0.5.0 → 0.6.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.
package/dist/index.cjs CHANGED
@@ -2220,8 +2220,8 @@ var IRStyleSchema = import_zod.z.object({
2220
2220
  var IRNodeStyleSchema = import_zod.z.object({
2221
2221
  padding: import_zod.z.string().optional(),
2222
2222
  gap: import_zod.z.string().optional(),
2223
- align: import_zod.z.enum(["left", "center", "right", "justify"]).optional(),
2224
- justify: import_zod.z.enum(["start", "center", "end"]).optional(),
2223
+ justify: import_zod.z.enum(["start", "center", "end", "stretch", "spaceBetween", "spaceAround"]).optional(),
2224
+ align: import_zod.z.enum(["start", "center", "end"]).optional(),
2225
2225
  background: import_zod.z.string().optional()
2226
2226
  });
2227
2227
  var IRMetaSchema = import_zod.z.object({
@@ -2507,6 +2507,9 @@ ${messages}`);
2507
2507
  if (layoutParams.gap !== void 0) {
2508
2508
  style.gap = String(layoutParams.gap);
2509
2509
  }
2510
+ if (layoutParams.justify !== void 0) {
2511
+ style.justify = layoutParams.justify;
2512
+ }
2510
2513
  if (layoutParams.align !== void 0) {
2511
2514
  style.align = layoutParams.align;
2512
2515
  }
@@ -3153,8 +3156,9 @@ var LayoutEngine = class {
3153
3156
  }
3154
3157
  });
3155
3158
  } else {
3156
- const align = node.style.align || "justify";
3157
- if (align === "justify") {
3159
+ const justify = node.style.justify || "stretch";
3160
+ const crossAlign = node.style.align || "start";
3161
+ if (justify === "stretch") {
3158
3162
  let currentX = x;
3159
3163
  const childWidth = this.calculateChildWidth(children.length, width, gap);
3160
3164
  let stackHeight = 0;
@@ -3176,43 +3180,44 @@ var LayoutEngine = class {
3176
3180
  });
3177
3181
  } else {
3178
3182
  const childWidths = [];
3183
+ const childHeights = [];
3179
3184
  const explicitHeightFlags = [];
3180
- const blockButtonIndices = /* @__PURE__ */ new Set();
3185
+ const flexIndices = /* @__PURE__ */ new Set();
3181
3186
  let stackHeight = 0;
3182
3187
  children.forEach((childRef, index) => {
3183
3188
  const childNode = this.nodes[childRef.ref];
3184
- let childWidth = this.getIntrinsicComponentWidth(childNode);
3185
- let childHeight = this.getComponentHeight();
3186
3189
  const hasExplicitHeight = childNode?.kind === "component" && !!childNode.props.height;
3187
3190
  const hasExplicitWidth = childNode?.kind === "component" && !!childNode.props.width;
3188
3191
  const isBlockButton = childNode?.kind === "component" && childNode.componentType === "Button" && !hasExplicitWidth && this.parseBooleanProp(childNode.props.block, false);
3189
- if (isBlockButton) {
3192
+ const isFlexContainer = !hasExplicitWidth && childNode?.kind === "container" && !this.containerHasIntrinsicWidth(childNode);
3193
+ let childWidth;
3194
+ if (isBlockButton || isFlexContainer) {
3190
3195
  childWidth = 0;
3191
- blockButtonIndices.add(index);
3196
+ flexIndices.add(index);
3192
3197
  } else if (hasExplicitWidth) {
3193
3198
  childWidth = Number(childNode.props.width);
3194
- }
3195
- if (hasExplicitHeight) {
3196
- childHeight = Number(childNode.props.height);
3199
+ } else {
3200
+ childWidth = this.getIntrinsicWidth(childNode, width);
3197
3201
  }
3198
3202
  childWidths.push(childWidth);
3203
+ childHeights.push(this.getComponentHeight());
3199
3204
  explicitHeightFlags.push(hasExplicitHeight);
3200
3205
  });
3201
3206
  const totalGapWidth = gap * Math.max(0, children.length - 1);
3202
- if (blockButtonIndices.size > 0) {
3207
+ if (flexIndices.size > 0) {
3203
3208
  const fixedWidth = childWidths.reduce((sum, w, idx) => {
3204
- return blockButtonIndices.has(idx) ? sum : sum + w;
3209
+ return flexIndices.has(idx) ? sum : sum + w;
3205
3210
  }, 0);
3206
3211
  const remainingWidth = width - totalGapWidth - fixedWidth;
3207
- const widthPerBlock = Math.max(1, remainingWidth / blockButtonIndices.size);
3208
- blockButtonIndices.forEach((index) => {
3209
- childWidths[index] = widthPerBlock;
3212
+ const widthPerFlex = Math.max(1, remainingWidth / flexIndices.size);
3213
+ flexIndices.forEach((idx) => {
3214
+ childWidths[idx] = widthPerFlex;
3210
3215
  });
3211
3216
  }
3212
3217
  children.forEach((childRef, index) => {
3213
3218
  const childNode = this.nodes[childRef.ref];
3214
- let childHeight = this.getComponentHeight();
3215
3219
  const childWidth = childWidths[index];
3220
+ let childHeight = this.getComponentHeight();
3216
3221
  if (explicitHeightFlags[index] && childNode?.kind === "component") {
3217
3222
  childHeight = Number(childNode.props.height);
3218
3223
  } else if (childNode?.kind === "container") {
@@ -3220,21 +3225,37 @@ var LayoutEngine = class {
3220
3225
  } else if (childNode?.kind === "component") {
3221
3226
  childHeight = this.getIntrinsicComponentHeight(childNode, childWidth);
3222
3227
  }
3228
+ childHeights[index] = childHeight;
3223
3229
  stackHeight = Math.max(stackHeight, childHeight);
3224
3230
  });
3225
3231
  const totalChildWidth = childWidths.reduce((sum, w) => sum + w, 0);
3226
3232
  const totalContentWidth = totalChildWidth + totalGapWidth;
3227
3233
  let startX = x;
3228
- if (align === "center") {
3234
+ let dynamicGap = gap;
3235
+ if (justify === "center") {
3229
3236
  startX = x + (width - totalContentWidth) / 2;
3230
- } else if (align === "right") {
3237
+ } else if (justify === "end") {
3231
3238
  startX = x + width - totalContentWidth;
3239
+ } else if (justify === "spaceBetween") {
3240
+ startX = x;
3241
+ dynamicGap = children.length > 1 ? (width - totalChildWidth) / (children.length - 1) : 0;
3242
+ } else if (justify === "spaceAround") {
3243
+ const spacing = children.length > 0 ? (width - totalChildWidth) / children.length : 0;
3244
+ startX = x + spacing / 2;
3245
+ dynamicGap = spacing;
3232
3246
  }
3233
3247
  let currentX = startX;
3234
3248
  children.forEach((childRef, index) => {
3235
3249
  const childWidth = childWidths[index];
3236
- this.calculateNode(childRef.ref, currentX, y, childWidth, stackHeight, "stack");
3237
- currentX += childWidth + gap;
3250
+ const childHeight = childHeights[index];
3251
+ let childY = y;
3252
+ if (crossAlign === "center") {
3253
+ childY = y + Math.round((stackHeight - childHeight) / 2);
3254
+ } else if (crossAlign === "end") {
3255
+ childY = y + stackHeight - childHeight;
3256
+ }
3257
+ this.calculateNode(childRef.ref, currentX, childY, childWidth, childHeight, "stack");
3258
+ currentX += childWidth + dynamicGap;
3238
3259
  });
3239
3260
  }
3240
3261
  }
@@ -3757,6 +3778,47 @@ var LayoutEngine = class {
3757
3778
  getControlLabelOffset(label) {
3758
3779
  return label.trim().length > 0 ? 18 : 0;
3759
3780
  }
3781
+ /**
3782
+ * Returns true when a container's width can be calculated from its children
3783
+ * (i.e. it is a horizontal non-stretch stack). False means the container
3784
+ * behaves like `flex-grow:1` and should absorb remaining space.
3785
+ */
3786
+ containerHasIntrinsicWidth(node) {
3787
+ if (node.kind !== "container") return false;
3788
+ return node.containerType === "stack" && String(node.params.direction || "vertical") === "horizontal" && (node.style.justify || "stretch") !== "stretch";
3789
+ }
3790
+ /**
3791
+ * Returns the natural (intrinsic) width of any node — component or container.
3792
+ * For horizontal non-stretch containers the width is the sum of their children's
3793
+ * intrinsic widths plus gaps, capped at `availableWidth`. All other containers
3794
+ * are assumed to take the full available width (they stretch or grow).
3795
+ */
3796
+ getIntrinsicWidth(node, availableWidth) {
3797
+ if (!node) return 120;
3798
+ if (node.kind === "component") {
3799
+ return this.getIntrinsicComponentWidth(node);
3800
+ }
3801
+ if (node.kind === "container") {
3802
+ if (this.containerHasIntrinsicWidth(node)) {
3803
+ const gap = this.resolveSpacing(node.style.gap);
3804
+ const padding = this.resolveSpacing(node.style.padding);
3805
+ const innerAvailable = Math.max(0, availableWidth - padding * 2);
3806
+ const children = node.children ?? [];
3807
+ let total = padding * 2;
3808
+ children.forEach((childRef, idx) => {
3809
+ const child = this.nodes[childRef.ref];
3810
+ total += this.getIntrinsicWidth(child, innerAvailable);
3811
+ if (idx < children.length - 1) total += gap;
3812
+ });
3813
+ return Math.min(total, availableWidth);
3814
+ }
3815
+ return availableWidth;
3816
+ }
3817
+ if (node.kind === "instance") {
3818
+ return availableWidth;
3819
+ }
3820
+ return 120;
3821
+ }
3760
3822
  getIntrinsicComponentWidth(node) {
3761
3823
  if (!node || node.kind !== "component") {
3762
3824
  return 120;
@@ -5617,7 +5679,8 @@ var SVGRenderer = class {
5617
5679
  const fontSize = this.tokens.text.fontSize;
5618
5680
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
5619
5681
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
5620
- const firstLineY = pos.y + fontSize;
5682
+ const totalTextHeight = lines.length * lineHeightPx;
5683
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
5621
5684
  const tspans = lines.map(
5622
5685
  (line, index) => `<tspan x="${pos.x}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
5623
5686
  ).join("");
@@ -5630,8 +5693,9 @@ var SVGRenderer = class {
5630
5693
  }
5631
5694
  renderLabel(node, pos) {
5632
5695
  const text = String(node.props.text || "Label");
5696
+ const textY = pos.y + Math.round(pos.height / 2) + 4;
5633
5697
  return `<g${this.getDataNodeId(node)}>
5634
- <text x="${pos.x}" y="${pos.y + 12}"
5698
+ <text x="${pos.x}" y="${textY}"
5635
5699
  font-family="Arial, Helvetica, sans-serif"
5636
5700
  font-size="12"
5637
5701
  fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
@@ -6768,8 +6832,8 @@ var SVGRenderer = class {
6768
6832
  return false;
6769
6833
  }
6770
6834
  const direction = String(parent.params.direction || "vertical");
6771
- const align = parent.style.align || "justify";
6772
- return direction === "horizontal" && align === "justify";
6835
+ const justify = parent.style.justify || "stretch";
6836
+ return direction === "horizontal" && justify === "stretch";
6773
6837
  }
6774
6838
  buildParentContainerIndex() {
6775
6839
  this.parentContainerByChildId.clear();
package/dist/index.d.cts CHANGED
@@ -290,8 +290,8 @@ interface IRComponentNode {
290
290
  interface IRNodeStyle {
291
291
  padding?: string;
292
292
  gap?: string;
293
- align?: 'left' | 'center' | 'right' | 'justify';
294
- justify?: 'start' | 'center' | 'end';
293
+ justify?: 'start' | 'center' | 'end' | 'stretch' | 'spaceBetween' | 'spaceAround';
294
+ align?: 'start' | 'center' | 'end';
295
295
  background?: string;
296
296
  }
297
297
  interface IRMeta {
@@ -480,6 +480,19 @@ declare class LayoutEngine {
480
480
  private wrapTextToLines;
481
481
  private getIntrinsicComponentHeight;
482
482
  private getControlLabelOffset;
483
+ /**
484
+ * Returns true when a container's width can be calculated from its children
485
+ * (i.e. it is a horizontal non-stretch stack). False means the container
486
+ * behaves like `flex-grow:1` and should absorb remaining space.
487
+ */
488
+ private containerHasIntrinsicWidth;
489
+ /**
490
+ * Returns the natural (intrinsic) width of any node — component or container.
491
+ * For horizontal non-stretch containers the width is the sum of their children's
492
+ * intrinsic widths plus gaps, capped at `availableWidth`. All other containers
493
+ * are assumed to take the full available width (they stretch or grow).
494
+ */
495
+ private getIntrinsicWidth;
483
496
  private getIntrinsicComponentWidth;
484
497
  private calculateChildHeight;
485
498
  private calculateChildWidth;
package/dist/index.d.ts CHANGED
@@ -290,8 +290,8 @@ interface IRComponentNode {
290
290
  interface IRNodeStyle {
291
291
  padding?: string;
292
292
  gap?: string;
293
- align?: 'left' | 'center' | 'right' | 'justify';
294
- justify?: 'start' | 'center' | 'end';
293
+ justify?: 'start' | 'center' | 'end' | 'stretch' | 'spaceBetween' | 'spaceAround';
294
+ align?: 'start' | 'center' | 'end';
295
295
  background?: string;
296
296
  }
297
297
  interface IRMeta {
@@ -480,6 +480,19 @@ declare class LayoutEngine {
480
480
  private wrapTextToLines;
481
481
  private getIntrinsicComponentHeight;
482
482
  private getControlLabelOffset;
483
+ /**
484
+ * Returns true when a container's width can be calculated from its children
485
+ * (i.e. it is a horizontal non-stretch stack). False means the container
486
+ * behaves like `flex-grow:1` and should absorb remaining space.
487
+ */
488
+ private containerHasIntrinsicWidth;
489
+ /**
490
+ * Returns the natural (intrinsic) width of any node — component or container.
491
+ * For horizontal non-stretch containers the width is the sum of their children's
492
+ * intrinsic widths plus gaps, capped at `availableWidth`. All other containers
493
+ * are assumed to take the full available width (they stretch or grow).
494
+ */
495
+ private getIntrinsicWidth;
483
496
  private getIntrinsicComponentWidth;
484
497
  private calculateChildHeight;
485
498
  private calculateChildWidth;
package/dist/index.js CHANGED
@@ -2174,8 +2174,8 @@ var IRStyleSchema = z.object({
2174
2174
  var IRNodeStyleSchema = z.object({
2175
2175
  padding: z.string().optional(),
2176
2176
  gap: z.string().optional(),
2177
- align: z.enum(["left", "center", "right", "justify"]).optional(),
2178
- justify: z.enum(["start", "center", "end"]).optional(),
2177
+ justify: z.enum(["start", "center", "end", "stretch", "spaceBetween", "spaceAround"]).optional(),
2178
+ align: z.enum(["start", "center", "end"]).optional(),
2179
2179
  background: z.string().optional()
2180
2180
  });
2181
2181
  var IRMetaSchema = z.object({
@@ -2461,6 +2461,9 @@ ${messages}`);
2461
2461
  if (layoutParams.gap !== void 0) {
2462
2462
  style.gap = String(layoutParams.gap);
2463
2463
  }
2464
+ if (layoutParams.justify !== void 0) {
2465
+ style.justify = layoutParams.justify;
2466
+ }
2464
2467
  if (layoutParams.align !== void 0) {
2465
2468
  style.align = layoutParams.align;
2466
2469
  }
@@ -3107,8 +3110,9 @@ var LayoutEngine = class {
3107
3110
  }
3108
3111
  });
3109
3112
  } else {
3110
- const align = node.style.align || "justify";
3111
- if (align === "justify") {
3113
+ const justify = node.style.justify || "stretch";
3114
+ const crossAlign = node.style.align || "start";
3115
+ if (justify === "stretch") {
3112
3116
  let currentX = x;
3113
3117
  const childWidth = this.calculateChildWidth(children.length, width, gap);
3114
3118
  let stackHeight = 0;
@@ -3130,43 +3134,44 @@ var LayoutEngine = class {
3130
3134
  });
3131
3135
  } else {
3132
3136
  const childWidths = [];
3137
+ const childHeights = [];
3133
3138
  const explicitHeightFlags = [];
3134
- const blockButtonIndices = /* @__PURE__ */ new Set();
3139
+ const flexIndices = /* @__PURE__ */ new Set();
3135
3140
  let stackHeight = 0;
3136
3141
  children.forEach((childRef, index) => {
3137
3142
  const childNode = this.nodes[childRef.ref];
3138
- let childWidth = this.getIntrinsicComponentWidth(childNode);
3139
- let childHeight = this.getComponentHeight();
3140
3143
  const hasExplicitHeight = childNode?.kind === "component" && !!childNode.props.height;
3141
3144
  const hasExplicitWidth = childNode?.kind === "component" && !!childNode.props.width;
3142
3145
  const isBlockButton = childNode?.kind === "component" && childNode.componentType === "Button" && !hasExplicitWidth && this.parseBooleanProp(childNode.props.block, false);
3143
- if (isBlockButton) {
3146
+ const isFlexContainer = !hasExplicitWidth && childNode?.kind === "container" && !this.containerHasIntrinsicWidth(childNode);
3147
+ let childWidth;
3148
+ if (isBlockButton || isFlexContainer) {
3144
3149
  childWidth = 0;
3145
- blockButtonIndices.add(index);
3150
+ flexIndices.add(index);
3146
3151
  } else if (hasExplicitWidth) {
3147
3152
  childWidth = Number(childNode.props.width);
3148
- }
3149
- if (hasExplicitHeight) {
3150
- childHeight = Number(childNode.props.height);
3153
+ } else {
3154
+ childWidth = this.getIntrinsicWidth(childNode, width);
3151
3155
  }
3152
3156
  childWidths.push(childWidth);
3157
+ childHeights.push(this.getComponentHeight());
3153
3158
  explicitHeightFlags.push(hasExplicitHeight);
3154
3159
  });
3155
3160
  const totalGapWidth = gap * Math.max(0, children.length - 1);
3156
- if (blockButtonIndices.size > 0) {
3161
+ if (flexIndices.size > 0) {
3157
3162
  const fixedWidth = childWidths.reduce((sum, w, idx) => {
3158
- return blockButtonIndices.has(idx) ? sum : sum + w;
3163
+ return flexIndices.has(idx) ? sum : sum + w;
3159
3164
  }, 0);
3160
3165
  const remainingWidth = width - totalGapWidth - fixedWidth;
3161
- const widthPerBlock = Math.max(1, remainingWidth / blockButtonIndices.size);
3162
- blockButtonIndices.forEach((index) => {
3163
- childWidths[index] = widthPerBlock;
3166
+ const widthPerFlex = Math.max(1, remainingWidth / flexIndices.size);
3167
+ flexIndices.forEach((idx) => {
3168
+ childWidths[idx] = widthPerFlex;
3164
3169
  });
3165
3170
  }
3166
3171
  children.forEach((childRef, index) => {
3167
3172
  const childNode = this.nodes[childRef.ref];
3168
- let childHeight = this.getComponentHeight();
3169
3173
  const childWidth = childWidths[index];
3174
+ let childHeight = this.getComponentHeight();
3170
3175
  if (explicitHeightFlags[index] && childNode?.kind === "component") {
3171
3176
  childHeight = Number(childNode.props.height);
3172
3177
  } else if (childNode?.kind === "container") {
@@ -3174,21 +3179,37 @@ var LayoutEngine = class {
3174
3179
  } else if (childNode?.kind === "component") {
3175
3180
  childHeight = this.getIntrinsicComponentHeight(childNode, childWidth);
3176
3181
  }
3182
+ childHeights[index] = childHeight;
3177
3183
  stackHeight = Math.max(stackHeight, childHeight);
3178
3184
  });
3179
3185
  const totalChildWidth = childWidths.reduce((sum, w) => sum + w, 0);
3180
3186
  const totalContentWidth = totalChildWidth + totalGapWidth;
3181
3187
  let startX = x;
3182
- if (align === "center") {
3188
+ let dynamicGap = gap;
3189
+ if (justify === "center") {
3183
3190
  startX = x + (width - totalContentWidth) / 2;
3184
- } else if (align === "right") {
3191
+ } else if (justify === "end") {
3185
3192
  startX = x + width - totalContentWidth;
3193
+ } else if (justify === "spaceBetween") {
3194
+ startX = x;
3195
+ dynamicGap = children.length > 1 ? (width - totalChildWidth) / (children.length - 1) : 0;
3196
+ } else if (justify === "spaceAround") {
3197
+ const spacing = children.length > 0 ? (width - totalChildWidth) / children.length : 0;
3198
+ startX = x + spacing / 2;
3199
+ dynamicGap = spacing;
3186
3200
  }
3187
3201
  let currentX = startX;
3188
3202
  children.forEach((childRef, index) => {
3189
3203
  const childWidth = childWidths[index];
3190
- this.calculateNode(childRef.ref, currentX, y, childWidth, stackHeight, "stack");
3191
- currentX += childWidth + gap;
3204
+ const childHeight = childHeights[index];
3205
+ let childY = y;
3206
+ if (crossAlign === "center") {
3207
+ childY = y + Math.round((stackHeight - childHeight) / 2);
3208
+ } else if (crossAlign === "end") {
3209
+ childY = y + stackHeight - childHeight;
3210
+ }
3211
+ this.calculateNode(childRef.ref, currentX, childY, childWidth, childHeight, "stack");
3212
+ currentX += childWidth + dynamicGap;
3192
3213
  });
3193
3214
  }
3194
3215
  }
@@ -3711,6 +3732,47 @@ var LayoutEngine = class {
3711
3732
  getControlLabelOffset(label) {
3712
3733
  return label.trim().length > 0 ? 18 : 0;
3713
3734
  }
3735
+ /**
3736
+ * Returns true when a container's width can be calculated from its children
3737
+ * (i.e. it is a horizontal non-stretch stack). False means the container
3738
+ * behaves like `flex-grow:1` and should absorb remaining space.
3739
+ */
3740
+ containerHasIntrinsicWidth(node) {
3741
+ if (node.kind !== "container") return false;
3742
+ return node.containerType === "stack" && String(node.params.direction || "vertical") === "horizontal" && (node.style.justify || "stretch") !== "stretch";
3743
+ }
3744
+ /**
3745
+ * Returns the natural (intrinsic) width of any node — component or container.
3746
+ * For horizontal non-stretch containers the width is the sum of their children's
3747
+ * intrinsic widths plus gaps, capped at `availableWidth`. All other containers
3748
+ * are assumed to take the full available width (they stretch or grow).
3749
+ */
3750
+ getIntrinsicWidth(node, availableWidth) {
3751
+ if (!node) return 120;
3752
+ if (node.kind === "component") {
3753
+ return this.getIntrinsicComponentWidth(node);
3754
+ }
3755
+ if (node.kind === "container") {
3756
+ if (this.containerHasIntrinsicWidth(node)) {
3757
+ const gap = this.resolveSpacing(node.style.gap);
3758
+ const padding = this.resolveSpacing(node.style.padding);
3759
+ const innerAvailable = Math.max(0, availableWidth - padding * 2);
3760
+ const children = node.children ?? [];
3761
+ let total = padding * 2;
3762
+ children.forEach((childRef, idx) => {
3763
+ const child = this.nodes[childRef.ref];
3764
+ total += this.getIntrinsicWidth(child, innerAvailable);
3765
+ if (idx < children.length - 1) total += gap;
3766
+ });
3767
+ return Math.min(total, availableWidth);
3768
+ }
3769
+ return availableWidth;
3770
+ }
3771
+ if (node.kind === "instance") {
3772
+ return availableWidth;
3773
+ }
3774
+ return 120;
3775
+ }
3714
3776
  getIntrinsicComponentWidth(node) {
3715
3777
  if (!node || node.kind !== "component") {
3716
3778
  return 120;
@@ -5571,7 +5633,8 @@ var SVGRenderer = class {
5571
5633
  const fontSize = this.tokens.text.fontSize;
5572
5634
  const lineHeightPx = Math.ceil(fontSize * this.tokens.text.lineHeight);
5573
5635
  const lines = this.wrapTextToLines(text, pos.width, fontSize);
5574
- const firstLineY = pos.y + fontSize;
5636
+ const totalTextHeight = lines.length * lineHeightPx;
5637
+ const firstLineY = pos.y + Math.round(Math.max(0, (pos.height - totalTextHeight) / 2)) + fontSize;
5575
5638
  const tspans = lines.map(
5576
5639
  (line, index) => `<tspan x="${pos.x}" dy="${index === 0 ? 0 : lineHeightPx}">${this.escapeXml(line)}</tspan>`
5577
5640
  ).join("");
@@ -5584,8 +5647,9 @@ var SVGRenderer = class {
5584
5647
  }
5585
5648
  renderLabel(node, pos) {
5586
5649
  const text = String(node.props.text || "Label");
5650
+ const textY = pos.y + Math.round(pos.height / 2) + 4;
5587
5651
  return `<g${this.getDataNodeId(node)}>
5588
- <text x="${pos.x}" y="${pos.y + 12}"
5652
+ <text x="${pos.x}" y="${textY}"
5589
5653
  font-family="Arial, Helvetica, sans-serif"
5590
5654
  font-size="12"
5591
5655
  fill="${this.renderTheme.textMuted}">${this.escapeXml(text)}</text>
@@ -6722,8 +6786,8 @@ var SVGRenderer = class {
6722
6786
  return false;
6723
6787
  }
6724
6788
  const direction = String(parent.params.direction || "vertical");
6725
- const align = parent.style.align || "justify";
6726
- return direction === "horizontal" && align === "justify";
6789
+ const justify = parent.style.justify || "stretch";
6790
+ return direction === "horizontal" && justify === "stretch";
6727
6791
  }
6728
6792
  buildParentContainerIndex() {
6729
6793
  this.parentContainerByChildId.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wire-dsl/engine",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "WireDSL engine - Parser, IR generator, layout engine, and SVG renderer (browser-safe, pure JS/TS)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "chevrotain": "11.1.1",
34
34
  "zod": "4.3.6",
35
- "@wire-dsl/language-support": "0.5.0"
35
+ "@wire-dsl/language-support": "0.6.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "25.2.0",