@open-feishu-card/renderer 0.2.26 → 0.2.28
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 +5 -9
- package/dist/index.cjs +213 -34
- package/dist/index.d.cts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +213 -34
- package/dist/styles.css +5 -9
- package/package.json +2 -2
package/dist/all.css
CHANGED
|
@@ -384,14 +384,9 @@
|
|
|
384
384
|
flex-shrink: 0; height: 1.2em; color: inherit;
|
|
385
385
|
}
|
|
386
386
|
.universal-card-button__icon .universal-card-icon { color: inherit; }
|
|
387
|
-
/*
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
width: auto;
|
|
391
|
-
max-width: 100%;
|
|
392
|
-
align-self: flex-start;
|
|
393
|
-
}
|
|
394
|
-
/* fill(width:"fill"):撑满所在列 */
|
|
387
|
+
/* 宽度:对齐真机——body > * { width:100% }、列 flex stretch 会把按钮撑满所在行/列。
|
|
388
|
+
不再强制 width:auto(那是网页版观感;真机独立成行的按钮默认占满整行)。
|
|
389
|
+
width:"fill" 显式挂 --block,与缺省撑满同宽,并供 margin calc 识别。 */
|
|
395
390
|
.universal-card-button--block { width: 100%; }
|
|
396
391
|
|
|
397
392
|
/* —— outlined 描边族 —— */
|
|
@@ -413,11 +408,12 @@
|
|
|
413
408
|
}
|
|
414
409
|
.universal-card-button--danger:not(.universal-card-button--static):hover { background: #fef0f0; }
|
|
415
410
|
|
|
416
|
-
/* —— text
|
|
411
|
+
/* —— text 纯文字族(无边框/无底色;撑满时文字靠左,对齐真机) —— */
|
|
417
412
|
.universal-card-button--text,
|
|
418
413
|
.universal-card-button--primary_text,
|
|
419
414
|
.universal-card-button--danger_text {
|
|
420
415
|
border-color: transparent; background: transparent; padding: 4px 8px;
|
|
416
|
+
justify-content: flex-start;
|
|
421
417
|
}
|
|
422
418
|
.universal-card-button--text { color: #1f2329; }
|
|
423
419
|
.universal-card-button--primary_text { color: #1456f0; }
|
package/dist/index.cjs
CHANGED
|
@@ -392,16 +392,36 @@ var TABLE_HEAD_BG = {
|
|
|
392
392
|
default: "transparent"
|
|
393
393
|
};
|
|
394
394
|
var CHART_PALETTE = [
|
|
395
|
-
"#
|
|
396
|
-
"#
|
|
397
|
-
"#
|
|
398
|
-
"#
|
|
399
|
-
"#
|
|
400
|
-
"#
|
|
401
|
-
"#
|
|
402
|
-
"#
|
|
403
|
-
"#
|
|
404
|
-
"#
|
|
395
|
+
"#4e83fd",
|
|
396
|
+
"#50cefb",
|
|
397
|
+
"#935af6",
|
|
398
|
+
"#fad355",
|
|
399
|
+
"#f76964",
|
|
400
|
+
"#ffa53d",
|
|
401
|
+
"#82a7fc",
|
|
402
|
+
"#7edafb",
|
|
403
|
+
"#ad82f7",
|
|
404
|
+
"#f7dc82",
|
|
405
|
+
"#f98e8b",
|
|
406
|
+
"#ffba6b",
|
|
407
|
+
"#3370ff",
|
|
408
|
+
"#14c0ff",
|
|
409
|
+
"#7f3bf5",
|
|
410
|
+
"#ffc60a",
|
|
411
|
+
"#f54a45",
|
|
412
|
+
"#ff8800",
|
|
413
|
+
"#bacefd",
|
|
414
|
+
"#b1e8fc",
|
|
415
|
+
"#cdb2fa",
|
|
416
|
+
"#f8e6ab",
|
|
417
|
+
"#fbbfbc",
|
|
418
|
+
"#fed4a4",
|
|
419
|
+
"#245bdb",
|
|
420
|
+
"#049fd7",
|
|
421
|
+
"#6425d0",
|
|
422
|
+
"#dc9b04",
|
|
423
|
+
"#d83931",
|
|
424
|
+
"#de7802"
|
|
405
425
|
];
|
|
406
426
|
var CHART_DEFAULT_ASPECT_RATIO = "16:9";
|
|
407
427
|
var CHART_MIN_HEIGHT = 150;
|
|
@@ -686,6 +706,25 @@ function personAvatarSrc(person, ctx) {
|
|
|
686
706
|
const suffix = ctx.options.avatarURLSuffix ?? PERSON_AVATAR_SUFFIX;
|
|
687
707
|
return base + person.avatar_key + suffix;
|
|
688
708
|
}
|
|
709
|
+
var HORIZONTAL_SPACING_PX = { small: 4, default: 8, large: 12 };
|
|
710
|
+
var VERTICAL_SPACING_PX = { default: 8, medium: 12, large: 16 };
|
|
711
|
+
function namedSpacingToPx(v, map) {
|
|
712
|
+
const named = map[v];
|
|
713
|
+
if (named != null) return named;
|
|
714
|
+
const m = /^(-?\d+(?:\.\d+)?)(?:px)?$/.exec(v.trim());
|
|
715
|
+
if (m) return Number(m[1]);
|
|
716
|
+
return map.default ?? 8;
|
|
717
|
+
}
|
|
718
|
+
function horizontalSpacingToPx(v) {
|
|
719
|
+
if (typeof v === "number") return v;
|
|
720
|
+
if (typeof v === "string") return namedSpacingToPx(v, HORIZONTAL_SPACING_PX);
|
|
721
|
+
return HORIZONTAL_SPACING_PX.default;
|
|
722
|
+
}
|
|
723
|
+
function verticalSpacingToPx(v) {
|
|
724
|
+
if (typeof v === "number") return v;
|
|
725
|
+
if (typeof v === "string") return namedSpacingToPx(v, VERTICAL_SPACING_PX);
|
|
726
|
+
return VERTICAL_SPACING_PX.default;
|
|
727
|
+
}
|
|
689
728
|
function spacingToPx(v) {
|
|
690
729
|
if (typeof v === "number") return v;
|
|
691
730
|
if (typeof v === "string") {
|
|
@@ -1313,14 +1352,9 @@ var BASE_STYLES_CSS = `/**
|
|
|
1313
1352
|
flex-shrink: 0; height: 1.2em; color: inherit;
|
|
1314
1353
|
}
|
|
1315
1354
|
.universal-card-button__icon .universal-card-icon { color: inherit; }
|
|
1316
|
-
/*
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
width: auto;
|
|
1320
|
-
max-width: 100%;
|
|
1321
|
-
align-self: flex-start;
|
|
1322
|
-
}
|
|
1323
|
-
/* fill\uFF08width:"fill"\uFF09\uFF1A\u6491\u6EE1\u6240\u5728\u5217 */
|
|
1355
|
+
/* \u5BBD\u5EA6\uFF1A\u5BF9\u9F50\u771F\u673A\u2014\u2014body > * { width:100% }\u3001\u5217 flex stretch \u4F1A\u628A\u6309\u94AE\u6491\u6EE1\u6240\u5728\u884C/\u5217\u3002
|
|
1356
|
+
\u4E0D\u518D\u5F3A\u5236 width:auto\uFF08\u90A3\u662F\u7F51\u9875\u7248\u89C2\u611F\uFF1B\u771F\u673A\u72EC\u7ACB\u6210\u884C\u7684\u6309\u94AE\u9ED8\u8BA4\u5360\u6EE1\u6574\u884C\uFF09\u3002
|
|
1357
|
+
width:"fill" \u663E\u5F0F\u6302 --block\uFF0C\u4E0E\u7F3A\u7701\u6491\u6EE1\u540C\u5BBD\uFF0C\u5E76\u4F9B margin calc \u8BC6\u522B\u3002 */
|
|
1324
1358
|
.universal-card-button--block { width: 100%; }
|
|
1325
1359
|
|
|
1326
1360
|
/* \u2014\u2014 outlined \u63CF\u8FB9\u65CF \u2014\u2014 */
|
|
@@ -1342,11 +1376,12 @@ var BASE_STYLES_CSS = `/**
|
|
|
1342
1376
|
}
|
|
1343
1377
|
.universal-card-button--danger:not(.universal-card-button--static):hover { background: #fef0f0; }
|
|
1344
1378
|
|
|
1345
|
-
/* \u2014\u2014 text \u7EAF\u6587\u5B57\u65CF\uFF08\u65E0\u8FB9\u6846/\u65E0\u5E95\u8272\uFF0C\
|
|
1379
|
+
/* \u2014\u2014 text \u7EAF\u6587\u5B57\u65CF\uFF08\u65E0\u8FB9\u6846/\u65E0\u5E95\u8272\uFF1B\u6491\u6EE1\u65F6\u6587\u5B57\u9760\u5DE6\uFF0C\u5BF9\u9F50\u771F\u673A\uFF09 \u2014\u2014 */
|
|
1346
1380
|
.universal-card-button--text,
|
|
1347
1381
|
.universal-card-button--primary_text,
|
|
1348
1382
|
.universal-card-button--danger_text {
|
|
1349
1383
|
border-color: transparent; background: transparent; padding: 4px 8px;
|
|
1384
|
+
justify-content: flex-start;
|
|
1350
1385
|
}
|
|
1351
1386
|
.universal-card-button--text { color: #1f2329; }
|
|
1352
1387
|
.universal-card-button--primary_text { color: #1456f0; }
|
|
@@ -4899,7 +4934,7 @@ function parseInline(text) {
|
|
|
4899
4934
|
if (attrs.show_name != null) property.showName = attrs.show_name !== "false";
|
|
4900
4935
|
if (attrs.show_avatar != null) property.showAvatar = attrs.show_avatar !== "false";
|
|
4901
4936
|
if (attrs.size) property.size = attrs.size;
|
|
4902
|
-
if (attrs.style
|
|
4937
|
+
if (attrs.style) property.style = attrs.style;
|
|
4903
4938
|
out.push({ tag: "person_v1", property });
|
|
4904
4939
|
i += m[0].length;
|
|
4905
4940
|
continue;
|
|
@@ -5500,8 +5535,9 @@ function registerBuiltins() {
|
|
|
5500
5535
|
const style = {};
|
|
5501
5536
|
const pad = boxToStyle(p.padding);
|
|
5502
5537
|
if (pad) style.padding = pad;
|
|
5538
|
+
if (!pad && ctx.bodyPaddingTop != null) style.paddingTop = ctx.bodyPaddingTop + "px";
|
|
5503
5539
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5504
|
-
if (vGap != null) style.rowGap =
|
|
5540
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5505
5541
|
const props = { className: `${CLS}-body` };
|
|
5506
5542
|
if (Object.keys(style).length) props.style = style;
|
|
5507
5543
|
return h("div", props, renderElements(getChildElements(el), ctx));
|
|
@@ -5511,7 +5547,8 @@ function registerBuiltins() {
|
|
|
5511
5547
|
const tplKey = p.template || "default";
|
|
5512
5548
|
const tpl = HEADER_TEMPLATE[tplKey] || HEADER_TEMPLATE.default;
|
|
5513
5549
|
const tags = p.text_tag_list || p.textTagList;
|
|
5514
|
-
const
|
|
5550
|
+
const tagCtx = { ...ctx, textMetrics: { fontSize: 16, lineHeight: 24 } };
|
|
5551
|
+
const tagVNodes = Array.isArray(tags) && tags.length ? renderElements(tags.slice(0, 3), tagCtx) : [];
|
|
5515
5552
|
const iconRaw = p.udIcon || p.icon;
|
|
5516
5553
|
let iconVNode = null;
|
|
5517
5554
|
if (iconRaw && iconRaw.tag) {
|
|
@@ -5521,6 +5558,21 @@ function registerBuiltins() {
|
|
|
5521
5558
|
const inner = renderIcon(iconEl, ctx);
|
|
5522
5559
|
if (inner) iconVNode = h("div", { className: `${CLS}-header__icon` }, inner);
|
|
5523
5560
|
}
|
|
5561
|
+
if (!iconVNode) {
|
|
5562
|
+
const avatarEl = p.avatar;
|
|
5563
|
+
const avatarSrc = avatarEl && avatarEl.property ? resolveImageSrc(avatarEl.property, ctx) : null;
|
|
5564
|
+
if (avatarSrc) {
|
|
5565
|
+
iconVNode = h(
|
|
5566
|
+
"div",
|
|
5567
|
+
{ className: `${CLS}-header__icon` },
|
|
5568
|
+
h("img", {
|
|
5569
|
+
src: avatarSrc,
|
|
5570
|
+
alt: "",
|
|
5571
|
+
style: { width: "16px", height: "16px", borderRadius: "4px", objectFit: "cover" }
|
|
5572
|
+
})
|
|
5573
|
+
);
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5524
5576
|
const titleVNode = renderHeaderTextLeaf(p.title, ctx, `${CLS}-header__text-title`) ?? h("div", { className: `${CLS}-header__text-title` });
|
|
5525
5577
|
const subtitleVNode = renderHeaderTextLeaf(p.subtitle, ctx, `${CLS}-header__text-subtitle`);
|
|
5526
5578
|
const textChildren = [
|
|
@@ -5559,7 +5611,7 @@ function registerBuiltins() {
|
|
|
5559
5611
|
)
|
|
5560
5612
|
);
|
|
5561
5613
|
});
|
|
5562
|
-
|
|
5614
|
+
function MarkdownComponent(el, ctx) {
|
|
5563
5615
|
const p = el.property || {};
|
|
5564
5616
|
const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
|
|
5565
5617
|
applyOuterMargin(style, p.margin);
|
|
@@ -5573,12 +5625,28 @@ function registerBuiltins() {
|
|
|
5573
5625
|
style.lineHeight = lineHeight + "px";
|
|
5574
5626
|
}
|
|
5575
5627
|
const childCtx = { ...ctx, textMetrics: { fontSize, lineHeight } };
|
|
5628
|
+
const linesStyle = lineClampStyle(p.lines ?? p.textLines);
|
|
5576
5629
|
return h(
|
|
5577
5630
|
"div",
|
|
5578
5631
|
{ className: `${CLS}-markdown`, style },
|
|
5579
|
-
h("div", {
|
|
5632
|
+
h("div", {
|
|
5633
|
+
className: `${CLS}-markdown__children`,
|
|
5634
|
+
style: Object.keys(linesStyle).length ? linesStyle : void 0
|
|
5635
|
+
}, renderElements(p.elements, childCtx))
|
|
5580
5636
|
);
|
|
5581
|
-
}
|
|
5637
|
+
}
|
|
5638
|
+
registerComponent("markdown", MarkdownComponent);
|
|
5639
|
+
registerComponent("markdown_v1", MarkdownComponent);
|
|
5640
|
+
function lineClampStyle(lines) {
|
|
5641
|
+
const n = Number(lines);
|
|
5642
|
+
if (!Number.isFinite(n) || n <= 0) return {};
|
|
5643
|
+
return {
|
|
5644
|
+
display: "-webkit-box",
|
|
5645
|
+
WebkitLineClamp: String(Math.floor(n)),
|
|
5646
|
+
WebkitBoxOrient: "vertical",
|
|
5647
|
+
overflow: "hidden"
|
|
5648
|
+
};
|
|
5649
|
+
}
|
|
5582
5650
|
registerComponent("plain_text", (el) => {
|
|
5583
5651
|
const p = el.property || {};
|
|
5584
5652
|
const mods = textStyleToModifiers(p.textStyle);
|
|
@@ -5587,6 +5655,7 @@ function registerBuiltins() {
|
|
|
5587
5655
|
delete style.fontWeight;
|
|
5588
5656
|
delete style.fontStyle;
|
|
5589
5657
|
delete style.textDecoration;
|
|
5658
|
+
Object.assign(style, lineClampStyle(p.lines));
|
|
5590
5659
|
return h("span", { className, style }, p.content != null ? String(p.content) : "");
|
|
5591
5660
|
});
|
|
5592
5661
|
registerComponent("br", () => h("br", {}));
|
|
@@ -5611,16 +5680,16 @@ function registerBuiltins() {
|
|
|
5611
5680
|
registerComponent("column_set", (el, ctx) => {
|
|
5612
5681
|
const p = el.property || {};
|
|
5613
5682
|
const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
|
|
5614
|
-
const gap = gapRaw != null ?
|
|
5683
|
+
const gap = gapRaw != null ? horizontalSpacingToPx(gapRaw) : 0;
|
|
5615
5684
|
const flexMode = p.flexMode || p.flex_mode;
|
|
5616
5685
|
const hAlign = p.horizontalAlign || p.horizontal_align;
|
|
5617
5686
|
const style = {
|
|
5618
5687
|
display: "flex",
|
|
5619
5688
|
flexWrap: flexMode === "flow" || flexMode === "wrap" ? "wrap" : "nowrap",
|
|
5620
|
-
columnGap: gap + "px",
|
|
5621
5689
|
rowGap: "8px",
|
|
5622
5690
|
alignItems: "stretch"
|
|
5623
5691
|
};
|
|
5692
|
+
if (gap > 0) style.columnGap = gap + "px";
|
|
5624
5693
|
if (hAlign) {
|
|
5625
5694
|
style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
|
|
5626
5695
|
}
|
|
@@ -5638,7 +5707,8 @@ function registerBuiltins() {
|
|
|
5638
5707
|
style.margin = "0";
|
|
5639
5708
|
style.padding = "0";
|
|
5640
5709
|
}
|
|
5641
|
-
|
|
5710
|
+
const childCtx = { ...ctx, columnSet: { flexMode, gap } };
|
|
5711
|
+
return h("div", { className: cls, style }, renderElements(p.columns, childCtx));
|
|
5642
5712
|
});
|
|
5643
5713
|
function isStateHost(el) {
|
|
5644
5714
|
if (el.id === "card_state") return true;
|
|
@@ -5673,12 +5743,30 @@ function registerBuiltins() {
|
|
|
5673
5743
|
} else {
|
|
5674
5744
|
style.flexGrow = String(p.weight || 1);
|
|
5675
5745
|
}
|
|
5746
|
+
const cs = ctx.columnSet;
|
|
5747
|
+
if (cs && cs.flexMode) {
|
|
5748
|
+
const fm = String(cs.flexMode).toLowerCase();
|
|
5749
|
+
const g = cs.gap || 0;
|
|
5750
|
+
if (fm === "stretch") {
|
|
5751
|
+
style.width = "100%";
|
|
5752
|
+
style.flexGrow = "0";
|
|
5753
|
+
style.flexBasis = "auto";
|
|
5754
|
+
} else if (fm === "bisect") {
|
|
5755
|
+
style.width = `calc(50% - ${g / 2}px)`;
|
|
5756
|
+
style.flexGrow = "0";
|
|
5757
|
+
style.flexBasis = "auto";
|
|
5758
|
+
} else if (fm === "trisect") {
|
|
5759
|
+
style.width = `calc(33.33% - ${2 * g / 3}px)`;
|
|
5760
|
+
style.flexGrow = "0";
|
|
5761
|
+
style.flexBasis = "auto";
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5676
5764
|
const vAlign = p.verticalAlign || p.vertical_align;
|
|
5677
5765
|
if (vAlign) {
|
|
5678
5766
|
style.justifyContent = { top: "flex-start", center: "center", bottom: "flex-end" }[vAlign] || "flex-start";
|
|
5679
5767
|
}
|
|
5680
5768
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5681
|
-
style.rowGap = (vGap != null ?
|
|
5769
|
+
style.rowGap = (vGap != null ? verticalSpacingToPx(vGap) : 8) + "px";
|
|
5682
5770
|
const bg = propertyBackgroundColor(p);
|
|
5683
5771
|
if (bg) style.background = bg;
|
|
5684
5772
|
const pad = boxToStyle(p.padding);
|
|
@@ -5886,7 +5974,7 @@ function registerBuiltins() {
|
|
|
5886
5974
|
const radius = resolveCornerRadius(p);
|
|
5887
5975
|
if (radius != null) style.borderRadius = radius;
|
|
5888
5976
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5889
|
-
if (vGap != null) style.rowGap =
|
|
5977
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5890
5978
|
const icWv = p.widthValue;
|
|
5891
5979
|
const icHv = p.heightValue;
|
|
5892
5980
|
const w = resolveDimensionPx(p.width, icWv);
|
|
@@ -5988,14 +6076,13 @@ function registerBuiltins() {
|
|
|
5988
6076
|
const children = iconVNode ? [iconVNode, label] : [label];
|
|
5989
6077
|
const actionUrl = Array.isArray(p.actions) ? resolveURL(p.actions.find((a) => a.type === "open_url")?.action?.url) : "";
|
|
5990
6078
|
const url = actionUrl || resolveURL(p.url) || p.behaviors?.[0]?.default_url?.url;
|
|
5991
|
-
const isText = type === "text" || type === "primary_text" || type === "danger_text";
|
|
5992
6079
|
const wv = p.widthValue;
|
|
5993
6080
|
const widthTok = p.width ?? wv?.value;
|
|
5994
|
-
const block = widthTok === "fill"
|
|
6081
|
+
const block = widthTok === "fill";
|
|
5995
6082
|
const base = `${CLS}-button ${CLS}-button--${type}${block ? ` ${CLS}-button--block` : ""}`;
|
|
5996
6083
|
const style = {};
|
|
5997
6084
|
if (p.margin) {
|
|
5998
|
-
style.width =
|
|
6085
|
+
style.width = "100%";
|
|
5999
6086
|
applyOuterMargin(style, p.margin);
|
|
6000
6087
|
}
|
|
6001
6088
|
const styleProp = Object.keys(style).length ? { style } : {};
|
|
@@ -6200,6 +6287,93 @@ function registerBuiltins() {
|
|
|
6200
6287
|
const style = textTagStyle(p.backgroundColor || c.bg, c.text);
|
|
6201
6288
|
return h("span", { className: `${CLS}-text-tag ${CLS}-text-tag--${colorName}`, style }, extractText(p.text));
|
|
6202
6289
|
});
|
|
6290
|
+
registerComponent("local_datetime", (el) => {
|
|
6291
|
+
const p = el.property || {};
|
|
6292
|
+
let ms = Number(p.milliseconds ?? p.millisecond ?? p.value ?? 0);
|
|
6293
|
+
if (!Number.isFinite(ms) || ms === 0) return null;
|
|
6294
|
+
if (ms < 1e12) ms = ms * 1e3;
|
|
6295
|
+
const d = new Date(ms);
|
|
6296
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
6297
|
+
const ft = String(p.formatType || p.format_type || "").toLowerCase();
|
|
6298
|
+
const opts = ft.includes("time") && !ft.includes("date") ? { hour: "2-digit", minute: "2-digit", hour12: false } : ft.includes("date") && !ft.includes("time") ? { year: "numeric", month: "2-digit", day: "2-digit" } : { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false };
|
|
6299
|
+
let text;
|
|
6300
|
+
try {
|
|
6301
|
+
text = new Intl.DateTimeFormat("zh-CN", opts).format(d);
|
|
6302
|
+
} catch {
|
|
6303
|
+
text = d.toLocaleString();
|
|
6304
|
+
}
|
|
6305
|
+
return h("span", { className: `${CLS}-local-datetime` }, text);
|
|
6306
|
+
});
|
|
6307
|
+
registerComponent("number_tag", (el, ctx) => {
|
|
6308
|
+
const p = el.property || {};
|
|
6309
|
+
const raw = extractText(p.text);
|
|
6310
|
+
const text = raw != null && raw !== "" ? raw : p.content != null ? String(p.content) : "";
|
|
6311
|
+
if (text === "") return null;
|
|
6312
|
+
const parentFs = ctx.textMetrics?.fontSize ?? BASE_FONT_SIZE;
|
|
6313
|
+
const fs = Math.max(11, Math.round(parentFs * 0.85));
|
|
6314
|
+
const c = TAG_COLOR.blue;
|
|
6315
|
+
const style = {
|
|
6316
|
+
backgroundColor: c.bg,
|
|
6317
|
+
color: c.text,
|
|
6318
|
+
fontSize: fs + "px",
|
|
6319
|
+
lineHeight: "1.4",
|
|
6320
|
+
padding: "0 4px",
|
|
6321
|
+
borderRadius: "4px",
|
|
6322
|
+
display: "inline-flex",
|
|
6323
|
+
alignItems: "center",
|
|
6324
|
+
whiteSpace: "nowrap",
|
|
6325
|
+
margin: "0 2px",
|
|
6326
|
+
verticalAlign: "baseline"
|
|
6327
|
+
};
|
|
6328
|
+
return h("span", { className: `${CLS}-number-tag`, style }, text);
|
|
6329
|
+
});
|
|
6330
|
+
registerComponent("vote-card-select", (el) => {
|
|
6331
|
+
const p = el.property || {};
|
|
6332
|
+
const text = extractText(p.text) || (p.content != null ? String(p.content) : "");
|
|
6333
|
+
if (!text) return null;
|
|
6334
|
+
const voted = p.voted === true || p.isVoted === true || p.selected === true;
|
|
6335
|
+
const count = Number(p.count ?? p.voteCount ?? 0) || 0;
|
|
6336
|
+
const total = Number(p.total ?? p.totalCount ?? 0) || 0;
|
|
6337
|
+
let percent = Number(p.percent ?? p.ratio ?? NaN);
|
|
6338
|
+
if (!Number.isFinite(percent)) {
|
|
6339
|
+
percent = total > 0 ? Math.round(count / total * 1e3) / 10 : 0;
|
|
6340
|
+
}
|
|
6341
|
+
const rootCls = `${CLS}-vote-card-select` + (voted ? ` ${CLS}-vote-card-select--voted` : "");
|
|
6342
|
+
const kids = [
|
|
6343
|
+
h("div", { className: `${CLS}-vote-card-select-content` }, text)
|
|
6344
|
+
];
|
|
6345
|
+
if (voted) {
|
|
6346
|
+
kids.push(
|
|
6347
|
+
h("div", {
|
|
6348
|
+
className: `${CLS}-vote-card-select-result`,
|
|
6349
|
+
style: { fontSize: "12px", color: "#646a73", marginTop: "2px" }
|
|
6350
|
+
}, `${count} \u7968 ${percent}%`),
|
|
6351
|
+
h(
|
|
6352
|
+
"div",
|
|
6353
|
+
{
|
|
6354
|
+
className: `${CLS}-vote-card-select-progress`,
|
|
6355
|
+
style: {
|
|
6356
|
+
height: "4px",
|
|
6357
|
+
borderRadius: "2px",
|
|
6358
|
+
background: "#f2f3f5",
|
|
6359
|
+
marginTop: "4px",
|
|
6360
|
+
overflow: "hidden"
|
|
6361
|
+
}
|
|
6362
|
+
},
|
|
6363
|
+
h("div", {
|
|
6364
|
+
className: `${CLS}-vote-card-select-progress-bar`,
|
|
6365
|
+
style: {
|
|
6366
|
+
width: Math.min(100, Math.max(0, percent)) + "%",
|
|
6367
|
+
height: "100%",
|
|
6368
|
+
background: "#4e83fd",
|
|
6369
|
+
borderRadius: "2px"
|
|
6370
|
+
}
|
|
6371
|
+
})
|
|
6372
|
+
)
|
|
6373
|
+
);
|
|
6374
|
+
}
|
|
6375
|
+
return h("div", { className: rootCls, style: { padding: "8px 0" } }, kids);
|
|
6376
|
+
});
|
|
6203
6377
|
const EMOJI_FONT_SIZE = Number((BASE_FONT_SIZE * BASE_LINE_HEIGHT).toFixed(2));
|
|
6204
6378
|
registerComponent("emoji", (el, ctx) => {
|
|
6205
6379
|
const p = el.property || {};
|
|
@@ -6849,7 +7023,12 @@ function buildCardVNode(card, options) {
|
|
|
6849
7023
|
const parts = [];
|
|
6850
7024
|
if (jc.header) parts.push(renderElement(jc.header, ctx));
|
|
6851
7025
|
const bodyEl = jc.body || jc.newBody;
|
|
6852
|
-
if (bodyEl)
|
|
7026
|
+
if (bodyEl) {
|
|
7027
|
+
const headerTpl = jc.header?.property?.template;
|
|
7028
|
+
const compactHeader = !!jc.header && (!headerTpl || headerTpl === "default" || headerTpl === "disabled");
|
|
7029
|
+
const bodyCtx = compactHeader ? { ...ctx, bodyPaddingTop: 8 } : ctx;
|
|
7030
|
+
parts.push(renderElement(bodyEl.tag ? bodyEl : { ...bodyEl, tag: "body" }, bodyCtx));
|
|
7031
|
+
}
|
|
6853
7032
|
return h("div", {
|
|
6854
7033
|
className: `${CLS}-root ${CLS}`,
|
|
6855
7034
|
style: { fontSize: BASE_FONT_SIZE + "px", lineHeight: String(BASE_LINE_HEIGHT) }
|
package/dist/index.d.cts
CHANGED
|
@@ -149,13 +149,14 @@ interface ElementProperty {
|
|
|
149
149
|
emoji_type?: string;
|
|
150
150
|
/** 图标名,形如 "warning_outlined",拼成 class="ud-icon_<token>" */
|
|
151
151
|
token?: string;
|
|
152
|
-
style?: {
|
|
152
|
+
style?: string | {
|
|
153
153
|
color?: string;
|
|
154
154
|
size?: string;
|
|
155
155
|
[key: string]: unknown;
|
|
156
156
|
};
|
|
157
157
|
icon?: CardElement;
|
|
158
158
|
udIcon?: CardElement;
|
|
159
|
+
avatar?: CardElement;
|
|
159
160
|
/** VChart 规格对象(原样透传给 VChart 构造) */
|
|
160
161
|
chart_spec?: Record<string, unknown>;
|
|
161
162
|
chartSpec?: Record<string, unknown>;
|
|
@@ -309,6 +310,15 @@ interface RenderOptions {
|
|
|
309
310
|
* 交互预览。默认关闭:回调按钮是不可点的 span,输入框是静态占位。
|
|
310
311
|
* 打开后,回调按钮变成带 data-feishu-action 的 button,输入框/下拉框变成真实表单控件。
|
|
311
312
|
* 宿主监听这些控件,自己把点击发回卡片服务。
|
|
313
|
+
*
|
|
314
|
+
* #23/D13 —— 宿主上报契约(静态渲染器不替宿主发请求):
|
|
315
|
+
* 飞书客户端点击后组装 event_type="card.action.client_msg" 事件,并经
|
|
316
|
+
* im.v2.PutActionRequest(PUT_ACTION_V2,service id 2199)上报,信封必须自带:
|
|
317
|
+
* - messageId / actionId / traceId(宿主从消息上下文取)
|
|
318
|
+
* - form_value(表单控件当前值,key=字段 name)
|
|
319
|
+
* - client_channel / value(来自 data-feishu-action 上的 action 原文)
|
|
320
|
+
* 若宿主直接复用 data-feishu-action 里的 action 原文上报,会缺上述信封字段,
|
|
321
|
+
* 须自行补齐 PUT_ACTION_V2 信封后再发。
|
|
312
322
|
*/
|
|
313
323
|
interactive?: boolean;
|
|
314
324
|
}
|
|
@@ -425,6 +435,19 @@ interface RenderContext {
|
|
|
425
435
|
fontSize: number;
|
|
426
436
|
lineHeight: number;
|
|
427
437
|
};
|
|
438
|
+
/**
|
|
439
|
+
* column_set 注入给子 column 的分栏模式(#17/D7):flexMode 为 stretch/bisect/trisect 时,
|
|
440
|
+
* 子列无视自身 weight,按客户端同款 calc 宽度(用实际 columnGap)。
|
|
441
|
+
*/
|
|
442
|
+
columnSet?: {
|
|
443
|
+
flexMode?: string;
|
|
444
|
+
gap: number;
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* body 顶部 padding(#18/D8):header 存在且模板为 default/disabled/空时,
|
|
448
|
+
* 客户端把 body paddingTop 从 12 改为 8。由 index.ts 在渲染 body 前注入。
|
|
449
|
+
*/
|
|
450
|
+
bodyPaddingTop?: number;
|
|
428
451
|
}
|
|
429
452
|
/** 组件渲染函数 */
|
|
430
453
|
type ComponentFn = (el: CardElement, ctx: RenderContext) => VNode | null;
|
|
@@ -577,8 +600,9 @@ declare function feishuAudioHydrateJs(): string;
|
|
|
577
600
|
*/
|
|
578
601
|
|
|
579
602
|
/**
|
|
580
|
-
* 图表(chart
|
|
581
|
-
*
|
|
603
|
+
* 图表(chart)默认序列色板。#21/D11:对齐飞书卡片图表 VChart light 主题
|
|
604
|
+
* brand.light.normal 的 30 色序列(首色 #4e83fd),而非 VChart 通用默认。
|
|
605
|
+
* hydrate 时若 spec 未显式给 color,注入此序列。
|
|
582
606
|
*/
|
|
583
607
|
declare const CHART_PALETTE: string[];
|
|
584
608
|
|
package/dist/index.d.ts
CHANGED
|
@@ -149,13 +149,14 @@ interface ElementProperty {
|
|
|
149
149
|
emoji_type?: string;
|
|
150
150
|
/** 图标名,形如 "warning_outlined",拼成 class="ud-icon_<token>" */
|
|
151
151
|
token?: string;
|
|
152
|
-
style?: {
|
|
152
|
+
style?: string | {
|
|
153
153
|
color?: string;
|
|
154
154
|
size?: string;
|
|
155
155
|
[key: string]: unknown;
|
|
156
156
|
};
|
|
157
157
|
icon?: CardElement;
|
|
158
158
|
udIcon?: CardElement;
|
|
159
|
+
avatar?: CardElement;
|
|
159
160
|
/** VChart 规格对象(原样透传给 VChart 构造) */
|
|
160
161
|
chart_spec?: Record<string, unknown>;
|
|
161
162
|
chartSpec?: Record<string, unknown>;
|
|
@@ -309,6 +310,15 @@ interface RenderOptions {
|
|
|
309
310
|
* 交互预览。默认关闭:回调按钮是不可点的 span,输入框是静态占位。
|
|
310
311
|
* 打开后,回调按钮变成带 data-feishu-action 的 button,输入框/下拉框变成真实表单控件。
|
|
311
312
|
* 宿主监听这些控件,自己把点击发回卡片服务。
|
|
313
|
+
*
|
|
314
|
+
* #23/D13 —— 宿主上报契约(静态渲染器不替宿主发请求):
|
|
315
|
+
* 飞书客户端点击后组装 event_type="card.action.client_msg" 事件,并经
|
|
316
|
+
* im.v2.PutActionRequest(PUT_ACTION_V2,service id 2199)上报,信封必须自带:
|
|
317
|
+
* - messageId / actionId / traceId(宿主从消息上下文取)
|
|
318
|
+
* - form_value(表单控件当前值,key=字段 name)
|
|
319
|
+
* - client_channel / value(来自 data-feishu-action 上的 action 原文)
|
|
320
|
+
* 若宿主直接复用 data-feishu-action 里的 action 原文上报,会缺上述信封字段,
|
|
321
|
+
* 须自行补齐 PUT_ACTION_V2 信封后再发。
|
|
312
322
|
*/
|
|
313
323
|
interactive?: boolean;
|
|
314
324
|
}
|
|
@@ -425,6 +435,19 @@ interface RenderContext {
|
|
|
425
435
|
fontSize: number;
|
|
426
436
|
lineHeight: number;
|
|
427
437
|
};
|
|
438
|
+
/**
|
|
439
|
+
* column_set 注入给子 column 的分栏模式(#17/D7):flexMode 为 stretch/bisect/trisect 时,
|
|
440
|
+
* 子列无视自身 weight,按客户端同款 calc 宽度(用实际 columnGap)。
|
|
441
|
+
*/
|
|
442
|
+
columnSet?: {
|
|
443
|
+
flexMode?: string;
|
|
444
|
+
gap: number;
|
|
445
|
+
};
|
|
446
|
+
/**
|
|
447
|
+
* body 顶部 padding(#18/D8):header 存在且模板为 default/disabled/空时,
|
|
448
|
+
* 客户端把 body paddingTop 从 12 改为 8。由 index.ts 在渲染 body 前注入。
|
|
449
|
+
*/
|
|
450
|
+
bodyPaddingTop?: number;
|
|
428
451
|
}
|
|
429
452
|
/** 组件渲染函数 */
|
|
430
453
|
type ComponentFn = (el: CardElement, ctx: RenderContext) => VNode | null;
|
|
@@ -577,8 +600,9 @@ declare function feishuAudioHydrateJs(): string;
|
|
|
577
600
|
*/
|
|
578
601
|
|
|
579
602
|
/**
|
|
580
|
-
* 图表(chart
|
|
581
|
-
*
|
|
603
|
+
* 图表(chart)默认序列色板。#21/D11:对齐飞书卡片图表 VChart light 主题
|
|
604
|
+
* brand.light.normal 的 30 色序列(首色 #4e83fd),而非 VChart 通用默认。
|
|
605
|
+
* hydrate 时若 spec 未显式给 color,注入此序列。
|
|
582
606
|
*/
|
|
583
607
|
declare const CHART_PALETTE: string[];
|
|
584
608
|
|
package/dist/index.js
CHANGED
|
@@ -391,16 +391,36 @@ var TABLE_HEAD_BG = {
|
|
|
391
391
|
default: "transparent"
|
|
392
392
|
};
|
|
393
393
|
var CHART_PALETTE = [
|
|
394
|
-
"#
|
|
395
|
-
"#
|
|
396
|
-
"#
|
|
397
|
-
"#
|
|
398
|
-
"#
|
|
399
|
-
"#
|
|
400
|
-
"#
|
|
401
|
-
"#
|
|
402
|
-
"#
|
|
403
|
-
"#
|
|
394
|
+
"#4e83fd",
|
|
395
|
+
"#50cefb",
|
|
396
|
+
"#935af6",
|
|
397
|
+
"#fad355",
|
|
398
|
+
"#f76964",
|
|
399
|
+
"#ffa53d",
|
|
400
|
+
"#82a7fc",
|
|
401
|
+
"#7edafb",
|
|
402
|
+
"#ad82f7",
|
|
403
|
+
"#f7dc82",
|
|
404
|
+
"#f98e8b",
|
|
405
|
+
"#ffba6b",
|
|
406
|
+
"#3370ff",
|
|
407
|
+
"#14c0ff",
|
|
408
|
+
"#7f3bf5",
|
|
409
|
+
"#ffc60a",
|
|
410
|
+
"#f54a45",
|
|
411
|
+
"#ff8800",
|
|
412
|
+
"#bacefd",
|
|
413
|
+
"#b1e8fc",
|
|
414
|
+
"#cdb2fa",
|
|
415
|
+
"#f8e6ab",
|
|
416
|
+
"#fbbfbc",
|
|
417
|
+
"#fed4a4",
|
|
418
|
+
"#245bdb",
|
|
419
|
+
"#049fd7",
|
|
420
|
+
"#6425d0",
|
|
421
|
+
"#dc9b04",
|
|
422
|
+
"#d83931",
|
|
423
|
+
"#de7802"
|
|
404
424
|
];
|
|
405
425
|
var CHART_DEFAULT_ASPECT_RATIO = "16:9";
|
|
406
426
|
var CHART_MIN_HEIGHT = 150;
|
|
@@ -685,6 +705,25 @@ function personAvatarSrc(person, ctx) {
|
|
|
685
705
|
const suffix = ctx.options.avatarURLSuffix ?? PERSON_AVATAR_SUFFIX;
|
|
686
706
|
return base + person.avatar_key + suffix;
|
|
687
707
|
}
|
|
708
|
+
var HORIZONTAL_SPACING_PX = { small: 4, default: 8, large: 12 };
|
|
709
|
+
var VERTICAL_SPACING_PX = { default: 8, medium: 12, large: 16 };
|
|
710
|
+
function namedSpacingToPx(v, map) {
|
|
711
|
+
const named = map[v];
|
|
712
|
+
if (named != null) return named;
|
|
713
|
+
const m = /^(-?\d+(?:\.\d+)?)(?:px)?$/.exec(v.trim());
|
|
714
|
+
if (m) return Number(m[1]);
|
|
715
|
+
return map.default ?? 8;
|
|
716
|
+
}
|
|
717
|
+
function horizontalSpacingToPx(v) {
|
|
718
|
+
if (typeof v === "number") return v;
|
|
719
|
+
if (typeof v === "string") return namedSpacingToPx(v, HORIZONTAL_SPACING_PX);
|
|
720
|
+
return HORIZONTAL_SPACING_PX.default;
|
|
721
|
+
}
|
|
722
|
+
function verticalSpacingToPx(v) {
|
|
723
|
+
if (typeof v === "number") return v;
|
|
724
|
+
if (typeof v === "string") return namedSpacingToPx(v, VERTICAL_SPACING_PX);
|
|
725
|
+
return VERTICAL_SPACING_PX.default;
|
|
726
|
+
}
|
|
688
727
|
function spacingToPx(v) {
|
|
689
728
|
if (typeof v === "number") return v;
|
|
690
729
|
if (typeof v === "string") {
|
|
@@ -1312,14 +1351,9 @@ var BASE_STYLES_CSS = `/**
|
|
|
1312
1351
|
flex-shrink: 0; height: 1.2em; color: inherit;
|
|
1313
1352
|
}
|
|
1314
1353
|
.universal-card-button__icon .universal-card-icon { color: inherit; }
|
|
1315
|
-
/*
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
width: auto;
|
|
1319
|
-
max-width: 100%;
|
|
1320
|
-
align-self: flex-start;
|
|
1321
|
-
}
|
|
1322
|
-
/* fill\uFF08width:"fill"\uFF09\uFF1A\u6491\u6EE1\u6240\u5728\u5217 */
|
|
1354
|
+
/* \u5BBD\u5EA6\uFF1A\u5BF9\u9F50\u771F\u673A\u2014\u2014body > * { width:100% }\u3001\u5217 flex stretch \u4F1A\u628A\u6309\u94AE\u6491\u6EE1\u6240\u5728\u884C/\u5217\u3002
|
|
1355
|
+
\u4E0D\u518D\u5F3A\u5236 width:auto\uFF08\u90A3\u662F\u7F51\u9875\u7248\u89C2\u611F\uFF1B\u771F\u673A\u72EC\u7ACB\u6210\u884C\u7684\u6309\u94AE\u9ED8\u8BA4\u5360\u6EE1\u6574\u884C\uFF09\u3002
|
|
1356
|
+
width:"fill" \u663E\u5F0F\u6302 --block\uFF0C\u4E0E\u7F3A\u7701\u6491\u6EE1\u540C\u5BBD\uFF0C\u5E76\u4F9B margin calc \u8BC6\u522B\u3002 */
|
|
1323
1357
|
.universal-card-button--block { width: 100%; }
|
|
1324
1358
|
|
|
1325
1359
|
/* \u2014\u2014 outlined \u63CF\u8FB9\u65CF \u2014\u2014 */
|
|
@@ -1341,11 +1375,12 @@ var BASE_STYLES_CSS = `/**
|
|
|
1341
1375
|
}
|
|
1342
1376
|
.universal-card-button--danger:not(.universal-card-button--static):hover { background: #fef0f0; }
|
|
1343
1377
|
|
|
1344
|
-
/* \u2014\u2014 text \u7EAF\u6587\u5B57\u65CF\uFF08\u65E0\u8FB9\u6846/\u65E0\u5E95\u8272\uFF0C\
|
|
1378
|
+
/* \u2014\u2014 text \u7EAF\u6587\u5B57\u65CF\uFF08\u65E0\u8FB9\u6846/\u65E0\u5E95\u8272\uFF1B\u6491\u6EE1\u65F6\u6587\u5B57\u9760\u5DE6\uFF0C\u5BF9\u9F50\u771F\u673A\uFF09 \u2014\u2014 */
|
|
1345
1379
|
.universal-card-button--text,
|
|
1346
1380
|
.universal-card-button--primary_text,
|
|
1347
1381
|
.universal-card-button--danger_text {
|
|
1348
1382
|
border-color: transparent; background: transparent; padding: 4px 8px;
|
|
1383
|
+
justify-content: flex-start;
|
|
1349
1384
|
}
|
|
1350
1385
|
.universal-card-button--text { color: #1f2329; }
|
|
1351
1386
|
.universal-card-button--primary_text { color: #1456f0; }
|
|
@@ -4898,7 +4933,7 @@ function parseInline(text) {
|
|
|
4898
4933
|
if (attrs.show_name != null) property.showName = attrs.show_name !== "false";
|
|
4899
4934
|
if (attrs.show_avatar != null) property.showAvatar = attrs.show_avatar !== "false";
|
|
4900
4935
|
if (attrs.size) property.size = attrs.size;
|
|
4901
|
-
if (attrs.style
|
|
4936
|
+
if (attrs.style) property.style = attrs.style;
|
|
4902
4937
|
out.push({ tag: "person_v1", property });
|
|
4903
4938
|
i += m[0].length;
|
|
4904
4939
|
continue;
|
|
@@ -5499,8 +5534,9 @@ function registerBuiltins() {
|
|
|
5499
5534
|
const style = {};
|
|
5500
5535
|
const pad = boxToStyle(p.padding);
|
|
5501
5536
|
if (pad) style.padding = pad;
|
|
5537
|
+
if (!pad && ctx.bodyPaddingTop != null) style.paddingTop = ctx.bodyPaddingTop + "px";
|
|
5502
5538
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5503
|
-
if (vGap != null) style.rowGap =
|
|
5539
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5504
5540
|
const props = { className: `${CLS}-body` };
|
|
5505
5541
|
if (Object.keys(style).length) props.style = style;
|
|
5506
5542
|
return h("div", props, renderElements(getChildElements(el), ctx));
|
|
@@ -5510,7 +5546,8 @@ function registerBuiltins() {
|
|
|
5510
5546
|
const tplKey = p.template || "default";
|
|
5511
5547
|
const tpl = HEADER_TEMPLATE[tplKey] || HEADER_TEMPLATE.default;
|
|
5512
5548
|
const tags = p.text_tag_list || p.textTagList;
|
|
5513
|
-
const
|
|
5549
|
+
const tagCtx = { ...ctx, textMetrics: { fontSize: 16, lineHeight: 24 } };
|
|
5550
|
+
const tagVNodes = Array.isArray(tags) && tags.length ? renderElements(tags.slice(0, 3), tagCtx) : [];
|
|
5514
5551
|
const iconRaw = p.udIcon || p.icon;
|
|
5515
5552
|
let iconVNode = null;
|
|
5516
5553
|
if (iconRaw && iconRaw.tag) {
|
|
@@ -5520,6 +5557,21 @@ function registerBuiltins() {
|
|
|
5520
5557
|
const inner = renderIcon(iconEl, ctx);
|
|
5521
5558
|
if (inner) iconVNode = h("div", { className: `${CLS}-header__icon` }, inner);
|
|
5522
5559
|
}
|
|
5560
|
+
if (!iconVNode) {
|
|
5561
|
+
const avatarEl = p.avatar;
|
|
5562
|
+
const avatarSrc = avatarEl && avatarEl.property ? resolveImageSrc(avatarEl.property, ctx) : null;
|
|
5563
|
+
if (avatarSrc) {
|
|
5564
|
+
iconVNode = h(
|
|
5565
|
+
"div",
|
|
5566
|
+
{ className: `${CLS}-header__icon` },
|
|
5567
|
+
h("img", {
|
|
5568
|
+
src: avatarSrc,
|
|
5569
|
+
alt: "",
|
|
5570
|
+
style: { width: "16px", height: "16px", borderRadius: "4px", objectFit: "cover" }
|
|
5571
|
+
})
|
|
5572
|
+
);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5523
5575
|
const titleVNode = renderHeaderTextLeaf(p.title, ctx, `${CLS}-header__text-title`) ?? h("div", { className: `${CLS}-header__text-title` });
|
|
5524
5576
|
const subtitleVNode = renderHeaderTextLeaf(p.subtitle, ctx, `${CLS}-header__text-subtitle`);
|
|
5525
5577
|
const textChildren = [
|
|
@@ -5558,7 +5610,7 @@ function registerBuiltins() {
|
|
|
5558
5610
|
)
|
|
5559
5611
|
);
|
|
5560
5612
|
});
|
|
5561
|
-
|
|
5613
|
+
function MarkdownComponent(el, ctx) {
|
|
5562
5614
|
const p = el.property || {};
|
|
5563
5615
|
const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
|
|
5564
5616
|
applyOuterMargin(style, p.margin);
|
|
@@ -5572,12 +5624,28 @@ function registerBuiltins() {
|
|
|
5572
5624
|
style.lineHeight = lineHeight + "px";
|
|
5573
5625
|
}
|
|
5574
5626
|
const childCtx = { ...ctx, textMetrics: { fontSize, lineHeight } };
|
|
5627
|
+
const linesStyle = lineClampStyle(p.lines ?? p.textLines);
|
|
5575
5628
|
return h(
|
|
5576
5629
|
"div",
|
|
5577
5630
|
{ className: `${CLS}-markdown`, style },
|
|
5578
|
-
h("div", {
|
|
5631
|
+
h("div", {
|
|
5632
|
+
className: `${CLS}-markdown__children`,
|
|
5633
|
+
style: Object.keys(linesStyle).length ? linesStyle : void 0
|
|
5634
|
+
}, renderElements(p.elements, childCtx))
|
|
5579
5635
|
);
|
|
5580
|
-
}
|
|
5636
|
+
}
|
|
5637
|
+
registerComponent("markdown", MarkdownComponent);
|
|
5638
|
+
registerComponent("markdown_v1", MarkdownComponent);
|
|
5639
|
+
function lineClampStyle(lines) {
|
|
5640
|
+
const n = Number(lines);
|
|
5641
|
+
if (!Number.isFinite(n) || n <= 0) return {};
|
|
5642
|
+
return {
|
|
5643
|
+
display: "-webkit-box",
|
|
5644
|
+
WebkitLineClamp: String(Math.floor(n)),
|
|
5645
|
+
WebkitBoxOrient: "vertical",
|
|
5646
|
+
overflow: "hidden"
|
|
5647
|
+
};
|
|
5648
|
+
}
|
|
5581
5649
|
registerComponent("plain_text", (el) => {
|
|
5582
5650
|
const p = el.property || {};
|
|
5583
5651
|
const mods = textStyleToModifiers(p.textStyle);
|
|
@@ -5586,6 +5654,7 @@ function registerBuiltins() {
|
|
|
5586
5654
|
delete style.fontWeight;
|
|
5587
5655
|
delete style.fontStyle;
|
|
5588
5656
|
delete style.textDecoration;
|
|
5657
|
+
Object.assign(style, lineClampStyle(p.lines));
|
|
5589
5658
|
return h("span", { className, style }, p.content != null ? String(p.content) : "");
|
|
5590
5659
|
});
|
|
5591
5660
|
registerComponent("br", () => h("br", {}));
|
|
@@ -5610,16 +5679,16 @@ function registerBuiltins() {
|
|
|
5610
5679
|
registerComponent("column_set", (el, ctx) => {
|
|
5611
5680
|
const p = el.property || {};
|
|
5612
5681
|
const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
|
|
5613
|
-
const gap = gapRaw != null ?
|
|
5682
|
+
const gap = gapRaw != null ? horizontalSpacingToPx(gapRaw) : 0;
|
|
5614
5683
|
const flexMode = p.flexMode || p.flex_mode;
|
|
5615
5684
|
const hAlign = p.horizontalAlign || p.horizontal_align;
|
|
5616
5685
|
const style = {
|
|
5617
5686
|
display: "flex",
|
|
5618
5687
|
flexWrap: flexMode === "flow" || flexMode === "wrap" ? "wrap" : "nowrap",
|
|
5619
|
-
columnGap: gap + "px",
|
|
5620
5688
|
rowGap: "8px",
|
|
5621
5689
|
alignItems: "stretch"
|
|
5622
5690
|
};
|
|
5691
|
+
if (gap > 0) style.columnGap = gap + "px";
|
|
5623
5692
|
if (hAlign) {
|
|
5624
5693
|
style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
|
|
5625
5694
|
}
|
|
@@ -5637,7 +5706,8 @@ function registerBuiltins() {
|
|
|
5637
5706
|
style.margin = "0";
|
|
5638
5707
|
style.padding = "0";
|
|
5639
5708
|
}
|
|
5640
|
-
|
|
5709
|
+
const childCtx = { ...ctx, columnSet: { flexMode, gap } };
|
|
5710
|
+
return h("div", { className: cls, style }, renderElements(p.columns, childCtx));
|
|
5641
5711
|
});
|
|
5642
5712
|
function isStateHost(el) {
|
|
5643
5713
|
if (el.id === "card_state") return true;
|
|
@@ -5672,12 +5742,30 @@ function registerBuiltins() {
|
|
|
5672
5742
|
} else {
|
|
5673
5743
|
style.flexGrow = String(p.weight || 1);
|
|
5674
5744
|
}
|
|
5745
|
+
const cs = ctx.columnSet;
|
|
5746
|
+
if (cs && cs.flexMode) {
|
|
5747
|
+
const fm = String(cs.flexMode).toLowerCase();
|
|
5748
|
+
const g = cs.gap || 0;
|
|
5749
|
+
if (fm === "stretch") {
|
|
5750
|
+
style.width = "100%";
|
|
5751
|
+
style.flexGrow = "0";
|
|
5752
|
+
style.flexBasis = "auto";
|
|
5753
|
+
} else if (fm === "bisect") {
|
|
5754
|
+
style.width = `calc(50% - ${g / 2}px)`;
|
|
5755
|
+
style.flexGrow = "0";
|
|
5756
|
+
style.flexBasis = "auto";
|
|
5757
|
+
} else if (fm === "trisect") {
|
|
5758
|
+
style.width = `calc(33.33% - ${2 * g / 3}px)`;
|
|
5759
|
+
style.flexGrow = "0";
|
|
5760
|
+
style.flexBasis = "auto";
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5675
5763
|
const vAlign = p.verticalAlign || p.vertical_align;
|
|
5676
5764
|
if (vAlign) {
|
|
5677
5765
|
style.justifyContent = { top: "flex-start", center: "center", bottom: "flex-end" }[vAlign] || "flex-start";
|
|
5678
5766
|
}
|
|
5679
5767
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5680
|
-
style.rowGap = (vGap != null ?
|
|
5768
|
+
style.rowGap = (vGap != null ? verticalSpacingToPx(vGap) : 8) + "px";
|
|
5681
5769
|
const bg = propertyBackgroundColor(p);
|
|
5682
5770
|
if (bg) style.background = bg;
|
|
5683
5771
|
const pad = boxToStyle(p.padding);
|
|
@@ -5885,7 +5973,7 @@ function registerBuiltins() {
|
|
|
5885
5973
|
const radius = resolveCornerRadius(p);
|
|
5886
5974
|
if (radius != null) style.borderRadius = radius;
|
|
5887
5975
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5888
|
-
if (vGap != null) style.rowGap =
|
|
5976
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5889
5977
|
const icWv = p.widthValue;
|
|
5890
5978
|
const icHv = p.heightValue;
|
|
5891
5979
|
const w = resolveDimensionPx(p.width, icWv);
|
|
@@ -5987,14 +6075,13 @@ function registerBuiltins() {
|
|
|
5987
6075
|
const children = iconVNode ? [iconVNode, label] : [label];
|
|
5988
6076
|
const actionUrl = Array.isArray(p.actions) ? resolveURL(p.actions.find((a) => a.type === "open_url")?.action?.url) : "";
|
|
5989
6077
|
const url = actionUrl || resolveURL(p.url) || p.behaviors?.[0]?.default_url?.url;
|
|
5990
|
-
const isText = type === "text" || type === "primary_text" || type === "danger_text";
|
|
5991
6078
|
const wv = p.widthValue;
|
|
5992
6079
|
const widthTok = p.width ?? wv?.value;
|
|
5993
|
-
const block = widthTok === "fill"
|
|
6080
|
+
const block = widthTok === "fill";
|
|
5994
6081
|
const base = `${CLS}-button ${CLS}-button--${type}${block ? ` ${CLS}-button--block` : ""}`;
|
|
5995
6082
|
const style = {};
|
|
5996
6083
|
if (p.margin) {
|
|
5997
|
-
style.width =
|
|
6084
|
+
style.width = "100%";
|
|
5998
6085
|
applyOuterMargin(style, p.margin);
|
|
5999
6086
|
}
|
|
6000
6087
|
const styleProp = Object.keys(style).length ? { style } : {};
|
|
@@ -6199,6 +6286,93 @@ function registerBuiltins() {
|
|
|
6199
6286
|
const style = textTagStyle(p.backgroundColor || c.bg, c.text);
|
|
6200
6287
|
return h("span", { className: `${CLS}-text-tag ${CLS}-text-tag--${colorName}`, style }, extractText(p.text));
|
|
6201
6288
|
});
|
|
6289
|
+
registerComponent("local_datetime", (el) => {
|
|
6290
|
+
const p = el.property || {};
|
|
6291
|
+
let ms = Number(p.milliseconds ?? p.millisecond ?? p.value ?? 0);
|
|
6292
|
+
if (!Number.isFinite(ms) || ms === 0) return null;
|
|
6293
|
+
if (ms < 1e12) ms = ms * 1e3;
|
|
6294
|
+
const d = new Date(ms);
|
|
6295
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
6296
|
+
const ft = String(p.formatType || p.format_type || "").toLowerCase();
|
|
6297
|
+
const opts = ft.includes("time") && !ft.includes("date") ? { hour: "2-digit", minute: "2-digit", hour12: false } : ft.includes("date") && !ft.includes("time") ? { year: "numeric", month: "2-digit", day: "2-digit" } : { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false };
|
|
6298
|
+
let text;
|
|
6299
|
+
try {
|
|
6300
|
+
text = new Intl.DateTimeFormat("zh-CN", opts).format(d);
|
|
6301
|
+
} catch {
|
|
6302
|
+
text = d.toLocaleString();
|
|
6303
|
+
}
|
|
6304
|
+
return h("span", { className: `${CLS}-local-datetime` }, text);
|
|
6305
|
+
});
|
|
6306
|
+
registerComponent("number_tag", (el, ctx) => {
|
|
6307
|
+
const p = el.property || {};
|
|
6308
|
+
const raw = extractText(p.text);
|
|
6309
|
+
const text = raw != null && raw !== "" ? raw : p.content != null ? String(p.content) : "";
|
|
6310
|
+
if (text === "") return null;
|
|
6311
|
+
const parentFs = ctx.textMetrics?.fontSize ?? BASE_FONT_SIZE;
|
|
6312
|
+
const fs = Math.max(11, Math.round(parentFs * 0.85));
|
|
6313
|
+
const c = TAG_COLOR.blue;
|
|
6314
|
+
const style = {
|
|
6315
|
+
backgroundColor: c.bg,
|
|
6316
|
+
color: c.text,
|
|
6317
|
+
fontSize: fs + "px",
|
|
6318
|
+
lineHeight: "1.4",
|
|
6319
|
+
padding: "0 4px",
|
|
6320
|
+
borderRadius: "4px",
|
|
6321
|
+
display: "inline-flex",
|
|
6322
|
+
alignItems: "center",
|
|
6323
|
+
whiteSpace: "nowrap",
|
|
6324
|
+
margin: "0 2px",
|
|
6325
|
+
verticalAlign: "baseline"
|
|
6326
|
+
};
|
|
6327
|
+
return h("span", { className: `${CLS}-number-tag`, style }, text);
|
|
6328
|
+
});
|
|
6329
|
+
registerComponent("vote-card-select", (el) => {
|
|
6330
|
+
const p = el.property || {};
|
|
6331
|
+
const text = extractText(p.text) || (p.content != null ? String(p.content) : "");
|
|
6332
|
+
if (!text) return null;
|
|
6333
|
+
const voted = p.voted === true || p.isVoted === true || p.selected === true;
|
|
6334
|
+
const count = Number(p.count ?? p.voteCount ?? 0) || 0;
|
|
6335
|
+
const total = Number(p.total ?? p.totalCount ?? 0) || 0;
|
|
6336
|
+
let percent = Number(p.percent ?? p.ratio ?? NaN);
|
|
6337
|
+
if (!Number.isFinite(percent)) {
|
|
6338
|
+
percent = total > 0 ? Math.round(count / total * 1e3) / 10 : 0;
|
|
6339
|
+
}
|
|
6340
|
+
const rootCls = `${CLS}-vote-card-select` + (voted ? ` ${CLS}-vote-card-select--voted` : "");
|
|
6341
|
+
const kids = [
|
|
6342
|
+
h("div", { className: `${CLS}-vote-card-select-content` }, text)
|
|
6343
|
+
];
|
|
6344
|
+
if (voted) {
|
|
6345
|
+
kids.push(
|
|
6346
|
+
h("div", {
|
|
6347
|
+
className: `${CLS}-vote-card-select-result`,
|
|
6348
|
+
style: { fontSize: "12px", color: "#646a73", marginTop: "2px" }
|
|
6349
|
+
}, `${count} \u7968 ${percent}%`),
|
|
6350
|
+
h(
|
|
6351
|
+
"div",
|
|
6352
|
+
{
|
|
6353
|
+
className: `${CLS}-vote-card-select-progress`,
|
|
6354
|
+
style: {
|
|
6355
|
+
height: "4px",
|
|
6356
|
+
borderRadius: "2px",
|
|
6357
|
+
background: "#f2f3f5",
|
|
6358
|
+
marginTop: "4px",
|
|
6359
|
+
overflow: "hidden"
|
|
6360
|
+
}
|
|
6361
|
+
},
|
|
6362
|
+
h("div", {
|
|
6363
|
+
className: `${CLS}-vote-card-select-progress-bar`,
|
|
6364
|
+
style: {
|
|
6365
|
+
width: Math.min(100, Math.max(0, percent)) + "%",
|
|
6366
|
+
height: "100%",
|
|
6367
|
+
background: "#4e83fd",
|
|
6368
|
+
borderRadius: "2px"
|
|
6369
|
+
}
|
|
6370
|
+
})
|
|
6371
|
+
)
|
|
6372
|
+
);
|
|
6373
|
+
}
|
|
6374
|
+
return h("div", { className: rootCls, style: { padding: "8px 0" } }, kids);
|
|
6375
|
+
});
|
|
6202
6376
|
const EMOJI_FONT_SIZE = Number((BASE_FONT_SIZE * BASE_LINE_HEIGHT).toFixed(2));
|
|
6203
6377
|
registerComponent("emoji", (el, ctx) => {
|
|
6204
6378
|
const p = el.property || {};
|
|
@@ -6848,7 +7022,12 @@ function buildCardVNode(card, options) {
|
|
|
6848
7022
|
const parts = [];
|
|
6849
7023
|
if (jc.header) parts.push(renderElement(jc.header, ctx));
|
|
6850
7024
|
const bodyEl = jc.body || jc.newBody;
|
|
6851
|
-
if (bodyEl)
|
|
7025
|
+
if (bodyEl) {
|
|
7026
|
+
const headerTpl = jc.header?.property?.template;
|
|
7027
|
+
const compactHeader = !!jc.header && (!headerTpl || headerTpl === "default" || headerTpl === "disabled");
|
|
7028
|
+
const bodyCtx = compactHeader ? { ...ctx, bodyPaddingTop: 8 } : ctx;
|
|
7029
|
+
parts.push(renderElement(bodyEl.tag ? bodyEl : { ...bodyEl, tag: "body" }, bodyCtx));
|
|
7030
|
+
}
|
|
6852
7031
|
return h("div", {
|
|
6853
7032
|
className: `${CLS}-root ${CLS}`,
|
|
6854
7033
|
style: { fontSize: BASE_FONT_SIZE + "px", lineHeight: String(BASE_LINE_HEIGHT) }
|
package/dist/styles.css
CHANGED
|
@@ -384,14 +384,9 @@
|
|
|
384
384
|
flex-shrink: 0; height: 1.2em; color: inherit;
|
|
385
385
|
}
|
|
386
386
|
.universal-card-button__icon .universal-card-icon { color: inherit; }
|
|
387
|
-
/*
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
width: auto;
|
|
391
|
-
max-width: 100%;
|
|
392
|
-
align-self: flex-start;
|
|
393
|
-
}
|
|
394
|
-
/* fill(width:"fill"):撑满所在列 */
|
|
387
|
+
/* 宽度:对齐真机——body > * { width:100% }、列 flex stretch 会把按钮撑满所在行/列。
|
|
388
|
+
不再强制 width:auto(那是网页版观感;真机独立成行的按钮默认占满整行)。
|
|
389
|
+
width:"fill" 显式挂 --block,与缺省撑满同宽,并供 margin calc 识别。 */
|
|
395
390
|
.universal-card-button--block { width: 100%; }
|
|
396
391
|
|
|
397
392
|
/* —— outlined 描边族 —— */
|
|
@@ -413,11 +408,12 @@
|
|
|
413
408
|
}
|
|
414
409
|
.universal-card-button--danger:not(.universal-card-button--static):hover { background: #fef0f0; }
|
|
415
410
|
|
|
416
|
-
/* —— text
|
|
411
|
+
/* —— text 纯文字族(无边框/无底色;撑满时文字靠左,对齐真机) —— */
|
|
417
412
|
.universal-card-button--text,
|
|
418
413
|
.universal-card-button--primary_text,
|
|
419
414
|
.universal-card-button--danger_text {
|
|
420
415
|
border-color: transparent; background: transparent; padding: 4px 8px;
|
|
416
|
+
justify-content: flex-start;
|
|
421
417
|
}
|
|
422
418
|
.universal-card-button--text { color: #1f2329; }
|
|
423
419
|
.universal-card-button--primary_text { color: #1456f0; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-feishu-card/renderer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.28",
|
|
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.
|
|
31
|
+
"@open-feishu-card/adapter": "0.3.18"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"tsup": "^8.3.5",
|