@open-feishu-card/renderer 0.2.27 → 0.2.29
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/index.cjs +207 -23
- package/dist/index.d.cts +27 -3
- package/dist/index.d.ts +27 -3
- package/dist/index.js +207 -23
- package/package.json +2 -2
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") {
|
|
@@ -4895,7 +4934,7 @@ function parseInline(text) {
|
|
|
4895
4934
|
if (attrs.show_name != null) property.showName = attrs.show_name !== "false";
|
|
4896
4935
|
if (attrs.show_avatar != null) property.showAvatar = attrs.show_avatar !== "false";
|
|
4897
4936
|
if (attrs.size) property.size = attrs.size;
|
|
4898
|
-
if (attrs.style
|
|
4937
|
+
if (attrs.style) property.style = attrs.style;
|
|
4899
4938
|
out.push({ tag: "person_v1", property });
|
|
4900
4939
|
i += m[0].length;
|
|
4901
4940
|
continue;
|
|
@@ -5496,8 +5535,9 @@ function registerBuiltins() {
|
|
|
5496
5535
|
const style = {};
|
|
5497
5536
|
const pad = boxToStyle(p.padding);
|
|
5498
5537
|
if (pad) style.padding = pad;
|
|
5538
|
+
if (!pad && ctx.bodyPaddingTop != null) style.paddingTop = ctx.bodyPaddingTop + "px";
|
|
5499
5539
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5500
|
-
if (vGap != null) style.rowGap =
|
|
5540
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5501
5541
|
const props = { className: `${CLS}-body` };
|
|
5502
5542
|
if (Object.keys(style).length) props.style = style;
|
|
5503
5543
|
return h("div", props, renderElements(getChildElements(el), ctx));
|
|
@@ -5507,7 +5547,8 @@ function registerBuiltins() {
|
|
|
5507
5547
|
const tplKey = p.template || "default";
|
|
5508
5548
|
const tpl = HEADER_TEMPLATE[tplKey] || HEADER_TEMPLATE.default;
|
|
5509
5549
|
const tags = p.text_tag_list || p.textTagList;
|
|
5510
|
-
const
|
|
5550
|
+
const tagCtx = { ...ctx, textMetrics: { fontSize: 16, lineHeight: 24 } };
|
|
5551
|
+
const tagVNodes = Array.isArray(tags) && tags.length ? renderElements(tags.slice(0, 3), tagCtx) : [];
|
|
5511
5552
|
const iconRaw = p.udIcon || p.icon;
|
|
5512
5553
|
let iconVNode = null;
|
|
5513
5554
|
if (iconRaw && iconRaw.tag) {
|
|
@@ -5517,6 +5558,21 @@ function registerBuiltins() {
|
|
|
5517
5558
|
const inner = renderIcon(iconEl, ctx);
|
|
5518
5559
|
if (inner) iconVNode = h("div", { className: `${CLS}-header__icon` }, inner);
|
|
5519
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
|
+
}
|
|
5520
5576
|
const titleVNode = renderHeaderTextLeaf(p.title, ctx, `${CLS}-header__text-title`) ?? h("div", { className: `${CLS}-header__text-title` });
|
|
5521
5577
|
const subtitleVNode = renderHeaderTextLeaf(p.subtitle, ctx, `${CLS}-header__text-subtitle`);
|
|
5522
5578
|
const textChildren = [
|
|
@@ -5555,7 +5611,7 @@ function registerBuiltins() {
|
|
|
5555
5611
|
)
|
|
5556
5612
|
);
|
|
5557
5613
|
});
|
|
5558
|
-
|
|
5614
|
+
function MarkdownComponent(el, ctx) {
|
|
5559
5615
|
const p = el.property || {};
|
|
5560
5616
|
const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
|
|
5561
5617
|
applyOuterMargin(style, p.margin);
|
|
@@ -5569,12 +5625,28 @@ function registerBuiltins() {
|
|
|
5569
5625
|
style.lineHeight = lineHeight + "px";
|
|
5570
5626
|
}
|
|
5571
5627
|
const childCtx = { ...ctx, textMetrics: { fontSize, lineHeight } };
|
|
5628
|
+
const linesStyle = lineClampStyle(p.lines ?? p.textLines);
|
|
5572
5629
|
return h(
|
|
5573
5630
|
"div",
|
|
5574
5631
|
{ className: `${CLS}-markdown`, style },
|
|
5575
|
-
h("div", {
|
|
5632
|
+
h("div", {
|
|
5633
|
+
className: `${CLS}-markdown__children`,
|
|
5634
|
+
style: Object.keys(linesStyle).length ? linesStyle : void 0
|
|
5635
|
+
}, renderElements(p.elements, childCtx))
|
|
5576
5636
|
);
|
|
5577
|
-
}
|
|
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
|
+
}
|
|
5578
5650
|
registerComponent("plain_text", (el) => {
|
|
5579
5651
|
const p = el.property || {};
|
|
5580
5652
|
const mods = textStyleToModifiers(p.textStyle);
|
|
@@ -5583,6 +5655,7 @@ function registerBuiltins() {
|
|
|
5583
5655
|
delete style.fontWeight;
|
|
5584
5656
|
delete style.fontStyle;
|
|
5585
5657
|
delete style.textDecoration;
|
|
5658
|
+
Object.assign(style, lineClampStyle(p.lines));
|
|
5586
5659
|
return h("span", { className, style }, p.content != null ? String(p.content) : "");
|
|
5587
5660
|
});
|
|
5588
5661
|
registerComponent("br", () => h("br", {}));
|
|
@@ -5607,15 +5680,15 @@ function registerBuiltins() {
|
|
|
5607
5680
|
registerComponent("column_set", (el, ctx) => {
|
|
5608
5681
|
const p = el.property || {};
|
|
5609
5682
|
const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
|
|
5610
|
-
const gap = gapRaw != null ?
|
|
5683
|
+
const gap = gapRaw != null ? horizontalSpacingToPx(gapRaw) : horizontalSpacingToPx("default");
|
|
5611
5684
|
const flexMode = p.flexMode || p.flex_mode;
|
|
5612
5685
|
const hAlign = p.horizontalAlign || p.horizontal_align;
|
|
5613
5686
|
const style = {
|
|
5614
5687
|
display: "flex",
|
|
5615
5688
|
flexWrap: flexMode === "flow" || flexMode === "wrap" ? "wrap" : "nowrap",
|
|
5616
|
-
columnGap: gap + "px",
|
|
5617
5689
|
rowGap: "8px",
|
|
5618
|
-
alignItems: "stretch"
|
|
5690
|
+
alignItems: "stretch",
|
|
5691
|
+
columnGap: gap + "px"
|
|
5619
5692
|
};
|
|
5620
5693
|
if (hAlign) {
|
|
5621
5694
|
style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
|
|
@@ -5634,7 +5707,8 @@ function registerBuiltins() {
|
|
|
5634
5707
|
style.margin = "0";
|
|
5635
5708
|
style.padding = "0";
|
|
5636
5709
|
}
|
|
5637
|
-
|
|
5710
|
+
const childCtx = { ...ctx, columnSet: { flexMode, gap } };
|
|
5711
|
+
return h("div", { className: cls, style }, renderElements(p.columns, childCtx));
|
|
5638
5712
|
});
|
|
5639
5713
|
function isStateHost(el) {
|
|
5640
5714
|
if (el.id === "card_state") return true;
|
|
@@ -5669,12 +5743,30 @@ function registerBuiltins() {
|
|
|
5669
5743
|
} else {
|
|
5670
5744
|
style.flexGrow = String(p.weight || 1);
|
|
5671
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
|
+
}
|
|
5672
5764
|
const vAlign = p.verticalAlign || p.vertical_align;
|
|
5673
5765
|
if (vAlign) {
|
|
5674
5766
|
style.justifyContent = { top: "flex-start", center: "center", bottom: "flex-end" }[vAlign] || "flex-start";
|
|
5675
5767
|
}
|
|
5676
5768
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5677
|
-
style.rowGap = (vGap != null ?
|
|
5769
|
+
style.rowGap = (vGap != null ? verticalSpacingToPx(vGap) : 8) + "px";
|
|
5678
5770
|
const bg = propertyBackgroundColor(p);
|
|
5679
5771
|
if (bg) style.background = bg;
|
|
5680
5772
|
const pad = boxToStyle(p.padding);
|
|
@@ -5882,7 +5974,7 @@ function registerBuiltins() {
|
|
|
5882
5974
|
const radius = resolveCornerRadius(p);
|
|
5883
5975
|
if (radius != null) style.borderRadius = radius;
|
|
5884
5976
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5885
|
-
if (vGap != null) style.rowGap =
|
|
5977
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5886
5978
|
const icWv = p.widthValue;
|
|
5887
5979
|
const icHv = p.heightValue;
|
|
5888
5980
|
const w = resolveDimensionPx(p.width, icWv);
|
|
@@ -6195,6 +6287,93 @@ function registerBuiltins() {
|
|
|
6195
6287
|
const style = textTagStyle(p.backgroundColor || c.bg, c.text);
|
|
6196
6288
|
return h("span", { className: `${CLS}-text-tag ${CLS}-text-tag--${colorName}`, style }, extractText(p.text));
|
|
6197
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
|
+
});
|
|
6198
6377
|
const EMOJI_FONT_SIZE = Number((BASE_FONT_SIZE * BASE_LINE_HEIGHT).toFixed(2));
|
|
6199
6378
|
registerComponent("emoji", (el, ctx) => {
|
|
6200
6379
|
const p = el.property || {};
|
|
@@ -6844,7 +7023,12 @@ function buildCardVNode(card, options) {
|
|
|
6844
7023
|
const parts = [];
|
|
6845
7024
|
if (jc.header) parts.push(renderElement(jc.header, ctx));
|
|
6846
7025
|
const bodyEl = jc.body || jc.newBody;
|
|
6847
|
-
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
|
+
}
|
|
6848
7032
|
return h("div", {
|
|
6849
7033
|
className: `${CLS}-root ${CLS}`,
|
|
6850
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") {
|
|
@@ -4894,7 +4933,7 @@ function parseInline(text) {
|
|
|
4894
4933
|
if (attrs.show_name != null) property.showName = attrs.show_name !== "false";
|
|
4895
4934
|
if (attrs.show_avatar != null) property.showAvatar = attrs.show_avatar !== "false";
|
|
4896
4935
|
if (attrs.size) property.size = attrs.size;
|
|
4897
|
-
if (attrs.style
|
|
4936
|
+
if (attrs.style) property.style = attrs.style;
|
|
4898
4937
|
out.push({ tag: "person_v1", property });
|
|
4899
4938
|
i += m[0].length;
|
|
4900
4939
|
continue;
|
|
@@ -5495,8 +5534,9 @@ function registerBuiltins() {
|
|
|
5495
5534
|
const style = {};
|
|
5496
5535
|
const pad = boxToStyle(p.padding);
|
|
5497
5536
|
if (pad) style.padding = pad;
|
|
5537
|
+
if (!pad && ctx.bodyPaddingTop != null) style.paddingTop = ctx.bodyPaddingTop + "px";
|
|
5498
5538
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5499
|
-
if (vGap != null) style.rowGap =
|
|
5539
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5500
5540
|
const props = { className: `${CLS}-body` };
|
|
5501
5541
|
if (Object.keys(style).length) props.style = style;
|
|
5502
5542
|
return h("div", props, renderElements(getChildElements(el), ctx));
|
|
@@ -5506,7 +5546,8 @@ function registerBuiltins() {
|
|
|
5506
5546
|
const tplKey = p.template || "default";
|
|
5507
5547
|
const tpl = HEADER_TEMPLATE[tplKey] || HEADER_TEMPLATE.default;
|
|
5508
5548
|
const tags = p.text_tag_list || p.textTagList;
|
|
5509
|
-
const
|
|
5549
|
+
const tagCtx = { ...ctx, textMetrics: { fontSize: 16, lineHeight: 24 } };
|
|
5550
|
+
const tagVNodes = Array.isArray(tags) && tags.length ? renderElements(tags.slice(0, 3), tagCtx) : [];
|
|
5510
5551
|
const iconRaw = p.udIcon || p.icon;
|
|
5511
5552
|
let iconVNode = null;
|
|
5512
5553
|
if (iconRaw && iconRaw.tag) {
|
|
@@ -5516,6 +5557,21 @@ function registerBuiltins() {
|
|
|
5516
5557
|
const inner = renderIcon(iconEl, ctx);
|
|
5517
5558
|
if (inner) iconVNode = h("div", { className: `${CLS}-header__icon` }, inner);
|
|
5518
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
|
+
}
|
|
5519
5575
|
const titleVNode = renderHeaderTextLeaf(p.title, ctx, `${CLS}-header__text-title`) ?? h("div", { className: `${CLS}-header__text-title` });
|
|
5520
5576
|
const subtitleVNode = renderHeaderTextLeaf(p.subtitle, ctx, `${CLS}-header__text-subtitle`);
|
|
5521
5577
|
const textChildren = [
|
|
@@ -5554,7 +5610,7 @@ function registerBuiltins() {
|
|
|
5554
5610
|
)
|
|
5555
5611
|
);
|
|
5556
5612
|
});
|
|
5557
|
-
|
|
5613
|
+
function MarkdownComponent(el, ctx) {
|
|
5558
5614
|
const p = el.property || {};
|
|
5559
5615
|
const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
|
|
5560
5616
|
applyOuterMargin(style, p.margin);
|
|
@@ -5568,12 +5624,28 @@ function registerBuiltins() {
|
|
|
5568
5624
|
style.lineHeight = lineHeight + "px";
|
|
5569
5625
|
}
|
|
5570
5626
|
const childCtx = { ...ctx, textMetrics: { fontSize, lineHeight } };
|
|
5627
|
+
const linesStyle = lineClampStyle(p.lines ?? p.textLines);
|
|
5571
5628
|
return h(
|
|
5572
5629
|
"div",
|
|
5573
5630
|
{ className: `${CLS}-markdown`, style },
|
|
5574
|
-
h("div", {
|
|
5631
|
+
h("div", {
|
|
5632
|
+
className: `${CLS}-markdown__children`,
|
|
5633
|
+
style: Object.keys(linesStyle).length ? linesStyle : void 0
|
|
5634
|
+
}, renderElements(p.elements, childCtx))
|
|
5575
5635
|
);
|
|
5576
|
-
}
|
|
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
|
+
}
|
|
5577
5649
|
registerComponent("plain_text", (el) => {
|
|
5578
5650
|
const p = el.property || {};
|
|
5579
5651
|
const mods = textStyleToModifiers(p.textStyle);
|
|
@@ -5582,6 +5654,7 @@ function registerBuiltins() {
|
|
|
5582
5654
|
delete style.fontWeight;
|
|
5583
5655
|
delete style.fontStyle;
|
|
5584
5656
|
delete style.textDecoration;
|
|
5657
|
+
Object.assign(style, lineClampStyle(p.lines));
|
|
5585
5658
|
return h("span", { className, style }, p.content != null ? String(p.content) : "");
|
|
5586
5659
|
});
|
|
5587
5660
|
registerComponent("br", () => h("br", {}));
|
|
@@ -5606,15 +5679,15 @@ function registerBuiltins() {
|
|
|
5606
5679
|
registerComponent("column_set", (el, ctx) => {
|
|
5607
5680
|
const p = el.property || {};
|
|
5608
5681
|
const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
|
|
5609
|
-
const gap = gapRaw != null ?
|
|
5682
|
+
const gap = gapRaw != null ? horizontalSpacingToPx(gapRaw) : horizontalSpacingToPx("default");
|
|
5610
5683
|
const flexMode = p.flexMode || p.flex_mode;
|
|
5611
5684
|
const hAlign = p.horizontalAlign || p.horizontal_align;
|
|
5612
5685
|
const style = {
|
|
5613
5686
|
display: "flex",
|
|
5614
5687
|
flexWrap: flexMode === "flow" || flexMode === "wrap" ? "wrap" : "nowrap",
|
|
5615
|
-
columnGap: gap + "px",
|
|
5616
5688
|
rowGap: "8px",
|
|
5617
|
-
alignItems: "stretch"
|
|
5689
|
+
alignItems: "stretch",
|
|
5690
|
+
columnGap: gap + "px"
|
|
5618
5691
|
};
|
|
5619
5692
|
if (hAlign) {
|
|
5620
5693
|
style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
|
|
@@ -5633,7 +5706,8 @@ function registerBuiltins() {
|
|
|
5633
5706
|
style.margin = "0";
|
|
5634
5707
|
style.padding = "0";
|
|
5635
5708
|
}
|
|
5636
|
-
|
|
5709
|
+
const childCtx = { ...ctx, columnSet: { flexMode, gap } };
|
|
5710
|
+
return h("div", { className: cls, style }, renderElements(p.columns, childCtx));
|
|
5637
5711
|
});
|
|
5638
5712
|
function isStateHost(el) {
|
|
5639
5713
|
if (el.id === "card_state") return true;
|
|
@@ -5668,12 +5742,30 @@ function registerBuiltins() {
|
|
|
5668
5742
|
} else {
|
|
5669
5743
|
style.flexGrow = String(p.weight || 1);
|
|
5670
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
|
+
}
|
|
5671
5763
|
const vAlign = p.verticalAlign || p.vertical_align;
|
|
5672
5764
|
if (vAlign) {
|
|
5673
5765
|
style.justifyContent = { top: "flex-start", center: "center", bottom: "flex-end" }[vAlign] || "flex-start";
|
|
5674
5766
|
}
|
|
5675
5767
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5676
|
-
style.rowGap = (vGap != null ?
|
|
5768
|
+
style.rowGap = (vGap != null ? verticalSpacingToPx(vGap) : 8) + "px";
|
|
5677
5769
|
const bg = propertyBackgroundColor(p);
|
|
5678
5770
|
if (bg) style.background = bg;
|
|
5679
5771
|
const pad = boxToStyle(p.padding);
|
|
@@ -5881,7 +5973,7 @@ function registerBuiltins() {
|
|
|
5881
5973
|
const radius = resolveCornerRadius(p);
|
|
5882
5974
|
if (radius != null) style.borderRadius = radius;
|
|
5883
5975
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5884
|
-
if (vGap != null) style.rowGap =
|
|
5976
|
+
if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
|
|
5885
5977
|
const icWv = p.widthValue;
|
|
5886
5978
|
const icHv = p.heightValue;
|
|
5887
5979
|
const w = resolveDimensionPx(p.width, icWv);
|
|
@@ -6194,6 +6286,93 @@ function registerBuiltins() {
|
|
|
6194
6286
|
const style = textTagStyle(p.backgroundColor || c.bg, c.text);
|
|
6195
6287
|
return h("span", { className: `${CLS}-text-tag ${CLS}-text-tag--${colorName}`, style }, extractText(p.text));
|
|
6196
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
|
+
});
|
|
6197
6376
|
const EMOJI_FONT_SIZE = Number((BASE_FONT_SIZE * BASE_LINE_HEIGHT).toFixed(2));
|
|
6198
6377
|
registerComponent("emoji", (el, ctx) => {
|
|
6199
6378
|
const p = el.property || {};
|
|
@@ -6843,7 +7022,12 @@ function buildCardVNode(card, options) {
|
|
|
6843
7022
|
const parts = [];
|
|
6844
7023
|
if (jc.header) parts.push(renderElement(jc.header, ctx));
|
|
6845
7024
|
const bodyEl = jc.body || jc.newBody;
|
|
6846
|
-
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
|
+
}
|
|
6847
7031
|
return h("div", {
|
|
6848
7032
|
className: `${CLS}-root ${CLS}`,
|
|
6849
7033
|
style: { fontSize: BASE_FONT_SIZE + "px", lineHeight: String(BASE_LINE_HEIGHT) }
|
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.29",
|
|
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",
|