@wire-dsl/engine 0.3.0 → 0.4.1
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 +555 -151
- package/dist/index.d.cts +54 -3
- package/dist/index.d.ts +54 -3
- package/dist/index.js +555 -151
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1647,7 +1647,8 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1647
1647
|
if (enumValues) {
|
|
1648
1648
|
const normalizedValue = String(propValue);
|
|
1649
1649
|
const isCustomVariantFromColors = propName === "variant" && !enumValues.includes(normalizedValue) && Object.prototype.hasOwnProperty.call(ast.colors || {}, normalizedValue);
|
|
1650
|
-
|
|
1650
|
+
const isPropReference = normalizedValue.startsWith("prop_");
|
|
1651
|
+
if (!enumValues.includes(normalizedValue) && !isCustomVariantFromColors && !isPropReference) {
|
|
1651
1652
|
emitWarning(
|
|
1652
1653
|
`Invalid value "${normalizedValue}" for property "${propName}" in component "${componentType}".`,
|
|
1653
1654
|
"COMPONENT_INVALID_PROPERTY_VALUE",
|
|
@@ -1757,7 +1758,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1757
1758
|
const enumValues = rules.enumParams?.[paramName];
|
|
1758
1759
|
if (enumValues) {
|
|
1759
1760
|
const normalizedValue = String(paramValue);
|
|
1760
|
-
if (!enumValues.includes(normalizedValue)) {
|
|
1761
|
+
if (!enumValues.includes(normalizedValue) && !normalizedValue.startsWith("prop_")) {
|
|
1761
1762
|
emitWarning(
|
|
1762
1763
|
`Invalid value "${normalizedValue}" for parameter "${paramName}" in layout "${layout.layoutType}".`,
|
|
1763
1764
|
"LAYOUT_INVALID_PARAMETER_VALUE",
|
|
@@ -2198,7 +2199,21 @@ var IRComponentNodeSchema = z.object({
|
|
|
2198
2199
|
style: IRNodeStyleSchema,
|
|
2199
2200
|
meta: IRMetaSchema
|
|
2200
2201
|
});
|
|
2201
|
-
var
|
|
2202
|
+
var IRInstanceNodeSchema = z.object({
|
|
2203
|
+
id: z.string(),
|
|
2204
|
+
kind: z.literal("instance"),
|
|
2205
|
+
definitionName: z.string(),
|
|
2206
|
+
definitionKind: z.enum(["component", "layout"]),
|
|
2207
|
+
invocationProps: z.record(z.string(), z.union([z.string(), z.number()])),
|
|
2208
|
+
expandedRoot: z.object({ ref: z.string() }),
|
|
2209
|
+
style: IRNodeStyleSchema,
|
|
2210
|
+
meta: IRMetaSchema
|
|
2211
|
+
});
|
|
2212
|
+
var IRNodeSchema = z.discriminatedUnion("kind", [
|
|
2213
|
+
IRContainerNodeSchema,
|
|
2214
|
+
IRComponentNodeSchema,
|
|
2215
|
+
IRInstanceNodeSchema
|
|
2216
|
+
]);
|
|
2202
2217
|
var IRScreenSchema = z.object({
|
|
2203
2218
|
id: z.string(),
|
|
2204
2219
|
name: z.string(),
|
|
@@ -2421,7 +2436,7 @@ ${messages}`);
|
|
|
2421
2436
|
const layoutChildren = layout.children;
|
|
2422
2437
|
const layoutDefinition = this.definedLayouts.get(layout.layoutType);
|
|
2423
2438
|
if (layoutDefinition) {
|
|
2424
|
-
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
|
|
2439
|
+
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context, layout._meta?.nodeId);
|
|
2425
2440
|
}
|
|
2426
2441
|
const nodeId = this.idGen.generate("node");
|
|
2427
2442
|
const childRefs = [];
|
|
@@ -2460,8 +2475,8 @@ ${messages}`);
|
|
|
2460
2475
|
children: childRefs,
|
|
2461
2476
|
style,
|
|
2462
2477
|
meta: {
|
|
2463
|
-
nodeId
|
|
2464
|
-
|
|
2478
|
+
// Scope nodeId per instance so each expansion gets a unique identifier
|
|
2479
|
+
nodeId: context?.instanceScope ? `${layout._meta?.nodeId}@${context.instanceScope}` : layout._meta?.nodeId
|
|
2465
2480
|
}
|
|
2466
2481
|
};
|
|
2467
2482
|
this.nodes[nodeId] = containerNode;
|
|
@@ -2490,8 +2505,7 @@ ${messages}`);
|
|
|
2490
2505
|
// Cells have no padding by default - grid gap handles spacing
|
|
2491
2506
|
meta: {
|
|
2492
2507
|
source: "cell",
|
|
2493
|
-
nodeId: cell._meta?.nodeId
|
|
2494
|
-
// Pass SourceMap nodeId from AST
|
|
2508
|
+
nodeId: context?.instanceScope ? `${cell._meta?.nodeId}@${context.instanceScope}` : cell._meta?.nodeId
|
|
2495
2509
|
}
|
|
2496
2510
|
};
|
|
2497
2511
|
this.nodes[nodeId] = containerNode;
|
|
@@ -2518,7 +2532,7 @@ ${messages}`);
|
|
|
2518
2532
|
const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
|
|
2519
2533
|
const definition = this.definedComponents.get(component.componentType);
|
|
2520
2534
|
if (definition) {
|
|
2521
|
-
return this.expandDefinedComponent(definition, resolvedProps, context);
|
|
2535
|
+
return this.expandDefinedComponent(definition, resolvedProps, component._meta?.nodeId, context);
|
|
2522
2536
|
}
|
|
2523
2537
|
const builtInComponents = /* @__PURE__ */ new Set([
|
|
2524
2538
|
"Button",
|
|
@@ -2564,35 +2578,49 @@ ${messages}`);
|
|
|
2564
2578
|
props: resolvedProps,
|
|
2565
2579
|
style: {},
|
|
2566
2580
|
meta: {
|
|
2567
|
-
nodeId
|
|
2568
|
-
|
|
2581
|
+
// Scope nodeId per instance so each expansion gets a unique identifier
|
|
2582
|
+
nodeId: context?.instanceScope ? `${component._meta?.nodeId}@${context.instanceScope}` : component._meta?.nodeId
|
|
2569
2583
|
}
|
|
2570
2584
|
};
|
|
2571
2585
|
this.nodes[nodeId] = componentNode;
|
|
2572
2586
|
return nodeId;
|
|
2573
2587
|
}
|
|
2574
|
-
expandDefinedComponent(definition, invocationArgs, parentContext) {
|
|
2588
|
+
expandDefinedComponent(definition, invocationArgs, callSiteNodeId, parentContext) {
|
|
2575
2589
|
const context = {
|
|
2576
2590
|
args: invocationArgs,
|
|
2577
2591
|
providedArgNames: new Set(Object.keys(invocationArgs)),
|
|
2578
2592
|
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2579
2593
|
definitionName: definition.name,
|
|
2580
2594
|
definitionKind: "component",
|
|
2581
|
-
allowChildrenSlot: false
|
|
2595
|
+
allowChildrenSlot: false,
|
|
2596
|
+
// Scope internal nodeIds using the call-site nodeId
|
|
2597
|
+
instanceScope: callSiteNodeId
|
|
2582
2598
|
};
|
|
2599
|
+
let expandedRootId = null;
|
|
2583
2600
|
if (definition.body.type === "layout") {
|
|
2584
|
-
|
|
2585
|
-
this.reportUnusedArguments(context);
|
|
2586
|
-
return result;
|
|
2601
|
+
expandedRootId = this.convertLayout(definition.body, context);
|
|
2587
2602
|
} else if (definition.body.type === "component") {
|
|
2588
|
-
|
|
2589
|
-
this.reportUnusedArguments(context);
|
|
2590
|
-
return result;
|
|
2603
|
+
expandedRootId = this.convertComponent(definition.body, context);
|
|
2591
2604
|
} else {
|
|
2592
2605
|
throw new Error(`Invalid defined component body type for "${definition.name}"`);
|
|
2593
2606
|
}
|
|
2607
|
+
this.reportUnusedArguments(context);
|
|
2608
|
+
if (!expandedRootId) return null;
|
|
2609
|
+
const instanceNodeId = this.idGen.generate("node");
|
|
2610
|
+
const instanceNode = {
|
|
2611
|
+
id: instanceNodeId,
|
|
2612
|
+
kind: "instance",
|
|
2613
|
+
definitionName: definition.name,
|
|
2614
|
+
definitionKind: "component",
|
|
2615
|
+
invocationProps: invocationArgs,
|
|
2616
|
+
expandedRoot: { ref: expandedRootId },
|
|
2617
|
+
style: {},
|
|
2618
|
+
meta: { nodeId: callSiteNodeId }
|
|
2619
|
+
};
|
|
2620
|
+
this.nodes[instanceNodeId] = instanceNode;
|
|
2621
|
+
return instanceNodeId;
|
|
2594
2622
|
}
|
|
2595
|
-
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
|
|
2623
|
+
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext, callSiteNodeId) {
|
|
2596
2624
|
if (invocationChildren.length !== 1) {
|
|
2597
2625
|
this.errors.push({
|
|
2598
2626
|
type: "layout-children-arity",
|
|
@@ -2608,11 +2636,26 @@ ${messages}`);
|
|
|
2608
2636
|
definitionName: definition.name,
|
|
2609
2637
|
definitionKind: "layout",
|
|
2610
2638
|
allowChildrenSlot: true,
|
|
2611
|
-
childrenSlot: resolvedSlot
|
|
2639
|
+
childrenSlot: resolvedSlot,
|
|
2640
|
+
// Scope internal nodeIds using the call-site nodeId
|
|
2641
|
+
instanceScope: callSiteNodeId
|
|
2612
2642
|
};
|
|
2613
|
-
const
|
|
2643
|
+
const expandedRootId = this.convertLayout(definition.body, context);
|
|
2614
2644
|
this.reportUnusedArguments(context);
|
|
2615
|
-
return
|
|
2645
|
+
if (!callSiteNodeId) return expandedRootId;
|
|
2646
|
+
const instanceNodeId = this.idGen.generate("node");
|
|
2647
|
+
const instanceNode = {
|
|
2648
|
+
id: instanceNodeId,
|
|
2649
|
+
kind: "instance",
|
|
2650
|
+
definitionName: definition.name,
|
|
2651
|
+
definitionKind: "layout",
|
|
2652
|
+
invocationProps: invocationParams,
|
|
2653
|
+
expandedRoot: { ref: expandedRootId },
|
|
2654
|
+
style: {},
|
|
2655
|
+
meta: { nodeId: callSiteNodeId }
|
|
2656
|
+
};
|
|
2657
|
+
this.nodes[instanceNodeId] = instanceNode;
|
|
2658
|
+
return instanceNodeId;
|
|
2616
2659
|
}
|
|
2617
2660
|
resolveChildrenSlot(slot, parentContext) {
|
|
2618
2661
|
if (slot.type === "component" && slot.componentType === "Children") {
|
|
@@ -2643,6 +2686,20 @@ ${messages}`);
|
|
|
2643
2686
|
key
|
|
2644
2687
|
);
|
|
2645
2688
|
if (resolvedValue !== void 0) {
|
|
2689
|
+
const wasPropReference = typeof value === "string" && value.startsWith("prop_");
|
|
2690
|
+
if (wasPropReference) {
|
|
2691
|
+
const layoutMetadata = LAYOUTS2[layoutType];
|
|
2692
|
+
const property = layoutMetadata?.properties?.[key];
|
|
2693
|
+
if (property?.type === "enum" && Array.isArray(property.options)) {
|
|
2694
|
+
const normalizedValue = String(resolvedValue);
|
|
2695
|
+
if (!property.options.includes(normalizedValue)) {
|
|
2696
|
+
this.warnings.push({
|
|
2697
|
+
type: "invalid-bound-enum-value",
|
|
2698
|
+
message: `Invalid value "${normalizedValue}" for parameter "${key}" in layout "${layoutType}". Expected one of: ${property.options.join(", ")}.`
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2646
2703
|
resolved[key] = resolvedValue;
|
|
2647
2704
|
}
|
|
2648
2705
|
}
|
|
@@ -2707,6 +2764,20 @@ ${messages}`);
|
|
|
2707
2764
|
key
|
|
2708
2765
|
);
|
|
2709
2766
|
if (resolvedValue !== void 0) {
|
|
2767
|
+
const wasPropReference = typeof value === "string" && value.startsWith("prop_");
|
|
2768
|
+
if (wasPropReference) {
|
|
2769
|
+
const metadata = COMPONENTS2[componentType];
|
|
2770
|
+
const property = metadata?.properties?.[key];
|
|
2771
|
+
if (property?.type === "enum" && Array.isArray(property.options)) {
|
|
2772
|
+
const normalizedValue = String(resolvedValue);
|
|
2773
|
+
if (!property.options.includes(normalizedValue)) {
|
|
2774
|
+
this.warnings.push({
|
|
2775
|
+
type: "invalid-bound-enum-value",
|
|
2776
|
+
message: `Invalid value "${normalizedValue}" for property "${key}" in component "${componentType}". Expected one of: ${property.options.join(", ")}.`
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2710
2781
|
resolved[key] = resolvedValue;
|
|
2711
2782
|
}
|
|
2712
2783
|
}
|
|
@@ -2950,6 +3021,8 @@ var LayoutEngine = class {
|
|
|
2950
3021
|
}
|
|
2951
3022
|
if (node.kind === "container") {
|
|
2952
3023
|
this.calculateContainer(node, nodeId, x, y, width, height);
|
|
3024
|
+
} else if (node.kind === "instance") {
|
|
3025
|
+
this.calculateInstance(node, nodeId, x, y, width, height, parentContainerType);
|
|
2953
3026
|
} else {
|
|
2954
3027
|
this.calculateComponent(node, nodeId, x, y, width, height);
|
|
2955
3028
|
}
|
|
@@ -2982,7 +3055,7 @@ var LayoutEngine = class {
|
|
|
2982
3055
|
this.calculateCard(node, innerX, innerY, innerWidth, innerHeight);
|
|
2983
3056
|
break;
|
|
2984
3057
|
}
|
|
2985
|
-
if (isVerticalStack || node.containerType === "card") {
|
|
3058
|
+
if ((isVerticalStack || node.containerType === "card") && node.children.length > 0) {
|
|
2986
3059
|
let containerMaxY = y;
|
|
2987
3060
|
node.children.forEach((childRef) => {
|
|
2988
3061
|
const childPos = this.result[childRef.ref];
|
|
@@ -3125,6 +3198,10 @@ var LayoutEngine = class {
|
|
|
3125
3198
|
const gap = this.resolveSpacing(node.style.gap);
|
|
3126
3199
|
const padding = this.resolveSpacing(node.style.padding);
|
|
3127
3200
|
let totalHeight = padding * 2;
|
|
3201
|
+
const EMPTY_CONTAINER_MIN_HEIGHT = 40;
|
|
3202
|
+
if (node.children.length === 0) {
|
|
3203
|
+
return Math.max(totalHeight, EMPTY_CONTAINER_MIN_HEIGHT);
|
|
3204
|
+
}
|
|
3128
3205
|
if (node.containerType === "grid") {
|
|
3129
3206
|
const columns = Number(node.params.columns) || 12;
|
|
3130
3207
|
const colWidth = (availableWidth - gap * (columns - 1)) / columns;
|
|
@@ -3377,6 +3454,20 @@ var LayoutEngine = class {
|
|
|
3377
3454
|
}
|
|
3378
3455
|
});
|
|
3379
3456
|
}
|
|
3457
|
+
/**
|
|
3458
|
+
* Calculate layout for an instance node.
|
|
3459
|
+
* The instance is a transparent wrapper — its bounding box equals the
|
|
3460
|
+
* expanded root's bounding box. We calculate the expanded root first and
|
|
3461
|
+
* then copy its position to the instance nodeId so the renderer can use it.
|
|
3462
|
+
*/
|
|
3463
|
+
calculateInstance(node, nodeId, x, y, width, height, parentContainerType) {
|
|
3464
|
+
const expandedRootId = node.expandedRoot.ref;
|
|
3465
|
+
this.calculateNode(expandedRootId, x, y, width, height, parentContainerType);
|
|
3466
|
+
const expandedPos = this.result[expandedRootId];
|
|
3467
|
+
if (expandedPos) {
|
|
3468
|
+
this.result[nodeId] = { ...expandedPos };
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3380
3471
|
calculateComponent(node, nodeId, x, y, width, height) {
|
|
3381
3472
|
if (node.kind !== "component") return;
|
|
3382
3473
|
const componentWidth = Number(node.props.width) || width;
|
|
@@ -4517,14 +4608,14 @@ var THEMES = {
|
|
|
4517
4608
|
primaryLight: "#EFF6FF"
|
|
4518
4609
|
},
|
|
4519
4610
|
dark: {
|
|
4520
|
-
bg: "#
|
|
4521
|
-
cardBg: "#
|
|
4522
|
-
border: "#
|
|
4523
|
-
text: "#
|
|
4524
|
-
textMuted: "#
|
|
4611
|
+
bg: "#111111",
|
|
4612
|
+
cardBg: "#1C1C1C",
|
|
4613
|
+
border: "#303030",
|
|
4614
|
+
text: "#F0F0F0",
|
|
4615
|
+
textMuted: "#808080",
|
|
4525
4616
|
primary: "#60A5FA",
|
|
4526
4617
|
primaryHover: "#3B82F6",
|
|
4527
|
-
primaryLight: "#
|
|
4618
|
+
primaryLight: "#1C2A3A"
|
|
4528
4619
|
}
|
|
4529
4620
|
};
|
|
4530
4621
|
var SVGRenderer = class {
|
|
@@ -4542,7 +4633,8 @@ var SVGRenderer = class {
|
|
|
4542
4633
|
height: options?.height || 720,
|
|
4543
4634
|
theme: colorScheme,
|
|
4544
4635
|
includeLabels: options?.includeLabels ?? true,
|
|
4545
|
-
screenName: options?.screenName
|
|
4636
|
+
screenName: options?.screenName,
|
|
4637
|
+
showDiagnostics: options?.showDiagnostics ?? false
|
|
4546
4638
|
};
|
|
4547
4639
|
this.colorResolver = new ColorResolver();
|
|
4548
4640
|
this.buildParentContainerIndex();
|
|
@@ -4642,13 +4734,30 @@ var SVGRenderer = class {
|
|
|
4642
4734
|
if (node.containerType === "split") {
|
|
4643
4735
|
this.renderSplitDecoration(node, pos, containerGroup);
|
|
4644
4736
|
}
|
|
4645
|
-
node.children.
|
|
4646
|
-
this.
|
|
4647
|
-
}
|
|
4737
|
+
if (node.children.length === 0 && this.options.showDiagnostics) {
|
|
4738
|
+
containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
|
|
4739
|
+
} else {
|
|
4740
|
+
node.children.forEach((childRef) => {
|
|
4741
|
+
this.renderNode(childRef.ref, containerGroup);
|
|
4742
|
+
});
|
|
4743
|
+
}
|
|
4648
4744
|
if (hasNodeId) {
|
|
4649
4745
|
containerGroup.push("</g>");
|
|
4650
4746
|
}
|
|
4651
4747
|
output.push(...containerGroup);
|
|
4748
|
+
} else if (node.kind === "instance") {
|
|
4749
|
+
const instanceGroup = [];
|
|
4750
|
+
if (node.meta.nodeId) {
|
|
4751
|
+
instanceGroup.push(`<g data-node-id="${node.meta.nodeId}">`);
|
|
4752
|
+
}
|
|
4753
|
+
instanceGroup.push(
|
|
4754
|
+
`<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="transparent" stroke="none" pointer-events="all"/>`
|
|
4755
|
+
);
|
|
4756
|
+
this.renderNode(node.expandedRoot.ref, instanceGroup);
|
|
4757
|
+
if (node.meta.nodeId) {
|
|
4758
|
+
instanceGroup.push("</g>");
|
|
4759
|
+
}
|
|
4760
|
+
output.push(...instanceGroup);
|
|
4652
4761
|
} else if (node.kind === "component") {
|
|
4653
4762
|
const componentSvg = this.renderComponent(node, pos);
|
|
4654
4763
|
if (componentSvg) {
|
|
@@ -4765,6 +4874,8 @@ var SVGRenderer = class {
|
|
|
4765
4874
|
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
4766
4875
|
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
4767
4876
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
4877
|
+
const iconName = String(node.props.icon || "").trim();
|
|
4878
|
+
const iconAlign = String(node.props.iconAlign || "left").toLowerCase();
|
|
4768
4879
|
const radius = this.tokens.button.radius;
|
|
4769
4880
|
const fontSize = this.tokens.button.fontSize;
|
|
4770
4881
|
const fontWeight = this.tokens.button.fontWeight;
|
|
@@ -4772,30 +4883,62 @@ var SVGRenderer = class {
|
|
|
4772
4883
|
const controlHeight = resolveActionControlHeight(size, density);
|
|
4773
4884
|
const buttonY = pos.y + labelOffset;
|
|
4774
4885
|
const buttonHeight = Math.max(16, Math.min(controlHeight, pos.height - labelOffset));
|
|
4886
|
+
const iconSvg = iconName ? getIcon(iconName) : null;
|
|
4887
|
+
const iconSize = iconSvg ? Math.round(fontSize * 1.1) : 0;
|
|
4888
|
+
const iconGap = iconSvg ? 8 : 0;
|
|
4889
|
+
const edgePad = 12;
|
|
4890
|
+
const textPad = paddingX + extraPadding;
|
|
4775
4891
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4776
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (
|
|
4777
|
-
const availableTextWidth = Math.max(0, buttonWidth - (
|
|
4892
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (iconSvg ? iconSize + iconGap : 0) + textPad * 2), 60), pos.width);
|
|
4893
|
+
const availableTextWidth = Math.max(0, buttonWidth - textPad * 2 - (iconSvg ? iconSize + iconGap : 0));
|
|
4778
4894
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4779
4895
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
4780
4896
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
4781
4897
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4782
|
-
const
|
|
4898
|
+
const isDarkMode = this.options.theme === "dark";
|
|
4899
|
+
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : isDarkMode ? "rgba(48, 48, 55, 0.9)" : "rgba(226, 232, 240, 0.9)";
|
|
4783
4900
|
const textColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.85);
|
|
4784
|
-
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
4785
|
-
|
|
4901
|
+
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : isDarkMode ? "rgba(75, 75, 88, 0.8)" : "rgba(100, 116, 139, 0.4)";
|
|
4902
|
+
const iconOffsetY = buttonY + (buttonHeight - iconSize) / 2;
|
|
4903
|
+
const iconX = iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize : pos.x + edgePad;
|
|
4904
|
+
const textAlign = String(node.props.align || "center").toLowerCase();
|
|
4905
|
+
const sidePad = textPad + 4;
|
|
4906
|
+
let textX;
|
|
4907
|
+
let textAnchor;
|
|
4908
|
+
if (textAlign === "left") {
|
|
4909
|
+
textX = iconSvg && iconAlign === "left" ? pos.x + edgePad + iconSize + iconGap : pos.x + sidePad;
|
|
4910
|
+
textAnchor = "start";
|
|
4911
|
+
} else if (textAlign === "right") {
|
|
4912
|
+
textX = iconSvg && iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize - iconGap : pos.x + buttonWidth - sidePad;
|
|
4913
|
+
textAnchor = "end";
|
|
4914
|
+
} else {
|
|
4915
|
+
textX = pos.x + buttonWidth / 2;
|
|
4916
|
+
textAnchor = "middle";
|
|
4917
|
+
}
|
|
4918
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
4786
4919
|
<rect x="${pos.x}" y="${buttonY}"
|
|
4787
4920
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4788
4921
|
rx="${radius}"
|
|
4789
4922
|
fill="${bgColor}"
|
|
4790
4923
|
stroke="${borderColor}"
|
|
4791
|
-
stroke-width="1"
|
|
4792
|
-
|
|
4924
|
+
stroke-width="1"/>`;
|
|
4925
|
+
if (iconSvg) {
|
|
4926
|
+
svg += `
|
|
4927
|
+
<g transform="translate(${iconX}, ${iconOffsetY})">
|
|
4928
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${textColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
4929
|
+
${this.extractSvgContent(iconSvg)}
|
|
4930
|
+
</svg>
|
|
4931
|
+
</g>`;
|
|
4932
|
+
}
|
|
4933
|
+
svg += `
|
|
4934
|
+
<text x="${textX}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
4793
4935
|
font-family="Arial, Helvetica, sans-serif"
|
|
4794
4936
|
font-size="${fontSize}"
|
|
4795
4937
|
font-weight="${fontWeight}"
|
|
4796
4938
|
fill="${textColor}"
|
|
4797
|
-
text-anchor="
|
|
4939
|
+
text-anchor="${textAnchor}">${this.escapeXml(visibleText)}</text>
|
|
4798
4940
|
</g>`;
|
|
4941
|
+
return svg;
|
|
4799
4942
|
}
|
|
4800
4943
|
renderLink(node, pos) {
|
|
4801
4944
|
const text = String(node.props.text || "Link");
|
|
@@ -4836,28 +4979,66 @@ var SVGRenderer = class {
|
|
|
4836
4979
|
renderInput(node, pos) {
|
|
4837
4980
|
const label = String(node.props.label || "");
|
|
4838
4981
|
const placeholder = String(node.props.placeholder || "");
|
|
4982
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
4983
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
4839
4984
|
const radius = this.tokens.input.radius;
|
|
4840
4985
|
const fontSize = this.tokens.input.fontSize;
|
|
4841
4986
|
const paddingX = this.tokens.input.paddingX;
|
|
4842
4987
|
const labelOffset = this.getControlLabelOffset(label);
|
|
4843
4988
|
const controlY = pos.y + labelOffset;
|
|
4844
4989
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
4845
|
-
|
|
4846
|
-
|
|
4990
|
+
const iconSize = 16;
|
|
4991
|
+
const iconPad = 12;
|
|
4992
|
+
const iconInnerGap = 8;
|
|
4993
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
4994
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
4995
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
4996
|
+
const rightOffset = iconRightSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
4997
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : paddingX);
|
|
4998
|
+
const iconColor = this.hexToRgba(this.resolveMutedColor(), 0.8);
|
|
4999
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
5000
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5001
|
+
if (label) {
|
|
5002
|
+
svg += `
|
|
5003
|
+
<text x="${pos.x + paddingX}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4847
5004
|
font-family="Arial, Helvetica, sans-serif"
|
|
4848
5005
|
font-size="12"
|
|
4849
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
5006
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
5007
|
+
}
|
|
5008
|
+
svg += `
|
|
4850
5009
|
<rect x="${pos.x}" y="${controlY}"
|
|
4851
5010
|
width="${pos.width}" height="${controlHeight}"
|
|
4852
5011
|
rx="${radius}"
|
|
4853
5012
|
fill="${this.renderTheme.cardBg}"
|
|
4854
5013
|
stroke="${this.renderTheme.border}"
|
|
4855
|
-
stroke-width="1"
|
|
4856
|
-
|
|
5014
|
+
stroke-width="1"/>`;
|
|
5015
|
+
if (iconLeftSvg) {
|
|
5016
|
+
svg += `
|
|
5017
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
5018
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5019
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
5020
|
+
</svg>
|
|
5021
|
+
</g>`;
|
|
5022
|
+
}
|
|
5023
|
+
if (iconRightSvg) {
|
|
5024
|
+
svg += `
|
|
5025
|
+
<g transform="translate(${pos.x + pos.width - iconPad - iconSize}, ${iconCenterY})">
|
|
5026
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5027
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
5028
|
+
</svg>
|
|
5029
|
+
</g>`;
|
|
5030
|
+
}
|
|
5031
|
+
if (placeholder) {
|
|
5032
|
+
const availPlaceholderWidth = pos.width - (iconLeftSvg ? leftOffset : paddingX) - (iconRightSvg ? rightOffset : paddingX);
|
|
5033
|
+
const visiblePlaceholder = this.truncateTextToWidth(placeholder, Math.max(0, availPlaceholderWidth), fontSize);
|
|
5034
|
+
svg += `
|
|
5035
|
+
<text x="${textX}" y="${controlY + controlHeight / 2 + 5}"
|
|
4857
5036
|
font-family="Arial, Helvetica, sans-serif"
|
|
4858
5037
|
font-size="${fontSize}"
|
|
4859
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
4860
|
-
|
|
5038
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePlaceholder)}</text>`;
|
|
5039
|
+
}
|
|
5040
|
+
svg += "\n </g>";
|
|
5041
|
+
return svg;
|
|
4861
5042
|
}
|
|
4862
5043
|
renderTopbar(node, pos) {
|
|
4863
5044
|
const title = String(node.props.title || "App");
|
|
@@ -4867,7 +5048,7 @@ var SVGRenderer = class {
|
|
|
4867
5048
|
const variant = String(node.props.variant || "default");
|
|
4868
5049
|
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
4869
5050
|
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
4870
|
-
const showBackground = this.parseBooleanProp(node.props.background
|
|
5051
|
+
const showBackground = this.parseBooleanProp(node.props.background, false);
|
|
4871
5052
|
const radiusMap = {
|
|
4872
5053
|
none: 0,
|
|
4873
5054
|
sm: 4,
|
|
@@ -5006,6 +5187,21 @@ var SVGRenderer = class {
|
|
|
5006
5187
|
</g>`;
|
|
5007
5188
|
output.push(svg);
|
|
5008
5189
|
}
|
|
5190
|
+
/**
|
|
5191
|
+
* Renders a yellow warning placeholder for containers with no children.
|
|
5192
|
+
* Only shown when `showDiagnostics` is enabled (editor/canvas mode).
|
|
5193
|
+
*/
|
|
5194
|
+
renderEmptyContainerDiagnostic(pos, containerType) {
|
|
5195
|
+
const diagColor = "#F59E0B";
|
|
5196
|
+
const diagBg = "#FFFBEB";
|
|
5197
|
+
const diagText = "#92400E";
|
|
5198
|
+
const minHeight = 40;
|
|
5199
|
+
const h = Math.max(pos.height, minHeight);
|
|
5200
|
+
const cx = pos.x + pos.width / 2;
|
|
5201
|
+
const cy = pos.y + h / 2;
|
|
5202
|
+
const label = containerType ? `Empty ${containerType}` : "Empty layout";
|
|
5203
|
+
return `<g><rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${h}" rx="4" fill="${diagBg}" stroke="${diagColor}" stroke-width="1" stroke-dasharray="6 3"/><text x="${cx}" y="${cy}" font-family="Arial, Helvetica, sans-serif" font-size="12" fill="${diagText}" text-anchor="middle" dominant-baseline="middle">${label}</text></g>`;
|
|
5204
|
+
}
|
|
5009
5205
|
renderSplitDecoration(node, pos, output) {
|
|
5010
5206
|
if (node.kind !== "container") return;
|
|
5011
5207
|
const gap = this.resolveSpacing(node.style.gap);
|
|
@@ -5054,7 +5250,7 @@ var SVGRenderer = class {
|
|
|
5054
5250
|
const hasCaption = caption.length > 0;
|
|
5055
5251
|
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
5056
5252
|
const showOuterBackground = this.parseBooleanProp(
|
|
5057
|
-
node.props.background
|
|
5253
|
+
node.props.background,
|
|
5058
5254
|
false
|
|
5059
5255
|
);
|
|
5060
5256
|
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
@@ -5437,30 +5633,66 @@ var SVGRenderer = class {
|
|
|
5437
5633
|
renderSelect(node, pos) {
|
|
5438
5634
|
const label = String(node.props.label || "");
|
|
5439
5635
|
const placeholder = String(node.props.placeholder || "Select...");
|
|
5636
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
5637
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
5440
5638
|
const labelOffset = this.getControlLabelOffset(label);
|
|
5441
5639
|
const controlY = pos.y + labelOffset;
|
|
5442
5640
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
5443
5641
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
font-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5642
|
+
const iconSize = 16;
|
|
5643
|
+
const iconPad = 12;
|
|
5644
|
+
const iconInnerGap = 8;
|
|
5645
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
5646
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
5647
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
5648
|
+
const chevronWidth = 20;
|
|
5649
|
+
const iconColor = this.hexToRgba(this.resolveMutedColor(), 0.8);
|
|
5650
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
5651
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5652
|
+
if (label) {
|
|
5653
|
+
svg += `
|
|
5654
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
5655
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5656
|
+
font-size="12"
|
|
5657
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
5658
|
+
}
|
|
5659
|
+
svg += `
|
|
5660
|
+
<rect x="${pos.x}" y="${controlY}"
|
|
5661
|
+
width="${pos.width}" height="${controlHeight}"
|
|
5662
|
+
rx="6"
|
|
5663
|
+
fill="${this.renderTheme.cardBg}"
|
|
5664
|
+
stroke="${this.renderTheme.border}"
|
|
5665
|
+
stroke-width="1"/>`;
|
|
5666
|
+
if (iconLeftSvg) {
|
|
5667
|
+
svg += `
|
|
5668
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
5669
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5670
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
5671
|
+
</svg>
|
|
5672
|
+
</g>`;
|
|
5673
|
+
}
|
|
5674
|
+
if (iconRightSvg) {
|
|
5675
|
+
svg += `
|
|
5676
|
+
<g transform="translate(${pos.x + pos.width - chevronWidth - iconPad - iconSize}, ${iconCenterY})">
|
|
5677
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5678
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
5679
|
+
</svg>
|
|
5680
|
+
</g>`;
|
|
5681
|
+
}
|
|
5682
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : 12);
|
|
5683
|
+
const availPlaceholderWidth = pos.width - (iconLeftSvg ? leftOffset : 12) - chevronWidth - (iconRightSvg ? iconPad + iconSize + iconInnerGap : 0);
|
|
5684
|
+
const visiblePlaceholder = this.truncateTextToWidth(placeholder, Math.max(0, availPlaceholderWidth), 14);
|
|
5685
|
+
svg += `
|
|
5686
|
+
<text x="${textX}" y="${centerY}"
|
|
5687
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5688
|
+
font-size="14"
|
|
5689
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePlaceholder)}</text>
|
|
5690
|
+
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
5691
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5692
|
+
font-size="16"
|
|
5462
5693
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
5463
5694
|
</g>`;
|
|
5695
|
+
return svg;
|
|
5464
5696
|
}
|
|
5465
5697
|
renderCheckbox(node, pos) {
|
|
5466
5698
|
const label = String(node.props.label || "Checkbox");
|
|
@@ -5893,7 +6125,9 @@ var SVGRenderer = class {
|
|
|
5893
6125
|
renderImage(node, pos) {
|
|
5894
6126
|
const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
|
|
5895
6127
|
const placeholderIcon = String(node.props.icon || "").trim();
|
|
6128
|
+
const variant = String(node.props.variant || "").trim();
|
|
5896
6129
|
const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
|
|
6130
|
+
const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
|
|
5897
6131
|
const aspectRatios = {
|
|
5898
6132
|
landscape: 16 / 9,
|
|
5899
6133
|
portrait: 2 / 3,
|
|
@@ -5911,30 +6145,29 @@ var SVGRenderer = class {
|
|
|
5911
6145
|
}
|
|
5912
6146
|
const offsetX = pos.x + (pos.width - iconWidth) / 2;
|
|
5913
6147
|
const offsetY = pos.y + (pos.height - iconHeight) / 2;
|
|
5914
|
-
let svg = `<g${this.getDataNodeId(node)}>
|
|
5915
|
-
<!-- Image Background -->
|
|
5916
|
-
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="#E8E8E8"/>`;
|
|
5917
6148
|
if (placeholder === "icon" && placeholderIconSvg) {
|
|
5918
|
-
const
|
|
5919
|
-
const
|
|
5920
|
-
const
|
|
5921
|
-
const
|
|
5922
|
-
const
|
|
5923
|
-
const
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
rx="${Math.max(4, badgeSize * 0.2)}"
|
|
5929
|
-
fill="rgba(255, 255, 255, 0.6)"
|
|
5930
|
-
stroke="#888"
|
|
5931
|
-
stroke-width="1"/>
|
|
6149
|
+
const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
|
|
6150
|
+
const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
|
|
6151
|
+
const variantColor = hasVariant ? this.resolveVariantColor(variant, this.renderTheme.primary) : null;
|
|
6152
|
+
const bgColor = hasVariant ? this.hexToRgba(variantColor, 0.12) : imageBg;
|
|
6153
|
+
const iconColor = hasVariant ? variantColor : this.options.theme === "dark" ? "#888888" : "#666666";
|
|
6154
|
+
const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
|
|
6155
|
+
const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
|
|
6156
|
+
const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
|
|
6157
|
+
return `<g${this.getDataNodeId(node)}>
|
|
6158
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
|
|
5932
6159
|
<g transform="translate(${iconOffsetX}, ${iconOffsetY})">
|
|
5933
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
6160
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5934
6161
|
${this.extractSvgContent(placeholderIconSvg)}
|
|
5935
6162
|
</svg>
|
|
5936
|
-
</g
|
|
5937
|
-
}
|
|
6163
|
+
</g>
|
|
6164
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
|
|
6165
|
+
</g>`;
|
|
6166
|
+
}
|
|
6167
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
6168
|
+
<!-- Image Background -->
|
|
6169
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
|
|
6170
|
+
if (["landscape", "portrait", "square"].includes(placeholder)) {
|
|
5938
6171
|
const cameraCx = offsetX + iconWidth / 2;
|
|
5939
6172
|
const cameraCy = offsetY + iconHeight / 2;
|
|
5940
6173
|
const scale = Math.min(iconWidth, iconHeight) / 24;
|
|
@@ -6038,18 +6271,22 @@ var SVGRenderer = class {
|
|
|
6038
6271
|
const fontSize = 14;
|
|
6039
6272
|
const activeIndex = Number(node.props.active || 0);
|
|
6040
6273
|
const accentColor = this.resolveAccentColor();
|
|
6274
|
+
const variantProp = String(node.props.variant || "").trim();
|
|
6275
|
+
const semanticVariant = variantProp ? this.getSemanticVariantColor(variantProp) : void 0;
|
|
6276
|
+
const hasVariant = variantProp.length > 0 && (semanticVariant !== void 0 || this.colorResolver.hasColor(variantProp));
|
|
6277
|
+
const activeColor = hasVariant ? this.resolveVariantColor(variantProp, accentColor) : accentColor;
|
|
6041
6278
|
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6042
6279
|
items.forEach((item, index) => {
|
|
6043
6280
|
const itemY = pos.y + index * itemHeight;
|
|
6044
6281
|
const isActive = index === activeIndex;
|
|
6045
|
-
const bgColor = isActive ? this.hexToRgba(
|
|
6046
|
-
const textColor = isActive ? this.hexToRgba(
|
|
6282
|
+
const bgColor = isActive ? this.hexToRgba(activeColor, 0.15) : "transparent";
|
|
6283
|
+
const textColor = isActive ? this.hexToRgba(activeColor, 0.9) : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
6047
6284
|
const fontWeight = isActive ? "500" : "400";
|
|
6048
6285
|
if (isActive) {
|
|
6049
6286
|
svg += `
|
|
6050
|
-
<rect x="${pos.x}" y="${itemY}"
|
|
6051
|
-
width="${pos.width}" height="${itemHeight}"
|
|
6052
|
-
rx="6"
|
|
6287
|
+
<rect x="${pos.x}" y="${itemY}"
|
|
6288
|
+
width="${pos.width}" height="${itemHeight}"
|
|
6289
|
+
rx="6"
|
|
6053
6290
|
fill="${bgColor}"/>`;
|
|
6054
6291
|
}
|
|
6055
6292
|
let currentX = pos.x + 12;
|
|
@@ -6058,9 +6295,10 @@ var SVGRenderer = class {
|
|
|
6058
6295
|
if (iconSvg) {
|
|
6059
6296
|
const iconSize = 16;
|
|
6060
6297
|
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
6298
|
+
const iconColor = isActive ? this.hexToRgba(activeColor, 0.9) : this.hexToRgba(this.resolveMutedColor(), 0.9);
|
|
6061
6299
|
svg += `
|
|
6062
6300
|
<g transform="translate(${currentX}, ${iconY})">
|
|
6063
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${
|
|
6301
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
6064
6302
|
${this.extractSvgContent(iconSvg)}
|
|
6065
6303
|
</svg>
|
|
6066
6304
|
</g>`;
|
|
@@ -6068,10 +6306,10 @@ var SVGRenderer = class {
|
|
|
6068
6306
|
}
|
|
6069
6307
|
}
|
|
6070
6308
|
svg += `
|
|
6071
|
-
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
6072
|
-
font-family="Arial, Helvetica, sans-serif"
|
|
6073
|
-
font-size="${fontSize}"
|
|
6074
|
-
font-weight="${fontWeight}"
|
|
6309
|
+
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
6310
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
6311
|
+
font-size="${fontSize}"
|
|
6312
|
+
font-weight="${fontWeight}"
|
|
6075
6313
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
6076
6314
|
});
|
|
6077
6315
|
svg += "\n </g>";
|
|
@@ -6111,9 +6349,10 @@ var SVGRenderer = class {
|
|
|
6111
6349
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6112
6350
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6113
6351
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
6114
|
-
const
|
|
6352
|
+
const isDarkMode = this.options.theme === "dark";
|
|
6353
|
+
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : isDarkMode ? "rgba(48, 48, 55, 0.9)" : "rgba(226, 232, 240, 0.9)";
|
|
6115
6354
|
const iconColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
6116
|
-
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
6355
|
+
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : isDarkMode ? "rgba(75, 75, 88, 0.8)" : "rgba(100, 116, 139, 0.4)";
|
|
6117
6356
|
const opacity = disabled ? "0.5" : "1";
|
|
6118
6357
|
const iconSvg = getIcon(iconName);
|
|
6119
6358
|
const buttonSize = Math.max(
|
|
@@ -6171,14 +6410,37 @@ var SVGRenderer = class {
|
|
|
6171
6410
|
return this.colorResolver.resolveColor("muted", fallback);
|
|
6172
6411
|
}
|
|
6173
6412
|
getSemanticVariantColor(variant) {
|
|
6174
|
-
const
|
|
6413
|
+
const isDark = this.options.theme === "dark";
|
|
6414
|
+
const semantic = isDark ? {
|
|
6415
|
+
// Muted mid-range — readable on #111111 without being neon
|
|
6416
|
+
primary: this.renderTheme.primary,
|
|
6417
|
+
// already theme-aware (#60A5FA)
|
|
6418
|
+
secondary: "#7E8EA2",
|
|
6419
|
+
// desaturated slate
|
|
6420
|
+
success: "#22A06B",
|
|
6421
|
+
// muted emerald
|
|
6422
|
+
warning: "#B38010",
|
|
6423
|
+
// deep amber
|
|
6424
|
+
danger: "#CC4444",
|
|
6425
|
+
// muted red
|
|
6426
|
+
error: "#CC4444",
|
|
6427
|
+
info: "#2485AF"
|
|
6428
|
+
// muted sky
|
|
6429
|
+
} : {
|
|
6430
|
+
// Tailwind 500-level — works on white/light backgrounds
|
|
6175
6431
|
primary: this.renderTheme.primary,
|
|
6432
|
+
// #3B82F6
|
|
6176
6433
|
secondary: "#64748B",
|
|
6434
|
+
// Slate 500
|
|
6177
6435
|
success: "#10B981",
|
|
6436
|
+
// Emerald 500
|
|
6178
6437
|
warning: "#F59E0B",
|
|
6438
|
+
// Amber 500
|
|
6179
6439
|
danger: "#EF4444",
|
|
6440
|
+
// Red 500
|
|
6180
6441
|
error: "#EF4444",
|
|
6181
6442
|
info: "#0EA5E9"
|
|
6443
|
+
// Sky 500
|
|
6182
6444
|
};
|
|
6183
6445
|
return semantic[variant];
|
|
6184
6446
|
}
|
|
@@ -6455,10 +6717,11 @@ var SVGRenderer = class {
|
|
|
6455
6717
|
buildParentContainerIndex() {
|
|
6456
6718
|
this.parentContainerByChildId.clear();
|
|
6457
6719
|
Object.values(this.ir.project.nodes).forEach((node) => {
|
|
6458
|
-
if (node.kind
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6720
|
+
if (node.kind === "container") {
|
|
6721
|
+
node.children.forEach((childRef) => {
|
|
6722
|
+
this.parentContainerByChildId.set(childRef.ref, node);
|
|
6723
|
+
});
|
|
6724
|
+
}
|
|
6462
6725
|
});
|
|
6463
6726
|
}
|
|
6464
6727
|
escapeXml(text) {
|
|
@@ -6639,6 +6902,19 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6639
6902
|
renderLabel(node, pos) {
|
|
6640
6903
|
return this.renderTextBlock(node, pos, String(node.props.text || "Label"), 12, 1.2);
|
|
6641
6904
|
}
|
|
6905
|
+
/**
|
|
6906
|
+
* Render image as a plain skeleton rectangle — no icon, no placeholder label,
|
|
6907
|
+
* just a filled block with the correct dimensions (aspect-ratio is preserved
|
|
6908
|
+
* by the layout engine, so pos already has the right size).
|
|
6909
|
+
*/
|
|
6910
|
+
renderImage(node, pos) {
|
|
6911
|
+
return `<g${this.getDataNodeId(node)}>
|
|
6912
|
+
<rect x="${pos.x}" y="${pos.y}"
|
|
6913
|
+
width="${pos.width}" height="${pos.height}"
|
|
6914
|
+
rx="4"
|
|
6915
|
+
fill="${this.renderTheme.border}"/>
|
|
6916
|
+
</g>`;
|
|
6917
|
+
}
|
|
6642
6918
|
/**
|
|
6643
6919
|
* Render badge as shape only (no text)
|
|
6644
6920
|
*/
|
|
@@ -6880,7 +7156,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6880
7156
|
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
6881
7157
|
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
6882
7158
|
const showOuterBackground = this.parseBooleanProp(
|
|
6883
|
-
node.props.background
|
|
7159
|
+
node.props.background,
|
|
6884
7160
|
false
|
|
6885
7161
|
);
|
|
6886
7162
|
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
@@ -6995,7 +7271,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6995
7271
|
const variant = String(node.props.variant || "default");
|
|
6996
7272
|
const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
|
|
6997
7273
|
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
6998
|
-
const showBackground = this.parseBooleanProp(node.props.background
|
|
7274
|
+
const showBackground = this.parseBooleanProp(node.props.background, false);
|
|
6999
7275
|
const radiusMap = {
|
|
7000
7276
|
none: 0,
|
|
7001
7277
|
sm: 4,
|
|
@@ -7313,6 +7589,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7313
7589
|
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
7314
7590
|
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7315
7591
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
7592
|
+
const iconName = String(node.props.icon || "").trim();
|
|
7593
|
+
const iconAlign = String(node.props.iconAlign || "left").toLowerCase();
|
|
7316
7594
|
const radius = this.tokens.button.radius;
|
|
7317
7595
|
const fontSize = this.tokens.button.fontSize;
|
|
7318
7596
|
const fontWeight = this.tokens.button.fontWeight;
|
|
@@ -7322,9 +7600,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7322
7600
|
Math.min(resolveControlHeight(size, density), pos.height - labelOffset)
|
|
7323
7601
|
);
|
|
7324
7602
|
const buttonY = pos.y + labelOffset;
|
|
7603
|
+
const iconSvg = iconName ? getIcon(iconName) : null;
|
|
7604
|
+
const iconSize = iconSvg ? Math.round(fontSize * 1.1) : 0;
|
|
7605
|
+
const iconGap = iconSvg ? 8 : 0;
|
|
7606
|
+
const edgePad = 12;
|
|
7607
|
+
const textPad = paddingX + extraPadding;
|
|
7325
7608
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
7326
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (
|
|
7327
|
-
const availableTextWidth = Math.max(0, buttonWidth - (
|
|
7609
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (iconSvg ? iconSize + iconGap : 0) + textPad * 2, 60), pos.width);
|
|
7610
|
+
const availableTextWidth = Math.max(0, buttonWidth - textPad * 2 - (iconSvg ? iconSize + iconGap : 0));
|
|
7328
7611
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
7329
7612
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
7330
7613
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
@@ -7332,21 +7615,47 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7332
7615
|
const borderColor = variantColor;
|
|
7333
7616
|
const textColor = variantColor;
|
|
7334
7617
|
const strokeWidth = 0.5;
|
|
7335
|
-
|
|
7618
|
+
const iconOffsetY = buttonY + (buttonHeight - iconSize) / 2;
|
|
7619
|
+
const iconX = iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize : pos.x + edgePad;
|
|
7620
|
+
const textAlign = String(node.props.align || "center").toLowerCase();
|
|
7621
|
+
const sidePad = textPad + 4;
|
|
7622
|
+
let textX;
|
|
7623
|
+
let textAnchor;
|
|
7624
|
+
if (textAlign === "left") {
|
|
7625
|
+
textX = iconSvg && iconAlign === "left" ? pos.x + edgePad + iconSize + iconGap : pos.x + sidePad;
|
|
7626
|
+
textAnchor = "start";
|
|
7627
|
+
} else if (textAlign === "right") {
|
|
7628
|
+
textX = iconSvg && iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize - iconGap : pos.x + buttonWidth - sidePad;
|
|
7629
|
+
textAnchor = "end";
|
|
7630
|
+
} else {
|
|
7631
|
+
textX = pos.x + buttonWidth / 2;
|
|
7632
|
+
textAnchor = "middle";
|
|
7633
|
+
}
|
|
7634
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
7336
7635
|
<rect x="${pos.x}" y="${buttonY}"
|
|
7337
7636
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
7338
7637
|
rx="${radius}"
|
|
7339
7638
|
fill="none"
|
|
7340
7639
|
stroke="${borderColor}"
|
|
7341
7640
|
stroke-width="${strokeWidth}"
|
|
7342
|
-
filter="url(#sketch-rough)"
|
|
7343
|
-
|
|
7641
|
+
filter="url(#sketch-rough)"/>`;
|
|
7642
|
+
if (iconSvg) {
|
|
7643
|
+
svg += `
|
|
7644
|
+
<g transform="translate(${iconX}, ${iconOffsetY})">
|
|
7645
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${textColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7646
|
+
${this.extractSvgContent(iconSvg)}
|
|
7647
|
+
</svg>
|
|
7648
|
+
</g>`;
|
|
7649
|
+
}
|
|
7650
|
+
svg += `
|
|
7651
|
+
<text x="${textX}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
7344
7652
|
font-family="${this.fontFamily}"
|
|
7345
7653
|
font-size="${fontSize}"
|
|
7346
7654
|
font-weight="${fontWeight}"
|
|
7347
7655
|
fill="${textColor}"
|
|
7348
|
-
text-anchor="
|
|
7656
|
+
text-anchor="${textAnchor}">${this.escapeXml(visibleText)}</text>
|
|
7349
7657
|
</g>`;
|
|
7658
|
+
return svg;
|
|
7350
7659
|
}
|
|
7351
7660
|
/**
|
|
7352
7661
|
* Render badge with colored border instead of fill
|
|
@@ -7471,29 +7780,67 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7471
7780
|
renderInput(node, pos) {
|
|
7472
7781
|
const label = String(node.props.label || "");
|
|
7473
7782
|
const placeholder = String(node.props.placeholder || "");
|
|
7783
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
7784
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
7474
7785
|
const radius = this.tokens.input.radius;
|
|
7475
7786
|
const fontSize = this.tokens.input.fontSize;
|
|
7476
7787
|
const paddingX = this.tokens.input.paddingX;
|
|
7477
7788
|
const labelOffset = this.getControlLabelOffset(label);
|
|
7478
7789
|
const controlY = pos.y + labelOffset;
|
|
7479
7790
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
7480
|
-
|
|
7481
|
-
|
|
7791
|
+
const iconSize = 16;
|
|
7792
|
+
const iconPad = 12;
|
|
7793
|
+
const iconInnerGap = 8;
|
|
7794
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
7795
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
7796
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
7797
|
+
const rightOffset = iconRightSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
7798
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : paddingX);
|
|
7799
|
+
const iconColor = "#888888";
|
|
7800
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
7801
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7802
|
+
if (label) {
|
|
7803
|
+
svg += `
|
|
7804
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
7482
7805
|
font-family="${this.fontFamily}"
|
|
7483
7806
|
font-size="12"
|
|
7484
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
7807
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
7808
|
+
}
|
|
7809
|
+
svg += `
|
|
7485
7810
|
<rect x="${pos.x}" y="${controlY}"
|
|
7486
7811
|
width="${pos.width}" height="${controlHeight}"
|
|
7487
7812
|
rx="${radius}"
|
|
7488
7813
|
fill="${this.renderTheme.cardBg}"
|
|
7489
7814
|
stroke="#2D3748"
|
|
7490
7815
|
stroke-width="0.5"
|
|
7491
|
-
filter="url(#sketch-rough)"
|
|
7492
|
-
|
|
7816
|
+
filter="url(#sketch-rough)"/>`;
|
|
7817
|
+
if (iconLeftSvg) {
|
|
7818
|
+
svg += `
|
|
7819
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
7820
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7821
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
7822
|
+
</svg>
|
|
7823
|
+
</g>`;
|
|
7824
|
+
}
|
|
7825
|
+
if (iconRightSvg) {
|
|
7826
|
+
svg += `
|
|
7827
|
+
<g transform="translate(${pos.x + pos.width - iconPad - iconSize}, ${iconCenterY})">
|
|
7828
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7829
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
7830
|
+
</svg>
|
|
7831
|
+
</g>`;
|
|
7832
|
+
}
|
|
7833
|
+
if (placeholder) {
|
|
7834
|
+
const availWidth = pos.width - (iconLeftSvg ? leftOffset : paddingX) - (iconRightSvg ? rightOffset : paddingX);
|
|
7835
|
+
const visiblePh = this.truncateTextToWidth(placeholder, Math.max(0, availWidth), fontSize);
|
|
7836
|
+
svg += `
|
|
7837
|
+
<text x="${textX}" y="${controlY + controlHeight / 2 + 5}"
|
|
7493
7838
|
font-family="${this.fontFamily}"
|
|
7494
7839
|
font-size="${fontSize}"
|
|
7495
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
7496
|
-
|
|
7840
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePh)}</text>`;
|
|
7841
|
+
}
|
|
7842
|
+
svg += "\n </g>";
|
|
7843
|
+
return svg;
|
|
7497
7844
|
}
|
|
7498
7845
|
/**
|
|
7499
7846
|
* Render textarea with thicker border
|
|
@@ -7747,31 +8094,67 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7747
8094
|
renderSelect(node, pos) {
|
|
7748
8095
|
const label = String(node.props.label || "");
|
|
7749
8096
|
const placeholder = String(node.props.placeholder || "Select...");
|
|
8097
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
8098
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
7750
8099
|
const labelOffset = this.getControlLabelOffset(label);
|
|
7751
8100
|
const controlY = pos.y + labelOffset;
|
|
7752
8101
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
7753
8102
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
7754
|
-
|
|
7755
|
-
|
|
8103
|
+
const iconSize = 16;
|
|
8104
|
+
const iconPad = 12;
|
|
8105
|
+
const iconInnerGap = 8;
|
|
8106
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
8107
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
8108
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
8109
|
+
const chevronWidth = 20;
|
|
8110
|
+
const iconColor = "#888888";
|
|
8111
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
8112
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
8113
|
+
if (label) {
|
|
8114
|
+
svg += `
|
|
8115
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
7756
8116
|
font-family="${this.fontFamily}"
|
|
7757
8117
|
font-size="12"
|
|
7758
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
8118
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
8119
|
+
}
|
|
8120
|
+
svg += `
|
|
7759
8121
|
<rect x="${pos.x}" y="${controlY}"
|
|
7760
8122
|
width="${pos.width}" height="${controlHeight}"
|
|
7761
8123
|
rx="6"
|
|
7762
8124
|
fill="${this.renderTheme.cardBg}"
|
|
7763
8125
|
stroke="#2D3748"
|
|
7764
8126
|
stroke-width="0.5"
|
|
7765
|
-
filter="url(#sketch-rough)"
|
|
7766
|
-
|
|
8127
|
+
filter="url(#sketch-rough)"/>`;
|
|
8128
|
+
if (iconLeftSvg) {
|
|
8129
|
+
svg += `
|
|
8130
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
8131
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8132
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
8133
|
+
</svg>
|
|
8134
|
+
</g>`;
|
|
8135
|
+
}
|
|
8136
|
+
if (iconRightSvg) {
|
|
8137
|
+
svg += `
|
|
8138
|
+
<g transform="translate(${pos.x + pos.width - chevronWidth - iconPad - iconSize}, ${iconCenterY})">
|
|
8139
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8140
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
8141
|
+
</svg>
|
|
8142
|
+
</g>`;
|
|
8143
|
+
}
|
|
8144
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : 12);
|
|
8145
|
+
const availWidth = pos.width - (iconLeftSvg ? leftOffset : 12) - chevronWidth - (iconRightSvg ? iconPad + iconSize + iconInnerGap : 0);
|
|
8146
|
+
const visiblePh = this.truncateTextToWidth(placeholder, Math.max(0, availWidth), 14);
|
|
8147
|
+
svg += `
|
|
8148
|
+
<text x="${textX}" y="${centerY}"
|
|
7767
8149
|
font-family="${this.fontFamily}"
|
|
7768
8150
|
font-size="14"
|
|
7769
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
8151
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePh)}</text>
|
|
7770
8152
|
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
7771
8153
|
font-family="${this.fontFamily}"
|
|
7772
8154
|
font-size="16"
|
|
7773
8155
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
7774
8156
|
</g>`;
|
|
8157
|
+
return svg;
|
|
7775
8158
|
}
|
|
7776
8159
|
/**
|
|
7777
8160
|
* Render checkbox with sketch filter and Comic Sans
|
|
@@ -8170,44 +8553,43 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
8170
8553
|
renderImage(node, pos) {
|
|
8171
8554
|
const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
|
|
8172
8555
|
const iconType = String(node.props.icon || "").trim();
|
|
8556
|
+
const variant = String(node.props.variant || "").trim();
|
|
8173
8557
|
const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
|
|
8558
|
+
const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
|
|
8174
8559
|
if (iconSvg) {
|
|
8175
|
-
const
|
|
8176
|
-
const
|
|
8177
|
-
const
|
|
8178
|
-
const
|
|
8179
|
-
const
|
|
8180
|
-
const
|
|
8560
|
+
const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
|
|
8561
|
+
const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
|
|
8562
|
+
const variantColor = hasVariant ? this.resolveVariantColor(variant, this.renderTheme.primary) : null;
|
|
8563
|
+
const bgColor = hasVariant ? this.hexToRgba(variantColor, 0.12) : imageBg;
|
|
8564
|
+
const iconColor = hasVariant ? variantColor : "#666666";
|
|
8565
|
+
const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
|
|
8566
|
+
const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
|
|
8567
|
+
const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
|
|
8181
8568
|
return `<g${this.getDataNodeId(node)}>
|
|
8182
|
-
<!-- Image Background -->
|
|
8183
8569
|
<rect x="${pos.x}" y="${pos.y}"
|
|
8184
8570
|
width="${pos.width}" height="${pos.height}"
|
|
8185
|
-
fill="
|
|
8186
|
-
stroke="#2D3748"
|
|
8187
|
-
stroke-width="0.5"
|
|
8571
|
+
fill="${bgColor}"
|
|
8188
8572
|
rx="4"
|
|
8189
8573
|
filter="url(#sketch-rough)"/>
|
|
8190
|
-
|
|
8191
|
-
<!-- Custom Icon Placeholder -->
|
|
8192
|
-
<rect x="${badgeX}" y="${badgeY}"
|
|
8193
|
-
width="${badgeSize}" height="${badgeSize}"
|
|
8194
|
-
rx="${Math.max(4, badgeSize * 0.2)}"
|
|
8195
|
-
fill="none"
|
|
8196
|
-
stroke="#2D3748"
|
|
8197
|
-
stroke-width="0.5"
|
|
8198
|
-
filter="url(#sketch-rough)"/>
|
|
8199
8574
|
<g transform="translate(${iconOffsetX}, ${iconOffsetY})">
|
|
8200
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
8575
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8201
8576
|
${this.extractSvgContent(iconSvg)}
|
|
8202
8577
|
</svg>
|
|
8203
8578
|
</g>
|
|
8579
|
+
<rect x="${pos.x}" y="${pos.y}"
|
|
8580
|
+
width="${pos.width}" height="${pos.height}"
|
|
8581
|
+
fill="none"
|
|
8582
|
+
stroke="#2D3748"
|
|
8583
|
+
stroke-width="0.5"
|
|
8584
|
+
rx="4"
|
|
8585
|
+
filter="url(#sketch-rough)"/>
|
|
8204
8586
|
</g>`;
|
|
8205
8587
|
}
|
|
8206
8588
|
return `<g${this.getDataNodeId(node)}>
|
|
8207
8589
|
<!-- Image Background -->
|
|
8208
8590
|
<rect x="${pos.x}" y="${pos.y}"
|
|
8209
8591
|
width="${pos.width}" height="${pos.height}"
|
|
8210
|
-
fill="
|
|
8592
|
+
fill="${imageBg}"
|
|
8211
8593
|
stroke="#2D3748"
|
|
8212
8594
|
stroke-width="0.5"
|
|
8213
8595
|
rx="4"
|
|
@@ -8262,17 +8644,23 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
8262
8644
|
*/
|
|
8263
8645
|
renderSidebarMenu(node, pos) {
|
|
8264
8646
|
const itemsStr = String(node.props.items || "Item 1,Item 2,Item 3");
|
|
8647
|
+
const iconsStr = String(node.props.icons || "");
|
|
8265
8648
|
const items = itemsStr.split(",").map((s) => s.trim());
|
|
8649
|
+
const icons = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
|
|
8266
8650
|
const itemHeight = 40;
|
|
8267
8651
|
const fontSize = 14;
|
|
8268
8652
|
const activeIndex = Number(node.props.active || 0);
|
|
8269
8653
|
const accentColor = this.resolveAccentColor();
|
|
8654
|
+
const variantProp = String(node.props.variant || "").trim();
|
|
8655
|
+
const semanticVariant = variantProp ? this.getSemanticVariantColor(variantProp) : void 0;
|
|
8656
|
+
const hasVariant = variantProp.length > 0 && (semanticVariant !== void 0 || this.colorResolver.hasColor(variantProp));
|
|
8657
|
+
const activeColor = hasVariant ? this.resolveVariantColor(variantProp, accentColor) : accentColor;
|
|
8270
8658
|
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
8271
8659
|
items.forEach((item, index) => {
|
|
8272
8660
|
const itemY = pos.y + index * itemHeight;
|
|
8273
8661
|
const isActive = index === activeIndex;
|
|
8274
|
-
const bgColor = isActive ? this.hexToRgba(
|
|
8275
|
-
const textColor = isActive ?
|
|
8662
|
+
const bgColor = isActive ? this.hexToRgba(activeColor, 0.15) : "transparent";
|
|
8663
|
+
const textColor = isActive ? activeColor : this.resolveTextColor();
|
|
8276
8664
|
const fontWeight = isActive ? "500" : "400";
|
|
8277
8665
|
if (isActive) {
|
|
8278
8666
|
svg += `
|
|
@@ -8282,8 +8670,24 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
8282
8670
|
fill="${bgColor}"
|
|
8283
8671
|
filter="url(#sketch-rough)"/>`;
|
|
8284
8672
|
}
|
|
8673
|
+
let currentX = pos.x + 12;
|
|
8674
|
+
if (icons[index]) {
|
|
8675
|
+
const iconSvg = getIcon(icons[index]);
|
|
8676
|
+
if (iconSvg) {
|
|
8677
|
+
const iconSize = 16;
|
|
8678
|
+
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
8679
|
+
const iconColor = isActive ? activeColor : this.resolveMutedColor();
|
|
8680
|
+
svg += `
|
|
8681
|
+
<g transform="translate(${currentX}, ${iconY})">
|
|
8682
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8683
|
+
${this.extractSvgContent(iconSvg)}
|
|
8684
|
+
</svg>
|
|
8685
|
+
</g>`;
|
|
8686
|
+
currentX += iconSize + 8;
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8285
8689
|
svg += `
|
|
8286
|
-
<text x="${
|
|
8690
|
+
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
8287
8691
|
font-family="${this.fontFamily}"
|
|
8288
8692
|
font-size="${fontSize}"
|
|
8289
8693
|
font-weight="${fontWeight}"
|