Skip to content

Commit 2cae67f

Browse files
committed
fix(tsx-generator): use _props param in editorPreview when no label prop
1 parent 39031ef commit 2cae67f

1 file changed

Lines changed: 67 additions & 7 deletions

File tree

packages/pluggable-widgets-mcp/src/generators/tsx-generator.ts

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export interface TsxGeneratorResult {
2222
/** Generated main component content (src/[Widget].tsx) */
2323
mainComponent?: string;
2424

25+
/** Generated editor preview content (src/[Widget].editorPreview.tsx) */
26+
editorPreview?: string;
27+
2528
/** Detected or specified widget pattern */
2629
pattern?: WidgetPattern;
2730

@@ -96,7 +99,9 @@ function generateImports(widgetName: string, properties: PropertyDefinition[], p
9699
p => p.type === "attribute" && p.attributeTypes?.some(t => ["Integer", "Long", "Decimal"].includes(t))
97100
);
98101

99-
if (hasAction || hasAttribute) {
102+
// useCallback is needed for action handlers (all patterns) and attribute setValue
103+
// callbacks (only input/dataList patterns — button/display/container don't call setValue)
104+
if (hasAction || (hasAttribute && (pattern === "input" || pattern === "dataList"))) {
100105
reactImports.add("useCallback");
101106
}
102107
if (pattern === "container") {
@@ -127,8 +132,9 @@ function generateImports(widgetName: string, properties: PropertyDefinition[], p
127132
imports.push('import { ValueStatus } from "mendix";');
128133
}
129134

130-
// Big.js for numeric attributes (Integer, Long, Decimal use Big internally)
131-
if (hasIntegerAttribute) {
135+
// Big.js is only needed by the input pattern — it's the only pattern that calls
136+
// attribute.setValue() with a Big value. Other patterns don't write back to attributes.
137+
if (hasIntegerAttribute && pattern === "input") {
132138
imports.push('import Big from "big.js";');
133139
}
134140

@@ -306,10 +312,10 @@ function generateInputPattern(widgetName: string, properties: PropertyDefinition
306312
const attributeProps = properties.filter(p => p.type === "attribute");
307313
const mainAttribute = attributeProps[0];
308314
const actionProps = properties.filter(p => p.type === "action");
309-
const textProps = properties.filter(p => p.type === "textTemplate" || p.type === "string");
310315

311-
// Generate destructuring
312-
const allProps = [...attributeProps, ...actionProps, ...textProps];
316+
// Generate destructuring — only include props that are actually used in the rendered input.
317+
// Text/string props are not rendered by the input element, so omit them to avoid unused-var errors.
318+
const allProps = [...attributeProps, ...actionProps];
313319
const propsToDestructure = ["class: className", "style", "tabIndex", ...allProps.map(p => p.key)];
314320

315321
// Determine input type based on attribute type
@@ -325,7 +331,7 @@ function generateInputPattern(widgetName: string, properties: PropertyDefinition
325331

326332
// Find change action
327333
const changeAction = actionProps.find(p => p.key.toLowerCase().includes("change"));
328-
const changeHandler = changeAction ? true : false;
334+
const changeHandler = !!changeAction;
329335

330336
// Determine if we need Big conversion for numeric attributes
331337
const usesBig = inputType === "number";
@@ -495,6 +501,59 @@ export default function ${widgetName}(props: ${widgetName}ContainerProps): React
495501
`;
496502
}
497503

504+
/**
505+
* Generates a Studio Pro design-mode preview component (src/[Widget].editorPreview.tsx).
506+
*
507+
* In preview mode Mendix simplifies all property types to primitives. The generated
508+
* stub picks the first "displayable" property and renders its value, so `props` is
509+
* always read and TS6133 never fires. Displayable types:
510+
* string / textTemplate / attribute → rendered as-is (falsy-safe with ||)
511+
* integer / decimal / boolean → rendered via String() with explicit null check
512+
*
513+
* Non-displayable types (action, enumeration, datasource, …) are skipped. If no
514+
* displayable property exists, _props is used as the TypeScript convention for an
515+
* intentionally unused parameter.
516+
*/
517+
export function generateEditorPreview(widgetName: string, properties: PropertyDefinition[]): string {
518+
const widgetClass = `widget-${widgetName.toLowerCase()}`;
519+
520+
const STRING_TYPES = new Set(["string", "textTemplate", "attribute"]);
521+
const NUMERIC_BOOL_TYPES = new Set(["integer", "decimal", "boolean"]);
522+
523+
// Find the first property that can produce a meaningful display value in PreviewProps
524+
const displayProp = properties.find(p => STRING_TYPES.has(p.type) || NUMERIC_BOOL_TYPES.has(p.type));
525+
526+
// Generate a type-appropriate expression so props is always read when displayProp exists
527+
let previewContent: string;
528+
if (!displayProp) {
529+
previewContent = `"[${widgetName}]"`;
530+
} else if (STRING_TYPES.has(displayProp.type)) {
531+
previewContent = `props.${displayProp.key} || "[${widgetName}]"`;
532+
} else {
533+
// integer, decimal, boolean: explicit null check because 0 and false are falsy
534+
previewContent = `props.${displayProp.key} != null ? String(props.${displayProp.key}) : "[${widgetName}]"`;
535+
}
536+
537+
// _props only when no property is displayed (TS6133: declared but never read)
538+
const previewParam = displayProp ? "props" : "_props";
539+
540+
return `import { ReactElement, createElement } from "react";
541+
import { ${widgetName}PreviewProps } from "../typings/${widgetName}Props";
542+
543+
export function preview(${previewParam}: ${widgetName}PreviewProps): ReactElement {
544+
return (
545+
<div className="${widgetClass}">
546+
{${previewContent}}
547+
</div>
548+
);
549+
}
550+
551+
export function getPreviewCss(): string {
552+
return "";
553+
}
554+
`;
555+
}
556+
498557
/**
499558
* Generates the complete widget TSX from a widget definition.
500559
*/
@@ -532,6 +591,7 @@ export function generateWidgetTsx(
532591
return {
533592
success: true,
534593
mainComponent,
594+
editorPreview: generateEditorPreview(widgetName, properties),
535595
pattern: detectedPattern
536596
};
537597
} catch (error) {

0 commit comments

Comments
 (0)