@@ -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