@open-feishu-card/renderer 0.2.14 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/all.css CHANGED
@@ -530,8 +530,9 @@
530
530
  .universal-card-collapsible-panel {
531
531
  box-sizing: border-box; overflow: hidden;
532
532
  }
533
- /* header(<summary>):可点击整行;去掉浏览器默认三角标记,标题占满、箭头贴右
534
- (icon_position=left 时靠 DOM 顺序在左)。 */
533
+ /* header(<summary>):可点击整行;去掉浏览器默认三角标记。
534
+ 默认 title flex:1 把箭头推到右侧(icon_position=right);
535
+ icon_position=left 时靠 DOM 顺序在左。 */
535
536
  .universal-card-collapsible-panel__header {
536
537
  display: flex; align-items: center; gap: 8px;
537
538
  padding: 8px 12px;
@@ -544,6 +545,14 @@
544
545
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px; color: #1f2329;
545
546
  word-break: break-word;
546
547
  }
548
+ /* follow_text:箭头紧贴文字(真机 __header-content--icon-follow),不拉满整行 */
549
+ .universal-card-collapsible-panel__header--icon-follow {
550
+ gap: 4px;
551
+ }
552
+ .universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
553
+ flex: 0 1 auto;
554
+ width: auto;
555
+ }
547
556
  /* 箭头图标包裹:收起态角度 0;展开态(details[open])旋转到 --panel-icon-angle(内联给出,默认 -180deg)。
548
557
  由用户点击 <summary> 切换 [open],箭头方向随之自动更新,无需 JS。 */
549
558
  .universal-card-collapsible-panel__icon {
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,8 +1452,9 @@ 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\uFF0C\u6807\u9898\u5360\u6EE1\u3001\u7BAD\u5934\u8D34\u53F3
1371
- \uFF08icon_position=left \u65F6\u9760 DOM \u987A\u5E8F\u5728\u5DE6\uFF09\u3002 */
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
+ \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 */
1372
1458
  .universal-card-collapsible-panel__header {
1373
1459
  display: flex; align-items: center; gap: 8px;
1374
1460
  padding: 8px 12px;
@@ -1381,6 +1467,14 @@ var BASE_STYLES_CSS = `/**
1381
1467
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px; color: #1f2329;
1382
1468
  word-break: break-word;
1383
1469
  }
1470
+ /* follow_text\uFF1A\u7BAD\u5934\u7D27\u8D34\u6587\u5B57\uFF08\u771F\u673A __header-content--icon-follow\uFF09\uFF0C\u4E0D\u62C9\u6EE1\u6574\u884C */
1471
+ .universal-card-collapsible-panel__header--icon-follow {
1472
+ gap: 4px;
1473
+ }
1474
+ .universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
1475
+ flex: 0 1 auto;
1476
+ width: auto;
1477
+ }
1384
1478
  /* \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
1479
  \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 */
1386
1480
  .universal-card-collapsible-panel__icon {
@@ -5435,8 +5529,7 @@ function registerBuiltins() {
5435
5529
  registerComponent("markdown", (el, ctx) => {
5436
5530
  const p = el.property || {};
5437
5531
  const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
5438
- const mg = marginToStyle(p.margin);
5439
- if (mg) style.margin = mg;
5532
+ applyOuterMargin(style, p.margin);
5440
5533
  const sizeKey = resolveTextSizeKey(p);
5441
5534
  let fontSize = BASE_FONT_SIZE;
5442
5535
  let lineHeight = BASE_FONT_SIZE + 8;
@@ -5502,8 +5595,7 @@ function registerBuiltins() {
5502
5595
  if (hAlign) {
5503
5596
  style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
5504
5597
  }
5505
- const mg = boxToStyle(p.margin);
5506
- if (mg) style.margin = mg;
5598
+ applyOuterMargin(style, p.margin);
5507
5599
  const bg = propertyBackgroundColor(p);
5508
5600
  if (bg) style.background = bg;
5509
5601
  const pad = boxToStyle(p.padding);
@@ -5557,7 +5649,21 @@ function registerBuiltins() {
5557
5649
  const wrapStyle = {};
5558
5650
  const imgStyle = {};
5559
5651
  if (p.corner_radius != null) imgStyle.borderRadius = sizeToPx(p.corner_radius);
5560
- if (scale === "fit_horizontal" || scale === "stretch") {
5652
+ const sized = resolveImageSize(p);
5653
+ if (sized.width && sized.height) {
5654
+ wrapStyle.width = sized.width;
5655
+ wrapStyle.height = sized.height;
5656
+ wrapStyle.flexShrink = "0";
5657
+ imgStyle.width = "100%";
5658
+ imgStyle.height = "100%";
5659
+ imgStyle.objectFit = scale === "fit_horizontal" || scale === "stretch" ? "fill" : "cover";
5660
+ } else if (sized.aspectRatio) {
5661
+ wrapStyle.width = "100%";
5662
+ imgStyle.width = "100%";
5663
+ imgStyle.aspectRatio = sized.aspectRatio;
5664
+ imgStyle.height = "auto";
5665
+ imgStyle.objectFit = "cover";
5666
+ } else if (scale === "fit_horizontal" || scale === "stretch") {
5561
5667
  wrapStyle.width = "100%";
5562
5668
  imgStyle.width = "100%";
5563
5669
  imgStyle.height = "auto";
@@ -5565,7 +5671,7 @@ function registerBuiltins() {
5565
5671
  return h(
5566
5672
  "div",
5567
5673
  { 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` }, altText || "[\u56FE\u7247]")
5674
+ src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder`, style: imgStyle }, altText || "[\u56FE\u7247]")
5569
5675
  );
5570
5676
  });
5571
5677
  registerComponent("video", (el, ctx) => {
@@ -5713,7 +5819,7 @@ function registerBuiltins() {
5713
5819
  const tip = extractText(p.hoverTips);
5714
5820
  const borderNamed = p.border_color ?? p.borderColor;
5715
5821
  const borderCustom = adapter.resolveCustomBorderLight(p);
5716
- const borderColor = resolveColor(borderNamed) ?? (borderCustom ? resolveColor(borderCustom) : void 0);
5822
+ const borderColor = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
5717
5823
  const hasBorder = borderColor != null || p.hasBorder === true || p.has_border === true;
5718
5824
  const cls = `${CLS}-interactive-container` + (clickable ? ` ${CLS}-interactive-container--action` : "") + (hasBorder && !borderColor ? ` ${CLS}-interactive-container--bordered` : "") + (disabled ? ` ${CLS}-interactive-container--disabled` : "");
5719
5825
  const style = {};
@@ -5727,7 +5833,16 @@ function registerBuiltins() {
5727
5833
  const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
5728
5834
  if (vGap != null) style.rowGap = spacingToPx(vGap) + "px";
5729
5835
  const icWv = p.widthValue;
5730
- if ((p.width ?? icWv?.value) === "fill") style.width = "100%";
5836
+ const icHv = p.heightValue;
5837
+ const w = resolveDimensionPx(p.width, icWv);
5838
+ const hPx = resolveDimensionPx(p.height, icHv);
5839
+ if (w) style.width = w;
5840
+ if (hPx) {
5841
+ style.height = hPx;
5842
+ style.flexShrink = "0";
5843
+ style.boxSizing = "border-box";
5844
+ }
5845
+ applyOuterMargin(style, p.margin);
5731
5846
  const props = { className: cls };
5732
5847
  if (Object.keys(style).length) props.style = style;
5733
5848
  if (url && !disabled) props["data-url"] = url;
@@ -5747,10 +5862,16 @@ function registerBuiltins() {
5747
5862
  const bg = bgStyleToColor(bgRaw);
5748
5863
  if (bg) style.background = bg;
5749
5864
  }
5750
- const border = p.border && typeof p.border === "object" ? p.border : {};
5751
- const borderColor = resolveColor(border.color) || "#dee0e3";
5752
- style.border = `1px solid ${borderColor}`;
5753
- style.borderRadius = border.corner_radius != null ? sizeToPx(border.corner_radius) : "8px";
5865
+ const borderRaw = p.border;
5866
+ if (borderRaw && typeof borderRaw === "object") {
5867
+ const border = borderRaw;
5868
+ const borderColor = resolveColor(border.color) || "#dee0e3";
5869
+ style.border = `1px solid ${borderColor}`;
5870
+ style.borderRadius = border.corner_radius != null ? sizeToPx(border.corner_radius) : "8px";
5871
+ } else {
5872
+ style.borderRadius = "8px";
5873
+ }
5874
+ applyOuterMargin(style, p.margin);
5754
5875
  const headerStyle = {};
5755
5876
  const hpad = typeof hdr.padding === "string" ? hdr.padding : boxToStyle(hdr.padding);
5756
5877
  if (hpad) headerStyle.padding = hpad;
@@ -5758,7 +5879,7 @@ function registerBuiltins() {
5758
5879
  const titleVNode = h("div", { className: `${CLS}-collapsible-panel__title` }, title);
5759
5880
  let iconVNode = null;
5760
5881
  const iconRaw = hdr.icon;
5761
- const openAngle = Number(hdr.icon_expanded_angle ?? -180);
5882
+ const openAngle = Number(hdr.icon_expanded_angle ?? hdr.iconExpandedAngle ?? -180);
5762
5883
  if (iconRaw && iconRaw.tag) {
5763
5884
  const iconEl = iconRaw.property ? iconRaw : { tag: iconRaw.tag, property: iconRaw };
5764
5885
  const inner = renderIcon(iconEl, ctx);
@@ -5766,10 +5887,13 @@ function registerBuiltins() {
5766
5887
  iconVNode = h("div", { className: `${CLS}-collapsible-panel__icon` }, inner);
5767
5888
  }
5768
5889
  }
5769
- const iconLeft = hdr.icon_position === "left";
5890
+ const iconPos = String(hdr.icon_position ?? hdr.iconPosition ?? "right");
5891
+ const iconLeft = iconPos === "left";
5892
+ const iconFollow = iconPos === "follow_text" || iconPos === "followText";
5770
5893
  const headerChildren = iconLeft ? [iconVNode, titleVNode] : [titleVNode, iconVNode];
5771
5894
  const summaryStyle = { ...headerStyle, "--panel-icon-angle": `${openAngle}deg` };
5772
- const summary = h("summary", { className: `${CLS}-collapsible-panel__header`, style: summaryStyle }, headerChildren);
5895
+ const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "");
5896
+ const summary = h("summary", { className: headerCls, style: summaryStyle }, headerChildren);
5773
5897
  const contentStyle = {};
5774
5898
  const cpad = typeof p.padding === "string" ? p.padding : boxToStyle(p.padding);
5775
5899
  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,8 +1451,9 @@ 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\uFF0C\u6807\u9898\u5360\u6EE1\u3001\u7BAD\u5934\u8D34\u53F3
1370
- \uFF08icon_position=left \u65F6\u9760 DOM \u987A\u5E8F\u5728\u5DE6\uFF09\u3002 */
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
+ \u9ED8\u8BA4 title flex:1 \u628A\u7BAD\u5934\u63A8\u5230\u53F3\u4FA7\uFF08icon_position=right\uFF09\uFF1B
1456
+ icon_position=left \u65F6\u9760 DOM \u987A\u5E8F\u5728\u5DE6\u3002 */
1371
1457
  .universal-card-collapsible-panel__header {
1372
1458
  display: flex; align-items: center; gap: 8px;
1373
1459
  padding: 8px 12px;
@@ -1380,6 +1466,14 @@ var BASE_STYLES_CSS = `/**
1380
1466
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px; color: #1f2329;
1381
1467
  word-break: break-word;
1382
1468
  }
1469
+ /* follow_text\uFF1A\u7BAD\u5934\u7D27\u8D34\u6587\u5B57\uFF08\u771F\u673A __header-content--icon-follow\uFF09\uFF0C\u4E0D\u62C9\u6EE1\u6574\u884C */
1470
+ .universal-card-collapsible-panel__header--icon-follow {
1471
+ gap: 4px;
1472
+ }
1473
+ .universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
1474
+ flex: 0 1 auto;
1475
+ width: auto;
1476
+ }
1383
1477
  /* \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
1478
  \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 */
1385
1479
  .universal-card-collapsible-panel__icon {
@@ -5434,8 +5528,7 @@ function registerBuiltins() {
5434
5528
  registerComponent("markdown", (el, ctx) => {
5435
5529
  const p = el.property || {};
5436
5530
  const style = { ...alignToStyle(p.textAlign), ...textStyleToStyle(p.textStyle) };
5437
- const mg = marginToStyle(p.margin);
5438
- if (mg) style.margin = mg;
5531
+ applyOuterMargin(style, p.margin);
5439
5532
  const sizeKey = resolveTextSizeKey(p);
5440
5533
  let fontSize = BASE_FONT_SIZE;
5441
5534
  let lineHeight = BASE_FONT_SIZE + 8;
@@ -5501,8 +5594,7 @@ function registerBuiltins() {
5501
5594
  if (hAlign) {
5502
5595
  style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
5503
5596
  }
5504
- const mg = boxToStyle(p.margin);
5505
- if (mg) style.margin = mg;
5597
+ applyOuterMargin(style, p.margin);
5506
5598
  const bg = propertyBackgroundColor(p);
5507
5599
  if (bg) style.background = bg;
5508
5600
  const pad = boxToStyle(p.padding);
@@ -5556,7 +5648,21 @@ function registerBuiltins() {
5556
5648
  const wrapStyle = {};
5557
5649
  const imgStyle = {};
5558
5650
  if (p.corner_radius != null) imgStyle.borderRadius = sizeToPx(p.corner_radius);
5559
- if (scale === "fit_horizontal" || scale === "stretch") {
5651
+ const sized = resolveImageSize(p);
5652
+ if (sized.width && sized.height) {
5653
+ wrapStyle.width = sized.width;
5654
+ wrapStyle.height = sized.height;
5655
+ wrapStyle.flexShrink = "0";
5656
+ imgStyle.width = "100%";
5657
+ imgStyle.height = "100%";
5658
+ imgStyle.objectFit = scale === "fit_horizontal" || scale === "stretch" ? "fill" : "cover";
5659
+ } else if (sized.aspectRatio) {
5660
+ wrapStyle.width = "100%";
5661
+ imgStyle.width = "100%";
5662
+ imgStyle.aspectRatio = sized.aspectRatio;
5663
+ imgStyle.height = "auto";
5664
+ imgStyle.objectFit = "cover";
5665
+ } else if (scale === "fit_horizontal" || scale === "stretch") {
5560
5666
  wrapStyle.width = "100%";
5561
5667
  imgStyle.width = "100%";
5562
5668
  imgStyle.height = "auto";
@@ -5564,7 +5670,7 @@ function registerBuiltins() {
5564
5670
  return h(
5565
5671
  "div",
5566
5672
  { 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` }, altText || "[\u56FE\u7247]")
5673
+ src ? h("img", { className: `${CLS}-image__host`, src, alt: altText, style: imgStyle }) : h("div", { className: `${CLS}-image__placeholder`, style: imgStyle }, altText || "[\u56FE\u7247]")
5568
5674
  );
5569
5675
  });
5570
5676
  registerComponent("video", (el, ctx) => {
@@ -5712,7 +5818,7 @@ function registerBuiltins() {
5712
5818
  const tip = extractText(p.hoverTips);
5713
5819
  const borderNamed = p.border_color ?? p.borderColor;
5714
5820
  const borderCustom = resolveCustomBorderLight(p);
5715
- const borderColor = resolveColor(borderNamed) ?? (borderCustom ? resolveColor(borderCustom) : void 0);
5821
+ const borderColor = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
5716
5822
  const hasBorder = borderColor != null || p.hasBorder === true || p.has_border === true;
5717
5823
  const cls = `${CLS}-interactive-container` + (clickable ? ` ${CLS}-interactive-container--action` : "") + (hasBorder && !borderColor ? ` ${CLS}-interactive-container--bordered` : "") + (disabled ? ` ${CLS}-interactive-container--disabled` : "");
5718
5824
  const style = {};
@@ -5726,7 +5832,16 @@ function registerBuiltins() {
5726
5832
  const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
5727
5833
  if (vGap != null) style.rowGap = spacingToPx(vGap) + "px";
5728
5834
  const icWv = p.widthValue;
5729
- if ((p.width ?? icWv?.value) === "fill") style.width = "100%";
5835
+ const icHv = p.heightValue;
5836
+ const w = resolveDimensionPx(p.width, icWv);
5837
+ const hPx = resolveDimensionPx(p.height, icHv);
5838
+ if (w) style.width = w;
5839
+ if (hPx) {
5840
+ style.height = hPx;
5841
+ style.flexShrink = "0";
5842
+ style.boxSizing = "border-box";
5843
+ }
5844
+ applyOuterMargin(style, p.margin);
5730
5845
  const props = { className: cls };
5731
5846
  if (Object.keys(style).length) props.style = style;
5732
5847
  if (url && !disabled) props["data-url"] = url;
@@ -5746,10 +5861,16 @@ function registerBuiltins() {
5746
5861
  const bg = bgStyleToColor(bgRaw);
5747
5862
  if (bg) style.background = bg;
5748
5863
  }
5749
- const border = p.border && typeof p.border === "object" ? p.border : {};
5750
- const borderColor = resolveColor(border.color) || "#dee0e3";
5751
- style.border = `1px solid ${borderColor}`;
5752
- style.borderRadius = border.corner_radius != null ? sizeToPx(border.corner_radius) : "8px";
5864
+ const borderRaw = p.border;
5865
+ if (borderRaw && typeof borderRaw === "object") {
5866
+ const border = borderRaw;
5867
+ const borderColor = resolveColor(border.color) || "#dee0e3";
5868
+ style.border = `1px solid ${borderColor}`;
5869
+ style.borderRadius = border.corner_radius != null ? sizeToPx(border.corner_radius) : "8px";
5870
+ } else {
5871
+ style.borderRadius = "8px";
5872
+ }
5873
+ applyOuterMargin(style, p.margin);
5753
5874
  const headerStyle = {};
5754
5875
  const hpad = typeof hdr.padding === "string" ? hdr.padding : boxToStyle(hdr.padding);
5755
5876
  if (hpad) headerStyle.padding = hpad;
@@ -5757,7 +5878,7 @@ function registerBuiltins() {
5757
5878
  const titleVNode = h("div", { className: `${CLS}-collapsible-panel__title` }, title);
5758
5879
  let iconVNode = null;
5759
5880
  const iconRaw = hdr.icon;
5760
- const openAngle = Number(hdr.icon_expanded_angle ?? -180);
5881
+ const openAngle = Number(hdr.icon_expanded_angle ?? hdr.iconExpandedAngle ?? -180);
5761
5882
  if (iconRaw && iconRaw.tag) {
5762
5883
  const iconEl = iconRaw.property ? iconRaw : { tag: iconRaw.tag, property: iconRaw };
5763
5884
  const inner = renderIcon(iconEl, ctx);
@@ -5765,10 +5886,13 @@ function registerBuiltins() {
5765
5886
  iconVNode = h("div", { className: `${CLS}-collapsible-panel__icon` }, inner);
5766
5887
  }
5767
5888
  }
5768
- const iconLeft = hdr.icon_position === "left";
5889
+ const iconPos = String(hdr.icon_position ?? hdr.iconPosition ?? "right");
5890
+ const iconLeft = iconPos === "left";
5891
+ const iconFollow = iconPos === "follow_text" || iconPos === "followText";
5769
5892
  const headerChildren = iconLeft ? [iconVNode, titleVNode] : [titleVNode, iconVNode];
5770
5893
  const summaryStyle = { ...headerStyle, "--panel-icon-angle": `${openAngle}deg` };
5771
- const summary = h("summary", { className: `${CLS}-collapsible-panel__header`, style: summaryStyle }, headerChildren);
5894
+ const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "");
5895
+ const summary = h("summary", { className: headerCls, style: summaryStyle }, headerChildren);
5772
5896
  const contentStyle = {};
5773
5897
  const cpad = typeof p.padding === "string" ? p.padding : boxToStyle(p.padding);
5774
5898
  if (cpad) contentStyle.padding = cpad;
package/dist/styles.css CHANGED
@@ -530,8 +530,9 @@
530
530
  .universal-card-collapsible-panel {
531
531
  box-sizing: border-box; overflow: hidden;
532
532
  }
533
- /* header(<summary>):可点击整行;去掉浏览器默认三角标记,标题占满、箭头贴右
534
- (icon_position=left 时靠 DOM 顺序在左)。 */
533
+ /* header(<summary>):可点击整行;去掉浏览器默认三角标记。
534
+ 默认 title flex:1 把箭头推到右侧(icon_position=right);
535
+ icon_position=left 时靠 DOM 顺序在左。 */
535
536
  .universal-card-collapsible-panel__header {
536
537
  display: flex; align-items: center; gap: 8px;
537
538
  padding: 8px 12px;
@@ -544,6 +545,14 @@
544
545
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px; color: #1f2329;
545
546
  word-break: break-word;
546
547
  }
548
+ /* follow_text:箭头紧贴文字(真机 __header-content--icon-follow),不拉满整行 */
549
+ .universal-card-collapsible-panel__header--icon-follow {
550
+ gap: 4px;
551
+ }
552
+ .universal-card-collapsible-panel__header--icon-follow .universal-card-collapsible-panel__title {
553
+ flex: 0 1 auto;
554
+ width: auto;
555
+ }
547
556
  /* 箭头图标包裹:收起态角度 0;展开态(details[open])旋转到 --panel-icon-angle(内联给出,默认 -180deg)。
548
557
  由用户点击 <summary> 切换 [open],箭头方向随之自动更新,无需 JS。 */
549
558
  .universal-card-collapsible-panel__icon {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
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.10"
31
+ "@open-feishu-card/adapter": "0.3.11"
32
32
  },
33
33
  "devDependencies": {
34
34
  "tsup": "^8.3.5",