@open-feishu-card/renderer 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/all.css CHANGED
@@ -707,10 +707,32 @@
707
707
  }
708
708
  .universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
709
709
  .universal-card-collapsible-panel__header::marker { content: ''; }
710
+ /* header.vertical_align:center(默认)/ top / bottom */
711
+ .universal-card-collapsible-panel__header--valign-top { align-items: flex-start; }
712
+ .universal-card-collapsible-panel__header--valign-bottom { align-items: flex-end; }
713
+ /* header.width:fill(默认,撑满整行)/ auto(自适应内容宽)/
714
+ auto_when_fold(折叠自适应、展开撑满——真机语义) */
715
+ .universal-card-collapsible-panel__header--w-auto,
716
+ .universal-card-collapsible-panel__header--w-auto-when-fold {
717
+ width: max-content; max-width: 100%;
718
+ }
719
+ .universal-card-collapsible-panel[open] > .universal-card-collapsible-panel__header--w-auto-when-fold {
720
+ width: 100%;
721
+ }
710
722
  .universal-card-collapsible-panel__title {
711
723
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
712
724
  word-break: break-word;
713
725
  }
726
+ /* title ↔ expanded_title:有 expanded_title 时,折叠展示 title、展开展示 expanded_title
727
+ (真机语义)。纯 CSS 由 details[open] 驱动,不学要 JS 同步。 */
728
+ .universal-card-collapsible-panel__title-text { display: block; }
729
+ .universal-card-collapsible-panel__title--has-expanded
730
+ .universal-card-collapsible-panel__title-text--expanded { display: none; }
731
+ .universal-card-collapsible-panel[open]
732
+ .universal-card-collapsible-panel__title--has-expanded .universal-card-collapsible-panel__title-text { display: none; }
733
+ .universal-card-collapsible-panel[open]
734
+ .universal-card-collapsible-panel__title--has-expanded
735
+ .universal-card-collapsible-panel__title-text--expanded { display: block; }
714
736
  /* follow_text:箭头紧贴文字(真机 __header-content--icon-follow),不拉满整行 */
715
737
  .universal-card-collapsible-panel__header--icon-follow {
716
738
  gap: 4px;
package/dist/index.cjs CHANGED
@@ -1684,10 +1684,32 @@ var BASE_STYLES_CSS = `/**
1684
1684
  }
1685
1685
  .universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
1686
1686
  .universal-card-collapsible-panel__header::marker { content: ''; }
1687
+ /* header.vertical_align\uFF1Acenter\uFF08\u9ED8\u8BA4\uFF09/ top / bottom */
1688
+ .universal-card-collapsible-panel__header--valign-top { align-items: flex-start; }
1689
+ .universal-card-collapsible-panel__header--valign-bottom { align-items: flex-end; }
1690
+ /* header.width\uFF1Afill\uFF08\u9ED8\u8BA4\uFF0C\u6491\u6EE1\u6574\u884C\uFF09/ auto\uFF08\u81EA\u9002\u5E94\u5185\u5BB9\u5BBD\uFF09/
1691
+ auto_when_fold\uFF08\u6298\u53E0\u81EA\u9002\u5E94\u3001\u5C55\u5F00\u6491\u6EE1\u2014\u2014\u771F\u673A\u8BED\u4E49\uFF09 */
1692
+ .universal-card-collapsible-panel__header--w-auto,
1693
+ .universal-card-collapsible-panel__header--w-auto-when-fold {
1694
+ width: max-content; max-width: 100%;
1695
+ }
1696
+ .universal-card-collapsible-panel[open] > .universal-card-collapsible-panel__header--w-auto-when-fold {
1697
+ width: 100%;
1698
+ }
1687
1699
  .universal-card-collapsible-panel__title {
1688
1700
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
1689
1701
  word-break: break-word;
1690
1702
  }
1703
+ /* title \u2194 expanded_title\uFF1A\u6709 expanded_title \u65F6\uFF0C\u6298\u53E0\u5C55\u793A title\u3001\u5C55\u5F00\u5C55\u793A expanded_title
1704
+ \uFF08\u771F\u673A\u8BED\u4E49\uFF09\u3002\u7EAF CSS \u7531 details[open] \u9A71\u52A8\uFF0C\u4E0D\u5B66\u8981 JS \u540C\u6B65\u3002 */
1705
+ .universal-card-collapsible-panel__title-text { display: block; }
1706
+ .universal-card-collapsible-panel__title--has-expanded
1707
+ .universal-card-collapsible-panel__title-text--expanded { display: none; }
1708
+ .universal-card-collapsible-panel[open]
1709
+ .universal-card-collapsible-panel__title--has-expanded .universal-card-collapsible-panel__title-text { display: none; }
1710
+ .universal-card-collapsible-panel[open]
1711
+ .universal-card-collapsible-panel__title--has-expanded
1712
+ .universal-card-collapsible-panel__title-text--expanded { display: block; }
1691
1713
  /* follow_text\uFF1A\u7BAD\u5934\u7D27\u8D34\u6587\u5B57\uFF08\u771F\u673A __header-content--icon-follow\uFF09\uFF0C\u4E0D\u62C9\u6EE1\u6574\u884C */
1692
1714
  .universal-card-collapsible-panel__header--icon-follow {
1693
1715
  gap: 4px;
@@ -5944,6 +5966,8 @@ function registerBuiltins() {
5944
5966
  }
5945
5967
  return h("div", { className: `${CLS}-div` }, content);
5946
5968
  });
5969
+ const H_ALIGN_MAP = { left: "flex-start", center: "center", right: "flex-end" };
5970
+ const V_ALIGN_MAP = { top: "flex-start", center: "center", bottom: "flex-end" };
5947
5971
  registerComponent("column_set", (el, ctx) => {
5948
5972
  const p = el.property || {};
5949
5973
  const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
@@ -5958,7 +5982,7 @@ function registerBuiltins() {
5958
5982
  columnGap: gap + "px"
5959
5983
  };
5960
5984
  if (hAlign) {
5961
- style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
5985
+ style.justifyContent = H_ALIGN_MAP[hAlign] || "flex-start";
5962
5986
  }
5963
5987
  applyOuterMargin(style, p.margin);
5964
5988
  const bg = propertyBackgroundColor(p);
@@ -5993,6 +6017,7 @@ function registerBuiltins() {
5993
6017
  flexShrink: 1,
5994
6018
  overflow: "hidden",
5995
6019
  display: "flex",
6020
+ // 缺省纵向排列;direction=horizontal 时改行,两个 align 落到哪个 flex 属性也随之交换
5996
6021
  flexDirection: "column"
5997
6022
  };
5998
6023
  const wv = p.widthValue;
@@ -6028,10 +6053,16 @@ function registerBuiltins() {
6028
6053
  style.flexBasis = "auto";
6029
6054
  }
6030
6055
  }
6031
- const vAlign = p.verticalAlign || p.vertical_align;
6032
- if (vAlign) {
6033
- style.justifyContent = { top: "flex-start", center: "center", bottom: "flex-end" }[vAlign] || "flex-start";
6034
- }
6056
+ const horizontalDir = p.direction === "horizontal";
6057
+ if (horizontalDir) style.flexDirection = "row";
6058
+ const hAlign = p.horizontal_align ?? p.horizontalAlign;
6059
+ const vAlign = p.vertical_align ?? p.verticalAlign;
6060
+ const justify = horizontalDir ? H_ALIGN_MAP[hAlign] : V_ALIGN_MAP[vAlign];
6061
+ const align = horizontalDir ? V_ALIGN_MAP[vAlign] : H_ALIGN_MAP[hAlign];
6062
+ if (justify) style.justifyContent = justify;
6063
+ if (align) style.alignItems = align;
6064
+ const hGap = p.horizontalSpacingValue?.value ?? p.horizontal_spacing ?? p.horizontalSpacing;
6065
+ if (hGap != null) style.columnGap = horizontalSpacingToPx(hGap) + "px";
6035
6066
  const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
6036
6067
  style.rowGap = (vGap != null ? verticalSpacingToPx(vGap) : 8) + "px";
6037
6068
  const bg = propertyBackgroundColor(p);
@@ -6362,10 +6393,8 @@ function registerBuiltins() {
6362
6393
  if (horizontalDir) style.flexDirection = "row";
6363
6394
  const hAlign = p.horizontal_align ?? p.horizontalAlign;
6364
6395
  const vAlign = p.vertical_align ?? p.verticalAlign;
6365
- const H_ALIGN = { left: "flex-start", center: "center", right: "flex-end" };
6366
- const V_ALIGN = { top: "flex-start", center: "center", bottom: "flex-end" };
6367
- const justify = horizontalDir ? H_ALIGN[hAlign] : V_ALIGN[vAlign];
6368
- const align = horizontalDir ? V_ALIGN[vAlign] : H_ALIGN[hAlign];
6396
+ const justify = horizontalDir ? H_ALIGN_MAP[hAlign] : V_ALIGN_MAP[vAlign];
6397
+ const align = horizontalDir ? V_ALIGN_MAP[vAlign] : H_ALIGN_MAP[hAlign];
6369
6398
  if (justify) style.justifyContent = justify;
6370
6399
  if (align) style.alignItems = align;
6371
6400
  const hGap = p.horizontalSpacingValue?.value ?? p.horizontal_spacing ?? p.horizontalSpacing;
@@ -6392,7 +6421,7 @@ function registerBuiltins() {
6392
6421
  registerComponent("collapsible_panel", (el, ctx) => {
6393
6422
  const p = el.property || {};
6394
6423
  const hdr = p.header && typeof p.header === "object" ? p.header : {};
6395
- const expanded = p.expanded !== false;
6424
+ const expanded = p.expanded === true;
6396
6425
  const style = { width: "100%" };
6397
6426
  const bgRaw = p.background_color ?? p.backgroundColor;
6398
6427
  const bgCustom = propertyBackgroundColor(p);
@@ -6411,12 +6440,36 @@ function registerBuiltins() {
6411
6440
  } else {
6412
6441
  style.borderRadius = "8px";
6413
6442
  }
6443
+ const headerBottom = String(hdr.position ?? "top") === "bottom";
6444
+ if (headerBottom) {
6445
+ style.display = "flex";
6446
+ style.flexDirection = "column";
6447
+ }
6414
6448
  applyOuterMargin(style, p.margin);
6415
6449
  const headerStyle = {};
6416
6450
  const hpad = typeof hdr.padding === "string" ? hdr.padding : boxToStyle(hdr.padding);
6417
6451
  if (hpad) headerStyle.padding = hpad;
6418
- const title = extractPlainText(hdr.title) || extractText(hdr.title);
6419
- const titleVNode = h("div", { className: `${CLS}-collapsible-panel__title` }, title);
6452
+ const hbg = resolveColor(hdr.background_color ?? hdr.backgroundColor);
6453
+ if (hbg) headerStyle.background = hbg;
6454
+ if (headerBottom) headerStyle.order = "2";
6455
+ const titleLeaf = (raw, className) => {
6456
+ if (!raw || typeof raw !== "object") return null;
6457
+ const t = raw;
6458
+ if (typeof t.tag !== "string") return null;
6459
+ const property = t.property && typeof t.property === "object" ? t.property : t;
6460
+ const node = renderHeaderTextLeaf({ tag: t.tag, property }, ctx, className);
6461
+ if (node) return node;
6462
+ const text = extractPlainText(raw) || extractText(raw);
6463
+ return text ? h("div", { className }, text) : null;
6464
+ };
6465
+ const primaryTitle = titleLeaf(hdr.title, `${CLS}-collapsible-panel__title-text`);
6466
+ const expandedTitleRaw = hdr.expanded_title ?? hdr.expandedTitle;
6467
+ const expandedTitle = titleLeaf(
6468
+ expandedTitleRaw,
6469
+ `${CLS}-collapsible-panel__title-text ${CLS}-collapsible-panel__title-text--expanded`
6470
+ );
6471
+ const titleCls = `${CLS}-collapsible-panel__title` + (expandedTitle ? ` ${CLS}-collapsible-panel__title--has-expanded` : "");
6472
+ const titleVNode = h("div", { className: titleCls }, [primaryTitle, expandedTitle].filter(Boolean));
6420
6473
  let iconVNode = null;
6421
6474
  const iconRaw = hdr.icon;
6422
6475
  const openAngle = Number(hdr.icon_expanded_angle ?? hdr.iconExpandedAngle ?? -180);
@@ -6432,11 +6485,38 @@ function registerBuiltins() {
6432
6485
  const iconFollow = iconPos === "follow_text" || iconPos === "followText";
6433
6486
  const headerChildren = iconLeft ? [iconVNode, titleVNode] : [titleVNode, iconVNode];
6434
6487
  const summaryStyle = { ...headerStyle, "--panel-icon-angle": `${openAngle}deg` };
6435
- const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "");
6488
+ const headerWidth = String(hdr.width ?? hdr.headerWidth ?? "fill");
6489
+ const headerVAlign = String(hdr.vertical_align ?? hdr.verticalAlign ?? "center");
6490
+ const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "") + (headerWidth === "auto" ? ` ${CLS}-collapsible-panel__header--w-auto` : "") + (headerWidth === "auto_when_fold" ? ` ${CLS}-collapsible-panel__header--w-auto-when-fold` : "") + (headerVAlign === "top" ? ` ${CLS}-collapsible-panel__header--valign-top` : "") + (headerVAlign === "bottom" ? ` ${CLS}-collapsible-panel__header--valign-bottom` : "");
6436
6491
  const summary = h("summary", { className: headerCls, style: summaryStyle }, headerChildren);
6437
6492
  const contentStyle = {};
6438
6493
  const cpad = typeof p.padding === "string" ? p.padding : boxToStyle(p.padding);
6439
6494
  if (cpad) contentStyle.padding = cpad;
6495
+ if (headerBottom) contentStyle.order = "1";
6496
+ const direction = String(p.direction ?? "vertical");
6497
+ const isRow = direction === "horizontal";
6498
+ const hSpacing = p.horizontal_spacing ?? p.horizontalSpacing;
6499
+ const vSpacing = p.vertical_spacing ?? p.verticalSpacing;
6500
+ const hAlign = p.horizontal_align ?? p.horizontalAlign;
6501
+ const vAlign = p.vertical_align ?? p.verticalAlign;
6502
+ if (isRow || hSpacing != null || vSpacing != null || hAlign != null || vAlign != null) {
6503
+ contentStyle.display = "flex";
6504
+ contentStyle.flexDirection = isRow ? "row" : "column";
6505
+ const gapX = hSpacing != null ? horizontalSpacingToPx(hSpacing) : isRow ? 8 : 0;
6506
+ const gapY = vSpacing != null ? verticalSpacingToPx(vSpacing) : 0;
6507
+ if (gapX) contentStyle.columnGap = gapX + "px";
6508
+ if (gapY) contentStyle.rowGap = gapY + "px";
6509
+ const hVal = hAlign ? H_ALIGN_MAP[hAlign] : void 0;
6510
+ const vVal = vAlign ? V_ALIGN_MAP[vAlign] : void 0;
6511
+ if (hVal) {
6512
+ if (isRow) contentStyle.justifyContent = hVal;
6513
+ else contentStyle.alignItems = hVal;
6514
+ }
6515
+ if (vVal) {
6516
+ if (isRow) contentStyle.alignItems = vVal;
6517
+ else contentStyle.justifyContent = vVal;
6518
+ }
6519
+ }
6440
6520
  const content = h(
6441
6521
  "div",
6442
6522
  { className: `${CLS}-collapsible-panel__content`, style: contentStyle },
package/dist/index.js CHANGED
@@ -1683,10 +1683,32 @@ var BASE_STYLES_CSS = `/**
1683
1683
  }
1684
1684
  .universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
1685
1685
  .universal-card-collapsible-panel__header::marker { content: ''; }
1686
+ /* header.vertical_align\uFF1Acenter\uFF08\u9ED8\u8BA4\uFF09/ top / bottom */
1687
+ .universal-card-collapsible-panel__header--valign-top { align-items: flex-start; }
1688
+ .universal-card-collapsible-panel__header--valign-bottom { align-items: flex-end; }
1689
+ /* header.width\uFF1Afill\uFF08\u9ED8\u8BA4\uFF0C\u6491\u6EE1\u6574\u884C\uFF09/ auto\uFF08\u81EA\u9002\u5E94\u5185\u5BB9\u5BBD\uFF09/
1690
+ auto_when_fold\uFF08\u6298\u53E0\u81EA\u9002\u5E94\u3001\u5C55\u5F00\u6491\u6EE1\u2014\u2014\u771F\u673A\u8BED\u4E49\uFF09 */
1691
+ .universal-card-collapsible-panel__header--w-auto,
1692
+ .universal-card-collapsible-panel__header--w-auto-when-fold {
1693
+ width: max-content; max-width: 100%;
1694
+ }
1695
+ .universal-card-collapsible-panel[open] > .universal-card-collapsible-panel__header--w-auto-when-fold {
1696
+ width: 100%;
1697
+ }
1686
1698
  .universal-card-collapsible-panel__title {
1687
1699
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
1688
1700
  word-break: break-word;
1689
1701
  }
1702
+ /* title \u2194 expanded_title\uFF1A\u6709 expanded_title \u65F6\uFF0C\u6298\u53E0\u5C55\u793A title\u3001\u5C55\u5F00\u5C55\u793A expanded_title
1703
+ \uFF08\u771F\u673A\u8BED\u4E49\uFF09\u3002\u7EAF CSS \u7531 details[open] \u9A71\u52A8\uFF0C\u4E0D\u5B66\u8981 JS \u540C\u6B65\u3002 */
1704
+ .universal-card-collapsible-panel__title-text { display: block; }
1705
+ .universal-card-collapsible-panel__title--has-expanded
1706
+ .universal-card-collapsible-panel__title-text--expanded { display: none; }
1707
+ .universal-card-collapsible-panel[open]
1708
+ .universal-card-collapsible-panel__title--has-expanded .universal-card-collapsible-panel__title-text { display: none; }
1709
+ .universal-card-collapsible-panel[open]
1710
+ .universal-card-collapsible-panel__title--has-expanded
1711
+ .universal-card-collapsible-panel__title-text--expanded { display: block; }
1690
1712
  /* follow_text\uFF1A\u7BAD\u5934\u7D27\u8D34\u6587\u5B57\uFF08\u771F\u673A __header-content--icon-follow\uFF09\uFF0C\u4E0D\u62C9\u6EE1\u6574\u884C */
1691
1713
  .universal-card-collapsible-panel__header--icon-follow {
1692
1714
  gap: 4px;
@@ -5943,6 +5965,8 @@ function registerBuiltins() {
5943
5965
  }
5944
5966
  return h("div", { className: `${CLS}-div` }, content);
5945
5967
  });
5968
+ const H_ALIGN_MAP = { left: "flex-start", center: "center", right: "flex-end" };
5969
+ const V_ALIGN_MAP = { top: "flex-start", center: "center", bottom: "flex-end" };
5946
5970
  registerComponent("column_set", (el, ctx) => {
5947
5971
  const p = el.property || {};
5948
5972
  const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
@@ -5957,7 +5981,7 @@ function registerBuiltins() {
5957
5981
  columnGap: gap + "px"
5958
5982
  };
5959
5983
  if (hAlign) {
5960
- style.justifyContent = { left: "flex-start", center: "center", right: "flex-end" }[hAlign] || "flex-start";
5984
+ style.justifyContent = H_ALIGN_MAP[hAlign] || "flex-start";
5961
5985
  }
5962
5986
  applyOuterMargin(style, p.margin);
5963
5987
  const bg = propertyBackgroundColor(p);
@@ -5992,6 +6016,7 @@ function registerBuiltins() {
5992
6016
  flexShrink: 1,
5993
6017
  overflow: "hidden",
5994
6018
  display: "flex",
6019
+ // 缺省纵向排列;direction=horizontal 时改行,两个 align 落到哪个 flex 属性也随之交换
5995
6020
  flexDirection: "column"
5996
6021
  };
5997
6022
  const wv = p.widthValue;
@@ -6027,10 +6052,16 @@ function registerBuiltins() {
6027
6052
  style.flexBasis = "auto";
6028
6053
  }
6029
6054
  }
6030
- const vAlign = p.verticalAlign || p.vertical_align;
6031
- if (vAlign) {
6032
- style.justifyContent = { top: "flex-start", center: "center", bottom: "flex-end" }[vAlign] || "flex-start";
6033
- }
6055
+ const horizontalDir = p.direction === "horizontal";
6056
+ if (horizontalDir) style.flexDirection = "row";
6057
+ const hAlign = p.horizontal_align ?? p.horizontalAlign;
6058
+ const vAlign = p.vertical_align ?? p.verticalAlign;
6059
+ const justify = horizontalDir ? H_ALIGN_MAP[hAlign] : V_ALIGN_MAP[vAlign];
6060
+ const align = horizontalDir ? V_ALIGN_MAP[vAlign] : H_ALIGN_MAP[hAlign];
6061
+ if (justify) style.justifyContent = justify;
6062
+ if (align) style.alignItems = align;
6063
+ const hGap = p.horizontalSpacingValue?.value ?? p.horizontal_spacing ?? p.horizontalSpacing;
6064
+ if (hGap != null) style.columnGap = horizontalSpacingToPx(hGap) + "px";
6034
6065
  const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
6035
6066
  style.rowGap = (vGap != null ? verticalSpacingToPx(vGap) : 8) + "px";
6036
6067
  const bg = propertyBackgroundColor(p);
@@ -6361,10 +6392,8 @@ function registerBuiltins() {
6361
6392
  if (horizontalDir) style.flexDirection = "row";
6362
6393
  const hAlign = p.horizontal_align ?? p.horizontalAlign;
6363
6394
  const vAlign = p.vertical_align ?? p.verticalAlign;
6364
- const H_ALIGN = { left: "flex-start", center: "center", right: "flex-end" };
6365
- const V_ALIGN = { top: "flex-start", center: "center", bottom: "flex-end" };
6366
- const justify = horizontalDir ? H_ALIGN[hAlign] : V_ALIGN[vAlign];
6367
- const align = horizontalDir ? V_ALIGN[vAlign] : H_ALIGN[hAlign];
6395
+ const justify = horizontalDir ? H_ALIGN_MAP[hAlign] : V_ALIGN_MAP[vAlign];
6396
+ const align = horizontalDir ? V_ALIGN_MAP[vAlign] : H_ALIGN_MAP[hAlign];
6368
6397
  if (justify) style.justifyContent = justify;
6369
6398
  if (align) style.alignItems = align;
6370
6399
  const hGap = p.horizontalSpacingValue?.value ?? p.horizontal_spacing ?? p.horizontalSpacing;
@@ -6391,7 +6420,7 @@ function registerBuiltins() {
6391
6420
  registerComponent("collapsible_panel", (el, ctx) => {
6392
6421
  const p = el.property || {};
6393
6422
  const hdr = p.header && typeof p.header === "object" ? p.header : {};
6394
- const expanded = p.expanded !== false;
6423
+ const expanded = p.expanded === true;
6395
6424
  const style = { width: "100%" };
6396
6425
  const bgRaw = p.background_color ?? p.backgroundColor;
6397
6426
  const bgCustom = propertyBackgroundColor(p);
@@ -6410,12 +6439,36 @@ function registerBuiltins() {
6410
6439
  } else {
6411
6440
  style.borderRadius = "8px";
6412
6441
  }
6442
+ const headerBottom = String(hdr.position ?? "top") === "bottom";
6443
+ if (headerBottom) {
6444
+ style.display = "flex";
6445
+ style.flexDirection = "column";
6446
+ }
6413
6447
  applyOuterMargin(style, p.margin);
6414
6448
  const headerStyle = {};
6415
6449
  const hpad = typeof hdr.padding === "string" ? hdr.padding : boxToStyle(hdr.padding);
6416
6450
  if (hpad) headerStyle.padding = hpad;
6417
- const title = extractPlainText(hdr.title) || extractText(hdr.title);
6418
- const titleVNode = h("div", { className: `${CLS}-collapsible-panel__title` }, title);
6451
+ const hbg = resolveColor(hdr.background_color ?? hdr.backgroundColor);
6452
+ if (hbg) headerStyle.background = hbg;
6453
+ if (headerBottom) headerStyle.order = "2";
6454
+ const titleLeaf = (raw, className) => {
6455
+ if (!raw || typeof raw !== "object") return null;
6456
+ const t = raw;
6457
+ if (typeof t.tag !== "string") return null;
6458
+ const property = t.property && typeof t.property === "object" ? t.property : t;
6459
+ const node = renderHeaderTextLeaf({ tag: t.tag, property }, ctx, className);
6460
+ if (node) return node;
6461
+ const text = extractPlainText(raw) || extractText(raw);
6462
+ return text ? h("div", { className }, text) : null;
6463
+ };
6464
+ const primaryTitle = titleLeaf(hdr.title, `${CLS}-collapsible-panel__title-text`);
6465
+ const expandedTitleRaw = hdr.expanded_title ?? hdr.expandedTitle;
6466
+ const expandedTitle = titleLeaf(
6467
+ expandedTitleRaw,
6468
+ `${CLS}-collapsible-panel__title-text ${CLS}-collapsible-panel__title-text--expanded`
6469
+ );
6470
+ const titleCls = `${CLS}-collapsible-panel__title` + (expandedTitle ? ` ${CLS}-collapsible-panel__title--has-expanded` : "");
6471
+ const titleVNode = h("div", { className: titleCls }, [primaryTitle, expandedTitle].filter(Boolean));
6419
6472
  let iconVNode = null;
6420
6473
  const iconRaw = hdr.icon;
6421
6474
  const openAngle = Number(hdr.icon_expanded_angle ?? hdr.iconExpandedAngle ?? -180);
@@ -6431,11 +6484,38 @@ function registerBuiltins() {
6431
6484
  const iconFollow = iconPos === "follow_text" || iconPos === "followText";
6432
6485
  const headerChildren = iconLeft ? [iconVNode, titleVNode] : [titleVNode, iconVNode];
6433
6486
  const summaryStyle = { ...headerStyle, "--panel-icon-angle": `${openAngle}deg` };
6434
- const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "");
6487
+ const headerWidth = String(hdr.width ?? hdr.headerWidth ?? "fill");
6488
+ const headerVAlign = String(hdr.vertical_align ?? hdr.verticalAlign ?? "center");
6489
+ const headerCls = `${CLS}-collapsible-panel__header` + (iconFollow ? ` ${CLS}-collapsible-panel__header--icon-follow` : "") + (headerWidth === "auto" ? ` ${CLS}-collapsible-panel__header--w-auto` : "") + (headerWidth === "auto_when_fold" ? ` ${CLS}-collapsible-panel__header--w-auto-when-fold` : "") + (headerVAlign === "top" ? ` ${CLS}-collapsible-panel__header--valign-top` : "") + (headerVAlign === "bottom" ? ` ${CLS}-collapsible-panel__header--valign-bottom` : "");
6435
6490
  const summary = h("summary", { className: headerCls, style: summaryStyle }, headerChildren);
6436
6491
  const contentStyle = {};
6437
6492
  const cpad = typeof p.padding === "string" ? p.padding : boxToStyle(p.padding);
6438
6493
  if (cpad) contentStyle.padding = cpad;
6494
+ if (headerBottom) contentStyle.order = "1";
6495
+ const direction = String(p.direction ?? "vertical");
6496
+ const isRow = direction === "horizontal";
6497
+ const hSpacing = p.horizontal_spacing ?? p.horizontalSpacing;
6498
+ const vSpacing = p.vertical_spacing ?? p.verticalSpacing;
6499
+ const hAlign = p.horizontal_align ?? p.horizontalAlign;
6500
+ const vAlign = p.vertical_align ?? p.verticalAlign;
6501
+ if (isRow || hSpacing != null || vSpacing != null || hAlign != null || vAlign != null) {
6502
+ contentStyle.display = "flex";
6503
+ contentStyle.flexDirection = isRow ? "row" : "column";
6504
+ const gapX = hSpacing != null ? horizontalSpacingToPx(hSpacing) : isRow ? 8 : 0;
6505
+ const gapY = vSpacing != null ? verticalSpacingToPx(vSpacing) : 0;
6506
+ if (gapX) contentStyle.columnGap = gapX + "px";
6507
+ if (gapY) contentStyle.rowGap = gapY + "px";
6508
+ const hVal = hAlign ? H_ALIGN_MAP[hAlign] : void 0;
6509
+ const vVal = vAlign ? V_ALIGN_MAP[vAlign] : void 0;
6510
+ if (hVal) {
6511
+ if (isRow) contentStyle.justifyContent = hVal;
6512
+ else contentStyle.alignItems = hVal;
6513
+ }
6514
+ if (vVal) {
6515
+ if (isRow) contentStyle.alignItems = vVal;
6516
+ else contentStyle.justifyContent = vVal;
6517
+ }
6518
+ }
6439
6519
  const content = h(
6440
6520
  "div",
6441
6521
  { className: `${CLS}-collapsible-panel__content`, style: contentStyle },
package/dist/styles.css CHANGED
@@ -707,10 +707,32 @@
707
707
  }
708
708
  .universal-card-collapsible-panel__header::-webkit-details-marker { display: none; }
709
709
  .universal-card-collapsible-panel__header::marker { content: ''; }
710
+ /* header.vertical_align:center(默认)/ top / bottom */
711
+ .universal-card-collapsible-panel__header--valign-top { align-items: flex-start; }
712
+ .universal-card-collapsible-panel__header--valign-bottom { align-items: flex-end; }
713
+ /* header.width:fill(默认,撑满整行)/ auto(自适应内容宽)/
714
+ auto_when_fold(折叠自适应、展开撑满——真机语义) */
715
+ .universal-card-collapsible-panel__header--w-auto,
716
+ .universal-card-collapsible-panel__header--w-auto-when-fold {
717
+ width: max-content; max-width: 100%;
718
+ }
719
+ .universal-card-collapsible-panel[open] > .universal-card-collapsible-panel__header--w-auto-when-fold {
720
+ width: 100%;
721
+ }
710
722
  .universal-card-collapsible-panel__title {
711
723
  flex: 1; min-width: 0; font-size: 14px; line-height: 22px;
712
724
  word-break: break-word;
713
725
  }
726
+ /* title ↔ expanded_title:有 expanded_title 时,折叠展示 title、展开展示 expanded_title
727
+ (真机语义)。纯 CSS 由 details[open] 驱动,不学要 JS 同步。 */
728
+ .universal-card-collapsible-panel__title-text { display: block; }
729
+ .universal-card-collapsible-panel__title--has-expanded
730
+ .universal-card-collapsible-panel__title-text--expanded { display: none; }
731
+ .universal-card-collapsible-panel[open]
732
+ .universal-card-collapsible-panel__title--has-expanded .universal-card-collapsible-panel__title-text { display: none; }
733
+ .universal-card-collapsible-panel[open]
734
+ .universal-card-collapsible-panel__title--has-expanded
735
+ .universal-card-collapsible-panel__title-text--expanded { display: block; }
714
736
  /* follow_text:箭头紧贴文字(真机 __header-content--icon-follow),不拉满整行 */
715
737
  .universal-card-collapsible-panel__header--icon-follow {
716
738
  gap: 4px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "零框架依赖的飞书卡片渲染引擎:Web / DOM / SSR,输入 RAW 或公开 DSL,输出真实 DOM 或 HTML 字符串",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,7 +28,7 @@
28
28
  "./dist/*.cjs"
29
29
  ],
30
30
  "dependencies": {
31
- "@open-feishu-card/adapter": "0.4.0"
31
+ "@open-feishu-card/adapter": "0.4.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "tsup": "^8.3.5",