@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.cjs
CHANGED
|
@@ -1696,7 +1696,8 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1696
1696
|
if (enumValues) {
|
|
1697
1697
|
const normalizedValue = String(propValue);
|
|
1698
1698
|
const isCustomVariantFromColors = propName === "variant" && !enumValues.includes(normalizedValue) && Object.prototype.hasOwnProperty.call(ast.colors || {}, normalizedValue);
|
|
1699
|
-
|
|
1699
|
+
const isPropReference = normalizedValue.startsWith("prop_");
|
|
1700
|
+
if (!enumValues.includes(normalizedValue) && !isCustomVariantFromColors && !isPropReference) {
|
|
1700
1701
|
emitWarning(
|
|
1701
1702
|
`Invalid value "${normalizedValue}" for property "${propName}" in component "${componentType}".`,
|
|
1702
1703
|
"COMPONENT_INVALID_PROPERTY_VALUE",
|
|
@@ -1806,7 +1807,7 @@ function validateSemanticDiagnostics(ast, sourceMap) {
|
|
|
1806
1807
|
const enumValues = rules.enumParams?.[paramName];
|
|
1807
1808
|
if (enumValues) {
|
|
1808
1809
|
const normalizedValue = String(paramValue);
|
|
1809
|
-
if (!enumValues.includes(normalizedValue)) {
|
|
1810
|
+
if (!enumValues.includes(normalizedValue) && !normalizedValue.startsWith("prop_")) {
|
|
1810
1811
|
emitWarning(
|
|
1811
1812
|
`Invalid value "${normalizedValue}" for parameter "${paramName}" in layout "${layout.layoutType}".`,
|
|
1812
1813
|
"LAYOUT_INVALID_PARAMETER_VALUE",
|
|
@@ -2244,7 +2245,21 @@ var IRComponentNodeSchema = import_zod.z.object({
|
|
|
2244
2245
|
style: IRNodeStyleSchema,
|
|
2245
2246
|
meta: IRMetaSchema
|
|
2246
2247
|
});
|
|
2247
|
-
var
|
|
2248
|
+
var IRInstanceNodeSchema = import_zod.z.object({
|
|
2249
|
+
id: import_zod.z.string(),
|
|
2250
|
+
kind: import_zod.z.literal("instance"),
|
|
2251
|
+
definitionName: import_zod.z.string(),
|
|
2252
|
+
definitionKind: import_zod.z.enum(["component", "layout"]),
|
|
2253
|
+
invocationProps: import_zod.z.record(import_zod.z.string(), import_zod.z.union([import_zod.z.string(), import_zod.z.number()])),
|
|
2254
|
+
expandedRoot: import_zod.z.object({ ref: import_zod.z.string() }),
|
|
2255
|
+
style: IRNodeStyleSchema,
|
|
2256
|
+
meta: IRMetaSchema
|
|
2257
|
+
});
|
|
2258
|
+
var IRNodeSchema = import_zod.z.discriminatedUnion("kind", [
|
|
2259
|
+
IRContainerNodeSchema,
|
|
2260
|
+
IRComponentNodeSchema,
|
|
2261
|
+
IRInstanceNodeSchema
|
|
2262
|
+
]);
|
|
2248
2263
|
var IRScreenSchema = import_zod.z.object({
|
|
2249
2264
|
id: import_zod.z.string(),
|
|
2250
2265
|
name: import_zod.z.string(),
|
|
@@ -2467,7 +2482,7 @@ ${messages}`);
|
|
|
2467
2482
|
const layoutChildren = layout.children;
|
|
2468
2483
|
const layoutDefinition = this.definedLayouts.get(layout.layoutType);
|
|
2469
2484
|
if (layoutDefinition) {
|
|
2470
|
-
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context);
|
|
2485
|
+
return this.expandDefinedLayout(layoutDefinition, layoutParams, layoutChildren, context, layout._meta?.nodeId);
|
|
2471
2486
|
}
|
|
2472
2487
|
const nodeId = this.idGen.generate("node");
|
|
2473
2488
|
const childRefs = [];
|
|
@@ -2506,8 +2521,8 @@ ${messages}`);
|
|
|
2506
2521
|
children: childRefs,
|
|
2507
2522
|
style,
|
|
2508
2523
|
meta: {
|
|
2509
|
-
nodeId
|
|
2510
|
-
|
|
2524
|
+
// Scope nodeId per instance so each expansion gets a unique identifier
|
|
2525
|
+
nodeId: context?.instanceScope ? `${layout._meta?.nodeId}@${context.instanceScope}` : layout._meta?.nodeId
|
|
2511
2526
|
}
|
|
2512
2527
|
};
|
|
2513
2528
|
this.nodes[nodeId] = containerNode;
|
|
@@ -2536,8 +2551,7 @@ ${messages}`);
|
|
|
2536
2551
|
// Cells have no padding by default - grid gap handles spacing
|
|
2537
2552
|
meta: {
|
|
2538
2553
|
source: "cell",
|
|
2539
|
-
nodeId: cell._meta?.nodeId
|
|
2540
|
-
// Pass SourceMap nodeId from AST
|
|
2554
|
+
nodeId: context?.instanceScope ? `${cell._meta?.nodeId}@${context.instanceScope}` : cell._meta?.nodeId
|
|
2541
2555
|
}
|
|
2542
2556
|
};
|
|
2543
2557
|
this.nodes[nodeId] = containerNode;
|
|
@@ -2564,7 +2578,7 @@ ${messages}`);
|
|
|
2564
2578
|
const resolvedProps = this.resolveComponentProps(component.componentType, component.props, context);
|
|
2565
2579
|
const definition = this.definedComponents.get(component.componentType);
|
|
2566
2580
|
if (definition) {
|
|
2567
|
-
return this.expandDefinedComponent(definition, resolvedProps, context);
|
|
2581
|
+
return this.expandDefinedComponent(definition, resolvedProps, component._meta?.nodeId, context);
|
|
2568
2582
|
}
|
|
2569
2583
|
const builtInComponents = /* @__PURE__ */ new Set([
|
|
2570
2584
|
"Button",
|
|
@@ -2610,35 +2624,49 @@ ${messages}`);
|
|
|
2610
2624
|
props: resolvedProps,
|
|
2611
2625
|
style: {},
|
|
2612
2626
|
meta: {
|
|
2613
|
-
nodeId
|
|
2614
|
-
|
|
2627
|
+
// Scope nodeId per instance so each expansion gets a unique identifier
|
|
2628
|
+
nodeId: context?.instanceScope ? `${component._meta?.nodeId}@${context.instanceScope}` : component._meta?.nodeId
|
|
2615
2629
|
}
|
|
2616
2630
|
};
|
|
2617
2631
|
this.nodes[nodeId] = componentNode;
|
|
2618
2632
|
return nodeId;
|
|
2619
2633
|
}
|
|
2620
|
-
expandDefinedComponent(definition, invocationArgs, parentContext) {
|
|
2634
|
+
expandDefinedComponent(definition, invocationArgs, callSiteNodeId, parentContext) {
|
|
2621
2635
|
const context = {
|
|
2622
2636
|
args: invocationArgs,
|
|
2623
2637
|
providedArgNames: new Set(Object.keys(invocationArgs)),
|
|
2624
2638
|
usedArgNames: /* @__PURE__ */ new Set(),
|
|
2625
2639
|
definitionName: definition.name,
|
|
2626
2640
|
definitionKind: "component",
|
|
2627
|
-
allowChildrenSlot: false
|
|
2641
|
+
allowChildrenSlot: false,
|
|
2642
|
+
// Scope internal nodeIds using the call-site nodeId
|
|
2643
|
+
instanceScope: callSiteNodeId
|
|
2628
2644
|
};
|
|
2645
|
+
let expandedRootId = null;
|
|
2629
2646
|
if (definition.body.type === "layout") {
|
|
2630
|
-
|
|
2631
|
-
this.reportUnusedArguments(context);
|
|
2632
|
-
return result;
|
|
2647
|
+
expandedRootId = this.convertLayout(definition.body, context);
|
|
2633
2648
|
} else if (definition.body.type === "component") {
|
|
2634
|
-
|
|
2635
|
-
this.reportUnusedArguments(context);
|
|
2636
|
-
return result;
|
|
2649
|
+
expandedRootId = this.convertComponent(definition.body, context);
|
|
2637
2650
|
} else {
|
|
2638
2651
|
throw new Error(`Invalid defined component body type for "${definition.name}"`);
|
|
2639
2652
|
}
|
|
2653
|
+
this.reportUnusedArguments(context);
|
|
2654
|
+
if (!expandedRootId) return null;
|
|
2655
|
+
const instanceNodeId = this.idGen.generate("node");
|
|
2656
|
+
const instanceNode = {
|
|
2657
|
+
id: instanceNodeId,
|
|
2658
|
+
kind: "instance",
|
|
2659
|
+
definitionName: definition.name,
|
|
2660
|
+
definitionKind: "component",
|
|
2661
|
+
invocationProps: invocationArgs,
|
|
2662
|
+
expandedRoot: { ref: expandedRootId },
|
|
2663
|
+
style: {},
|
|
2664
|
+
meta: { nodeId: callSiteNodeId }
|
|
2665
|
+
};
|
|
2666
|
+
this.nodes[instanceNodeId] = instanceNode;
|
|
2667
|
+
return instanceNodeId;
|
|
2640
2668
|
}
|
|
2641
|
-
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext) {
|
|
2669
|
+
expandDefinedLayout(definition, invocationParams, invocationChildren, parentContext, callSiteNodeId) {
|
|
2642
2670
|
if (invocationChildren.length !== 1) {
|
|
2643
2671
|
this.errors.push({
|
|
2644
2672
|
type: "layout-children-arity",
|
|
@@ -2654,11 +2682,26 @@ ${messages}`);
|
|
|
2654
2682
|
definitionName: definition.name,
|
|
2655
2683
|
definitionKind: "layout",
|
|
2656
2684
|
allowChildrenSlot: true,
|
|
2657
|
-
childrenSlot: resolvedSlot
|
|
2685
|
+
childrenSlot: resolvedSlot,
|
|
2686
|
+
// Scope internal nodeIds using the call-site nodeId
|
|
2687
|
+
instanceScope: callSiteNodeId
|
|
2658
2688
|
};
|
|
2659
|
-
const
|
|
2689
|
+
const expandedRootId = this.convertLayout(definition.body, context);
|
|
2660
2690
|
this.reportUnusedArguments(context);
|
|
2661
|
-
return
|
|
2691
|
+
if (!callSiteNodeId) return expandedRootId;
|
|
2692
|
+
const instanceNodeId = this.idGen.generate("node");
|
|
2693
|
+
const instanceNode = {
|
|
2694
|
+
id: instanceNodeId,
|
|
2695
|
+
kind: "instance",
|
|
2696
|
+
definitionName: definition.name,
|
|
2697
|
+
definitionKind: "layout",
|
|
2698
|
+
invocationProps: invocationParams,
|
|
2699
|
+
expandedRoot: { ref: expandedRootId },
|
|
2700
|
+
style: {},
|
|
2701
|
+
meta: { nodeId: callSiteNodeId }
|
|
2702
|
+
};
|
|
2703
|
+
this.nodes[instanceNodeId] = instanceNode;
|
|
2704
|
+
return instanceNodeId;
|
|
2662
2705
|
}
|
|
2663
2706
|
resolveChildrenSlot(slot, parentContext) {
|
|
2664
2707
|
if (slot.type === "component" && slot.componentType === "Children") {
|
|
@@ -2689,6 +2732,20 @@ ${messages}`);
|
|
|
2689
2732
|
key
|
|
2690
2733
|
);
|
|
2691
2734
|
if (resolvedValue !== void 0) {
|
|
2735
|
+
const wasPropReference = typeof value === "string" && value.startsWith("prop_");
|
|
2736
|
+
if (wasPropReference) {
|
|
2737
|
+
const layoutMetadata = import_components2.LAYOUTS[layoutType];
|
|
2738
|
+
const property = layoutMetadata?.properties?.[key];
|
|
2739
|
+
if (property?.type === "enum" && Array.isArray(property.options)) {
|
|
2740
|
+
const normalizedValue = String(resolvedValue);
|
|
2741
|
+
if (!property.options.includes(normalizedValue)) {
|
|
2742
|
+
this.warnings.push({
|
|
2743
|
+
type: "invalid-bound-enum-value",
|
|
2744
|
+
message: `Invalid value "${normalizedValue}" for parameter "${key}" in layout "${layoutType}". Expected one of: ${property.options.join(", ")}.`
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2692
2749
|
resolved[key] = resolvedValue;
|
|
2693
2750
|
}
|
|
2694
2751
|
}
|
|
@@ -2753,6 +2810,20 @@ ${messages}`);
|
|
|
2753
2810
|
key
|
|
2754
2811
|
);
|
|
2755
2812
|
if (resolvedValue !== void 0) {
|
|
2813
|
+
const wasPropReference = typeof value === "string" && value.startsWith("prop_");
|
|
2814
|
+
if (wasPropReference) {
|
|
2815
|
+
const metadata = import_components2.COMPONENTS[componentType];
|
|
2816
|
+
const property = metadata?.properties?.[key];
|
|
2817
|
+
if (property?.type === "enum" && Array.isArray(property.options)) {
|
|
2818
|
+
const normalizedValue = String(resolvedValue);
|
|
2819
|
+
if (!property.options.includes(normalizedValue)) {
|
|
2820
|
+
this.warnings.push({
|
|
2821
|
+
type: "invalid-bound-enum-value",
|
|
2822
|
+
message: `Invalid value "${normalizedValue}" for property "${key}" in component "${componentType}". Expected one of: ${property.options.join(", ")}.`
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2756
2827
|
resolved[key] = resolvedValue;
|
|
2757
2828
|
}
|
|
2758
2829
|
}
|
|
@@ -2996,6 +3067,8 @@ var LayoutEngine = class {
|
|
|
2996
3067
|
}
|
|
2997
3068
|
if (node.kind === "container") {
|
|
2998
3069
|
this.calculateContainer(node, nodeId, x, y, width, height);
|
|
3070
|
+
} else if (node.kind === "instance") {
|
|
3071
|
+
this.calculateInstance(node, nodeId, x, y, width, height, parentContainerType);
|
|
2999
3072
|
} else {
|
|
3000
3073
|
this.calculateComponent(node, nodeId, x, y, width, height);
|
|
3001
3074
|
}
|
|
@@ -3028,7 +3101,7 @@ var LayoutEngine = class {
|
|
|
3028
3101
|
this.calculateCard(node, innerX, innerY, innerWidth, innerHeight);
|
|
3029
3102
|
break;
|
|
3030
3103
|
}
|
|
3031
|
-
if (isVerticalStack || node.containerType === "card") {
|
|
3104
|
+
if ((isVerticalStack || node.containerType === "card") && node.children.length > 0) {
|
|
3032
3105
|
let containerMaxY = y;
|
|
3033
3106
|
node.children.forEach((childRef) => {
|
|
3034
3107
|
const childPos = this.result[childRef.ref];
|
|
@@ -3171,6 +3244,10 @@ var LayoutEngine = class {
|
|
|
3171
3244
|
const gap = this.resolveSpacing(node.style.gap);
|
|
3172
3245
|
const padding = this.resolveSpacing(node.style.padding);
|
|
3173
3246
|
let totalHeight = padding * 2;
|
|
3247
|
+
const EMPTY_CONTAINER_MIN_HEIGHT = 40;
|
|
3248
|
+
if (node.children.length === 0) {
|
|
3249
|
+
return Math.max(totalHeight, EMPTY_CONTAINER_MIN_HEIGHT);
|
|
3250
|
+
}
|
|
3174
3251
|
if (node.containerType === "grid") {
|
|
3175
3252
|
const columns = Number(node.params.columns) || 12;
|
|
3176
3253
|
const colWidth = (availableWidth - gap * (columns - 1)) / columns;
|
|
@@ -3423,6 +3500,20 @@ var LayoutEngine = class {
|
|
|
3423
3500
|
}
|
|
3424
3501
|
});
|
|
3425
3502
|
}
|
|
3503
|
+
/**
|
|
3504
|
+
* Calculate layout for an instance node.
|
|
3505
|
+
* The instance is a transparent wrapper — its bounding box equals the
|
|
3506
|
+
* expanded root's bounding box. We calculate the expanded root first and
|
|
3507
|
+
* then copy its position to the instance nodeId so the renderer can use it.
|
|
3508
|
+
*/
|
|
3509
|
+
calculateInstance(node, nodeId, x, y, width, height, parentContainerType) {
|
|
3510
|
+
const expandedRootId = node.expandedRoot.ref;
|
|
3511
|
+
this.calculateNode(expandedRootId, x, y, width, height, parentContainerType);
|
|
3512
|
+
const expandedPos = this.result[expandedRootId];
|
|
3513
|
+
if (expandedPos) {
|
|
3514
|
+
this.result[nodeId] = { ...expandedPos };
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3426
3517
|
calculateComponent(node, nodeId, x, y, width, height) {
|
|
3427
3518
|
if (node.kind !== "component") return;
|
|
3428
3519
|
const componentWidth = Number(node.props.width) || width;
|
|
@@ -4563,14 +4654,14 @@ var THEMES = {
|
|
|
4563
4654
|
primaryLight: "#EFF6FF"
|
|
4564
4655
|
},
|
|
4565
4656
|
dark: {
|
|
4566
|
-
bg: "#
|
|
4567
|
-
cardBg: "#
|
|
4568
|
-
border: "#
|
|
4569
|
-
text: "#
|
|
4570
|
-
textMuted: "#
|
|
4657
|
+
bg: "#111111",
|
|
4658
|
+
cardBg: "#1C1C1C",
|
|
4659
|
+
border: "#303030",
|
|
4660
|
+
text: "#F0F0F0",
|
|
4661
|
+
textMuted: "#808080",
|
|
4571
4662
|
primary: "#60A5FA",
|
|
4572
4663
|
primaryHover: "#3B82F6",
|
|
4573
|
-
primaryLight: "#
|
|
4664
|
+
primaryLight: "#1C2A3A"
|
|
4574
4665
|
}
|
|
4575
4666
|
};
|
|
4576
4667
|
var SVGRenderer = class {
|
|
@@ -4588,7 +4679,8 @@ var SVGRenderer = class {
|
|
|
4588
4679
|
height: options?.height || 720,
|
|
4589
4680
|
theme: colorScheme,
|
|
4590
4681
|
includeLabels: options?.includeLabels ?? true,
|
|
4591
|
-
screenName: options?.screenName
|
|
4682
|
+
screenName: options?.screenName,
|
|
4683
|
+
showDiagnostics: options?.showDiagnostics ?? false
|
|
4592
4684
|
};
|
|
4593
4685
|
this.colorResolver = new ColorResolver();
|
|
4594
4686
|
this.buildParentContainerIndex();
|
|
@@ -4688,13 +4780,30 @@ var SVGRenderer = class {
|
|
|
4688
4780
|
if (node.containerType === "split") {
|
|
4689
4781
|
this.renderSplitDecoration(node, pos, containerGroup);
|
|
4690
4782
|
}
|
|
4691
|
-
node.children.
|
|
4692
|
-
this.
|
|
4693
|
-
}
|
|
4783
|
+
if (node.children.length === 0 && this.options.showDiagnostics) {
|
|
4784
|
+
containerGroup.push(this.renderEmptyContainerDiagnostic(pos, node.containerType));
|
|
4785
|
+
} else {
|
|
4786
|
+
node.children.forEach((childRef) => {
|
|
4787
|
+
this.renderNode(childRef.ref, containerGroup);
|
|
4788
|
+
});
|
|
4789
|
+
}
|
|
4694
4790
|
if (hasNodeId) {
|
|
4695
4791
|
containerGroup.push("</g>");
|
|
4696
4792
|
}
|
|
4697
4793
|
output.push(...containerGroup);
|
|
4794
|
+
} else if (node.kind === "instance") {
|
|
4795
|
+
const instanceGroup = [];
|
|
4796
|
+
if (node.meta.nodeId) {
|
|
4797
|
+
instanceGroup.push(`<g data-node-id="${node.meta.nodeId}">`);
|
|
4798
|
+
}
|
|
4799
|
+
instanceGroup.push(
|
|
4800
|
+
`<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="transparent" stroke="none" pointer-events="all"/>`
|
|
4801
|
+
);
|
|
4802
|
+
this.renderNode(node.expandedRoot.ref, instanceGroup);
|
|
4803
|
+
if (node.meta.nodeId) {
|
|
4804
|
+
instanceGroup.push("</g>");
|
|
4805
|
+
}
|
|
4806
|
+
output.push(...instanceGroup);
|
|
4698
4807
|
} else if (node.kind === "component") {
|
|
4699
4808
|
const componentSvg = this.renderComponent(node, pos);
|
|
4700
4809
|
if (componentSvg) {
|
|
@@ -4811,6 +4920,8 @@ var SVGRenderer = class {
|
|
|
4811
4920
|
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
4812
4921
|
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
4813
4922
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
4923
|
+
const iconName = String(node.props.icon || "").trim();
|
|
4924
|
+
const iconAlign = String(node.props.iconAlign || "left").toLowerCase();
|
|
4814
4925
|
const radius = this.tokens.button.radius;
|
|
4815
4926
|
const fontSize = this.tokens.button.fontSize;
|
|
4816
4927
|
const fontWeight = this.tokens.button.fontWeight;
|
|
@@ -4818,30 +4929,62 @@ var SVGRenderer = class {
|
|
|
4818
4929
|
const controlHeight = resolveActionControlHeight(size, density);
|
|
4819
4930
|
const buttonY = pos.y + labelOffset;
|
|
4820
4931
|
const buttonHeight = Math.max(16, Math.min(controlHeight, pos.height - labelOffset));
|
|
4932
|
+
const iconSvg = iconName ? getIcon(iconName) : null;
|
|
4933
|
+
const iconSize = iconSvg ? Math.round(fontSize * 1.1) : 0;
|
|
4934
|
+
const iconGap = iconSvg ? 8 : 0;
|
|
4935
|
+
const edgePad = 12;
|
|
4936
|
+
const textPad = paddingX + extraPadding;
|
|
4821
4937
|
const idealTextWidth = this.estimateTextWidth(text, fontSize);
|
|
4822
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (
|
|
4823
|
-
const availableTextWidth = Math.max(0, buttonWidth - (
|
|
4938
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(Math.ceil(idealTextWidth + (iconSvg ? iconSize + iconGap : 0) + textPad * 2), 60), pos.width);
|
|
4939
|
+
const availableTextWidth = Math.max(0, buttonWidth - textPad * 2 - (iconSvg ? iconSize + iconGap : 0));
|
|
4824
4940
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
4825
4941
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
4826
4942
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
4827
4943
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
4828
|
-
const
|
|
4944
|
+
const isDarkMode = this.options.theme === "dark";
|
|
4945
|
+
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : isDarkMode ? "rgba(48, 48, 55, 0.9)" : "rgba(226, 232, 240, 0.9)";
|
|
4829
4946
|
const textColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.85);
|
|
4830
|
-
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
4831
|
-
|
|
4947
|
+
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : isDarkMode ? "rgba(75, 75, 88, 0.8)" : "rgba(100, 116, 139, 0.4)";
|
|
4948
|
+
const iconOffsetY = buttonY + (buttonHeight - iconSize) / 2;
|
|
4949
|
+
const iconX = iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize : pos.x + edgePad;
|
|
4950
|
+
const textAlign = String(node.props.align || "center").toLowerCase();
|
|
4951
|
+
const sidePad = textPad + 4;
|
|
4952
|
+
let textX;
|
|
4953
|
+
let textAnchor;
|
|
4954
|
+
if (textAlign === "left") {
|
|
4955
|
+
textX = iconSvg && iconAlign === "left" ? pos.x + edgePad + iconSize + iconGap : pos.x + sidePad;
|
|
4956
|
+
textAnchor = "start";
|
|
4957
|
+
} else if (textAlign === "right") {
|
|
4958
|
+
textX = iconSvg && iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize - iconGap : pos.x + buttonWidth - sidePad;
|
|
4959
|
+
textAnchor = "end";
|
|
4960
|
+
} else {
|
|
4961
|
+
textX = pos.x + buttonWidth / 2;
|
|
4962
|
+
textAnchor = "middle";
|
|
4963
|
+
}
|
|
4964
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
4832
4965
|
<rect x="${pos.x}" y="${buttonY}"
|
|
4833
4966
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
4834
4967
|
rx="${radius}"
|
|
4835
4968
|
fill="${bgColor}"
|
|
4836
4969
|
stroke="${borderColor}"
|
|
4837
|
-
stroke-width="1"
|
|
4838
|
-
|
|
4970
|
+
stroke-width="1"/>`;
|
|
4971
|
+
if (iconSvg) {
|
|
4972
|
+
svg += `
|
|
4973
|
+
<g transform="translate(${iconX}, ${iconOffsetY})">
|
|
4974
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${textColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
4975
|
+
${this.extractSvgContent(iconSvg)}
|
|
4976
|
+
</svg>
|
|
4977
|
+
</g>`;
|
|
4978
|
+
}
|
|
4979
|
+
svg += `
|
|
4980
|
+
<text x="${textX}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
4839
4981
|
font-family="Arial, Helvetica, sans-serif"
|
|
4840
4982
|
font-size="${fontSize}"
|
|
4841
4983
|
font-weight="${fontWeight}"
|
|
4842
4984
|
fill="${textColor}"
|
|
4843
|
-
text-anchor="
|
|
4985
|
+
text-anchor="${textAnchor}">${this.escapeXml(visibleText)}</text>
|
|
4844
4986
|
</g>`;
|
|
4987
|
+
return svg;
|
|
4845
4988
|
}
|
|
4846
4989
|
renderLink(node, pos) {
|
|
4847
4990
|
const text = String(node.props.text || "Link");
|
|
@@ -4882,28 +5025,66 @@ var SVGRenderer = class {
|
|
|
4882
5025
|
renderInput(node, pos) {
|
|
4883
5026
|
const label = String(node.props.label || "");
|
|
4884
5027
|
const placeholder = String(node.props.placeholder || "");
|
|
5028
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
5029
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
4885
5030
|
const radius = this.tokens.input.radius;
|
|
4886
5031
|
const fontSize = this.tokens.input.fontSize;
|
|
4887
5032
|
const paddingX = this.tokens.input.paddingX;
|
|
4888
5033
|
const labelOffset = this.getControlLabelOffset(label);
|
|
4889
5034
|
const controlY = pos.y + labelOffset;
|
|
4890
5035
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
4891
|
-
|
|
4892
|
-
|
|
5036
|
+
const iconSize = 16;
|
|
5037
|
+
const iconPad = 12;
|
|
5038
|
+
const iconInnerGap = 8;
|
|
5039
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
5040
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
5041
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
5042
|
+
const rightOffset = iconRightSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
5043
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : paddingX);
|
|
5044
|
+
const iconColor = this.hexToRgba(this.resolveMutedColor(), 0.8);
|
|
5045
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
5046
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5047
|
+
if (label) {
|
|
5048
|
+
svg += `
|
|
5049
|
+
<text x="${pos.x + paddingX}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
4893
5050
|
font-family="Arial, Helvetica, sans-serif"
|
|
4894
5051
|
font-size="12"
|
|
4895
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
5052
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
5053
|
+
}
|
|
5054
|
+
svg += `
|
|
4896
5055
|
<rect x="${pos.x}" y="${controlY}"
|
|
4897
5056
|
width="${pos.width}" height="${controlHeight}"
|
|
4898
5057
|
rx="${radius}"
|
|
4899
5058
|
fill="${this.renderTheme.cardBg}"
|
|
4900
5059
|
stroke="${this.renderTheme.border}"
|
|
4901
|
-
stroke-width="1"
|
|
4902
|
-
|
|
5060
|
+
stroke-width="1"/>`;
|
|
5061
|
+
if (iconLeftSvg) {
|
|
5062
|
+
svg += `
|
|
5063
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
5064
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5065
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
5066
|
+
</svg>
|
|
5067
|
+
</g>`;
|
|
5068
|
+
}
|
|
5069
|
+
if (iconRightSvg) {
|
|
5070
|
+
svg += `
|
|
5071
|
+
<g transform="translate(${pos.x + pos.width - iconPad - iconSize}, ${iconCenterY})">
|
|
5072
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5073
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
5074
|
+
</svg>
|
|
5075
|
+
</g>`;
|
|
5076
|
+
}
|
|
5077
|
+
if (placeholder) {
|
|
5078
|
+
const availPlaceholderWidth = pos.width - (iconLeftSvg ? leftOffset : paddingX) - (iconRightSvg ? rightOffset : paddingX);
|
|
5079
|
+
const visiblePlaceholder = this.truncateTextToWidth(placeholder, Math.max(0, availPlaceholderWidth), fontSize);
|
|
5080
|
+
svg += `
|
|
5081
|
+
<text x="${textX}" y="${controlY + controlHeight / 2 + 5}"
|
|
4903
5082
|
font-family="Arial, Helvetica, sans-serif"
|
|
4904
5083
|
font-size="${fontSize}"
|
|
4905
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
4906
|
-
|
|
5084
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePlaceholder)}</text>`;
|
|
5085
|
+
}
|
|
5086
|
+
svg += "\n </g>";
|
|
5087
|
+
return svg;
|
|
4907
5088
|
}
|
|
4908
5089
|
renderTopbar(node, pos) {
|
|
4909
5090
|
const title = String(node.props.title || "App");
|
|
@@ -4913,7 +5094,7 @@ var SVGRenderer = class {
|
|
|
4913
5094
|
const variant = String(node.props.variant || "default");
|
|
4914
5095
|
const accentColor = variant === "default" ? this.resolveAccentColor() : this.resolveVariantColor(variant, this.resolveAccentColor());
|
|
4915
5096
|
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
4916
|
-
const showBackground = this.parseBooleanProp(node.props.background
|
|
5097
|
+
const showBackground = this.parseBooleanProp(node.props.background, false);
|
|
4917
5098
|
const radiusMap = {
|
|
4918
5099
|
none: 0,
|
|
4919
5100
|
sm: 4,
|
|
@@ -5052,6 +5233,21 @@ var SVGRenderer = class {
|
|
|
5052
5233
|
</g>`;
|
|
5053
5234
|
output.push(svg);
|
|
5054
5235
|
}
|
|
5236
|
+
/**
|
|
5237
|
+
* Renders a yellow warning placeholder for containers with no children.
|
|
5238
|
+
* Only shown when `showDiagnostics` is enabled (editor/canvas mode).
|
|
5239
|
+
*/
|
|
5240
|
+
renderEmptyContainerDiagnostic(pos, containerType) {
|
|
5241
|
+
const diagColor = "#F59E0B";
|
|
5242
|
+
const diagBg = "#FFFBEB";
|
|
5243
|
+
const diagText = "#92400E";
|
|
5244
|
+
const minHeight = 40;
|
|
5245
|
+
const h = Math.max(pos.height, minHeight);
|
|
5246
|
+
const cx = pos.x + pos.width / 2;
|
|
5247
|
+
const cy = pos.y + h / 2;
|
|
5248
|
+
const label = containerType ? `Empty ${containerType}` : "Empty layout";
|
|
5249
|
+
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>`;
|
|
5250
|
+
}
|
|
5055
5251
|
renderSplitDecoration(node, pos, output) {
|
|
5056
5252
|
if (node.kind !== "container") return;
|
|
5057
5253
|
const gap = this.resolveSpacing(node.style.gap);
|
|
@@ -5100,7 +5296,7 @@ var SVGRenderer = class {
|
|
|
5100
5296
|
const hasCaption = caption.length > 0;
|
|
5101
5297
|
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
5102
5298
|
const showOuterBackground = this.parseBooleanProp(
|
|
5103
|
-
node.props.background
|
|
5299
|
+
node.props.background,
|
|
5104
5300
|
false
|
|
5105
5301
|
);
|
|
5106
5302
|
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
@@ -5483,30 +5679,66 @@ var SVGRenderer = class {
|
|
|
5483
5679
|
renderSelect(node, pos) {
|
|
5484
5680
|
const label = String(node.props.label || "");
|
|
5485
5681
|
const placeholder = String(node.props.placeholder || "Select...");
|
|
5682
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
5683
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
5486
5684
|
const labelOffset = this.getControlLabelOffset(label);
|
|
5487
5685
|
const controlY = pos.y + labelOffset;
|
|
5488
5686
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
5489
5687
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
font-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5688
|
+
const iconSize = 16;
|
|
5689
|
+
const iconPad = 12;
|
|
5690
|
+
const iconInnerGap = 8;
|
|
5691
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
5692
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
5693
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
5694
|
+
const chevronWidth = 20;
|
|
5695
|
+
const iconColor = this.hexToRgba(this.resolveMutedColor(), 0.8);
|
|
5696
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
5697
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
5698
|
+
if (label) {
|
|
5699
|
+
svg += `
|
|
5700
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
5701
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5702
|
+
font-size="12"
|
|
5703
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
5704
|
+
}
|
|
5705
|
+
svg += `
|
|
5706
|
+
<rect x="${pos.x}" y="${controlY}"
|
|
5707
|
+
width="${pos.width}" height="${controlHeight}"
|
|
5708
|
+
rx="6"
|
|
5709
|
+
fill="${this.renderTheme.cardBg}"
|
|
5710
|
+
stroke="${this.renderTheme.border}"
|
|
5711
|
+
stroke-width="1"/>`;
|
|
5712
|
+
if (iconLeftSvg) {
|
|
5713
|
+
svg += `
|
|
5714
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
5715
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5716
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
5717
|
+
</svg>
|
|
5718
|
+
</g>`;
|
|
5719
|
+
}
|
|
5720
|
+
if (iconRightSvg) {
|
|
5721
|
+
svg += `
|
|
5722
|
+
<g transform="translate(${pos.x + pos.width - chevronWidth - iconPad - iconSize}, ${iconCenterY})">
|
|
5723
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5724
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
5725
|
+
</svg>
|
|
5726
|
+
</g>`;
|
|
5727
|
+
}
|
|
5728
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : 12);
|
|
5729
|
+
const availPlaceholderWidth = pos.width - (iconLeftSvg ? leftOffset : 12) - chevronWidth - (iconRightSvg ? iconPad + iconSize + iconInnerGap : 0);
|
|
5730
|
+
const visiblePlaceholder = this.truncateTextToWidth(placeholder, Math.max(0, availPlaceholderWidth), 14);
|
|
5731
|
+
svg += `
|
|
5732
|
+
<text x="${textX}" y="${centerY}"
|
|
5733
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5734
|
+
font-size="14"
|
|
5735
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePlaceholder)}</text>
|
|
5736
|
+
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
5737
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
5738
|
+
font-size="16"
|
|
5508
5739
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
5509
5740
|
</g>`;
|
|
5741
|
+
return svg;
|
|
5510
5742
|
}
|
|
5511
5743
|
renderCheckbox(node, pos) {
|
|
5512
5744
|
const label = String(node.props.label || "Checkbox");
|
|
@@ -5939,7 +6171,9 @@ var SVGRenderer = class {
|
|
|
5939
6171
|
renderImage(node, pos) {
|
|
5940
6172
|
const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
|
|
5941
6173
|
const placeholderIcon = String(node.props.icon || "").trim();
|
|
6174
|
+
const variant = String(node.props.variant || "").trim();
|
|
5942
6175
|
const placeholderIconSvg = placeholder === "icon" && placeholderIcon ? getIcon(placeholderIcon) : null;
|
|
6176
|
+
const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
|
|
5943
6177
|
const aspectRatios = {
|
|
5944
6178
|
landscape: 16 / 9,
|
|
5945
6179
|
portrait: 2 / 3,
|
|
@@ -5957,30 +6191,29 @@ var SVGRenderer = class {
|
|
|
5957
6191
|
}
|
|
5958
6192
|
const offsetX = pos.x + (pos.width - iconWidth) / 2;
|
|
5959
6193
|
const offsetY = pos.y + (pos.height - iconHeight) / 2;
|
|
5960
|
-
let svg = `<g${this.getDataNodeId(node)}>
|
|
5961
|
-
<!-- Image Background -->
|
|
5962
|
-
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="#E8E8E8"/>`;
|
|
5963
6194
|
if (placeholder === "icon" && placeholderIconSvg) {
|
|
5964
|
-
const
|
|
5965
|
-
const
|
|
5966
|
-
const
|
|
5967
|
-
const
|
|
5968
|
-
const
|
|
5969
|
-
const
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
rx="${Math.max(4, badgeSize * 0.2)}"
|
|
5975
|
-
fill="rgba(255, 255, 255, 0.6)"
|
|
5976
|
-
stroke="#888"
|
|
5977
|
-
stroke-width="1"/>
|
|
6195
|
+
const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
|
|
6196
|
+
const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
|
|
6197
|
+
const variantColor = hasVariant ? this.resolveVariantColor(variant, this.renderTheme.primary) : null;
|
|
6198
|
+
const bgColor = hasVariant ? this.hexToRgba(variantColor, 0.12) : imageBg;
|
|
6199
|
+
const iconColor = hasVariant ? variantColor : this.options.theme === "dark" ? "#888888" : "#666666";
|
|
6200
|
+
const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
|
|
6201
|
+
const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
|
|
6202
|
+
const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
|
|
6203
|
+
return `<g${this.getDataNodeId(node)}>
|
|
6204
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${bgColor}" rx="4"/>
|
|
5978
6205
|
<g transform="translate(${iconOffsetX}, ${iconOffsetY})">
|
|
5979
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
6206
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
5980
6207
|
${this.extractSvgContent(placeholderIconSvg)}
|
|
5981
6208
|
</svg>
|
|
5982
|
-
</g
|
|
5983
|
-
}
|
|
6209
|
+
</g>
|
|
6210
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="none" stroke="${this.renderTheme.border}" stroke-width="1" rx="4"/>
|
|
6211
|
+
</g>`;
|
|
6212
|
+
}
|
|
6213
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
6214
|
+
<!-- Image Background -->
|
|
6215
|
+
<rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}" fill="${imageBg}"/>`;
|
|
6216
|
+
if (["landscape", "portrait", "square"].includes(placeholder)) {
|
|
5984
6217
|
const cameraCx = offsetX + iconWidth / 2;
|
|
5985
6218
|
const cameraCy = offsetY + iconHeight / 2;
|
|
5986
6219
|
const scale = Math.min(iconWidth, iconHeight) / 24;
|
|
@@ -6084,18 +6317,22 @@ var SVGRenderer = class {
|
|
|
6084
6317
|
const fontSize = 14;
|
|
6085
6318
|
const activeIndex = Number(node.props.active || 0);
|
|
6086
6319
|
const accentColor = this.resolveAccentColor();
|
|
6320
|
+
const variantProp = String(node.props.variant || "").trim();
|
|
6321
|
+
const semanticVariant = variantProp ? this.getSemanticVariantColor(variantProp) : void 0;
|
|
6322
|
+
const hasVariant = variantProp.length > 0 && (semanticVariant !== void 0 || this.colorResolver.hasColor(variantProp));
|
|
6323
|
+
const activeColor = hasVariant ? this.resolveVariantColor(variantProp, accentColor) : accentColor;
|
|
6087
6324
|
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
6088
6325
|
items.forEach((item, index) => {
|
|
6089
6326
|
const itemY = pos.y + index * itemHeight;
|
|
6090
6327
|
const isActive = index === activeIndex;
|
|
6091
|
-
const bgColor = isActive ? this.hexToRgba(
|
|
6092
|
-
const textColor = isActive ? this.hexToRgba(
|
|
6328
|
+
const bgColor = isActive ? this.hexToRgba(activeColor, 0.15) : "transparent";
|
|
6329
|
+
const textColor = isActive ? this.hexToRgba(activeColor, 0.9) : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
6093
6330
|
const fontWeight = isActive ? "500" : "400";
|
|
6094
6331
|
if (isActive) {
|
|
6095
6332
|
svg += `
|
|
6096
|
-
<rect x="${pos.x}" y="${itemY}"
|
|
6097
|
-
width="${pos.width}" height="${itemHeight}"
|
|
6098
|
-
rx="6"
|
|
6333
|
+
<rect x="${pos.x}" y="${itemY}"
|
|
6334
|
+
width="${pos.width}" height="${itemHeight}"
|
|
6335
|
+
rx="6"
|
|
6099
6336
|
fill="${bgColor}"/>`;
|
|
6100
6337
|
}
|
|
6101
6338
|
let currentX = pos.x + 12;
|
|
@@ -6104,9 +6341,10 @@ var SVGRenderer = class {
|
|
|
6104
6341
|
if (iconSvg) {
|
|
6105
6342
|
const iconSize = 16;
|
|
6106
6343
|
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
6344
|
+
const iconColor = isActive ? this.hexToRgba(activeColor, 0.9) : this.hexToRgba(this.resolveMutedColor(), 0.9);
|
|
6107
6345
|
svg += `
|
|
6108
6346
|
<g transform="translate(${currentX}, ${iconY})">
|
|
6109
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${
|
|
6347
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
6110
6348
|
${this.extractSvgContent(iconSvg)}
|
|
6111
6349
|
</svg>
|
|
6112
6350
|
</g>`;
|
|
@@ -6114,10 +6352,10 @@ var SVGRenderer = class {
|
|
|
6114
6352
|
}
|
|
6115
6353
|
}
|
|
6116
6354
|
svg += `
|
|
6117
|
-
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
6118
|
-
font-family="Arial, Helvetica, sans-serif"
|
|
6119
|
-
font-size="${fontSize}"
|
|
6120
|
-
font-weight="${fontWeight}"
|
|
6355
|
+
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
6356
|
+
font-family="Arial, Helvetica, sans-serif"
|
|
6357
|
+
font-size="${fontSize}"
|
|
6358
|
+
font-weight="${fontWeight}"
|
|
6121
6359
|
fill="${textColor}">${this.escapeXml(item)}</text>`;
|
|
6122
6360
|
});
|
|
6123
6361
|
svg += "\n </g>";
|
|
@@ -6157,9 +6395,10 @@ var SVGRenderer = class {
|
|
|
6157
6395
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
6158
6396
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
6159
6397
|
const resolvedBase = this.resolveVariantColor(variant, this.renderTheme.primary);
|
|
6160
|
-
const
|
|
6398
|
+
const isDarkMode = this.options.theme === "dark";
|
|
6399
|
+
const bgColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.85) : isDarkMode ? "rgba(48, 48, 55, 0.9)" : "rgba(226, 232, 240, 0.9)";
|
|
6161
6400
|
const iconColor = hasExplicitVariantColor ? "#FFFFFF" : this.hexToRgba(this.resolveTextColor(), 0.75);
|
|
6162
|
-
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : "rgba(100, 116, 139, 0.4)";
|
|
6401
|
+
const borderColor = hasExplicitVariantColor ? this.hexToRgba(resolvedBase, 0.7) : isDarkMode ? "rgba(75, 75, 88, 0.8)" : "rgba(100, 116, 139, 0.4)";
|
|
6163
6402
|
const opacity = disabled ? "0.5" : "1";
|
|
6164
6403
|
const iconSvg = getIcon(iconName);
|
|
6165
6404
|
const buttonSize = Math.max(
|
|
@@ -6217,14 +6456,37 @@ var SVGRenderer = class {
|
|
|
6217
6456
|
return this.colorResolver.resolveColor("muted", fallback);
|
|
6218
6457
|
}
|
|
6219
6458
|
getSemanticVariantColor(variant) {
|
|
6220
|
-
const
|
|
6459
|
+
const isDark = this.options.theme === "dark";
|
|
6460
|
+
const semantic = isDark ? {
|
|
6461
|
+
// Muted mid-range — readable on #111111 without being neon
|
|
6462
|
+
primary: this.renderTheme.primary,
|
|
6463
|
+
// already theme-aware (#60A5FA)
|
|
6464
|
+
secondary: "#7E8EA2",
|
|
6465
|
+
// desaturated slate
|
|
6466
|
+
success: "#22A06B",
|
|
6467
|
+
// muted emerald
|
|
6468
|
+
warning: "#B38010",
|
|
6469
|
+
// deep amber
|
|
6470
|
+
danger: "#CC4444",
|
|
6471
|
+
// muted red
|
|
6472
|
+
error: "#CC4444",
|
|
6473
|
+
info: "#2485AF"
|
|
6474
|
+
// muted sky
|
|
6475
|
+
} : {
|
|
6476
|
+
// Tailwind 500-level — works on white/light backgrounds
|
|
6221
6477
|
primary: this.renderTheme.primary,
|
|
6478
|
+
// #3B82F6
|
|
6222
6479
|
secondary: "#64748B",
|
|
6480
|
+
// Slate 500
|
|
6223
6481
|
success: "#10B981",
|
|
6482
|
+
// Emerald 500
|
|
6224
6483
|
warning: "#F59E0B",
|
|
6484
|
+
// Amber 500
|
|
6225
6485
|
danger: "#EF4444",
|
|
6486
|
+
// Red 500
|
|
6226
6487
|
error: "#EF4444",
|
|
6227
6488
|
info: "#0EA5E9"
|
|
6489
|
+
// Sky 500
|
|
6228
6490
|
};
|
|
6229
6491
|
return semantic[variant];
|
|
6230
6492
|
}
|
|
@@ -6501,10 +6763,11 @@ var SVGRenderer = class {
|
|
|
6501
6763
|
buildParentContainerIndex() {
|
|
6502
6764
|
this.parentContainerByChildId.clear();
|
|
6503
6765
|
Object.values(this.ir.project.nodes).forEach((node) => {
|
|
6504
|
-
if (node.kind
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6766
|
+
if (node.kind === "container") {
|
|
6767
|
+
node.children.forEach((childRef) => {
|
|
6768
|
+
this.parentContainerByChildId.set(childRef.ref, node);
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6508
6771
|
});
|
|
6509
6772
|
}
|
|
6510
6773
|
escapeXml(text) {
|
|
@@ -6685,6 +6948,19 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6685
6948
|
renderLabel(node, pos) {
|
|
6686
6949
|
return this.renderTextBlock(node, pos, String(node.props.text || "Label"), 12, 1.2);
|
|
6687
6950
|
}
|
|
6951
|
+
/**
|
|
6952
|
+
* Render image as a plain skeleton rectangle — no icon, no placeholder label,
|
|
6953
|
+
* just a filled block with the correct dimensions (aspect-ratio is preserved
|
|
6954
|
+
* by the layout engine, so pos already has the right size).
|
|
6955
|
+
*/
|
|
6956
|
+
renderImage(node, pos) {
|
|
6957
|
+
return `<g${this.getDataNodeId(node)}>
|
|
6958
|
+
<rect x="${pos.x}" y="${pos.y}"
|
|
6959
|
+
width="${pos.width}" height="${pos.height}"
|
|
6960
|
+
rx="4"
|
|
6961
|
+
fill="${this.renderTheme.border}"/>
|
|
6962
|
+
</g>`;
|
|
6963
|
+
}
|
|
6688
6964
|
/**
|
|
6689
6965
|
* Render badge as shape only (no text)
|
|
6690
6966
|
*/
|
|
@@ -6926,7 +7202,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
6926
7202
|
const hasCaption = String(node.props.caption || "").trim().length > 0;
|
|
6927
7203
|
const showOuterBorder = this.parseBooleanProp(node.props.border, false);
|
|
6928
7204
|
const showOuterBackground = this.parseBooleanProp(
|
|
6929
|
-
node.props.background
|
|
7205
|
+
node.props.background,
|
|
6930
7206
|
false
|
|
6931
7207
|
);
|
|
6932
7208
|
const showInnerBorder = this.parseBooleanProp(node.props.innerBorder, true);
|
|
@@ -7041,7 +7317,7 @@ var SkeletonSVGRenderer = class extends SVGRenderer {
|
|
|
7041
7317
|
const variant = String(node.props.variant || "default");
|
|
7042
7318
|
const accentBlock = variant === "default" ? this.renderTheme.border : this.hexToRgba(this.resolveVariantColor(variant, this.resolveAccentColor()), 0.35);
|
|
7043
7319
|
const showBorder = this.parseBooleanProp(node.props.border, false);
|
|
7044
|
-
const showBackground = this.parseBooleanProp(node.props.background
|
|
7320
|
+
const showBackground = this.parseBooleanProp(node.props.background, false);
|
|
7045
7321
|
const radiusMap = {
|
|
7046
7322
|
none: 0,
|
|
7047
7323
|
sm: 4,
|
|
@@ -7359,6 +7635,8 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7359
7635
|
const extraPadding = resolveControlHorizontalPadding(String(node.props.padding || "none"), density);
|
|
7360
7636
|
const labelOffset = this.parseBooleanProp(node.props.labelSpace, false) ? 18 : 0;
|
|
7361
7637
|
const fullWidth = this.shouldButtonFillAvailableWidth(node);
|
|
7638
|
+
const iconName = String(node.props.icon || "").trim();
|
|
7639
|
+
const iconAlign = String(node.props.iconAlign || "left").toLowerCase();
|
|
7362
7640
|
const radius = this.tokens.button.radius;
|
|
7363
7641
|
const fontSize = this.tokens.button.fontSize;
|
|
7364
7642
|
const fontWeight = this.tokens.button.fontWeight;
|
|
@@ -7368,9 +7646,14 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7368
7646
|
Math.min(resolveControlHeight(size, density), pos.height - labelOffset)
|
|
7369
7647
|
);
|
|
7370
7648
|
const buttonY = pos.y + labelOffset;
|
|
7649
|
+
const iconSvg = iconName ? getIcon(iconName) : null;
|
|
7650
|
+
const iconSize = iconSvg ? Math.round(fontSize * 1.1) : 0;
|
|
7651
|
+
const iconGap = iconSvg ? 8 : 0;
|
|
7652
|
+
const edgePad = 12;
|
|
7653
|
+
const textPad = paddingX + extraPadding;
|
|
7371
7654
|
const idealTextWidth = text.length * fontSize * 0.6;
|
|
7372
|
-
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (
|
|
7373
|
-
const availableTextWidth = Math.max(0, buttonWidth - (
|
|
7655
|
+
const buttonWidth = fullWidth ? Math.max(1, pos.width) : this.clampControlWidth(Math.max(idealTextWidth + (iconSvg ? iconSize + iconGap : 0) + textPad * 2, 60), pos.width);
|
|
7656
|
+
const availableTextWidth = Math.max(0, buttonWidth - textPad * 2 - (iconSvg ? iconSize + iconGap : 0));
|
|
7374
7657
|
const visibleText = this.truncateTextToWidth(text, availableTextWidth, fontSize);
|
|
7375
7658
|
const semanticBase = this.getSemanticVariantColor(variant);
|
|
7376
7659
|
const hasExplicitVariantColor = semanticBase !== void 0 || this.colorResolver.hasColor(variant);
|
|
@@ -7378,21 +7661,47 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7378
7661
|
const borderColor = variantColor;
|
|
7379
7662
|
const textColor = variantColor;
|
|
7380
7663
|
const strokeWidth = 0.5;
|
|
7381
|
-
|
|
7664
|
+
const iconOffsetY = buttonY + (buttonHeight - iconSize) / 2;
|
|
7665
|
+
const iconX = iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize : pos.x + edgePad;
|
|
7666
|
+
const textAlign = String(node.props.align || "center").toLowerCase();
|
|
7667
|
+
const sidePad = textPad + 4;
|
|
7668
|
+
let textX;
|
|
7669
|
+
let textAnchor;
|
|
7670
|
+
if (textAlign === "left") {
|
|
7671
|
+
textX = iconSvg && iconAlign === "left" ? pos.x + edgePad + iconSize + iconGap : pos.x + sidePad;
|
|
7672
|
+
textAnchor = "start";
|
|
7673
|
+
} else if (textAlign === "right") {
|
|
7674
|
+
textX = iconSvg && iconAlign === "right" ? pos.x + buttonWidth - edgePad - iconSize - iconGap : pos.x + buttonWidth - sidePad;
|
|
7675
|
+
textAnchor = "end";
|
|
7676
|
+
} else {
|
|
7677
|
+
textX = pos.x + buttonWidth / 2;
|
|
7678
|
+
textAnchor = "middle";
|
|
7679
|
+
}
|
|
7680
|
+
let svg = `<g${this.getDataNodeId(node)}>
|
|
7382
7681
|
<rect x="${pos.x}" y="${buttonY}"
|
|
7383
7682
|
width="${buttonWidth}" height="${buttonHeight}"
|
|
7384
7683
|
rx="${radius}"
|
|
7385
7684
|
fill="none"
|
|
7386
7685
|
stroke="${borderColor}"
|
|
7387
7686
|
stroke-width="${strokeWidth}"
|
|
7388
|
-
filter="url(#sketch-rough)"
|
|
7389
|
-
|
|
7687
|
+
filter="url(#sketch-rough)"/>`;
|
|
7688
|
+
if (iconSvg) {
|
|
7689
|
+
svg += `
|
|
7690
|
+
<g transform="translate(${iconX}, ${iconOffsetY})">
|
|
7691
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${textColor}" stroke-width="0.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7692
|
+
${this.extractSvgContent(iconSvg)}
|
|
7693
|
+
</svg>
|
|
7694
|
+
</g>`;
|
|
7695
|
+
}
|
|
7696
|
+
svg += `
|
|
7697
|
+
<text x="${textX}" y="${buttonY + buttonHeight / 2 + fontSize * 0.35}"
|
|
7390
7698
|
font-family="${this.fontFamily}"
|
|
7391
7699
|
font-size="${fontSize}"
|
|
7392
7700
|
font-weight="${fontWeight}"
|
|
7393
7701
|
fill="${textColor}"
|
|
7394
|
-
text-anchor="
|
|
7702
|
+
text-anchor="${textAnchor}">${this.escapeXml(visibleText)}</text>
|
|
7395
7703
|
</g>`;
|
|
7704
|
+
return svg;
|
|
7396
7705
|
}
|
|
7397
7706
|
/**
|
|
7398
7707
|
* Render badge with colored border instead of fill
|
|
@@ -7517,29 +7826,67 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7517
7826
|
renderInput(node, pos) {
|
|
7518
7827
|
const label = String(node.props.label || "");
|
|
7519
7828
|
const placeholder = String(node.props.placeholder || "");
|
|
7829
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
7830
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
7520
7831
|
const radius = this.tokens.input.radius;
|
|
7521
7832
|
const fontSize = this.tokens.input.fontSize;
|
|
7522
7833
|
const paddingX = this.tokens.input.paddingX;
|
|
7523
7834
|
const labelOffset = this.getControlLabelOffset(label);
|
|
7524
7835
|
const controlY = pos.y + labelOffset;
|
|
7525
7836
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
7526
|
-
|
|
7527
|
-
|
|
7837
|
+
const iconSize = 16;
|
|
7838
|
+
const iconPad = 12;
|
|
7839
|
+
const iconInnerGap = 8;
|
|
7840
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
7841
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
7842
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
7843
|
+
const rightOffset = iconRightSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
7844
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : paddingX);
|
|
7845
|
+
const iconColor = "#888888";
|
|
7846
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
7847
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
7848
|
+
if (label) {
|
|
7849
|
+
svg += `
|
|
7850
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
7528
7851
|
font-family="${this.fontFamily}"
|
|
7529
7852
|
font-size="12"
|
|
7530
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
7853
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
7854
|
+
}
|
|
7855
|
+
svg += `
|
|
7531
7856
|
<rect x="${pos.x}" y="${controlY}"
|
|
7532
7857
|
width="${pos.width}" height="${controlHeight}"
|
|
7533
7858
|
rx="${radius}"
|
|
7534
7859
|
fill="${this.renderTheme.cardBg}"
|
|
7535
7860
|
stroke="#2D3748"
|
|
7536
7861
|
stroke-width="0.5"
|
|
7537
|
-
filter="url(#sketch-rough)"
|
|
7538
|
-
|
|
7862
|
+
filter="url(#sketch-rough)"/>`;
|
|
7863
|
+
if (iconLeftSvg) {
|
|
7864
|
+
svg += `
|
|
7865
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
7866
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7867
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
7868
|
+
</svg>
|
|
7869
|
+
</g>`;
|
|
7870
|
+
}
|
|
7871
|
+
if (iconRightSvg) {
|
|
7872
|
+
svg += `
|
|
7873
|
+
<g transform="translate(${pos.x + pos.width - iconPad - iconSize}, ${iconCenterY})">
|
|
7874
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
7875
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
7876
|
+
</svg>
|
|
7877
|
+
</g>`;
|
|
7878
|
+
}
|
|
7879
|
+
if (placeholder) {
|
|
7880
|
+
const availWidth = pos.width - (iconLeftSvg ? leftOffset : paddingX) - (iconRightSvg ? rightOffset : paddingX);
|
|
7881
|
+
const visiblePh = this.truncateTextToWidth(placeholder, Math.max(0, availWidth), fontSize);
|
|
7882
|
+
svg += `
|
|
7883
|
+
<text x="${textX}" y="${controlY + controlHeight / 2 + 5}"
|
|
7539
7884
|
font-family="${this.fontFamily}"
|
|
7540
7885
|
font-size="${fontSize}"
|
|
7541
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
7542
|
-
|
|
7886
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePh)}</text>`;
|
|
7887
|
+
}
|
|
7888
|
+
svg += "\n </g>";
|
|
7889
|
+
return svg;
|
|
7543
7890
|
}
|
|
7544
7891
|
/**
|
|
7545
7892
|
* Render textarea with thicker border
|
|
@@ -7793,31 +8140,67 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
7793
8140
|
renderSelect(node, pos) {
|
|
7794
8141
|
const label = String(node.props.label || "");
|
|
7795
8142
|
const placeholder = String(node.props.placeholder || "Select...");
|
|
8143
|
+
const iconLeftName = String(node.props.iconLeft || "").trim();
|
|
8144
|
+
const iconRightName = String(node.props.iconRight || "").trim();
|
|
7796
8145
|
const labelOffset = this.getControlLabelOffset(label);
|
|
7797
8146
|
const controlY = pos.y + labelOffset;
|
|
7798
8147
|
const controlHeight = Math.max(16, pos.height - labelOffset);
|
|
7799
8148
|
const centerY = controlY + controlHeight / 2 + 5;
|
|
7800
|
-
|
|
7801
|
-
|
|
8149
|
+
const iconSize = 16;
|
|
8150
|
+
const iconPad = 12;
|
|
8151
|
+
const iconInnerGap = 8;
|
|
8152
|
+
const iconLeftSvg = iconLeftName ? getIcon(iconLeftName) : null;
|
|
8153
|
+
const iconRightSvg = iconRightName ? getIcon(iconRightName) : null;
|
|
8154
|
+
const leftOffset = iconLeftSvg ? iconPad + iconSize + iconInnerGap : 0;
|
|
8155
|
+
const chevronWidth = 20;
|
|
8156
|
+
const iconColor = "#888888";
|
|
8157
|
+
const iconCenterY = controlY + (controlHeight - iconSize) / 2;
|
|
8158
|
+
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
8159
|
+
if (label) {
|
|
8160
|
+
svg += `
|
|
8161
|
+
<text x="${pos.x}" y="${this.getControlLabelBaselineY(pos.y)}"
|
|
7802
8162
|
font-family="${this.fontFamily}"
|
|
7803
8163
|
font-size="12"
|
|
7804
|
-
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text
|
|
8164
|
+
fill="${this.renderTheme.text}">${this.escapeXml(label)}</text>`;
|
|
8165
|
+
}
|
|
8166
|
+
svg += `
|
|
7805
8167
|
<rect x="${pos.x}" y="${controlY}"
|
|
7806
8168
|
width="${pos.width}" height="${controlHeight}"
|
|
7807
8169
|
rx="6"
|
|
7808
8170
|
fill="${this.renderTheme.cardBg}"
|
|
7809
8171
|
stroke="#2D3748"
|
|
7810
8172
|
stroke-width="0.5"
|
|
7811
|
-
filter="url(#sketch-rough)"
|
|
7812
|
-
|
|
8173
|
+
filter="url(#sketch-rough)"/>`;
|
|
8174
|
+
if (iconLeftSvg) {
|
|
8175
|
+
svg += `
|
|
8176
|
+
<g transform="translate(${pos.x + iconPad}, ${iconCenterY})">
|
|
8177
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8178
|
+
${this.extractSvgContent(iconLeftSvg)}
|
|
8179
|
+
</svg>
|
|
8180
|
+
</g>`;
|
|
8181
|
+
}
|
|
8182
|
+
if (iconRightSvg) {
|
|
8183
|
+
svg += `
|
|
8184
|
+
<g transform="translate(${pos.x + pos.width - chevronWidth - iconPad - iconSize}, ${iconCenterY})">
|
|
8185
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8186
|
+
${this.extractSvgContent(iconRightSvg)}
|
|
8187
|
+
</svg>
|
|
8188
|
+
</g>`;
|
|
8189
|
+
}
|
|
8190
|
+
const textX = pos.x + (iconLeftSvg ? leftOffset : 12);
|
|
8191
|
+
const availWidth = pos.width - (iconLeftSvg ? leftOffset : 12) - chevronWidth - (iconRightSvg ? iconPad + iconSize + iconInnerGap : 0);
|
|
8192
|
+
const visiblePh = this.truncateTextToWidth(placeholder, Math.max(0, availWidth), 14);
|
|
8193
|
+
svg += `
|
|
8194
|
+
<text x="${textX}" y="${centerY}"
|
|
7813
8195
|
font-family="${this.fontFamily}"
|
|
7814
8196
|
font-size="14"
|
|
7815
|
-
fill="${this.renderTheme.textMuted}">${this.escapeXml(
|
|
8197
|
+
fill="${this.renderTheme.textMuted}">${this.escapeXml(visiblePh)}</text>
|
|
7816
8198
|
<text x="${pos.x + pos.width - 20}" y="${centerY}"
|
|
7817
8199
|
font-family="${this.fontFamily}"
|
|
7818
8200
|
font-size="16"
|
|
7819
8201
|
fill="${this.renderTheme.textMuted}">\u25BC</text>
|
|
7820
8202
|
</g>`;
|
|
8203
|
+
return svg;
|
|
7821
8204
|
}
|
|
7822
8205
|
/**
|
|
7823
8206
|
* Render checkbox with sketch filter and Comic Sans
|
|
@@ -8216,44 +8599,43 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
8216
8599
|
renderImage(node, pos) {
|
|
8217
8600
|
const placeholder = String(node.props.placeholder || "landscape").toLowerCase();
|
|
8218
8601
|
const iconType = String(node.props.icon || "").trim();
|
|
8602
|
+
const variant = String(node.props.variant || "").trim();
|
|
8219
8603
|
const iconSvg = placeholder === "icon" && iconType.length > 0 ? getIcon(iconType) : null;
|
|
8604
|
+
const imageBg = this.options.theme === "dark" ? "#2A2A2A" : "#E8E8E8";
|
|
8220
8605
|
if (iconSvg) {
|
|
8221
|
-
const
|
|
8222
|
-
const
|
|
8223
|
-
const
|
|
8224
|
-
const
|
|
8225
|
-
const
|
|
8226
|
-
const
|
|
8606
|
+
const semanticBase = variant ? this.getSemanticVariantColor(variant) : void 0;
|
|
8607
|
+
const hasVariant = variant.length > 0 && (semanticBase !== void 0 || this.colorResolver.hasColor(variant));
|
|
8608
|
+
const variantColor = hasVariant ? this.resolveVariantColor(variant, this.renderTheme.primary) : null;
|
|
8609
|
+
const bgColor = hasVariant ? this.hexToRgba(variantColor, 0.12) : imageBg;
|
|
8610
|
+
const iconColor = hasVariant ? variantColor : "#666666";
|
|
8611
|
+
const iconSize = Math.max(16, Math.min(pos.width, pos.height) * 0.6);
|
|
8612
|
+
const iconOffsetX = pos.x + (pos.width - iconSize) / 2;
|
|
8613
|
+
const iconOffsetY = pos.y + (pos.height - iconSize) / 2;
|
|
8227
8614
|
return `<g${this.getDataNodeId(node)}>
|
|
8228
|
-
<!-- Image Background -->
|
|
8229
8615
|
<rect x="${pos.x}" y="${pos.y}"
|
|
8230
8616
|
width="${pos.width}" height="${pos.height}"
|
|
8231
|
-
fill="
|
|
8232
|
-
stroke="#2D3748"
|
|
8233
|
-
stroke-width="0.5"
|
|
8617
|
+
fill="${bgColor}"
|
|
8234
8618
|
rx="4"
|
|
8235
8619
|
filter="url(#sketch-rough)"/>
|
|
8236
|
-
|
|
8237
|
-
<!-- Custom Icon Placeholder -->
|
|
8238
|
-
<rect x="${badgeX}" y="${badgeY}"
|
|
8239
|
-
width="${badgeSize}" height="${badgeSize}"
|
|
8240
|
-
rx="${Math.max(4, badgeSize * 0.2)}"
|
|
8241
|
-
fill="none"
|
|
8242
|
-
stroke="#2D3748"
|
|
8243
|
-
stroke-width="0.5"
|
|
8244
|
-
filter="url(#sketch-rough)"/>
|
|
8245
8620
|
<g transform="translate(${iconOffsetX}, ${iconOffsetY})">
|
|
8246
|
-
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="
|
|
8621
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8247
8622
|
${this.extractSvgContent(iconSvg)}
|
|
8248
8623
|
</svg>
|
|
8249
8624
|
</g>
|
|
8625
|
+
<rect x="${pos.x}" y="${pos.y}"
|
|
8626
|
+
width="${pos.width}" height="${pos.height}"
|
|
8627
|
+
fill="none"
|
|
8628
|
+
stroke="#2D3748"
|
|
8629
|
+
stroke-width="0.5"
|
|
8630
|
+
rx="4"
|
|
8631
|
+
filter="url(#sketch-rough)"/>
|
|
8250
8632
|
</g>`;
|
|
8251
8633
|
}
|
|
8252
8634
|
return `<g${this.getDataNodeId(node)}>
|
|
8253
8635
|
<!-- Image Background -->
|
|
8254
8636
|
<rect x="${pos.x}" y="${pos.y}"
|
|
8255
8637
|
width="${pos.width}" height="${pos.height}"
|
|
8256
|
-
fill="
|
|
8638
|
+
fill="${imageBg}"
|
|
8257
8639
|
stroke="#2D3748"
|
|
8258
8640
|
stroke-width="0.5"
|
|
8259
8641
|
rx="4"
|
|
@@ -8308,17 +8690,23 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
8308
8690
|
*/
|
|
8309
8691
|
renderSidebarMenu(node, pos) {
|
|
8310
8692
|
const itemsStr = String(node.props.items || "Item 1,Item 2,Item 3");
|
|
8693
|
+
const iconsStr = String(node.props.icons || "");
|
|
8311
8694
|
const items = itemsStr.split(",").map((s) => s.trim());
|
|
8695
|
+
const icons = iconsStr ? iconsStr.split(",").map((s) => s.trim()) : [];
|
|
8312
8696
|
const itemHeight = 40;
|
|
8313
8697
|
const fontSize = 14;
|
|
8314
8698
|
const activeIndex = Number(node.props.active || 0);
|
|
8315
8699
|
const accentColor = this.resolveAccentColor();
|
|
8700
|
+
const variantProp = String(node.props.variant || "").trim();
|
|
8701
|
+
const semanticVariant = variantProp ? this.getSemanticVariantColor(variantProp) : void 0;
|
|
8702
|
+
const hasVariant = variantProp.length > 0 && (semanticVariant !== void 0 || this.colorResolver.hasColor(variantProp));
|
|
8703
|
+
const activeColor = hasVariant ? this.resolveVariantColor(variantProp, accentColor) : accentColor;
|
|
8316
8704
|
let svg = `<g${this.getDataNodeId(node)}>`;
|
|
8317
8705
|
items.forEach((item, index) => {
|
|
8318
8706
|
const itemY = pos.y + index * itemHeight;
|
|
8319
8707
|
const isActive = index === activeIndex;
|
|
8320
|
-
const bgColor = isActive ? this.hexToRgba(
|
|
8321
|
-
const textColor = isActive ?
|
|
8708
|
+
const bgColor = isActive ? this.hexToRgba(activeColor, 0.15) : "transparent";
|
|
8709
|
+
const textColor = isActive ? activeColor : this.resolveTextColor();
|
|
8322
8710
|
const fontWeight = isActive ? "500" : "400";
|
|
8323
8711
|
if (isActive) {
|
|
8324
8712
|
svg += `
|
|
@@ -8328,8 +8716,24 @@ var SketchSVGRenderer = class extends SVGRenderer {
|
|
|
8328
8716
|
fill="${bgColor}"
|
|
8329
8717
|
filter="url(#sketch-rough)"/>`;
|
|
8330
8718
|
}
|
|
8719
|
+
let currentX = pos.x + 12;
|
|
8720
|
+
if (icons[index]) {
|
|
8721
|
+
const iconSvg = getIcon(icons[index]);
|
|
8722
|
+
if (iconSvg) {
|
|
8723
|
+
const iconSize = 16;
|
|
8724
|
+
const iconY = itemY + (itemHeight - iconSize) / 2;
|
|
8725
|
+
const iconColor = isActive ? activeColor : this.resolveMutedColor();
|
|
8726
|
+
svg += `
|
|
8727
|
+
<g transform="translate(${currentX}, ${iconY})">
|
|
8728
|
+
<svg width="${iconSize}" height="${iconSize}" viewBox="0 0 24 24" fill="none" stroke="${iconColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
8729
|
+
${this.extractSvgContent(iconSvg)}
|
|
8730
|
+
</svg>
|
|
8731
|
+
</g>`;
|
|
8732
|
+
currentX += iconSize + 8;
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8331
8735
|
svg += `
|
|
8332
|
-
<text x="${
|
|
8736
|
+
<text x="${currentX}" y="${itemY + itemHeight / 2 + 5}"
|
|
8333
8737
|
font-family="${this.fontFamily}"
|
|
8334
8738
|
font-size="${fontSize}"
|
|
8335
8739
|
font-weight="${fontWeight}"
|