@open-feishu-card/renderer 0.2.14 → 0.2.16
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 +16 -5
- package/dist/index.cjs +146 -20
- package/dist/index.js +146 -20
- package/dist/styles.css +16 -5
- package/package.json +2 -2
package/dist/all.css
CHANGED
|
@@ -530,25 +530,36 @@
|
|
|
530
530
|
.universal-card-collapsible-panel {
|
|
531
531
|
box-sizing: border-box; overflow: hidden;
|
|
532
532
|
}
|
|
533
|
-
/* header(<summary
|
|
534
|
-
|
|
533
|
+
/* header(<summary>):可点击整行;去掉浏览器默认三角标记。
|
|
534
|
+
真机 color:var(--text-caption) → N600;标题/箭头继承此色。
|
|
535
|
+
默认 title flex:1 把箭头推到右侧(icon_position=right);
|
|
536
|
+
icon_position=left 时靠 DOM 顺序在左。 */
|
|
535
537
|
.universal-card-collapsible-panel__header {
|
|
536
538
|
display: flex; align-items: center; gap: 8px;
|
|
537
539
|
padding: 8px 12px;
|
|
538
540
|
cursor: pointer; box-sizing: border-box;
|
|
539
541
|
list-style: none; user-select: none;
|
|
542
|
+
color: var(--N600);
|
|
540
543
|
}
|
|
541
544
|
.universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
|
|
542
545
|
.universal-card-collapsible-panel__header::marker { content: ''; }
|
|
543
546
|
.universal-card-collapsible-panel__title {
|
|
544
|
-
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
547
|
+
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
545
548
|
word-break: break-word;
|
|
546
549
|
}
|
|
550
|
+
/* follow_text:箭头紧贴文字(真机 __header-content--icon-follow),不拉满整行 */
|
|
551
|
+
.universal-card-collapsible-panel__header--icon-follow {
|
|
552
|
+
gap: 4px;
|
|
553
|
+
}
|
|
554
|
+
.universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
|
|
555
|
+
flex: 0 1 auto;
|
|
556
|
+
width: auto;
|
|
557
|
+
}
|
|
547
558
|
/* 箭头图标包裹:收起态角度 0;展开态(details[open])旋转到 --panel-icon-angle(内联给出,默认 -180deg)。
|
|
548
|
-
由用户点击 <summary> 切换 [open],箭头方向随之自动更新,无需 JS。 */
|
|
559
|
+
由用户点击 <summary> 切换 [open],箭头方向随之自动更新,无需 JS。颜色继承 header caption。 */
|
|
549
560
|
.universal-card-collapsible-panel__icon {
|
|
550
561
|
flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
551
|
-
transition: transform .2s;
|
|
562
|
+
transition: transform .2s; transform: rotate(0deg);
|
|
552
563
|
}
|
|
553
564
|
.universal-card-collapsible-panel[open] .universal-card-collapsible-panel__icon {
|
|
554
565
|
transform: rotate(var(--panel-icon-angle, -180deg));
|
package/dist/index.cjs
CHANGED
|
@@ -710,6 +710,91 @@ function marginToStyle(margin) {
|
|
|
710
710
|
if (typeof margin === "string" && margin.trim()) return margin.trim();
|
|
711
711
|
return boxToStyle(margin);
|
|
712
712
|
}
|
|
713
|
+
function horizontalMarginPx(margin) {
|
|
714
|
+
if (typeof margin === "string" && margin.trim()) {
|
|
715
|
+
const parts = margin.trim().split(/\s+/).map((t) => spacingToPx(t));
|
|
716
|
+
if (parts.length === 1) return { left: parts[0], right: parts[0] };
|
|
717
|
+
if (parts.length === 2 || parts.length === 3) return { left: parts[1], right: parts[1] };
|
|
718
|
+
if (parts.length >= 4) return { left: parts[3], right: parts[1] };
|
|
719
|
+
}
|
|
720
|
+
if (margin && typeof margin === "object") {
|
|
721
|
+
const m = margin;
|
|
722
|
+
return { left: Number(m.left?.value ?? 0), right: Number(m.right?.value ?? 0) };
|
|
723
|
+
}
|
|
724
|
+
return { left: 0, right: 0 };
|
|
725
|
+
}
|
|
726
|
+
function applyOuterMargin(style, margin) {
|
|
727
|
+
const mg = marginToStyle(margin);
|
|
728
|
+
if (!mg) return;
|
|
729
|
+
style.margin = mg;
|
|
730
|
+
const { left, right } = horizontalMarginPx(margin);
|
|
731
|
+
const inset = left + right;
|
|
732
|
+
if (inset <= 0) return;
|
|
733
|
+
const w = style.width;
|
|
734
|
+
if (w && /px$/i.test(w) && !w.includes("%")) return;
|
|
735
|
+
if (!w || w === "100%" || w === "fill") {
|
|
736
|
+
style.width = `calc(100% - ${inset}px)`;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function resolveDimensionPx(direct, valueObj) {
|
|
740
|
+
const fromObj = () => {
|
|
741
|
+
if (!valueObj) return void 0;
|
|
742
|
+
const t = String(valueObj.type || "").toLowerCase();
|
|
743
|
+
if (t === "pixels" || t === "pixel") {
|
|
744
|
+
const n = Number(valueObj.value);
|
|
745
|
+
return Number.isFinite(n) ? n + "px" : void 0;
|
|
746
|
+
}
|
|
747
|
+
const v = valueObj.value;
|
|
748
|
+
if (v === "fill") return "100%";
|
|
749
|
+
if (v === "auto" || v === "default") return void 0;
|
|
750
|
+
if (typeof v === "number" && Number.isFinite(v)) return v + "px";
|
|
751
|
+
if (typeof v === "string") {
|
|
752
|
+
const m = /^(-?\d+(?:\.\d+)?)(?:px)?$/i.exec(v.trim());
|
|
753
|
+
if (m) return m[1] + "px";
|
|
754
|
+
}
|
|
755
|
+
return void 0;
|
|
756
|
+
};
|
|
757
|
+
if (typeof direct === "number" && Number.isFinite(direct)) return direct + "px";
|
|
758
|
+
if (typeof direct === "string") {
|
|
759
|
+
const s = direct.trim();
|
|
760
|
+
if (s === "fill") return "100%";
|
|
761
|
+
if (s === "auto" || s === "default" || !s) return void 0;
|
|
762
|
+
const m = /^(-?\d+(?:\.\d+)?)(?:px)?$/i.exec(s);
|
|
763
|
+
if (m) return m[1] + "px";
|
|
764
|
+
}
|
|
765
|
+
return fromObj();
|
|
766
|
+
}
|
|
767
|
+
function resolveImageSize(property) {
|
|
768
|
+
if (!property) return {};
|
|
769
|
+
const size = property.size ?? property.size_value;
|
|
770
|
+
if (typeof size === "string") {
|
|
771
|
+
const s = size.trim();
|
|
772
|
+
const pxPair = /^(\d+(?:\.\d+)?)px\s+(\d+(?:\.\d+)?)px$/i.exec(s);
|
|
773
|
+
if (pxPair) return { width: pxPair[1] + "px", height: pxPair[2] + "px" };
|
|
774
|
+
const ratio = /^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/.exec(s);
|
|
775
|
+
if (ratio) return { aspectRatio: `${ratio[1]} / ${ratio[2]}` };
|
|
776
|
+
}
|
|
777
|
+
const echo = property.sizeValue ?? property.size_value;
|
|
778
|
+
if (echo && typeof echo === "object") {
|
|
779
|
+
const t = String(echo.type || "").toLowerCase();
|
|
780
|
+
const v = echo.value;
|
|
781
|
+
if (t === "width_height_pixels" && v && typeof v === "object") {
|
|
782
|
+
const w = Number(v.width);
|
|
783
|
+
const h2 = Number(v.height);
|
|
784
|
+
if (Number.isFinite(w) && Number.isFinite(h2)) {
|
|
785
|
+
return { width: w + "px", height: h2 + "px" };
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
if (t === "aspect_ratio" && v && typeof v === "object") {
|
|
789
|
+
const w = Number(v.width);
|
|
790
|
+
const h2 = Number(v.height);
|
|
791
|
+
if (Number.isFinite(w) && Number.isFinite(h2) && h2 !== 0) {
|
|
792
|
+
return { aspectRatio: `${w} / ${h2}` };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return {};
|
|
797
|
+
}
|
|
713
798
|
function resolveCornerRadius(property) {
|
|
714
799
|
if (!property) return void 0;
|
|
715
800
|
const direct = property.corner_radius ?? property.cornerRadius;
|
|
@@ -1367,25 +1452,36 @@ var BASE_STYLES_CSS = `/**
|
|
|
1367
1452
|
.universal-card-collapsible-panel {
|
|
1368
1453
|
box-sizing: border-box; overflow: hidden;
|
|
1369
1454
|
}
|
|
1370
|
-
/* header\uFF08<summary>\uFF09\uFF1A\u53EF\u70B9\u51FB\u6574\u884C\uFF1B\u53BB\u6389\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0\
|
|
1371
|
-
\
|
|
1455
|
+
/* header\uFF08<summary>\uFF09\uFF1A\u53EF\u70B9\u51FB\u6574\u884C\uFF1B\u53BB\u6389\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0\u3002
|
|
1456
|
+
\u771F\u673A color:var(--text-caption) \u2192 N600\uFF1B\u6807\u9898/\u7BAD\u5934\u7EE7\u627F\u6B64\u8272\u3002
|
|
1457
|
+
\u9ED8\u8BA4 title flex:1 \u628A\u7BAD\u5934\u63A8\u5230\u53F3\u4FA7\uFF08icon_position=right\uFF09\uFF1B
|
|
1458
|
+
icon_position=left \u65F6\u9760 DOM \u987A\u5E8F\u5728\u5DE6\u3002 */
|
|
1372
1459
|
.universal-card-collapsible-panel__header {
|
|
1373
1460
|
display: flex; align-items: center; gap: 8px;
|
|
1374
1461
|
padding: 8px 12px;
|
|
1375
1462
|
cursor: pointer; box-sizing: border-box;
|
|
1376
1463
|
list-style: none; user-select: none;
|
|
1464
|
+
color: var(--N600);
|
|
1377
1465
|
}
|
|
1378
1466
|
.universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
|
|
1379
1467
|
.universal-card-collapsible-panel__header::marker { content: ''; }
|
|
1380
1468
|
.universal-card-collapsible-panel__title {
|
|
1381
|
-
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
1469
|
+
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
1382
1470
|
word-break: break-word;
|
|
1383
1471
|
}
|
|
1472
|
+
/* follow_text\uFF1A\u7BAD\u5934\u7D27\u8D34\u6587\u5B57\uFF08\u771F\u673A __header-content--icon-follow\uFF09\uFF0C\u4E0D\u62C9\u6EE1\u6574\u884C */
|
|
1473
|
+
.universal-card-collapsible-panel__header--icon-follow {
|
|
1474
|
+
gap: 4px;
|
|
1475
|
+
}
|
|
1476
|
+
.universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
|
|
1477
|
+
flex: 0 1 auto;
|
|
1478
|
+
width: auto;
|
|
1479
|
+
}
|
|
1384
1480
|
/* \u7BAD\u5934\u56FE\u6807\u5305\u88F9\uFF1A\u6536\u8D77\u6001\u89D2\u5EA6 0\uFF1B\u5C55\u5F00\u6001\uFF08details[open]\uFF09\u65CB\u8F6C\u5230 --panel-icon-angle\uFF08\u5185\u8054\u7ED9\u51FA\uFF0C\u9ED8\u8BA4 -180deg\uFF09\u3002
|
|
1385
|
-
\u7531\u7528\u6237\u70B9\u51FB <summary> \u5207\u6362 [open]\uFF0C\u7BAD\u5934\u65B9\u5411\u968F\u4E4B\u81EA\u52A8\u66F4\u65B0\uFF0C\u65E0\u9700 JS\u3002 */
|
|
1481
|
+
\u7531\u7528\u6237\u70B9\u51FB <summary> \u5207\u6362 [open]\uFF0C\u7BAD\u5934\u65B9\u5411\u968F\u4E4B\u81EA\u52A8\u66F4\u65B0\uFF0C\u65E0\u9700 JS\u3002\u989C\u8272\u7EE7\u627F header caption\u3002 */
|
|
1386
1482
|
.universal-card-collapsible-panel__icon {
|
|
1387
1483
|
flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
1388
|
-
transition: transform .2s;
|
|
1484
|
+
transition: transform .2s; transform: rotate(0deg);
|
|
1389
1485
|
}
|
|
1390
1486
|
.universal-card-collapsible-panel[open] .universal-card-collapsible-panel__icon {
|
|
1391
1487
|
transform: rotate(var(--panel-icon-angle, -180deg));
|
|
@@ -5435,8 +5531,7 @@ function registerBuiltins() {
|
|
|
5435
5531
|
registerComponent("markdown", (el, ctx) => {
|
|
5436
5532
|
const p = el.property || {};
|
|
5437
5533
|
const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
|
|
5438
|
-
|
|
5439
|
-
if (mg) style.margin = mg;
|
|
5534
|
+
applyOuterMargin(style, p.margin);
|
|
5440
5535
|
const sizeKey = resolveTextSizeKey(p);
|
|
5441
5536
|
let fontSize = BASE_FONT_SIZE;
|
|
5442
5537
|
let lineHeight = BASE_FONT_SIZE + 8;
|
|
@@ -5502,8 +5597,7 @@ function registerBuiltins() {
|
|
|
5502
5597
|
if (hAlign) {
|
|
5503
5598
|
style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
|
|
5504
5599
|
}
|
|
5505
|
-
|
|
5506
|
-
if (mg) style.margin = mg;
|
|
5600
|
+
applyOuterMargin(style, p.margin);
|
|
5507
5601
|
const bg = propertyBackgroundColor(p);
|
|
5508
5602
|
if (bg) style.background = bg;
|
|
5509
5603
|
const pad = boxToStyle(p.padding);
|
|
@@ -5557,7 +5651,21 @@ function registerBuiltins() {
|
|
|
5557
5651
|
const wrapStyle = {};
|
|
5558
5652
|
const imgStyle = {};
|
|
5559
5653
|
if (p.corner_radius != null) imgStyle.borderRadius = sizeToPx(p.corner_radius);
|
|
5560
|
-
|
|
5654
|
+
const sized = resolveImageSize(p);
|
|
5655
|
+
if (sized.width && sized.height) {
|
|
5656
|
+
wrapStyle.width = sized.width;
|
|
5657
|
+
wrapStyle.height = sized.height;
|
|
5658
|
+
wrapStyle.flexShrink = "0";
|
|
5659
|
+
imgStyle.width = "100%";
|
|
5660
|
+
imgStyle.height = "100%";
|
|
5661
|
+
imgStyle.objectFit = scale === "fit_horizontal" || scale === "stretch" ? "fill" : "cover";
|
|
5662
|
+
} else if (sized.aspectRatio) {
|
|
5663
|
+
wrapStyle.width = "100%";
|
|
5664
|
+
imgStyle.width = "100%";
|
|
5665
|
+
imgStyle.aspectRatio = sized.aspectRatio;
|
|
5666
|
+
imgStyle.height = "auto";
|
|
5667
|
+
imgStyle.objectFit = "cover";
|
|
5668
|
+
} else if (scale === "fit_horizontal" || scale === "stretch") {
|
|
5561
5669
|
wrapStyle.width = "100%";
|
|
5562
5670
|
imgStyle.width = "100%";
|
|
5563
5671
|
imgStyle.height = "auto";
|
|
@@ -5565,7 +5673,7 @@ function registerBuiltins() {
|
|
|
5565
5673
|
return h(
|
|
5566
5674
|
"div",
|
|
5567
5675
|
{ className: `${CLS}-image__wrapper`, style: wrapStyle },
|
|
5568
|
-
src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder
|
|
5676
|
+
src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder`, style: imgStyle }, altText || "[\u56FE\u7247]")
|
|
5569
5677
|
);
|
|
5570
5678
|
});
|
|
5571
5679
|
registerComponent("video", (el, ctx) => {
|
|
@@ -5713,7 +5821,7 @@ function registerBuiltins() {
|
|
|
5713
5821
|
const tip = extractText(p.hoverTips);
|
|
5714
5822
|
const borderNamed = p.border_color ?? p.borderColor;
|
|
5715
5823
|
const borderCustom = adapter.resolveCustomBorderLight(p);
|
|
5716
|
-
const borderColor =
|
|
5824
|
+
const borderColor = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
|
|
5717
5825
|
const hasBorder = borderColor != null || p.hasBorder === true || p.has_border === true;
|
|
5718
5826
|
const cls = `${CLS}-interactive-container` + (clickable ? ` ${CLS}-interactive-container--action` : "") + (hasBorder && !borderColor ? ` ${CLS}-interactive-container--bordered` : "") + (disabled ? ` ${CLS}-interactive-container--disabled` : "");
|
|
5719
5827
|
const style = {};
|
|
@@ -5727,7 +5835,16 @@ function registerBuiltins() {
|
|
|
5727
5835
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5728
5836
|
if (vGap != null) style.rowGap = spacingToPx(vGap) + "px";
|
|
5729
5837
|
const icWv = p.widthValue;
|
|
5730
|
-
|
|
5838
|
+
const icHv = p.heightValue;
|
|
5839
|
+
const w = resolveDimensionPx(p.width, icWv);
|
|
5840
|
+
const hPx = resolveDimensionPx(p.height, icHv);
|
|
5841
|
+
if (w) style.width = w;
|
|
5842
|
+
if (hPx) {
|
|
5843
|
+
style.height = hPx;
|
|
5844
|
+
style.flexShrink = "0";
|
|
5845
|
+
style.boxSizing = "border-box";
|
|
5846
|
+
}
|
|
5847
|
+
applyOuterMargin(style, p.margin);
|
|
5731
5848
|
const props = { className: cls };
|
|
5732
5849
|
if (Object.keys(style).length) props.style = style;
|
|
5733
5850
|
if (url && !disabled) props["data-url"] = url;
|
|
@@ -5747,10 +5864,16 @@ function registerBuiltins() {
|
|
|
5747
5864
|
const bg = bgStyleToColor(bgRaw);
|
|
5748
5865
|
if (bg) style.background = bg;
|
|
5749
5866
|
}
|
|
5750
|
-
const
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5867
|
+
const borderRaw = p.border;
|
|
5868
|
+
if (borderRaw && typeof borderRaw === "object") {
|
|
5869
|
+
const border = borderRaw;
|
|
5870
|
+
const borderColor = resolveColor(border.color) || "#dee0e3";
|
|
5871
|
+
style.border = `1px solid ${borderColor}`;
|
|
5872
|
+
style.borderRadius = border.corner_radius != null ? sizeToPx(border.corner_radius) : "8px";
|
|
5873
|
+
} else {
|
|
5874
|
+
style.borderRadius = "8px";
|
|
5875
|
+
}
|
|
5876
|
+
applyOuterMargin(style, p.margin);
|
|
5754
5877
|
const headerStyle = {};
|
|
5755
5878
|
const hpad = typeof hdr.padding === "string" ? hdr.padding : boxToStyle(hdr.padding);
|
|
5756
5879
|
if (hpad) headerStyle.padding = hpad;
|
|
@@ -5758,7 +5881,7 @@ function registerBuiltins() {
|
|
|
5758
5881
|
const titleVNode = h("div", { className: `${CLS}-collapsible-panel__title` }, title);
|
|
5759
5882
|
let iconVNode = null;
|
|
5760
5883
|
const iconRaw = hdr.icon;
|
|
5761
|
-
const openAngle = Number(hdr.icon_expanded_angle ?? -180);
|
|
5884
|
+
const openAngle = Number(hdr.icon_expanded_angle ?? hdr.iconExpandedAngle ?? -180);
|
|
5762
5885
|
if (iconRaw && iconRaw.tag) {
|
|
5763
5886
|
const iconEl = iconRaw.property ? iconRaw : { tag: iconRaw.tag, property: iconRaw };
|
|
5764
5887
|
const inner = renderIcon(iconEl, ctx);
|
|
@@ -5766,10 +5889,13 @@ function registerBuiltins() {
|
|
|
5766
5889
|
iconVNode = h("div", { className: `${CLS}-collapsible-panel__icon` }, inner);
|
|
5767
5890
|
}
|
|
5768
5891
|
}
|
|
5769
|
-
const
|
|
5892
|
+
const iconPos = String(hdr.icon_position ?? hdr.iconPosition ?? "right");
|
|
5893
|
+
const iconLeft = iconPos === "left";
|
|
5894
|
+
const iconFollow = iconPos === "follow_text" || iconPos === "followText";
|
|
5770
5895
|
const headerChildren = iconLeft ? [iconVNode, titleVNode] : [titleVNode, iconVNode];
|
|
5771
5896
|
const summaryStyle = { ...headerStyle, "--panel-icon-angle": `${openAngle}deg` };
|
|
5772
|
-
const
|
|
5897
|
+
const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "");
|
|
5898
|
+
const summary = h("summary", { className: headerCls, style: summaryStyle }, headerChildren);
|
|
5773
5899
|
const contentStyle = {};
|
|
5774
5900
|
const cpad = typeof p.padding === "string" ? p.padding : boxToStyle(p.padding);
|
|
5775
5901
|
if (cpad) contentStyle.padding = cpad;
|
package/dist/index.js
CHANGED
|
@@ -709,6 +709,91 @@ function marginToStyle(margin) {
|
|
|
709
709
|
if (typeof margin === "string" && margin.trim()) return margin.trim();
|
|
710
710
|
return boxToStyle(margin);
|
|
711
711
|
}
|
|
712
|
+
function horizontalMarginPx(margin) {
|
|
713
|
+
if (typeof margin === "string" && margin.trim()) {
|
|
714
|
+
const parts = margin.trim().split(/\s+/).map((t) => spacingToPx(t));
|
|
715
|
+
if (parts.length === 1) return { left: parts[0], right: parts[0] };
|
|
716
|
+
if (parts.length === 2 || parts.length === 3) return { left: parts[1], right: parts[1] };
|
|
717
|
+
if (parts.length >= 4) return { left: parts[3], right: parts[1] };
|
|
718
|
+
}
|
|
719
|
+
if (margin && typeof margin === "object") {
|
|
720
|
+
const m = margin;
|
|
721
|
+
return { left: Number(m.left?.value ?? 0), right: Number(m.right?.value ?? 0) };
|
|
722
|
+
}
|
|
723
|
+
return { left: 0, right: 0 };
|
|
724
|
+
}
|
|
725
|
+
function applyOuterMargin(style, margin) {
|
|
726
|
+
const mg = marginToStyle(margin);
|
|
727
|
+
if (!mg) return;
|
|
728
|
+
style.margin = mg;
|
|
729
|
+
const { left, right } = horizontalMarginPx(margin);
|
|
730
|
+
const inset = left + right;
|
|
731
|
+
if (inset <= 0) return;
|
|
732
|
+
const w = style.width;
|
|
733
|
+
if (w && /px$/i.test(w) && !w.includes("%")) return;
|
|
734
|
+
if (!w || w === "100%" || w === "fill") {
|
|
735
|
+
style.width = `calc(100% - ${inset}px)`;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function resolveDimensionPx(direct, valueObj) {
|
|
739
|
+
const fromObj = () => {
|
|
740
|
+
if (!valueObj) return void 0;
|
|
741
|
+
const t = String(valueObj.type || "").toLowerCase();
|
|
742
|
+
if (t === "pixels" || t === "pixel") {
|
|
743
|
+
const n = Number(valueObj.value);
|
|
744
|
+
return Number.isFinite(n) ? n + "px" : void 0;
|
|
745
|
+
}
|
|
746
|
+
const v = valueObj.value;
|
|
747
|
+
if (v === "fill") return "100%";
|
|
748
|
+
if (v === "auto" || v === "default") return void 0;
|
|
749
|
+
if (typeof v === "number" && Number.isFinite(v)) return v + "px";
|
|
750
|
+
if (typeof v === "string") {
|
|
751
|
+
const m = /^(-?\d+(?:\.\d+)?)(?:px)?$/i.exec(v.trim());
|
|
752
|
+
if (m) return m[1] + "px";
|
|
753
|
+
}
|
|
754
|
+
return void 0;
|
|
755
|
+
};
|
|
756
|
+
if (typeof direct === "number" && Number.isFinite(direct)) return direct + "px";
|
|
757
|
+
if (typeof direct === "string") {
|
|
758
|
+
const s = direct.trim();
|
|
759
|
+
if (s === "fill") return "100%";
|
|
760
|
+
if (s === "auto" || s === "default" || !s) return void 0;
|
|
761
|
+
const m = /^(-?\d+(?:\.\d+)?)(?:px)?$/i.exec(s);
|
|
762
|
+
if (m) return m[1] + "px";
|
|
763
|
+
}
|
|
764
|
+
return fromObj();
|
|
765
|
+
}
|
|
766
|
+
function resolveImageSize(property) {
|
|
767
|
+
if (!property) return {};
|
|
768
|
+
const size = property.size ?? property.size_value;
|
|
769
|
+
if (typeof size === "string") {
|
|
770
|
+
const s = size.trim();
|
|
771
|
+
const pxPair = /^(\d+(?:\.\d+)?)px\s+(\d+(?:\.\d+)?)px$/i.exec(s);
|
|
772
|
+
if (pxPair) return { width: pxPair[1] + "px", height: pxPair[2] + "px" };
|
|
773
|
+
const ratio = /^(\d+(?:\.\d+)?)\s*:\s*(\d+(?:\.\d+)?)$/.exec(s);
|
|
774
|
+
if (ratio) return { aspectRatio: `${ratio[1]} / ${ratio[2]}` };
|
|
775
|
+
}
|
|
776
|
+
const echo = property.sizeValue ?? property.size_value;
|
|
777
|
+
if (echo && typeof echo === "object") {
|
|
778
|
+
const t = String(echo.type || "").toLowerCase();
|
|
779
|
+
const v = echo.value;
|
|
780
|
+
if (t === "width_height_pixels" && v && typeof v === "object") {
|
|
781
|
+
const w = Number(v.width);
|
|
782
|
+
const h2 = Number(v.height);
|
|
783
|
+
if (Number.isFinite(w) && Number.isFinite(h2)) {
|
|
784
|
+
return { width: w + "px", height: h2 + "px" };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (t === "aspect_ratio" && v && typeof v === "object") {
|
|
788
|
+
const w = Number(v.width);
|
|
789
|
+
const h2 = Number(v.height);
|
|
790
|
+
if (Number.isFinite(w) && Number.isFinite(h2) && h2 !== 0) {
|
|
791
|
+
return { aspectRatio: `${w} / ${h2}` };
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return {};
|
|
796
|
+
}
|
|
712
797
|
function resolveCornerRadius(property) {
|
|
713
798
|
if (!property) return void 0;
|
|
714
799
|
const direct = property.corner_radius ?? property.cornerRadius;
|
|
@@ -1366,25 +1451,36 @@ var BASE_STYLES_CSS = `/**
|
|
|
1366
1451
|
.universal-card-collapsible-panel {
|
|
1367
1452
|
box-sizing: border-box; overflow: hidden;
|
|
1368
1453
|
}
|
|
1369
|
-
/* header\uFF08<summary>\uFF09\uFF1A\u53EF\u70B9\u51FB\u6574\u884C\uFF1B\u53BB\u6389\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0\
|
|
1370
|
-
\
|
|
1454
|
+
/* header\uFF08<summary>\uFF09\uFF1A\u53EF\u70B9\u51FB\u6574\u884C\uFF1B\u53BB\u6389\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0\u3002
|
|
1455
|
+
\u771F\u673A color:var(--text-caption) \u2192 N600\uFF1B\u6807\u9898/\u7BAD\u5934\u7EE7\u627F\u6B64\u8272\u3002
|
|
1456
|
+
\u9ED8\u8BA4 title flex:1 \u628A\u7BAD\u5934\u63A8\u5230\u53F3\u4FA7\uFF08icon_position=right\uFF09\uFF1B
|
|
1457
|
+
icon_position=left \u65F6\u9760 DOM \u987A\u5E8F\u5728\u5DE6\u3002 */
|
|
1371
1458
|
.universal-card-collapsible-panel__header {
|
|
1372
1459
|
display: flex; align-items: center; gap: 8px;
|
|
1373
1460
|
padding: 8px 12px;
|
|
1374
1461
|
cursor: pointer; box-sizing: border-box;
|
|
1375
1462
|
list-style: none; user-select: none;
|
|
1463
|
+
color: var(--N600);
|
|
1376
1464
|
}
|
|
1377
1465
|
.universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
|
|
1378
1466
|
.universal-card-collapsible-panel__header::marker { content: ''; }
|
|
1379
1467
|
.universal-card-collapsible-panel__title {
|
|
1380
|
-
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
1468
|
+
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
1381
1469
|
word-break: break-word;
|
|
1382
1470
|
}
|
|
1471
|
+
/* follow_text\uFF1A\u7BAD\u5934\u7D27\u8D34\u6587\u5B57\uFF08\u771F\u673A __header-content--icon-follow\uFF09\uFF0C\u4E0D\u62C9\u6EE1\u6574\u884C */
|
|
1472
|
+
.universal-card-collapsible-panel__header--icon-follow {
|
|
1473
|
+
gap: 4px;
|
|
1474
|
+
}
|
|
1475
|
+
.universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
|
|
1476
|
+
flex: 0 1 auto;
|
|
1477
|
+
width: auto;
|
|
1478
|
+
}
|
|
1383
1479
|
/* \u7BAD\u5934\u56FE\u6807\u5305\u88F9\uFF1A\u6536\u8D77\u6001\u89D2\u5EA6 0\uFF1B\u5C55\u5F00\u6001\uFF08details[open]\uFF09\u65CB\u8F6C\u5230 --panel-icon-angle\uFF08\u5185\u8054\u7ED9\u51FA\uFF0C\u9ED8\u8BA4 -180deg\uFF09\u3002
|
|
1384
|
-
\u7531\u7528\u6237\u70B9\u51FB <summary> \u5207\u6362 [open]\uFF0C\u7BAD\u5934\u65B9\u5411\u968F\u4E4B\u81EA\u52A8\u66F4\u65B0\uFF0C\u65E0\u9700 JS\u3002 */
|
|
1480
|
+
\u7531\u7528\u6237\u70B9\u51FB <summary> \u5207\u6362 [open]\uFF0C\u7BAD\u5934\u65B9\u5411\u968F\u4E4B\u81EA\u52A8\u66F4\u65B0\uFF0C\u65E0\u9700 JS\u3002\u989C\u8272\u7EE7\u627F header caption\u3002 */
|
|
1385
1481
|
.universal-card-collapsible-panel__icon {
|
|
1386
1482
|
flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
1387
|
-
transition: transform .2s;
|
|
1483
|
+
transition: transform .2s; transform: rotate(0deg);
|
|
1388
1484
|
}
|
|
1389
1485
|
.universal-card-collapsible-panel[open] .universal-card-collapsible-panel__icon {
|
|
1390
1486
|
transform: rotate(var(--panel-icon-angle, -180deg));
|
|
@@ -5434,8 +5530,7 @@ function registerBuiltins() {
|
|
|
5434
5530
|
registerComponent("markdown", (el, ctx) => {
|
|
5435
5531
|
const p = el.property || {};
|
|
5436
5532
|
const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
|
|
5437
|
-
|
|
5438
|
-
if (mg) style.margin = mg;
|
|
5533
|
+
applyOuterMargin(style, p.margin);
|
|
5439
5534
|
const sizeKey = resolveTextSizeKey(p);
|
|
5440
5535
|
let fontSize = BASE_FONT_SIZE;
|
|
5441
5536
|
let lineHeight = BASE_FONT_SIZE + 8;
|
|
@@ -5501,8 +5596,7 @@ function registerBuiltins() {
|
|
|
5501
5596
|
if (hAlign) {
|
|
5502
5597
|
style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
|
|
5503
5598
|
}
|
|
5504
|
-
|
|
5505
|
-
if (mg) style.margin = mg;
|
|
5599
|
+
applyOuterMargin(style, p.margin);
|
|
5506
5600
|
const bg = propertyBackgroundColor(p);
|
|
5507
5601
|
if (bg) style.background = bg;
|
|
5508
5602
|
const pad = boxToStyle(p.padding);
|
|
@@ -5556,7 +5650,21 @@ function registerBuiltins() {
|
|
|
5556
5650
|
const wrapStyle = {};
|
|
5557
5651
|
const imgStyle = {};
|
|
5558
5652
|
if (p.corner_radius != null) imgStyle.borderRadius = sizeToPx(p.corner_radius);
|
|
5559
|
-
|
|
5653
|
+
const sized = resolveImageSize(p);
|
|
5654
|
+
if (sized.width && sized.height) {
|
|
5655
|
+
wrapStyle.width = sized.width;
|
|
5656
|
+
wrapStyle.height = sized.height;
|
|
5657
|
+
wrapStyle.flexShrink = "0";
|
|
5658
|
+
imgStyle.width = "100%";
|
|
5659
|
+
imgStyle.height = "100%";
|
|
5660
|
+
imgStyle.objectFit = scale === "fit_horizontal" || scale === "stretch" ? "fill" : "cover";
|
|
5661
|
+
} else if (sized.aspectRatio) {
|
|
5662
|
+
wrapStyle.width = "100%";
|
|
5663
|
+
imgStyle.width = "100%";
|
|
5664
|
+
imgStyle.aspectRatio = sized.aspectRatio;
|
|
5665
|
+
imgStyle.height = "auto";
|
|
5666
|
+
imgStyle.objectFit = "cover";
|
|
5667
|
+
} else if (scale === "fit_horizontal" || scale === "stretch") {
|
|
5560
5668
|
wrapStyle.width = "100%";
|
|
5561
5669
|
imgStyle.width = "100%";
|
|
5562
5670
|
imgStyle.height = "auto";
|
|
@@ -5564,7 +5672,7 @@ function registerBuiltins() {
|
|
|
5564
5672
|
return h(
|
|
5565
5673
|
"div",
|
|
5566
5674
|
{ className: `${CLS}-image__wrapper`, style: wrapStyle },
|
|
5567
|
-
src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder
|
|
5675
|
+
src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder`, style: imgStyle }, altText || "[\u56FE\u7247]")
|
|
5568
5676
|
);
|
|
5569
5677
|
});
|
|
5570
5678
|
registerComponent("video", (el, ctx) => {
|
|
@@ -5712,7 +5820,7 @@ function registerBuiltins() {
|
|
|
5712
5820
|
const tip = extractText(p.hoverTips);
|
|
5713
5821
|
const borderNamed = p.border_color ?? p.borderColor;
|
|
5714
5822
|
const borderCustom = resolveCustomBorderLight(p);
|
|
5715
|
-
const borderColor =
|
|
5823
|
+
const borderColor = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
|
|
5716
5824
|
const hasBorder = borderColor != null || p.hasBorder === true || p.has_border === true;
|
|
5717
5825
|
const cls = `${CLS}-interactive-container` + (clickable ? ` ${CLS}-interactive-container--action` : "") + (hasBorder && !borderColor ? ` ${CLS}-interactive-container--bordered` : "") + (disabled ? ` ${CLS}-interactive-container--disabled` : "");
|
|
5718
5826
|
const style = {};
|
|
@@ -5726,7 +5834,16 @@ function registerBuiltins() {
|
|
|
5726
5834
|
const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
|
|
5727
5835
|
if (vGap != null) style.rowGap = spacingToPx(vGap) + "px";
|
|
5728
5836
|
const icWv = p.widthValue;
|
|
5729
|
-
|
|
5837
|
+
const icHv = p.heightValue;
|
|
5838
|
+
const w = resolveDimensionPx(p.width, icWv);
|
|
5839
|
+
const hPx = resolveDimensionPx(p.height, icHv);
|
|
5840
|
+
if (w) style.width = w;
|
|
5841
|
+
if (hPx) {
|
|
5842
|
+
style.height = hPx;
|
|
5843
|
+
style.flexShrink = "0";
|
|
5844
|
+
style.boxSizing = "border-box";
|
|
5845
|
+
}
|
|
5846
|
+
applyOuterMargin(style, p.margin);
|
|
5730
5847
|
const props = { className: cls };
|
|
5731
5848
|
if (Object.keys(style).length) props.style = style;
|
|
5732
5849
|
if (url && !disabled) props["data-url"] = url;
|
|
@@ -5746,10 +5863,16 @@ function registerBuiltins() {
|
|
|
5746
5863
|
const bg = bgStyleToColor(bgRaw);
|
|
5747
5864
|
if (bg) style.background = bg;
|
|
5748
5865
|
}
|
|
5749
|
-
const
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5866
|
+
const borderRaw = p.border;
|
|
5867
|
+
if (borderRaw && typeof borderRaw === "object") {
|
|
5868
|
+
const border = borderRaw;
|
|
5869
|
+
const borderColor = resolveColor(border.color) || "#dee0e3";
|
|
5870
|
+
style.border = `1px solid ${borderColor}`;
|
|
5871
|
+
style.borderRadius = border.corner_radius != null ? sizeToPx(border.corner_radius) : "8px";
|
|
5872
|
+
} else {
|
|
5873
|
+
style.borderRadius = "8px";
|
|
5874
|
+
}
|
|
5875
|
+
applyOuterMargin(style, p.margin);
|
|
5753
5876
|
const headerStyle = {};
|
|
5754
5877
|
const hpad = typeof hdr.padding === "string" ? hdr.padding : boxToStyle(hdr.padding);
|
|
5755
5878
|
if (hpad) headerStyle.padding = hpad;
|
|
@@ -5757,7 +5880,7 @@ function registerBuiltins() {
|
|
|
5757
5880
|
const titleVNode = h("div", { className: `${CLS}-collapsible-panel__title` }, title);
|
|
5758
5881
|
let iconVNode = null;
|
|
5759
5882
|
const iconRaw = hdr.icon;
|
|
5760
|
-
const openAngle = Number(hdr.icon_expanded_angle ?? -180);
|
|
5883
|
+
const openAngle = Number(hdr.icon_expanded_angle ?? hdr.iconExpandedAngle ?? -180);
|
|
5761
5884
|
if (iconRaw && iconRaw.tag) {
|
|
5762
5885
|
const iconEl = iconRaw.property ? iconRaw : { tag: iconRaw.tag, property: iconRaw };
|
|
5763
5886
|
const inner = renderIcon(iconEl, ctx);
|
|
@@ -5765,10 +5888,13 @@ function registerBuiltins() {
|
|
|
5765
5888
|
iconVNode = h("div", { className: `${CLS}-collapsible-panel__icon` }, inner);
|
|
5766
5889
|
}
|
|
5767
5890
|
}
|
|
5768
|
-
const
|
|
5891
|
+
const iconPos = String(hdr.icon_position ?? hdr.iconPosition ?? "right");
|
|
5892
|
+
const iconLeft = iconPos === "left";
|
|
5893
|
+
const iconFollow = iconPos === "follow_text" || iconPos === "followText";
|
|
5769
5894
|
const headerChildren = iconLeft ? [iconVNode, titleVNode] : [titleVNode, iconVNode];
|
|
5770
5895
|
const summaryStyle = { ...headerStyle, "--panel-icon-angle": `${openAngle}deg` };
|
|
5771
|
-
const
|
|
5896
|
+
const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "");
|
|
5897
|
+
const summary = h("summary", { className: headerCls, style: summaryStyle }, headerChildren);
|
|
5772
5898
|
const contentStyle = {};
|
|
5773
5899
|
const cpad = typeof p.padding === "string" ? p.padding : boxToStyle(p.padding);
|
|
5774
5900
|
if (cpad) contentStyle.padding = cpad;
|
package/dist/styles.css
CHANGED
|
@@ -530,25 +530,36 @@
|
|
|
530
530
|
.universal-card-collapsible-panel {
|
|
531
531
|
box-sizing: border-box; overflow: hidden;
|
|
532
532
|
}
|
|
533
|
-
/* header(<summary
|
|
534
|
-
|
|
533
|
+
/* header(<summary>):可点击整行;去掉浏览器默认三角标记。
|
|
534
|
+
真机 color:var(--text-caption) → N600;标题/箭头继承此色。
|
|
535
|
+
默认 title flex:1 把箭头推到右侧(icon_position=right);
|
|
536
|
+
icon_position=left 时靠 DOM 顺序在左。 */
|
|
535
537
|
.universal-card-collapsible-panel__header {
|
|
536
538
|
display: flex; align-items: center; gap: 8px;
|
|
537
539
|
padding: 8px 12px;
|
|
538
540
|
cursor: pointer; box-sizing: border-box;
|
|
539
541
|
list-style: none; user-select: none;
|
|
542
|
+
color: var(--N600);
|
|
540
543
|
}
|
|
541
544
|
.universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
|
|
542
545
|
.universal-card-collapsible-panel__header::marker { content: ''; }
|
|
543
546
|
.universal-card-collapsible-panel__title {
|
|
544
|
-
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
547
|
+
flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
|
|
545
548
|
word-break: break-word;
|
|
546
549
|
}
|
|
550
|
+
/* follow_text:箭头紧贴文字(真机 __header-content--icon-follow),不拉满整行 */
|
|
551
|
+
.universal-card-collapsible-panel__header--icon-follow {
|
|
552
|
+
gap: 4px;
|
|
553
|
+
}
|
|
554
|
+
.universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
|
|
555
|
+
flex: 0 1 auto;
|
|
556
|
+
width: auto;
|
|
557
|
+
}
|
|
547
558
|
/* 箭头图标包裹:收起态角度 0;展开态(details[open])旋转到 --panel-icon-angle(内联给出,默认 -180deg)。
|
|
548
|
-
由用户点击 <summary> 切换 [open],箭头方向随之自动更新,无需 JS。 */
|
|
559
|
+
由用户点击 <summary> 切换 [open],箭头方向随之自动更新,无需 JS。颜色继承 header caption。 */
|
|
549
560
|
.universal-card-collapsible-panel__icon {
|
|
550
561
|
flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center;
|
|
551
|
-
transition: transform .2s;
|
|
562
|
+
transition: transform .2s; transform: rotate(0deg);
|
|
552
563
|
}
|
|
553
564
|
.universal-card-collapsible-panel[open] .universal-card-collapsible-panel__icon {
|
|
554
565
|
transform: rotate(var(--panel-icon-angle, -180deg));
|
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.16",
|
|
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.11"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"tsup": "^8.3.5",
|