@dongmingzhixiu/vuexir-compiler 0.7.8 → 0.7.9

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.
@@ -1529,17 +1529,18 @@ function emitNodeInner(node, ctx, depth, loop, useParamModifier = false, parentS
1529
1529
  }
1530
1530
  case 'image': {
1531
1531
  const source = p.src ?? '';
1532
+ const contentScale = p.objectFit === 'fill' ? 'FillBounds' : p.objectFit === 'contain' ? 'Fit' : 'Crop';
1532
1533
  const chain = modChain([...sizeModifiers(p, ctx), ...absM(p), ...clipModifiers(p), ...visualModifiers(p), ...paddingModifiers(p), ...alphaArr], pad + ' ');
1533
1534
  if (source.startsWith('asset://')) {
1534
1535
  const name = source.slice(8);
1535
- return pad + 'run { val imageContext = androidx.compose.ui.platform.LocalContext.current; val bitmap = remember { imageContext.assets.open(' + kotlinStr(name) + ').use { android.graphics.BitmapFactory.decodeStream(it) } }; if(bitmap != null) Image(bitmap = bitmap.asImageBitmap(), contentDescription = null, contentScale = androidx.compose.ui.layout.ContentScale.' + (p.objectFit === 'contain' ? 'Fit' : 'Crop') + ', modifier = Modifier' + chain + ') }';
1536
+ return pad + 'run { val imageContext = androidx.compose.ui.platform.LocalContext.current; val bitmap = remember { imageContext.assets.open(' + kotlinStr(name) + ').use { android.graphics.BitmapFactory.decodeStream(it) } }; if(bitmap != null) Image(bitmap = bitmap.asImageBitmap(), contentDescription = null, contentScale = androidx.compose.ui.layout.ContentScale.' + contentScale + ', modifier = Modifier' + chain + ') }';
1536
1537
  }
1537
1538
  if (/^data:image\/[^;]+;base64,/.test(source)) {
1538
1539
  const encoded = source.slice(source.indexOf(',') + 1);
1539
1540
  return pad + 'run {\n' + pad + ' val imageBytes = remember { android.util.Base64.decode(' + kotlinStr(encoded) + ', android.util.Base64.DEFAULT) }\n' +
1540
1541
  pad + ' val bitmap = remember { android.graphics.BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) }\n' +
1541
1542
  pad + ' if (bitmap != null) Image(bitmap = bitmap.asImageBitmap(), contentDescription = null, contentScale = androidx.compose.ui.layout.ContentScale.' +
1542
- (p.objectFit === 'contain' ? 'Fit' : 'Crop') + ', modifier = Modifier' + chain + ')\n' + pad + '}';
1543
+ contentScale + ', modifier = Modifier' + chain + ')\n' + pad + '}';
1543
1544
  }
1544
1545
  ctx.warnings.push(`图片 ${source.slice(0, 80)} 需要 data URI 资源映射,Android 未生成图片内容`);
1545
1546
  return pad + 'Box(modifier = Modifier' + chain + ')';
@@ -1292,7 +1292,9 @@ function emitNodeInner(node, ctx) {
1292
1292
  else {
1293
1293
  image = `Image(uiImage: UIImage(named: "${swiftStringEscape(source.replace(/^bundle:\/\//, ''))}") ?? UIImage()).resizable()`;
1294
1294
  }
1295
- image += props.objectFit === 'contain' ? '.scaledToFit()' : '.scaledToFill()';
1295
+ if (props.objectFit !== 'fill') {
1296
+ image += props.objectFit === 'contain' ? '.scaledToFit()' : '.scaledToFill()';
1297
+ }
1296
1298
  return pad(image) + box.map((m) => ' ' + m).join('');
1297
1299
  }
1298
1300
  case 'component': {
@@ -17,7 +17,7 @@ export type Justify = 'start' | 'center' | 'end' | 'spaceBetween' | 'spaceAround
17
17
  export type Align = 'start' | 'center' | 'end' | 'stretch';
18
18
  export type FontWeight = 'normal' | 'medium' | 'bold';
19
19
  export type TextAlign = 'left' | 'center' | 'right';
20
- export type ObjectFit = 'cover' | 'contain';
20
+ export type ObjectFit = 'cover' | 'contain' | 'fill';
21
21
  export interface IrProps {
22
22
  /** 尺寸 */
23
23
  width?: Size;
package/dist/ir/schema.js CHANGED
@@ -101,7 +101,7 @@ const PROPS_VALIDATORS = {
101
101
  lineHeight: (v) => typeof v === 'number' && v > 0,
102
102
  color: isColor,
103
103
  src: (v) => typeof v === 'string',
104
- objectFit: (v) => v === 'cover' || v === 'contain',
104
+ objectFit: (v) => v === 'cover' || v === 'contain' || v === 'fill',
105
105
  label: (v) => typeof v === 'string',
106
106
  onClick: (v) => typeof v === 'string',
107
107
  showsIndicators: (v) => typeof v === 'boolean',
@@ -122,7 +122,7 @@ function mapDeclInner(prop, value, warnings) {
122
122
  return []; // 变量声明由 token 解析器处理,不是未支持的视觉属性。
123
123
  switch (prop) {
124
124
  case 'object-fit':
125
- return v === 'contain' || v === 'cover' ? [['objectFit', v]] : null;
125
+ return v === 'contain' || v === 'cover' || v === 'fill' ? [['objectFit', v]] : null;
126
126
  case 'aspect-ratio': {
127
127
  const parts = v.split('/').map((part) => Number(part.trim()));
128
128
  const ratio = parts.length === 1 ? parts[0] : parts.length === 2 ? parts[0] / parts[1] : NaN;
@@ -2362,10 +2362,18 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
2362
2362
  const inlineTags = new Set(['span', 'b', 'strong', 'i', 'em', 'small', 'u', 'code', 'br']);
2363
2363
  const content = node.children.filter((c) => c.type !== NodeTypes.COMMENT && !(c.type === NodeTypes.TEXT && !c.content.trim()));
2364
2364
  const readable = (n) => n.type === NodeTypes.INTERPOLATION || (n.type === NodeTypes.TEXT && !!n.content.trim()) || (n.type === NodeTypes.ELEMENT && n.children.some(readable));
2365
- const inlineConditional = content.some((c) => c.type === NodeTypes.ELEMENT && c.props.some((p) => p.type === NodeTypes.DIRECTIVE && ['if', 'else-if', 'else', 'show'].includes(p.name)));
2366
- if (inlineComponentsFlag && mapping.type === 'group' && !inlineConditional && props.flexDirection === undefined && props.gridColumns === undefined && content.some(readable) && content.every((c) => c.type === NodeTypes.TEXT || c.type === NodeTypes.INTERPOLATION || (c.type === NodeTypes.ELEMENT && inlineTags.has(c.tag))))
2365
+ // 行内分支或循环需要独立节点;合并为富文本会丢弃这些动态子树。
2366
+ const hasInlineStructure = (child) => {
2367
+ if (child.type !== NodeTypes.ELEMENT)
2368
+ return false;
2369
+ return child.props.some((p) => p.type === NodeTypes.DIRECTIVE &&
2370
+ ['if', 'else-if', 'else', 'show', 'for'].includes(p.name)) ||
2371
+ (inlineTags.has(child.tag) && child.children.some(hasInlineStructure));
2372
+ };
2373
+ const inlineStructure = content.some(hasInlineStructure);
2374
+ if (inlineComponentsFlag && mapping.type === 'group' && !inlineStructure && props.flexDirection === undefined && props.gridColumns === undefined && content.some(readable) && content.every((c) => c.type === NodeTypes.TEXT || c.type === NodeTypes.INTERPOLATION || (c.type === NodeTypes.ELEMENT && inlineTags.has(c.tag))))
2367
2375
  mapping = { type: 'text' };
2368
- if (mapping.type === 'text' && inlineConditional)
2376
+ if (mapping.type === 'text' && inlineStructure)
2369
2377
  mapping = { type: 'group' };
2370
2378
  if (mapping.type === 'text' && content.length === 0 && ['i', 'span', 'b'].includes(node.tag))
2371
2379
  mapping = { type: 'spacer' };
@@ -2680,7 +2688,13 @@ function walkElement(node, classProps, comboRules, scopedRules, pseudoRules, war
2680
2688
  warnings.push(`<${node.tag}> 无子节点 → 降级为占位 spacer(R17/R77;可用 padding/height 表达留白)`);
2681
2689
  const keep = {};
2682
2690
  const src = props;
2683
- for (const k of ['width', 'height', 'widthFraction', 'widthFractionDynamic', 'flex', 'marginTop', 'background', 'gradient', 'radius', 'shadow', 'borderWidth', 'borderColor', 'padding', 'minHeight', 'minWidth', 'opacity']) {
2691
+ // 空盒仍需脱离文档流并保持叠放顺序,避免占位改变父容器布局。
2692
+ for (const k of [
2693
+ 'width', 'height', 'widthFraction', 'widthFractionDynamic', 'flex', 'marginTop',
2694
+ 'background', 'gradient', 'radius', 'shadow', 'borderWidth', 'borderColor',
2695
+ 'padding', 'minHeight', 'minWidth', 'opacity',
2696
+ 'position', 'absTop', 'absLeft', 'absRight', 'absBottom', 'zIndex',
2697
+ ]) {
2684
2698
  if (src[k] !== undefined)
2685
2699
  keep[k] = src[k];
2686
2700
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dongmingzhixiu/vuexir-compiler",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
4
4
  "description": "Vue SFC → IR → 原生源码(SwiftUI / Compose)编译器核心:纯函数、零 IO",
5
5
  "type": "module",
6
6
  "license": "MIT",