view-contracts 0.5.2 → 0.5.4
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/README.md +19 -0
- package/cli-contract.yaml +1 -1
- package/dist/preview.mjs +138 -138
- package/dist/preview.mjs.map +3 -3
- package/dist/view-contracts.bundle.mjs +222 -222
- package/dist/view-contracts.bundle.mjs.map +3 -3
- package/docs/cli-reference.md +1 -1
- package/package.json +1 -1
- package/renderers/compose/lowering/lowerToCompose.ts +53 -11
- package/renderers/compose/lowering/modifiers.ts +10 -0
- package/renderers/compose/renderTheme.ts +26 -7
- package/renderers/react/lowering/lowerToJsx.ts +22 -10
package/docs/cli-reference.md
CHANGED
package/package.json
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
extractClipRadiusDp,
|
|
25
25
|
extractPaddingModifiers,
|
|
26
26
|
extractTextMaxLines,
|
|
27
|
+
composeRowArrangement,
|
|
27
28
|
extractTextOverflow,
|
|
28
29
|
extractTextStyleModifiers,
|
|
29
30
|
hasFillMaxHeight,
|
|
@@ -429,11 +430,51 @@ function renderStyledBox(
|
|
|
429
430
|
].join('\n');
|
|
430
431
|
}
|
|
431
432
|
|
|
433
|
+
function isInlineDisplayVariant(
|
|
434
|
+
elementName: string,
|
|
435
|
+
variantLit: string,
|
|
436
|
+
ctx: ComposeRenderContext,
|
|
437
|
+
): boolean {
|
|
438
|
+
const merged = mergeVisualProps({
|
|
439
|
+
elementName,
|
|
440
|
+
theme: ctx.theme,
|
|
441
|
+
tokens: ctx.tokens,
|
|
442
|
+
variantLiteral: variantLit,
|
|
443
|
+
instanceLiterals: {},
|
|
444
|
+
});
|
|
445
|
+
const display = merged.display;
|
|
446
|
+
return typeof display === 'string' && display.includes('inline');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function isShrinkWrapStyledText(
|
|
450
|
+
props: Record<string, IrValue>,
|
|
451
|
+
ctx: ComposeRenderContext,
|
|
452
|
+
itemScope?: string,
|
|
453
|
+
): boolean {
|
|
454
|
+
if (ctx.cellTextBehavior) return true;
|
|
455
|
+
const variantLit = propLiteral(props, 'variant');
|
|
456
|
+
if (typeof variantLit === 'string') {
|
|
457
|
+
return isInlineDisplayVariant('Text', variantLit, ctx);
|
|
458
|
+
}
|
|
459
|
+
if (isDynamicVariant(props)) {
|
|
460
|
+
const v = props.variant;
|
|
461
|
+
if (typeof v === 'object' && v !== null && 'kind' in v && v.kind === 'concat') {
|
|
462
|
+
const concat = v as IrConcat;
|
|
463
|
+
const first = concat.parts[0];
|
|
464
|
+
if (first === 'badge-') return true;
|
|
465
|
+
if (typeof first === 'string' && first.startsWith('badge-')) return true;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
|
|
432
471
|
function formatTextComposable(
|
|
433
472
|
pad: string,
|
|
434
473
|
content: string,
|
|
435
474
|
modifiers: string[],
|
|
436
475
|
ctx: ComposeRenderContext,
|
|
476
|
+
textProps?: Record<string, IrValue>,
|
|
477
|
+
itemScope?: string,
|
|
437
478
|
): string {
|
|
438
479
|
const paddingMods = extractPaddingModifiers(modifiers);
|
|
439
480
|
const runtimeStyleExpr = extractApplyVcStyleExpr(applyModifiers('Modifier', modifiers));
|
|
@@ -464,7 +505,8 @@ function formatTextComposable(
|
|
|
464
505
|
const textLine = `${pad} Text(text = ${content}${maxLinesParam}${overflowParam}${styleParam})`;
|
|
465
506
|
|
|
466
507
|
if (runtimeStyleExpr) {
|
|
467
|
-
const
|
|
508
|
+
const shrinkWrap = textProps ? isShrinkWrapStyledText(textProps, ctx, itemScope) : ctx.cellTextBehavior === true;
|
|
509
|
+
const styleBase = shrinkWrap ? 'Modifier' : 'Modifier.fillMaxWidth()';
|
|
468
510
|
const styleModifier = `${styleBase}.applyVcStyle(${runtimeStyleExpr})`;
|
|
469
511
|
if (paddingMods.length > 0) {
|
|
470
512
|
const outerModifier = applyModifiers(modifierBase, [...layoutMods, ...paddingMods]);
|
|
@@ -670,7 +712,7 @@ function renderLeaf(
|
|
|
670
712
|
|
|
671
713
|
if (entry.leaf === 'text') {
|
|
672
714
|
const content = renderTextContent(node.children, ctx, itemScope) ?? '""';
|
|
673
|
-
return formatTextComposable(pad, content, modifiers, ctx);
|
|
715
|
+
return formatTextComposable(pad, content, modifiers, ctx, node.props, itemScope);
|
|
674
716
|
}
|
|
675
717
|
if (entry.leaf === 'button') {
|
|
676
718
|
return renderVcButton(node, level, itemScope, ctx, modifiers);
|
|
@@ -763,7 +805,9 @@ function renderLeaf(
|
|
|
763
805
|
return `${pad}Spacer(modifier = ${modifierExpr})`;
|
|
764
806
|
}
|
|
765
807
|
if (entry.leaf === 'divider') {
|
|
766
|
-
|
|
808
|
+
const axisLit = propLiteral(node.props, 'axis');
|
|
809
|
+
const divider = axisLit === 'vertical' ? 'VerticalDivider' : 'HorizontalDivider';
|
|
810
|
+
return `${pad}${divider}(modifier = ${modifierExpr})`;
|
|
767
811
|
}
|
|
768
812
|
if (entry.leaf === 'image') {
|
|
769
813
|
const src = propLiteral(node.props, 'src') ?? propLiteral(node.props, 'name');
|
|
@@ -814,13 +858,7 @@ function renderContainer(
|
|
|
814
858
|
const spacing = typeof spacingLit === 'number' ? `${spacingLit}.dp` : '0.dp';
|
|
815
859
|
const justifyLit = propLiteral(node.props, 'justify');
|
|
816
860
|
const wrapLit = propLiteral(node.props, 'wrap');
|
|
817
|
-
const arrangement = justifyLit
|
|
818
|
-
? 'Arrangement.SpaceBetween'
|
|
819
|
-
: justifyLit === 'end'
|
|
820
|
-
? 'Arrangement.End'
|
|
821
|
-
: justifyLit === 'center'
|
|
822
|
-
? 'Arrangement.Center'
|
|
823
|
-
: `Arrangement.spacedBy(${spacing})`;
|
|
861
|
+
const arrangement = composeRowArrangement(justifyLit, spacing);
|
|
824
862
|
const alignLit = propLiteral(node.props, 'align');
|
|
825
863
|
const alignItemsLit = propLiteral(node.props, 'alignItems');
|
|
826
864
|
const heightLit = propLiteral(node.props, 'height');
|
|
@@ -848,7 +886,7 @@ function renderContainer(
|
|
|
848
886
|
const childContent = wrapWithInheritedStyles(renderChildren(node.children, level + 1, renderNested, ctx, itemScope), modifiers, level + 1);
|
|
849
887
|
if (node.name === 'TableHeader') ctx.inTableHeader = prevTableHeader;
|
|
850
888
|
|
|
851
|
-
if (wrapLit === true && justifyLit !== 'between') {
|
|
889
|
+
if (wrapLit === true && justifyLit !== 'between' && justifyLit !== 'spaceBetween') {
|
|
852
890
|
return [
|
|
853
891
|
`${indent(level)}FlowRow(modifier = ${rowModifier}, horizontalArrangement = Arrangement.spacedBy(${spacing}), verticalArrangement = Arrangement.spacedBy(${spacing})) {`,
|
|
854
892
|
childContent,
|
|
@@ -987,6 +1025,8 @@ function renderContainer(
|
|
|
987
1025
|
JSON.stringify((child as IrTextNode).value),
|
|
988
1026
|
dedupeModifiers([...cellTextBehaviorMods(), `@textAlign(${textAlign})`, ...paddingMods, ...inheritedMods.map((m) => m)]),
|
|
989
1027
|
ctx,
|
|
1028
|
+
undefined,
|
|
1029
|
+
itemScope,
|
|
990
1030
|
);
|
|
991
1031
|
}
|
|
992
1032
|
if (child.kind === 'binding') {
|
|
@@ -995,6 +1035,8 @@ function renderContainer(
|
|
|
995
1035
|
bindingExpr(child as IrBinding, ctx, itemScope),
|
|
996
1036
|
dedupeModifiers([...cellTextBehaviorMods(), `@textAlign(${textAlign})`, ...paddingMods, ...inheritedMods.map((m) => m)]),
|
|
997
1037
|
ctx,
|
|
1038
|
+
undefined,
|
|
1039
|
+
itemScope,
|
|
998
1040
|
);
|
|
999
1041
|
}
|
|
1000
1042
|
return renderNested(child, level + 1, itemScope);
|
|
@@ -85,6 +85,16 @@ function composeSelfAlign(value: string | number | boolean): string | null {
|
|
|
85
85
|
return map[value] ?? null;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/** Map DSL justify enum to Compose Row horizontalArrangement (accepts schema + legacy aliases). */
|
|
89
|
+
export function composeRowArrangement(justifyLit: unknown, spacing: string): string {
|
|
90
|
+
if (justifyLit === 'between' || justifyLit === 'spaceBetween') return 'Arrangement.SpaceBetween';
|
|
91
|
+
if (justifyLit === 'around' || justifyLit === 'spaceAround') return 'Arrangement.SpaceAround';
|
|
92
|
+
if (justifyLit === 'spaceEvenly') return 'Arrangement.SpaceEvenly';
|
|
93
|
+
if (justifyLit === 'end') return 'Arrangement.End';
|
|
94
|
+
if (justifyLit === 'center') return 'Arrangement.Center';
|
|
95
|
+
return `Arrangement.spacedBy(${spacing})`;
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
export function composeContentAlignment(value: string): string {
|
|
89
99
|
const map: Record<string, string> = {
|
|
90
100
|
topStart: 'Alignment.TopStart',
|
|
@@ -89,7 +89,10 @@ function resolveVariantEntries(
|
|
|
89
89
|
case 'minHeight':
|
|
90
90
|
case 'maxWidth': {
|
|
91
91
|
const px = parsePx(value);
|
|
92
|
-
if (px !== null)
|
|
92
|
+
if (px !== null) {
|
|
93
|
+
const kotlinValue = property === 'fontSize' ? `${px}.sp` : `${px}.dp`;
|
|
94
|
+
entries.push({ property, kotlinValue });
|
|
95
|
+
}
|
|
93
96
|
break;
|
|
94
97
|
}
|
|
95
98
|
case 'width': {
|
|
@@ -127,6 +130,12 @@ function resolveVariantEntries(
|
|
|
127
130
|
entries.push({ property, kotlinValue: value === 'none' ? 'false' : 'true' });
|
|
128
131
|
break;
|
|
129
132
|
}
|
|
133
|
+
case 'display': {
|
|
134
|
+
if (value.includes('inline')) {
|
|
135
|
+
entries.push({ property: 'shrinkWrap', kotlinValue: 'true' });
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
130
139
|
default:
|
|
131
140
|
break;
|
|
132
141
|
}
|
|
@@ -135,13 +144,21 @@ function resolveVariantEntries(
|
|
|
135
144
|
}
|
|
136
145
|
|
|
137
146
|
const STRUCT_FIELD_ORDER = [
|
|
138
|
-
'foreground', 'background', 'padding', 'paddingH', 'paddingV', 'widthFill', 'heightFill',
|
|
147
|
+
'foreground', 'background', 'padding', 'paddingH', 'paddingV', 'widthFill', 'heightFill', 'shrinkWrap',
|
|
139
148
|
'radius', 'fontSize', 'fontWeight', 'shadow', 'borderWidth', 'borderColor', 'minHeight', 'maxWidth',
|
|
140
149
|
];
|
|
141
150
|
|
|
151
|
+
function finalizeVariantEntries(entries: ResolvedEntry[]): ResolvedEntry[] {
|
|
152
|
+
if (entries.some((entry) => entry.property === 'shrinkWrap')) {
|
|
153
|
+
return entries.filter((entry) => entry.property !== 'widthFill');
|
|
154
|
+
}
|
|
155
|
+
return entries;
|
|
156
|
+
}
|
|
157
|
+
|
|
142
158
|
function emitVariantStyleInit(entries: ResolvedEntry[]): string {
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
const finalized = finalizeVariantEntries(entries);
|
|
160
|
+
if (finalized.length === 0) return 'VcVariantStyle()';
|
|
161
|
+
const sorted = [...finalized].sort(
|
|
145
162
|
(a, b) => STRUCT_FIELD_ORDER.indexOf(a.property) - STRUCT_FIELD_ORDER.indexOf(b.property),
|
|
146
163
|
);
|
|
147
164
|
return `VcVariantStyle(${sorted.map((e) => `${e.property} = ${e.kotlinValue}`).join(', ')})`;
|
|
@@ -183,6 +200,7 @@ export function renderComposeTheme(theme: ThemeIR, tokens: TokenIR): string {
|
|
|
183
200
|
lines.push('import androidx.compose.ui.text.TextStyle');
|
|
184
201
|
lines.push('import androidx.compose.ui.text.font.FontWeight');
|
|
185
202
|
lines.push('import androidx.compose.ui.unit.Dp');
|
|
203
|
+
lines.push('import androidx.compose.ui.unit.TextUnit');
|
|
186
204
|
lines.push('import androidx.compose.ui.unit.dp');
|
|
187
205
|
lines.push('import androidx.compose.ui.unit.sp');
|
|
188
206
|
lines.push('');
|
|
@@ -194,8 +212,9 @@ export function renderComposeTheme(theme: ThemeIR, tokens: TokenIR): string {
|
|
|
194
212
|
lines.push(' val paddingV: Dp? = null,');
|
|
195
213
|
lines.push(' val widthFill: Boolean? = null,');
|
|
196
214
|
lines.push(' val heightFill: Boolean? = null,');
|
|
215
|
+
lines.push(' val shrinkWrap: Boolean? = null,');
|
|
197
216
|
lines.push(' val radius: Dp? = null,');
|
|
198
|
-
lines.push(' val fontSize:
|
|
217
|
+
lines.push(' val fontSize: TextUnit? = null,');
|
|
199
218
|
lines.push(' val fontWeight: FontWeight? = null,');
|
|
200
219
|
lines.push(' val shadow: Boolean? = null,');
|
|
201
220
|
lines.push(' val borderWidth: Dp? = null,');
|
|
@@ -207,7 +226,7 @@ export function renderComposeTheme(theme: ThemeIR, tokens: TokenIR): string {
|
|
|
207
226
|
lines.push('fun VcVariantStyle.asTextStyle(): TextStyle {');
|
|
208
227
|
lines.push(' var style = TextStyle()');
|
|
209
228
|
lines.push(' foreground?.let { style = style.copy(color = it) }');
|
|
210
|
-
lines.push(' fontSize?.let { style = style.copy(fontSize = it
|
|
229
|
+
lines.push(' fontSize?.let { style = style.copy(fontSize = it) }');
|
|
211
230
|
lines.push(' fontWeight?.let { style = style.copy(fontWeight = it) }');
|
|
212
231
|
lines.push(' return style');
|
|
213
232
|
lines.push('}');
|
|
@@ -223,7 +242,7 @@ export function renderComposeTheme(theme: ThemeIR, tokens: TokenIR): string {
|
|
|
223
242
|
lines.push('');
|
|
224
243
|
lines.push('fun Modifier.applyVcStyle(style: VcVariantStyle): Modifier {');
|
|
225
244
|
lines.push(' var out = this');
|
|
226
|
-
lines.push(' if (style.widthFill == true) out = out.fillMaxWidth()');
|
|
245
|
+
lines.push(' if (style.widthFill == true && style.shrinkWrap != true) out = out.fillMaxWidth()');
|
|
227
246
|
lines.push(' if (style.heightFill == true) out = out.fillMaxHeight()');
|
|
228
247
|
lines.push(' if (style.shadow == true) out = out.then(vcSoftShadowModifier(style.radius ?: 0.dp))');
|
|
229
248
|
lines.push(' style.background?.let { out = out.background(it, RoundedCornerShape(style.radius ?: 0.dp)) }');
|
|
@@ -109,19 +109,19 @@ function actionHandler(props: Record<string, IrValue>, event: 'onClick' | 'onSub
|
|
|
109
109
|
return `onClick={() => { void dispatchAction(${expr}); }}`;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
function onChangeHandler(props: Record<string, IrValue
|
|
112
|
+
function onChangeHandler(props: Record<string, IrValue>, targetProp = 'e.target.value'): string | undefined {
|
|
113
113
|
const onChange = props.onChange;
|
|
114
114
|
if (!onChange || typeof onChange !== 'object' || !('kind' in onChange) || onChange.kind !== 'action') {
|
|
115
115
|
return undefined;
|
|
116
116
|
}
|
|
117
117
|
const action = onChange as IrAction;
|
|
118
118
|
if (!action.params || Object.keys(action.params).length === 0) {
|
|
119
|
-
return `onChange={(e) => { void dispatchAction({ name: ${JSON.stringify(action.name)}, params: { value:
|
|
119
|
+
return `onChange={(e) => { void dispatchAction({ name: ${JSON.stringify(action.name)}, params: { value: ${targetProp} } } as ActionDescriptor); }}`;
|
|
120
120
|
}
|
|
121
121
|
const existingParams = Object.entries(action.params)
|
|
122
122
|
.map(([key, val]) => `${key}: ${renderValue(val, 0)}`)
|
|
123
123
|
.join(', ');
|
|
124
|
-
return `onChange={(e) => { void dispatchAction({ name: ${JSON.stringify(action.name)}, params: { ${existingParams}, value:
|
|
124
|
+
return `onChange={(e) => { void dispatchAction({ name: ${JSON.stringify(action.name)}, params: { ${existingParams}, value: ${targetProp} } } as ActionDescriptor); }}`;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
function componentToKebab(name: string): string {
|
|
@@ -197,10 +197,13 @@ function emitFieldWrap(inner: string, props: Record<string, IrValue>, level: num
|
|
|
197
197
|
function emitCheckbox(props: Record<string, IrValue>, level: number): string {
|
|
198
198
|
const name = propExpr(props, 'name') ?? '""';
|
|
199
199
|
const label = propExpr(props, 'label') ?? '""';
|
|
200
|
-
const checked = props.checked ? `defaultChecked={${propExpr(props, 'checked')}}` : '';
|
|
201
200
|
const disabled = props.enabled ? `disabled={${propExpr(props, 'enabled')} === false}` : '';
|
|
201
|
+
const changeAttr = onChangeHandler(props, 'e.target.checked') ?? '';
|
|
202
|
+
const checkedAttr = props.checked
|
|
203
|
+
? (changeAttr ? `checked={${propExpr(props, 'checked')}}` : `defaultChecked={${propExpr(props, 'checked')}}`)
|
|
204
|
+
: '';
|
|
202
205
|
return `${indent(level)}<label className="vc-checkbox">
|
|
203
|
-
${indent(level + 1)}<input type="checkbox" name={${name}} ${
|
|
206
|
+
${indent(level + 1)}<input type="checkbox" name={${name}} ${checkedAttr} ${disabled} ${changeAttr} />
|
|
204
207
|
${indent(level + 1)}<span>{${label}}</span>
|
|
205
208
|
${indent(level)}</label>`;
|
|
206
209
|
}
|
|
@@ -209,10 +212,13 @@ function emitRadio(props: Record<string, IrValue>, level: number): string {
|
|
|
209
212
|
const name = propExpr(props, 'binding') ?? propExpr(props, 'name') ?? '""';
|
|
210
213
|
const value = propExpr(props, 'value') ?? '""';
|
|
211
214
|
const label = propExpr(props, 'label') ?? '""';
|
|
212
|
-
const checked = props.checked ? `defaultChecked={${propExpr(props, 'checked')}}` : '';
|
|
213
215
|
const disabled = props.enabled ? `disabled={${propExpr(props, 'enabled')} === false}` : '';
|
|
216
|
+
const changeAttr = onChangeHandler(props, 'e.target.value') ?? '';
|
|
217
|
+
const checkedAttr = props.checked
|
|
218
|
+
? (changeAttr ? `checked={${propExpr(props, 'checked')}}` : `defaultChecked={${propExpr(props, 'checked')}}`)
|
|
219
|
+
: '';
|
|
214
220
|
return `${indent(level)}<label className="vc-radio">
|
|
215
|
-
${indent(level + 1)}<input type="radio" name={${name}} value={${value}} ${
|
|
221
|
+
${indent(level + 1)}<input type="radio" name={${name}} value={${value}} ${checkedAttr} ${disabled} ${changeAttr} />
|
|
216
222
|
${indent(level + 1)}<span>{${label}}</span>
|
|
217
223
|
${indent(level)}</label>`;
|
|
218
224
|
}
|
|
@@ -220,10 +226,13 @@ ${indent(level)}</label>`;
|
|
|
220
226
|
function emitSwitch(props: Record<string, IrValue>, level: number): string {
|
|
221
227
|
const name = propExpr(props, 'binding') ?? propExpr(props, 'name') ?? '""';
|
|
222
228
|
const label = propExpr(props, 'label') ?? '""';
|
|
223
|
-
const checked = props.checked ? `defaultChecked={${propExpr(props, 'checked')}}` : '';
|
|
224
229
|
const disabled = props.enabled ? `disabled={${propExpr(props, 'enabled')} === false}` : '';
|
|
230
|
+
const changeAttr = onChangeHandler(props, 'e.target.checked') ?? '';
|
|
231
|
+
const checkedAttr = props.checked
|
|
232
|
+
? (changeAttr ? `checked={${propExpr(props, 'checked')}}` : `defaultChecked={${propExpr(props, 'checked')}}`)
|
|
233
|
+
: '';
|
|
225
234
|
return `${indent(level)}<label className="vc-switch">
|
|
226
|
-
${indent(level + 1)}<input type="checkbox" role="switch" name={${name}} ${
|
|
235
|
+
${indent(level + 1)}<input type="checkbox" role="switch" name={${name}} ${checkedAttr} ${disabled} ${changeAttr} />
|
|
227
236
|
${indent(level + 1)}<span>{${label}}</span>
|
|
228
237
|
${indent(level)}</label>`;
|
|
229
238
|
}
|
|
@@ -282,7 +291,7 @@ function emitVoidInput(
|
|
|
282
291
|
}
|
|
283
292
|
if (props.name) attrs.push(`name={${propExpr(props, 'name')}}`);
|
|
284
293
|
if (props.placeholder) attrs.push(`placeholder={${propExpr(props, 'placeholder')}}`);
|
|
285
|
-
if (entry.inputType) attrs.push(`type={${propExpr(props, '
|
|
294
|
+
if (entry.inputType) attrs.push(`type={${propExpr(props, 'inputType') ?? JSON.stringify(entry.inputType)}}`);
|
|
286
295
|
if (props.required) attrs.push(`required={${propExpr(props, 'required')}}`);
|
|
287
296
|
if (props.readonly) attrs.push(`readOnly={${propExpr(props, 'readonly')}}`);
|
|
288
297
|
if (props.enabled) attrs.push(`disabled={${propExpr(props, 'enabled')} === false}`);
|
|
@@ -320,7 +329,10 @@ function emitOpenTag(
|
|
|
320
329
|
const handler = actionHandler(props, entry.actionEvent);
|
|
321
330
|
if (handler) attrs.push(handler);
|
|
322
331
|
}
|
|
332
|
+
if (props.name) attrs.push(`name={${propExpr(props, 'name')}}`);
|
|
323
333
|
if (entry.tag === 'select' || entry.tag === 'textarea' || entry.tag === 'input') {
|
|
334
|
+
if (props.required) attrs.push(`required={${propExpr(props, 'required')}}`);
|
|
335
|
+
if (props.enabled) attrs.push(`disabled={${propExpr(props, 'enabled')} === false}`);
|
|
324
336
|
const changeHandler = onChangeHandler(props);
|
|
325
337
|
if (changeHandler) {
|
|
326
338
|
if (props.value) attrs.push(`value={${propExpr(props, 'value')}}`);
|