@open-feishu-card/renderer 0.3.5 → 0.4.0

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/all.css CHANGED
@@ -380,6 +380,90 @@
380
380
  font-size: 12px;
381
381
  }
382
382
 
383
+ /* ---- img_combination 多图混排(比例逆向自网页版 .universal-card-img-combination) ---- */
384
+ .universal-card-img-combination { display: flex; width: 100%; justify-content: space-between; }
385
+ .universal-card-img-combination__item { min-width: 0; overflow: hidden; }
386
+ .universal-card-img-combination__item > .universal-card-image__host { display: block; width: 100%; height: 100%; object-fit: cover; }
387
+ .universal-card-img-combination__item > .universal-card-image__placeholder { width: 100%; height: 100%; }
388
+ /* double:左窄(32.4%, 24:33) + 右宽(67.6%, 49.33:33) */
389
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(odd) {
390
+ width: 32.4%; aspect-ratio: 24 / 33;
391
+ }
392
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(2n) {
393
+ width: calc(67.6% - 4px); aspect-ratio: 49.33 / 33;
394
+ }
395
+ /* triple:左大图(66.5%, 1:1) + 右列两张小图 */
396
+ .universal-card-img-combination--triple .universal-card-img-combination__left {
397
+ width: 66.5%; aspect-ratio: 1; margin-right: 1%;
398
+ }
399
+ .universal-card-img-combination--triple .universal-card-img-combination__right {
400
+ width: 33%; display: flex; flex-direction: column; justify-content: space-between;
401
+ }
402
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item {
403
+ width: 100%; aspect-ratio: 1;
404
+ }
405
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item:first-child {
406
+ margin-bottom: 1.5%;
407
+ }
408
+ /* bisect / trisect:等宽方格换行 */
409
+ .universal-card-img-combination--bisect { flex-wrap: wrap; gap: 4px; }
410
+ .universal-card-img-combination--bisect .universal-card-img-combination__item {
411
+ width: calc(50% - 2px); aspect-ratio: 1;
412
+ }
413
+ .universal-card-img-combination--trisect { flex-wrap: wrap; justify-content: flex-start; gap: 4px; }
414
+ .universal-card-img-combination--trisect .universal-card-img-combination__item {
415
+ width: calc(33.33% - 2.67px); aspect-ratio: 1;
416
+ }
417
+
418
+ /* ---- select_img 多图选择(对齐网页版 .universal-card-select-img) ---- */
419
+ .universal-card-select-img {
420
+ width: 100%; max-width: 100%; display: grid; grid-gap: 4px; box-sizing: border-box;
421
+ }
422
+ .universal-card-select-img--stretch { grid-template-columns: 1fr; }
423
+ .universal-card-select-img--bisect { grid-template-columns: 1fr 1fr; }
424
+ .universal-card-select-img--trisect { grid-template-columns: 1fr 1fr 1fr; }
425
+ .universal-card-select-img__item-wrapper { position: relative; border-radius: 6px; }
426
+ .universal-card-select-img__item-wrapper::after {
427
+ content: " "; position: absolute; inset: 0; box-sizing: border-box;
428
+ border: 1px solid var(--N300); border-radius: 6px; pointer-events: none; z-index: 1;
429
+ }
430
+ .universal-card-select-img__item-wrapper--selected::after { border: 2px solid var(--B500); }
431
+ .universal-card-select-img__item {
432
+ position: relative; overflow: hidden; box-sizing: border-box; border-radius: 6px;
433
+ cursor: pointer; width: 100%;
434
+ }
435
+ .universal-card-select-img__item--disabled { cursor: not-allowed; }
436
+ .universal-card-select-img__item--disabled::after {
437
+ content: " "; position: absolute; inset: 0; z-index: 1;
438
+ background-color: rgba(255, 255, 255, .5);
439
+ }
440
+ .universal-card-select-img__item > .universal-card-image__host {
441
+ display: block; width: 100%; height: 100%; object-fit: cover;
442
+ }
443
+ .universal-card-select-img__item > .universal-card-image__placeholder {
444
+ width: 100%; height: 100%;
445
+ }
446
+ /* 表单内复选/单选标记(真机仅 in-form 显示;表单外用选中描边表达状态) */
447
+ .universal-card-select-img__checkbox {
448
+ position: absolute; top: 0; right: 0; z-index: 2; padding: 5px 8px; cursor: pointer;
449
+ }
450
+ .universal-card-select-img__checkbox--disabled { cursor: not-allowed; }
451
+ .universal-card-select-img__checkbox-box {
452
+ display: inline-flex; align-items: center; justify-content: center;
453
+ width: 16px; height: 16px; box-sizing: border-box; border-radius: 50%;
454
+ background-color: rgba(0, 0, 0, .15); border: 1px solid #eff0f1;
455
+ box-shadow: 0 0 2px rgba(0, 0, 0, .1);
456
+ }
457
+ .universal-card-select-img__checkbox-box--square { border-radius: 4px; }
458
+ .universal-card-select-img__checkbox-box--checked {
459
+ background-color: var(--B500); border-color: var(--B500);
460
+ }
461
+ .universal-card-select-img__checkbox-box--checked::after {
462
+ content: ""; width: 3.5px; height: 7px;
463
+ border: solid #fff; border-width: 0 2px 2px 0;
464
+ transform: rotate(45deg) translateY(-1px);
465
+ }
466
+
383
467
  /* ---- 按钮 ---- */
384
468
  /* 飞书按钮文本是纯文本、单行:min-height 容纳偶发换行,文字超长省略号收尾。
385
469
  样式全集逆向自网页版 ud__button:三大族——outlined(描边)/ text(纯文字)/ filled(实心),
package/dist/index.cjs CHANGED
@@ -687,7 +687,8 @@ function resolveMediaSrc(p, kind, ctx) {
687
687
  return null;
688
688
  }
689
689
  function resolveVideoCoverSrc(p, ctx) {
690
- const coverId = p.coverImageID ?? p.image_key ?? p.cover_image_key;
690
+ const cover = p.cover;
691
+ const coverId = p.coverImageID ?? p.image_key ?? p.cover_image_key ?? (typeof cover === "string" ? cover : cover?.img_key ?? cover?.imageID ?? cover?.image_id ?? cover?.cover_image_id ?? cover?.coverImageId);
691
692
  if (!coverId) return null;
692
693
  return resolveImageSrc({ imageID: coverId }, ctx);
693
694
  }
@@ -849,8 +850,13 @@ function resolveCornerRadius(property) {
849
850
  }
850
851
  if (direct != null) return sizeToPx(direct);
851
852
  const echo = property.cornerRadiusValue;
852
- if (echo && typeof echo.value === "number") return echo.value + "px";
853
- if (echo && typeof echo.value === "string" && echo.value.trim()) return sizeToPx(echo.value);
853
+ if (typeof echo === "number") return echo + "px";
854
+ if (typeof echo === "string" && echo.trim()) return sizeToPx(echo);
855
+ if (echo && typeof echo === "object") {
856
+ const value = echo.value;
857
+ if (typeof value === "number") return value + "px";
858
+ if (typeof value === "string" && value.trim()) return sizeToPx(value);
859
+ }
854
860
  return void 0;
855
861
  }
856
862
  function boxToStyle(box) {
@@ -1351,6 +1357,90 @@ var BASE_STYLES_CSS = `/**
1351
1357
  font-size: 12px;
1352
1358
  }
1353
1359
 
1360
+ /* ---- img_combination \u591A\u56FE\u6DF7\u6392\uFF08\u6BD4\u4F8B\u9006\u5411\u81EA\u7F51\u9875\u7248 .universal-card-img-combination\uFF09 ---- */
1361
+ .universal-card-img-combination { display: flex; width: 100%; justify-content: space-between; }
1362
+ .universal-card-img-combination__item { min-width: 0; overflow: hidden; }
1363
+ .universal-card-img-combination__item > .universal-card-image__host { display: block; width: 100%; height: 100%; object-fit: cover; }
1364
+ .universal-card-img-combination__item > .universal-card-image__placeholder { width: 100%; height: 100%; }
1365
+ /* double\uFF1A\u5DE6\u7A84(32.4%, 24:33) + \u53F3\u5BBD(67.6%, 49.33:33) */
1366
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(odd) {
1367
+ width: 32.4%; aspect-ratio: 24 / 33;
1368
+ }
1369
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(2n) {
1370
+ width: calc(67.6% - 4px); aspect-ratio: 49.33 / 33;
1371
+ }
1372
+ /* triple\uFF1A\u5DE6\u5927\u56FE(66.5%, 1:1) + \u53F3\u5217\u4E24\u5F20\u5C0F\u56FE */
1373
+ .universal-card-img-combination--triple .universal-card-img-combination__left {
1374
+ width: 66.5%; aspect-ratio: 1; margin-right: 1%;
1375
+ }
1376
+ .universal-card-img-combination--triple .universal-card-img-combination__right {
1377
+ width: 33%; display: flex; flex-direction: column; justify-content: space-between;
1378
+ }
1379
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item {
1380
+ width: 100%; aspect-ratio: 1;
1381
+ }
1382
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item:first-child {
1383
+ margin-bottom: 1.5%;
1384
+ }
1385
+ /* bisect / trisect\uFF1A\u7B49\u5BBD\u65B9\u683C\u6362\u884C */
1386
+ .universal-card-img-combination--bisect { flex-wrap: wrap; gap: 4px; }
1387
+ .universal-card-img-combination--bisect .universal-card-img-combination__item {
1388
+ width: calc(50% - 2px); aspect-ratio: 1;
1389
+ }
1390
+ .universal-card-img-combination--trisect { flex-wrap: wrap; justify-content: flex-start; gap: 4px; }
1391
+ .universal-card-img-combination--trisect .universal-card-img-combination__item {
1392
+ width: calc(33.33% - 2.67px); aspect-ratio: 1;
1393
+ }
1394
+
1395
+ /* ---- select_img \u591A\u56FE\u9009\u62E9\uFF08\u5BF9\u9F50\u7F51\u9875\u7248 .universal-card-select-img\uFF09 ---- */
1396
+ .universal-card-select-img {
1397
+ width: 100%; max-width: 100%; display: grid; grid-gap: 4px; box-sizing: border-box;
1398
+ }
1399
+ .universal-card-select-img--stretch { grid-template-columns: 1fr; }
1400
+ .universal-card-select-img--bisect { grid-template-columns: 1fr 1fr; }
1401
+ .universal-card-select-img--trisect { grid-template-columns: 1fr 1fr 1fr; }
1402
+ .universal-card-select-img__item-wrapper { position: relative; border-radius: 6px; }
1403
+ .universal-card-select-img__item-wrapper::after {
1404
+ content: " "; position: absolute; inset: 0; box-sizing: border-box;
1405
+ border: 1px solid var(--N300); border-radius: 6px; pointer-events: none; z-index: 1;
1406
+ }
1407
+ .universal-card-select-img__item-wrapper--selected::after { border: 2px solid var(--B500); }
1408
+ .universal-card-select-img__item {
1409
+ position: relative; overflow: hidden; box-sizing: border-box; border-radius: 6px;
1410
+ cursor: pointer; width: 100%;
1411
+ }
1412
+ .universal-card-select-img__item--disabled { cursor: not-allowed; }
1413
+ .universal-card-select-img__item--disabled::after {
1414
+ content: " "; position: absolute; inset: 0; z-index: 1;
1415
+ background-color: rgba(255, 255, 255, .5);
1416
+ }
1417
+ .universal-card-select-img__item > .universal-card-image__host {
1418
+ display: block; width: 100%; height: 100%; object-fit: cover;
1419
+ }
1420
+ .universal-card-select-img__item > .universal-card-image__placeholder {
1421
+ width: 100%; height: 100%;
1422
+ }
1423
+ /* \u8868\u5355\u5185\u590D\u9009/\u5355\u9009\u6807\u8BB0\uFF08\u771F\u673A\u4EC5 in-form \u663E\u793A\uFF1B\u8868\u5355\u5916\u7528\u9009\u4E2D\u63CF\u8FB9\u8868\u8FBE\u72B6\u6001\uFF09 */
1424
+ .universal-card-select-img__checkbox {
1425
+ position: absolute; top: 0; right: 0; z-index: 2; padding: 5px 8px; cursor: pointer;
1426
+ }
1427
+ .universal-card-select-img__checkbox--disabled { cursor: not-allowed; }
1428
+ .universal-card-select-img__checkbox-box {
1429
+ display: inline-flex; align-items: center; justify-content: center;
1430
+ width: 16px; height: 16px; box-sizing: border-box; border-radius: 50%;
1431
+ background-color: rgba(0, 0, 0, .15); border: 1px solid #eff0f1;
1432
+ box-shadow: 0 0 2px rgba(0, 0, 0, .1);
1433
+ }
1434
+ .universal-card-select-img__checkbox-box--square { border-radius: 4px; }
1435
+ .universal-card-select-img__checkbox-box--checked {
1436
+ background-color: var(--B500); border-color: var(--B500);
1437
+ }
1438
+ .universal-card-select-img__checkbox-box--checked::after {
1439
+ content: ""; width: 3.5px; height: 7px;
1440
+ border: solid #fff; border-width: 0 2px 2px 0;
1441
+ transform: rotate(45deg) translateY(-1px);
1442
+ }
1443
+
1354
1444
  /* ---- \u6309\u94AE ---- */
1355
1445
  /* \u98DE\u4E66\u6309\u94AE\u6587\u672C\u662F\u7EAF\u6587\u672C\u3001\u5355\u884C\uFF1Amin-height \u5BB9\u7EB3\u5076\u53D1\u6362\u884C\uFF0C\u6587\u5B57\u8D85\u957F\u7701\u7565\u53F7\u6536\u5C3E\u3002
1356
1446
  \u6837\u5F0F\u5168\u96C6\u9006\u5411\u81EA\u7F51\u9875\u7248 ud__button\uFF1A\u4E09\u5927\u65CF\u2014\u2014outlined\uFF08\u63CF\u8FB9\uFF09/ text\uFF08\u7EAF\u6587\u5B57\uFF09/ filled\uFF08\u5B9E\u5FC3\uFF09\uFF0C
@@ -5987,6 +6077,123 @@ function registerBuiltins() {
5987
6077
  src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder`, style: imgStyle }, altText || "[\u56FE\u7247]")
5988
6078
  );
5989
6079
  });
6080
+ const IMG_COMBINATION_LIMIT = {
6081
+ double: 2,
6082
+ triple: 3
6083
+ };
6084
+ function renderCombinationImage(item, cornerRadius, ctx) {
6085
+ const src = resolveImageSrc(item, ctx);
6086
+ const style = {
6087
+ width: "100%",
6088
+ height: "100%",
6089
+ objectFit: "cover",
6090
+ display: "block"
6091
+ };
6092
+ if (cornerRadius) style.borderRadius = cornerRadius;
6093
+ return src ? h("img", { className: `${CLS}-image__host`, src, alt: "", style }) : h("div", { className: `${CLS}-image__placeholder`, style }, "[\u56FE\u7247]");
6094
+ }
6095
+ registerComponent("img_combination", (el, ctx) => {
6096
+ const p = el.property || {};
6097
+ const rawList = Array.isArray(p.imgList) ? p.imgList : Array.isArray(p.img_list) ? p.img_list : [];
6098
+ if (rawList.length === 0) return null;
6099
+ const mode = String(p.combinationMode ?? p.combination_mode ?? "double");
6100
+ const limit = IMG_COMBINATION_LIMIT[mode];
6101
+ const list = (limit ? rawList.slice(0, limit) : rawList).map((item) => typeof item === "string" ? { img_key: item } : item).filter(isRecord);
6102
+ if (list.length === 0) return null;
6103
+ const cornerRadius = resolveCornerRadius(p);
6104
+ const containerStyle = {};
6105
+ applyOuterMargin(containerStyle, p.margin);
6106
+ const cls = `${CLS}-img-combination ${CLS}-img-combination--${mode}`;
6107
+ const itemNode = (item) => h(
6108
+ "div",
6109
+ { className: `${CLS}-img-combination__item` },
6110
+ renderCombinationImage(item, cornerRadius, ctx)
6111
+ );
6112
+ if (mode === "triple") {
6113
+ const [first, ...rest] = list;
6114
+ return h(
6115
+ "div",
6116
+ { className: cls, style: containerStyle },
6117
+ h(
6118
+ "div",
6119
+ { className: `${CLS}-img-combination__item ${CLS}-img-combination__left` },
6120
+ renderCombinationImage(first, cornerRadius, ctx)
6121
+ ),
6122
+ h("div", { className: `${CLS}-img-combination__right` }, rest.map(itemNode))
6123
+ );
6124
+ }
6125
+ return h("div", { className: cls, style: containerStyle }, list.map(itemNode));
6126
+ });
6127
+ function readSelectImgValues(p) {
6128
+ const raw = p.selectedValues ?? p.selected_values ?? p.value;
6129
+ let value = raw;
6130
+ if (typeof value === "string") {
6131
+ try {
6132
+ value = JSON.parse(value);
6133
+ } catch {
6134
+ return typeof value === "string" && value ? [value] : [];
6135
+ }
6136
+ }
6137
+ if (isRecord(value) && Array.isArray(value.value)) value = value.value;
6138
+ if (!Array.isArray(value)) return [];
6139
+ return value.filter((item) => typeof item === "string");
6140
+ }
6141
+ function renderSelectImgCheckbox(checked, disabled, multiple) {
6142
+ const boxClass = `${CLS}-select-img__checkbox-box` + (multiple ? ` ${CLS}-select-img__checkbox-box--square` : "") + (checked ? ` ${CLS}-select-img__checkbox-box--checked` : "");
6143
+ return h(
6144
+ "div",
6145
+ {
6146
+ className: `${CLS}-select-img__checkbox` + (disabled ? ` ${CLS}-select-img__checkbox--disabled` : "")
6147
+ },
6148
+ h("span", { className: boxClass })
6149
+ );
6150
+ }
6151
+ registerComponent("select_img", (el, ctx) => {
6152
+ const p = el.property || {};
6153
+ const options = Array.isArray(p.options) ? p.options : [];
6154
+ if (options.length === 0) return null;
6155
+ const layout = String(p.layout ?? "bisect");
6156
+ const aspectRatio = String(p.aspectRatio ?? p.aspect_ratio ?? "16:9").replace(":", "/");
6157
+ const multiple = p.multiSelect === true || p.multi_select === true;
6158
+ const disabledAll = p.disabled === true;
6159
+ const selected = readSelectImgValues(p);
6160
+ const containerStyle = {};
6161
+ applyOuterMargin(containerStyle, p.margin);
6162
+ const items = options.map((rawOption) => {
6163
+ const option = isRecord(rawOption) ? rawOption : { img_key: rawOption };
6164
+ const value = typeof option.value === "string" ? option.value : void 0;
6165
+ const isSelected = value !== void 0 && selected.includes(value);
6166
+ const disabled = disabledAll || option.disabled === true;
6167
+ const tip = disabled ? extractText(option.disabled_tips ?? option.disabledTips) : extractText(option.hover_tips ?? option.hoverTips);
6168
+ const src = resolveImageSrc(option, ctx);
6169
+ const wrapperClass = `${CLS}-select-img__item-wrapper` + (isSelected ? ` ${CLS}-select-img__item-wrapper--selected` : "") + (disabled ? ` ${CLS}-select-img__item-wrapper--disabled` : "");
6170
+ const itemClass = `${CLS}-select-img__item` + (disabled ? ` ${CLS}-select-img__item--disabled` : "");
6171
+ return h(
6172
+ "div",
6173
+ { className: wrapperClass },
6174
+ h(
6175
+ "div",
6176
+ { className: itemClass, style: { aspectRatio }, title: tip || void 0 },
6177
+ ctx.inForm ? renderSelectImgCheckbox(isSelected, disabled, multiple) : null,
6178
+ src ? h("img", {
6179
+ className: `${CLS}-image__host`,
6180
+ src,
6181
+ alt: "",
6182
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
6183
+ }) : h(
6184
+ "div",
6185
+ { className: `${CLS}-image__placeholder`, style: { width: "100%", height: "100%" } },
6186
+ "[\u56FE\u7247]"
6187
+ )
6188
+ )
6189
+ );
6190
+ });
6191
+ return h(
6192
+ "div",
6193
+ { className: `${CLS}-select-img ${CLS}-select-img--${layout}`, style: containerStyle },
6194
+ items
6195
+ );
6196
+ });
5990
6197
  registerComponent("video", (el, ctx) => {
5991
6198
  const p = el.property || {};
5992
6199
  const src = resolveMediaSrc(p, "video", ctx);
@@ -6362,7 +6569,7 @@ function registerBuiltins() {
6362
6569
  );
6363
6570
  });
6364
6571
  registerComponent("form", (el, ctx) => {
6365
- const children = renderElements(el.property?.elements, ctx);
6572
+ const children = renderElements(el.property?.elements, { ...ctx, inForm: true });
6366
6573
  const className = `${CLS}-form`;
6367
6574
  if (!ctx.options.interactive) return h("div", { className }, children);
6368
6575
  const name = typeof el.property?.name === "string" ? el.property.name : "";
package/dist/index.d.cts CHANGED
@@ -100,6 +100,14 @@ interface ElementProperty {
100
100
  coverImageID?: string;
101
101
  image_key?: string;
102
102
  cover_image_key?: string;
103
+ /** 公开 DSL 官方封面形态 cover: { img_key };部分 RAW 嵌套 imageID / image_id 等 */
104
+ cover?: string | {
105
+ img_key?: string;
106
+ imageID?: string;
107
+ image_id?: string;
108
+ cover_image_id?: string;
109
+ coverImageId?: string;
110
+ };
103
111
  /** 是否显示时长(视频角标 / 音频时间) */
104
112
  showTime?: boolean;
105
113
  show_time?: boolean;
@@ -456,6 +464,11 @@ interface RenderContext {
456
464
  * 客户端把 body paddingTop 从 12 改为 8。由 index.ts 在渲染 body 前注入。
457
465
  */
458
466
  bodyPaddingTop?: number;
467
+ /**
468
+ * 是否处于 form 容器内。真机 select_img 仅在表单内显示选中态 checkbox/radio,
469
+ * 表单外只是可点击的图片选项。form 渲染子元素时注入,不改变外部 ctx。
470
+ */
471
+ inForm?: boolean;
459
472
  }
460
473
  /** 组件渲染函数 */
461
474
  type ComponentFn = (el: CardElement, ctx: RenderContext) => VNode | null;
package/dist/index.d.ts CHANGED
@@ -100,6 +100,14 @@ interface ElementProperty {
100
100
  coverImageID?: string;
101
101
  image_key?: string;
102
102
  cover_image_key?: string;
103
+ /** 公开 DSL 官方封面形态 cover: { img_key };部分 RAW 嵌套 imageID / image_id 等 */
104
+ cover?: string | {
105
+ img_key?: string;
106
+ imageID?: string;
107
+ image_id?: string;
108
+ cover_image_id?: string;
109
+ coverImageId?: string;
110
+ };
103
111
  /** 是否显示时长(视频角标 / 音频时间) */
104
112
  showTime?: boolean;
105
113
  show_time?: boolean;
@@ -456,6 +464,11 @@ interface RenderContext {
456
464
  * 客户端把 body paddingTop 从 12 改为 8。由 index.ts 在渲染 body 前注入。
457
465
  */
458
466
  bodyPaddingTop?: number;
467
+ /**
468
+ * 是否处于 form 容器内。真机 select_img 仅在表单内显示选中态 checkbox/radio,
469
+ * 表单外只是可点击的图片选项。form 渲染子元素时注入,不改变外部 ctx。
470
+ */
471
+ inForm?: boolean;
459
472
  }
460
473
  /** 组件渲染函数 */
461
474
  type ComponentFn = (el: CardElement, ctx: RenderContext) => VNode | null;
package/dist/index.js CHANGED
@@ -686,7 +686,8 @@ function resolveMediaSrc(p, kind, ctx) {
686
686
  return null;
687
687
  }
688
688
  function resolveVideoCoverSrc(p, ctx) {
689
- const coverId = p.coverImageID ?? p.image_key ?? p.cover_image_key;
689
+ const cover = p.cover;
690
+ const coverId = p.coverImageID ?? p.image_key ?? p.cover_image_key ?? (typeof cover === "string" ? cover : cover?.img_key ?? cover?.imageID ?? cover?.image_id ?? cover?.cover_image_id ?? cover?.coverImageId);
690
691
  if (!coverId) return null;
691
692
  return resolveImageSrc({ imageID: coverId }, ctx);
692
693
  }
@@ -848,8 +849,13 @@ function resolveCornerRadius(property) {
848
849
  }
849
850
  if (direct != null) return sizeToPx(direct);
850
851
  const echo = property.cornerRadiusValue;
851
- if (echo && typeof echo.value === "number") return echo.value + "px";
852
- if (echo && typeof echo.value === "string" && echo.value.trim()) return sizeToPx(echo.value);
852
+ if (typeof echo === "number") return echo + "px";
853
+ if (typeof echo === "string" && echo.trim()) return sizeToPx(echo);
854
+ if (echo && typeof echo === "object") {
855
+ const value = echo.value;
856
+ if (typeof value === "number") return value + "px";
857
+ if (typeof value === "string" && value.trim()) return sizeToPx(value);
858
+ }
853
859
  return void 0;
854
860
  }
855
861
  function boxToStyle(box) {
@@ -1350,6 +1356,90 @@ var BASE_STYLES_CSS = `/**
1350
1356
  font-size: 12px;
1351
1357
  }
1352
1358
 
1359
+ /* ---- img_combination \u591A\u56FE\u6DF7\u6392\uFF08\u6BD4\u4F8B\u9006\u5411\u81EA\u7F51\u9875\u7248 .universal-card-img-combination\uFF09 ---- */
1360
+ .universal-card-img-combination { display: flex; width: 100%; justify-content: space-between; }
1361
+ .universal-card-img-combination__item { min-width: 0; overflow: hidden; }
1362
+ .universal-card-img-combination__item > .universal-card-image__host { display: block; width: 100%; height: 100%; object-fit: cover; }
1363
+ .universal-card-img-combination__item > .universal-card-image__placeholder { width: 100%; height: 100%; }
1364
+ /* double\uFF1A\u5DE6\u7A84(32.4%, 24:33) + \u53F3\u5BBD(67.6%, 49.33:33) */
1365
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(odd) {
1366
+ width: 32.4%; aspect-ratio: 24 / 33;
1367
+ }
1368
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(2n) {
1369
+ width: calc(67.6% - 4px); aspect-ratio: 49.33 / 33;
1370
+ }
1371
+ /* triple\uFF1A\u5DE6\u5927\u56FE(66.5%, 1:1) + \u53F3\u5217\u4E24\u5F20\u5C0F\u56FE */
1372
+ .universal-card-img-combination--triple .universal-card-img-combination__left {
1373
+ width: 66.5%; aspect-ratio: 1; margin-right: 1%;
1374
+ }
1375
+ .universal-card-img-combination--triple .universal-card-img-combination__right {
1376
+ width: 33%; display: flex; flex-direction: column; justify-content: space-between;
1377
+ }
1378
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item {
1379
+ width: 100%; aspect-ratio: 1;
1380
+ }
1381
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item:first-child {
1382
+ margin-bottom: 1.5%;
1383
+ }
1384
+ /* bisect / trisect\uFF1A\u7B49\u5BBD\u65B9\u683C\u6362\u884C */
1385
+ .universal-card-img-combination--bisect { flex-wrap: wrap; gap: 4px; }
1386
+ .universal-card-img-combination--bisect .universal-card-img-combination__item {
1387
+ width: calc(50% - 2px); aspect-ratio: 1;
1388
+ }
1389
+ .universal-card-img-combination--trisect { flex-wrap: wrap; justify-content: flex-start; gap: 4px; }
1390
+ .universal-card-img-combination--trisect .universal-card-img-combination__item {
1391
+ width: calc(33.33% - 2.67px); aspect-ratio: 1;
1392
+ }
1393
+
1394
+ /* ---- select_img \u591A\u56FE\u9009\u62E9\uFF08\u5BF9\u9F50\u7F51\u9875\u7248 .universal-card-select-img\uFF09 ---- */
1395
+ .universal-card-select-img {
1396
+ width: 100%; max-width: 100%; display: grid; grid-gap: 4px; box-sizing: border-box;
1397
+ }
1398
+ .universal-card-select-img--stretch { grid-template-columns: 1fr; }
1399
+ .universal-card-select-img--bisect { grid-template-columns: 1fr 1fr; }
1400
+ .universal-card-select-img--trisect { grid-template-columns: 1fr 1fr 1fr; }
1401
+ .universal-card-select-img__item-wrapper { position: relative; border-radius: 6px; }
1402
+ .universal-card-select-img__item-wrapper::after {
1403
+ content: " "; position: absolute; inset: 0; box-sizing: border-box;
1404
+ border: 1px solid var(--N300); border-radius: 6px; pointer-events: none; z-index: 1;
1405
+ }
1406
+ .universal-card-select-img__item-wrapper--selected::after { border: 2px solid var(--B500); }
1407
+ .universal-card-select-img__item {
1408
+ position: relative; overflow: hidden; box-sizing: border-box; border-radius: 6px;
1409
+ cursor: pointer; width: 100%;
1410
+ }
1411
+ .universal-card-select-img__item--disabled { cursor: not-allowed; }
1412
+ .universal-card-select-img__item--disabled::after {
1413
+ content: " "; position: absolute; inset: 0; z-index: 1;
1414
+ background-color: rgba(255, 255, 255, .5);
1415
+ }
1416
+ .universal-card-select-img__item > .universal-card-image__host {
1417
+ display: block; width: 100%; height: 100%; object-fit: cover;
1418
+ }
1419
+ .universal-card-select-img__item > .universal-card-image__placeholder {
1420
+ width: 100%; height: 100%;
1421
+ }
1422
+ /* \u8868\u5355\u5185\u590D\u9009/\u5355\u9009\u6807\u8BB0\uFF08\u771F\u673A\u4EC5 in-form \u663E\u793A\uFF1B\u8868\u5355\u5916\u7528\u9009\u4E2D\u63CF\u8FB9\u8868\u8FBE\u72B6\u6001\uFF09 */
1423
+ .universal-card-select-img__checkbox {
1424
+ position: absolute; top: 0; right: 0; z-index: 2; padding: 5px 8px; cursor: pointer;
1425
+ }
1426
+ .universal-card-select-img__checkbox--disabled { cursor: not-allowed; }
1427
+ .universal-card-select-img__checkbox-box {
1428
+ display: inline-flex; align-items: center; justify-content: center;
1429
+ width: 16px; height: 16px; box-sizing: border-box; border-radius: 50%;
1430
+ background-color: rgba(0, 0, 0, .15); border: 1px solid #eff0f1;
1431
+ box-shadow: 0 0 2px rgba(0, 0, 0, .1);
1432
+ }
1433
+ .universal-card-select-img__checkbox-box--square { border-radius: 4px; }
1434
+ .universal-card-select-img__checkbox-box--checked {
1435
+ background-color: var(--B500); border-color: var(--B500);
1436
+ }
1437
+ .universal-card-select-img__checkbox-box--checked::after {
1438
+ content: ""; width: 3.5px; height: 7px;
1439
+ border: solid #fff; border-width: 0 2px 2px 0;
1440
+ transform: rotate(45deg) translateY(-1px);
1441
+ }
1442
+
1353
1443
  /* ---- \u6309\u94AE ---- */
1354
1444
  /* \u98DE\u4E66\u6309\u94AE\u6587\u672C\u662F\u7EAF\u6587\u672C\u3001\u5355\u884C\uFF1Amin-height \u5BB9\u7EB3\u5076\u53D1\u6362\u884C\uFF0C\u6587\u5B57\u8D85\u957F\u7701\u7565\u53F7\u6536\u5C3E\u3002
1355
1445
  \u6837\u5F0F\u5168\u96C6\u9006\u5411\u81EA\u7F51\u9875\u7248 ud__button\uFF1A\u4E09\u5927\u65CF\u2014\u2014outlined\uFF08\u63CF\u8FB9\uFF09/ text\uFF08\u7EAF\u6587\u5B57\uFF09/ filled\uFF08\u5B9E\u5FC3\uFF09\uFF0C
@@ -5986,6 +6076,123 @@ function registerBuiltins() {
5986
6076
  src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder`, style: imgStyle }, altText || "[\u56FE\u7247]")
5987
6077
  );
5988
6078
  });
6079
+ const IMG_COMBINATION_LIMIT = {
6080
+ double: 2,
6081
+ triple: 3
6082
+ };
6083
+ function renderCombinationImage(item, cornerRadius, ctx) {
6084
+ const src = resolveImageSrc(item, ctx);
6085
+ const style = {
6086
+ width: "100%",
6087
+ height: "100%",
6088
+ objectFit: "cover",
6089
+ display: "block"
6090
+ };
6091
+ if (cornerRadius) style.borderRadius = cornerRadius;
6092
+ return src ? h("img", { className: `${CLS}-image__host`, src, alt: "", style }) : h("div", { className: `${CLS}-image__placeholder`, style }, "[\u56FE\u7247]");
6093
+ }
6094
+ registerComponent("img_combination", (el, ctx) => {
6095
+ const p = el.property || {};
6096
+ const rawList = Array.isArray(p.imgList) ? p.imgList : Array.isArray(p.img_list) ? p.img_list : [];
6097
+ if (rawList.length === 0) return null;
6098
+ const mode = String(p.combinationMode ?? p.combination_mode ?? "double");
6099
+ const limit = IMG_COMBINATION_LIMIT[mode];
6100
+ const list = (limit ? rawList.slice(0, limit) : rawList).map((item) => typeof item === "string" ? { img_key: item } : item).filter(isRecord);
6101
+ if (list.length === 0) return null;
6102
+ const cornerRadius = resolveCornerRadius(p);
6103
+ const containerStyle = {};
6104
+ applyOuterMargin(containerStyle, p.margin);
6105
+ const cls = `${CLS}-img-combination ${CLS}-img-combination--${mode}`;
6106
+ const itemNode = (item) => h(
6107
+ "div",
6108
+ { className: `${CLS}-img-combination__item` },
6109
+ renderCombinationImage(item, cornerRadius, ctx)
6110
+ );
6111
+ if (mode === "triple") {
6112
+ const [first, ...rest] = list;
6113
+ return h(
6114
+ "div",
6115
+ { className: cls, style: containerStyle },
6116
+ h(
6117
+ "div",
6118
+ { className: `${CLS}-img-combination__item ${CLS}-img-combination__left` },
6119
+ renderCombinationImage(first, cornerRadius, ctx)
6120
+ ),
6121
+ h("div", { className: `${CLS}-img-combination__right` }, rest.map(itemNode))
6122
+ );
6123
+ }
6124
+ return h("div", { className: cls, style: containerStyle }, list.map(itemNode));
6125
+ });
6126
+ function readSelectImgValues(p) {
6127
+ const raw = p.selectedValues ?? p.selected_values ?? p.value;
6128
+ let value = raw;
6129
+ if (typeof value === "string") {
6130
+ try {
6131
+ value = JSON.parse(value);
6132
+ } catch {
6133
+ return typeof value === "string" && value ? [value] : [];
6134
+ }
6135
+ }
6136
+ if (isRecord(value) && Array.isArray(value.value)) value = value.value;
6137
+ if (!Array.isArray(value)) return [];
6138
+ return value.filter((item) => typeof item === "string");
6139
+ }
6140
+ function renderSelectImgCheckbox(checked, disabled, multiple) {
6141
+ const boxClass = `${CLS}-select-img__checkbox-box` + (multiple ? ` ${CLS}-select-img__checkbox-box--square` : "") + (checked ? ` ${CLS}-select-img__checkbox-box--checked` : "");
6142
+ return h(
6143
+ "div",
6144
+ {
6145
+ className: `${CLS}-select-img__checkbox` + (disabled ? ` ${CLS}-select-img__checkbox--disabled` : "")
6146
+ },
6147
+ h("span", { className: boxClass })
6148
+ );
6149
+ }
6150
+ registerComponent("select_img", (el, ctx) => {
6151
+ const p = el.property || {};
6152
+ const options = Array.isArray(p.options) ? p.options : [];
6153
+ if (options.length === 0) return null;
6154
+ const layout = String(p.layout ?? "bisect");
6155
+ const aspectRatio = String(p.aspectRatio ?? p.aspect_ratio ?? "16:9").replace(":", "/");
6156
+ const multiple = p.multiSelect === true || p.multi_select === true;
6157
+ const disabledAll = p.disabled === true;
6158
+ const selected = readSelectImgValues(p);
6159
+ const containerStyle = {};
6160
+ applyOuterMargin(containerStyle, p.margin);
6161
+ const items = options.map((rawOption) => {
6162
+ const option = isRecord(rawOption) ? rawOption : { img_key: rawOption };
6163
+ const value = typeof option.value === "string" ? option.value : void 0;
6164
+ const isSelected = value !== void 0 && selected.includes(value);
6165
+ const disabled = disabledAll || option.disabled === true;
6166
+ const tip = disabled ? extractText(option.disabled_tips ?? option.disabledTips) : extractText(option.hover_tips ?? option.hoverTips);
6167
+ const src = resolveImageSrc(option, ctx);
6168
+ const wrapperClass = `${CLS}-select-img__item-wrapper` + (isSelected ? ` ${CLS}-select-img__item-wrapper--selected` : "") + (disabled ? ` ${CLS}-select-img__item-wrapper--disabled` : "");
6169
+ const itemClass = `${CLS}-select-img__item` + (disabled ? ` ${CLS}-select-img__item--disabled` : "");
6170
+ return h(
6171
+ "div",
6172
+ { className: wrapperClass },
6173
+ h(
6174
+ "div",
6175
+ { className: itemClass, style: { aspectRatio }, title: tip || void 0 },
6176
+ ctx.inForm ? renderSelectImgCheckbox(isSelected, disabled, multiple) : null,
6177
+ src ? h("img", {
6178
+ className: `${CLS}-image__host`,
6179
+ src,
6180
+ alt: "",
6181
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
6182
+ }) : h(
6183
+ "div",
6184
+ { className: `${CLS}-image__placeholder`, style: { width: "100%", height: "100%" } },
6185
+ "[\u56FE\u7247]"
6186
+ )
6187
+ )
6188
+ );
6189
+ });
6190
+ return h(
6191
+ "div",
6192
+ { className: `${CLS}-select-img ${CLS}-select-img--${layout}`, style: containerStyle },
6193
+ items
6194
+ );
6195
+ });
5989
6196
  registerComponent("video", (el, ctx) => {
5990
6197
  const p = el.property || {};
5991
6198
  const src = resolveMediaSrc(p, "video", ctx);
@@ -6361,7 +6568,7 @@ function registerBuiltins() {
6361
6568
  );
6362
6569
  });
6363
6570
  registerComponent("form", (el, ctx) => {
6364
- const children = renderElements(el.property?.elements, ctx);
6571
+ const children = renderElements(el.property?.elements, { ...ctx, inForm: true });
6365
6572
  const className = `${CLS}-form`;
6366
6573
  if (!ctx.options.interactive) return h("div", { className }, children);
6367
6574
  const name = typeof el.property?.name === "string" ? el.property.name : "";
package/dist/styles.css CHANGED
@@ -380,6 +380,90 @@
380
380
  font-size: 12px;
381
381
  }
382
382
 
383
+ /* ---- img_combination 多图混排(比例逆向自网页版 .universal-card-img-combination) ---- */
384
+ .universal-card-img-combination { display: flex; width: 100%; justify-content: space-between; }
385
+ .universal-card-img-combination__item { min-width: 0; overflow: hidden; }
386
+ .universal-card-img-combination__item > .universal-card-image__host { display: block; width: 100%; height: 100%; object-fit: cover; }
387
+ .universal-card-img-combination__item > .universal-card-image__placeholder { width: 100%; height: 100%; }
388
+ /* double:左窄(32.4%, 24:33) + 右宽(67.6%, 49.33:33) */
389
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(odd) {
390
+ width: 32.4%; aspect-ratio: 24 / 33;
391
+ }
392
+ .universal-card-img-combination--double .universal-card-img-combination__item:nth-child(2n) {
393
+ width: calc(67.6% - 4px); aspect-ratio: 49.33 / 33;
394
+ }
395
+ /* triple:左大图(66.5%, 1:1) + 右列两张小图 */
396
+ .universal-card-img-combination--triple .universal-card-img-combination__left {
397
+ width: 66.5%; aspect-ratio: 1; margin-right: 1%;
398
+ }
399
+ .universal-card-img-combination--triple .universal-card-img-combination__right {
400
+ width: 33%; display: flex; flex-direction: column; justify-content: space-between;
401
+ }
402
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item {
403
+ width: 100%; aspect-ratio: 1;
404
+ }
405
+ .universal-card-img-combination--triple .universal-card-img-combination__right .universal-card-img-combination__item:first-child {
406
+ margin-bottom: 1.5%;
407
+ }
408
+ /* bisect / trisect:等宽方格换行 */
409
+ .universal-card-img-combination--bisect { flex-wrap: wrap; gap: 4px; }
410
+ .universal-card-img-combination--bisect .universal-card-img-combination__item {
411
+ width: calc(50% - 2px); aspect-ratio: 1;
412
+ }
413
+ .universal-card-img-combination--trisect { flex-wrap: wrap; justify-content: flex-start; gap: 4px; }
414
+ .universal-card-img-combination--trisect .universal-card-img-combination__item {
415
+ width: calc(33.33% - 2.67px); aspect-ratio: 1;
416
+ }
417
+
418
+ /* ---- select_img 多图选择(对齐网页版 .universal-card-select-img) ---- */
419
+ .universal-card-select-img {
420
+ width: 100%; max-width: 100%; display: grid; grid-gap: 4px; box-sizing: border-box;
421
+ }
422
+ .universal-card-select-img--stretch { grid-template-columns: 1fr; }
423
+ .universal-card-select-img--bisect { grid-template-columns: 1fr 1fr; }
424
+ .universal-card-select-img--trisect { grid-template-columns: 1fr 1fr 1fr; }
425
+ .universal-card-select-img__item-wrapper { position: relative; border-radius: 6px; }
426
+ .universal-card-select-img__item-wrapper::after {
427
+ content: " "; position: absolute; inset: 0; box-sizing: border-box;
428
+ border: 1px solid var(--N300); border-radius: 6px; pointer-events: none; z-index: 1;
429
+ }
430
+ .universal-card-select-img__item-wrapper--selected::after { border: 2px solid var(--B500); }
431
+ .universal-card-select-img__item {
432
+ position: relative; overflow: hidden; box-sizing: border-box; border-radius: 6px;
433
+ cursor: pointer; width: 100%;
434
+ }
435
+ .universal-card-select-img__item--disabled { cursor: not-allowed; }
436
+ .universal-card-select-img__item--disabled::after {
437
+ content: " "; position: absolute; inset: 0; z-index: 1;
438
+ background-color: rgba(255, 255, 255, .5);
439
+ }
440
+ .universal-card-select-img__item > .universal-card-image__host {
441
+ display: block; width: 100%; height: 100%; object-fit: cover;
442
+ }
443
+ .universal-card-select-img__item > .universal-card-image__placeholder {
444
+ width: 100%; height: 100%;
445
+ }
446
+ /* 表单内复选/单选标记(真机仅 in-form 显示;表单外用选中描边表达状态) */
447
+ .universal-card-select-img__checkbox {
448
+ position: absolute; top: 0; right: 0; z-index: 2; padding: 5px 8px; cursor: pointer;
449
+ }
450
+ .universal-card-select-img__checkbox--disabled { cursor: not-allowed; }
451
+ .universal-card-select-img__checkbox-box {
452
+ display: inline-flex; align-items: center; justify-content: center;
453
+ width: 16px; height: 16px; box-sizing: border-box; border-radius: 50%;
454
+ background-color: rgba(0, 0, 0, .15); border: 1px solid #eff0f1;
455
+ box-shadow: 0 0 2px rgba(0, 0, 0, .1);
456
+ }
457
+ .universal-card-select-img__checkbox-box--square { border-radius: 4px; }
458
+ .universal-card-select-img__checkbox-box--checked {
459
+ background-color: var(--B500); border-color: var(--B500);
460
+ }
461
+ .universal-card-select-img__checkbox-box--checked::after {
462
+ content: ""; width: 3.5px; height: 7px;
463
+ border: solid #fff; border-width: 0 2px 2px 0;
464
+ transform: rotate(45deg) translateY(-1px);
465
+ }
466
+
383
467
  /* ---- 按钮 ---- */
384
468
  /* 飞书按钮文本是纯文本、单行:min-height 容纳偶发换行,文字超长省略号收尾。
385
469
  样式全集逆向自网页版 ud__button:三大族——outlined(描边)/ text(纯文字)/ filled(实心),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "零框架依赖的飞书卡片渲染引擎:Web / DOM / SSR,输入 RAW 或公开 DSL,输出真实 DOM 或 HTML 字符串",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,7 +28,7 @@
28
28
  "./dist/*.cjs"
29
29
  ],
30
30
  "dependencies": {
31
- "@open-feishu-card/adapter": "0.3.19"
31
+ "@open-feishu-card/adapter": "0.4.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "tsup": "^8.3.5",