@open-feishu-card/renderer 0.2.32 → 0.3.1

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
@@ -443,6 +443,32 @@
443
443
  /* 非跳转按钮:保留外观,但不可点(默认光标、无 hover、不响应点击) */
444
444
  .universal-card-button--static { cursor: default; pointer-events: none; }
445
445
 
446
+ /* ---- overflow 折叠按钮组 ---- */
447
+ /* 真机是一个「…」图标按钮,点击弹出选项菜单。静态预览只画触发器(--static 不可点)。 */
448
+ .universal-card-overflow { display: inline-flex; position: relative; vertical-align: middle; }
449
+ .universal-card-overflow--static { display: inline-flex; }
450
+ .universal-card-overflow__summary {
451
+ list-style: none; cursor: pointer; display: inline-flex;
452
+ }
453
+ /* 去掉 <summary> 的浏览器默认三角标记 */
454
+ .universal-card-overflow__summary::-webkit-details-marker { display: none; }
455
+ .universal-card-overflow__summary::marker { content: ''; }
456
+ /* 菜单:默认隐藏,<details open> 时浮在触发器下方 */
457
+ .universal-card-overflow__menu {
458
+ display: none;
459
+ position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
460
+ min-width: 120px; padding: 4px 0;
461
+ background: #fff; border-radius: 6px;
462
+ box-shadow: 0 4px 12px rgba(31, 35, 41, 0.15), 0 0 1px rgba(31, 35, 41, 0.2);
463
+ }
464
+ .universal-card-overflow[open] > .universal-card-overflow__menu { display: block; }
465
+ .universal-card-overflow__item {
466
+ display: block; padding: 6px 12px;
467
+ font-size: 14px; line-height: 22px; color: #1f2329;
468
+ text-decoration: none; white-space: nowrap; cursor: pointer;
469
+ }
470
+ .universal-card-overflow__item:hover { background: #f5f6f7; }
471
+
446
472
  /* ---- 表单(预览:仅透传,无表单语义) ---- */
447
473
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
448
474
  .universal-card-form > * { width: 100%; }
package/dist/index.cjs CHANGED
@@ -1413,6 +1413,32 @@ var BASE_STYLES_CSS = `/**
1413
1413
  /* \u975E\u8DF3\u8F6C\u6309\u94AE\uFF1A\u4FDD\u7559\u5916\u89C2\uFF0C\u4F46\u4E0D\u53EF\u70B9\uFF08\u9ED8\u8BA4\u5149\u6807\u3001\u65E0 hover\u3001\u4E0D\u54CD\u5E94\u70B9\u51FB\uFF09 */
1414
1414
  .universal-card-button--static { cursor: default; pointer-events: none; }
1415
1415
 
1416
+ /* ---- overflow \u6298\u53E0\u6309\u94AE\u7EC4 ---- */
1417
+ /* \u771F\u673A\u662F\u4E00\u4E2A\u300C\u2026\u300D\u56FE\u6807\u6309\u94AE\uFF0C\u70B9\u51FB\u5F39\u51FA\u9009\u9879\u83DC\u5355\u3002\u9759\u6001\u9884\u89C8\u53EA\u753B\u89E6\u53D1\u5668\uFF08--static \u4E0D\u53EF\u70B9\uFF09\u3002 */
1418
+ .universal-card-overflow { display: inline-flex; position: relative; vertical-align: middle; }
1419
+ .universal-card-overflow--static { display: inline-flex; }
1420
+ .universal-card-overflow__summary {
1421
+ list-style: none; cursor: pointer; display: inline-flex;
1422
+ }
1423
+ /* \u53BB\u6389 <summary> \u7684\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0 */
1424
+ .universal-card-overflow__summary::-webkit-details-marker { display: none; }
1425
+ .universal-card-overflow__summary::marker { content: ''; }
1426
+ /* \u83DC\u5355\uFF1A\u9ED8\u8BA4\u9690\u85CF\uFF0C<details open> \u65F6\u6D6E\u5728\u89E6\u53D1\u5668\u4E0B\u65B9 */
1427
+ .universal-card-overflow__menu {
1428
+ display: none;
1429
+ position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
1430
+ min-width: 120px; padding: 4px 0;
1431
+ background: #fff; border-radius: 6px;
1432
+ box-shadow: 0 4px 12px rgba(31, 35, 41, 0.15), 0 0 1px rgba(31, 35, 41, 0.2);
1433
+ }
1434
+ .universal-card-overflow[open] > .universal-card-overflow__menu { display: block; }
1435
+ .universal-card-overflow__item {
1436
+ display: block; padding: 6px 12px;
1437
+ font-size: 14px; line-height: 22px; color: #1f2329;
1438
+ text-decoration: none; white-space: nowrap; cursor: pointer;
1439
+ }
1440
+ .universal-card-overflow__item:hover { background: #f5f6f7; }
1441
+
1416
1442
  /* ---- \u8868\u5355\uFF08\u9884\u89C8\uFF1A\u4EC5\u900F\u4F20\uFF0C\u65E0\u8868\u5355\u8BED\u4E49\uFF09 ---- */
1417
1443
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
1418
1444
  .universal-card-form > * { width: 100%; }
@@ -6035,8 +6061,9 @@ function registerBuiltins() {
6035
6061
  const tip = extractText(p.hoverTips);
6036
6062
  const borderNamed = p.border_color ?? p.borderColor;
6037
6063
  const borderCustom = adapter.resolveCustomBorderLight(p);
6038
- const borderColor = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
6039
- const hasBorder = borderColor != null || p.hasBorder === true || p.has_border === true;
6064
+ const borderColorResolved = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
6065
+ const hasBorder = p.hasBorder === true || p.has_border === true;
6066
+ const borderColor = hasBorder ? borderColorResolved : void 0;
6040
6067
  const cls = `${CLS}-interactive-container` + (clickable ? ` ${CLS}-interactive-container--action` : "") + (hasBorder && !borderColor ? ` ${CLS}-interactive-container--bordered` : "") + (disabled ? ` ${CLS}-interactive-container--disabled` : "");
6041
6068
  const style = {};
6042
6069
  const bg = propertyBackgroundColor(p);
@@ -6046,6 +6073,18 @@ function registerBuiltins() {
6046
6073
  if (borderColor) style.border = `1px solid ${borderColor}`;
6047
6074
  const radius = resolveCornerRadius(p);
6048
6075
  if (radius != null) style.borderRadius = radius;
6076
+ const horizontalDir = p.direction === "horizontal";
6077
+ if (horizontalDir) style.flexDirection = "row";
6078
+ const hAlign = p.horizontal_align ?? p.horizontalAlign;
6079
+ const vAlign = p.vertical_align ?? p.verticalAlign;
6080
+ const H_ALIGN = { left: "flex-start", center: "center", right: "flex-end" };
6081
+ const V_ALIGN = { top: "flex-start", center: "center", bottom: "flex-end" };
6082
+ const justify = horizontalDir ? H_ALIGN[hAlign] : V_ALIGN[vAlign];
6083
+ const align = horizontalDir ? V_ALIGN[vAlign] : H_ALIGN[hAlign];
6084
+ if (justify) style.justifyContent = justify;
6085
+ if (align) style.alignItems = align;
6086
+ const hGap = p.horizontalSpacingValue?.value ?? p.horizontal_spacing ?? p.horizontalSpacing;
6087
+ if (hGap != null) style.columnGap = horizontalSpacingToPx(hGap) + "px";
6049
6088
  const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
6050
6089
  if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
6051
6090
  const icWv = p.widthValue;
@@ -6192,6 +6231,43 @@ function registerBuiltins() {
6192
6231
  };
6193
6232
  return fromList(p.actions) ?? fromList(p.behaviors) ?? (isRecord(p.value) ? p.value : null);
6194
6233
  }
6234
+ registerComponent("overflow", (el, ctx) => {
6235
+ const p = el.property || {};
6236
+ const disabled = p.disabled === true;
6237
+ const options = Array.isArray(p.options) ? p.options : [];
6238
+ const widthTok = p.width ?? p.widthValue?.value;
6239
+ const block = widthTok === "fill";
6240
+ const triggerIcon = renderIcon(
6241
+ { property: { token: "more_outlined", size: "14px" } },
6242
+ ctx
6243
+ );
6244
+ const triggerCls = `${CLS}-button ${CLS}-button--default ${CLS}-button--static ${CLS}-overflow__trigger` + (disabled ? ` ${CLS}-button--disabled` : "") + (block ? ` ${CLS}-button--block` : "");
6245
+ const trigger = triggerIcon ? h("span", { className: triggerCls }, h("span", { className: `${CLS}-button__icon` }, triggerIcon)) : h("span", { className: triggerCls }, "\xB7\xB7\xB7");
6246
+ const items = options.map((raw) => {
6247
+ const opt = isRecord(raw) ? raw : {};
6248
+ const textObj = opt.text;
6249
+ const label = (isRecord(textObj) && typeof textObj.content === "string" ? textObj.content : extractPlainText(textObj)) || extractPlainText(opt);
6250
+ const multi = isRecord(opt.multi_url) ? opt.multi_url : void 0;
6251
+ const href = resolveURL(multi?.url ?? opt.url);
6252
+ const itemCls = `${CLS}-overflow__item`;
6253
+ if (href && !disabled) {
6254
+ return h("a", { className: itemCls, href, target: "_blank", rel: "noopener" }, label);
6255
+ }
6256
+ return h("span", { className: itemCls }, label);
6257
+ });
6258
+ const style = {};
6259
+ if (p.margin) applyOuterMargin(style, p.margin);
6260
+ const styleProp = Object.keys(style).length ? { style } : {};
6261
+ if (!ctx.options.interactive) {
6262
+ return h("span", { className: `${CLS}-overflow ${CLS}-overflow--static`, ...styleProp }, trigger);
6263
+ }
6264
+ return h(
6265
+ "details",
6266
+ { className: `${CLS}-overflow`, ...styleProp },
6267
+ h("summary", { className: `${CLS}-overflow__summary` }, trigger),
6268
+ h("div", { className: `${CLS}-overflow__menu` }, items)
6269
+ );
6270
+ });
6195
6271
  registerComponent("form", (el, ctx) => {
6196
6272
  const children = renderElements(el.property?.elements, ctx);
6197
6273
  const className = `${CLS}-form`;
package/dist/index.js CHANGED
@@ -1412,6 +1412,32 @@ var BASE_STYLES_CSS = `/**
1412
1412
  /* \u975E\u8DF3\u8F6C\u6309\u94AE\uFF1A\u4FDD\u7559\u5916\u89C2\uFF0C\u4F46\u4E0D\u53EF\u70B9\uFF08\u9ED8\u8BA4\u5149\u6807\u3001\u65E0 hover\u3001\u4E0D\u54CD\u5E94\u70B9\u51FB\uFF09 */
1413
1413
  .universal-card-button--static { cursor: default; pointer-events: none; }
1414
1414
 
1415
+ /* ---- overflow \u6298\u53E0\u6309\u94AE\u7EC4 ---- */
1416
+ /* \u771F\u673A\u662F\u4E00\u4E2A\u300C\u2026\u300D\u56FE\u6807\u6309\u94AE\uFF0C\u70B9\u51FB\u5F39\u51FA\u9009\u9879\u83DC\u5355\u3002\u9759\u6001\u9884\u89C8\u53EA\u753B\u89E6\u53D1\u5668\uFF08--static \u4E0D\u53EF\u70B9\uFF09\u3002 */
1417
+ .universal-card-overflow { display: inline-flex; position: relative; vertical-align: middle; }
1418
+ .universal-card-overflow--static { display: inline-flex; }
1419
+ .universal-card-overflow__summary {
1420
+ list-style: none; cursor: pointer; display: inline-flex;
1421
+ }
1422
+ /* \u53BB\u6389 <summary> \u7684\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0 */
1423
+ .universal-card-overflow__summary::-webkit-details-marker { display: none; }
1424
+ .universal-card-overflow__summary::marker { content: ''; }
1425
+ /* \u83DC\u5355\uFF1A\u9ED8\u8BA4\u9690\u85CF\uFF0C<details open> \u65F6\u6D6E\u5728\u89E6\u53D1\u5668\u4E0B\u65B9 */
1426
+ .universal-card-overflow__menu {
1427
+ display: none;
1428
+ position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
1429
+ min-width: 120px; padding: 4px 0;
1430
+ background: #fff; border-radius: 6px;
1431
+ box-shadow: 0 4px 12px rgba(31, 35, 41, 0.15), 0 0 1px rgba(31, 35, 41, 0.2);
1432
+ }
1433
+ .universal-card-overflow[open] > .universal-card-overflow__menu { display: block; }
1434
+ .universal-card-overflow__item {
1435
+ display: block; padding: 6px 12px;
1436
+ font-size: 14px; line-height: 22px; color: #1f2329;
1437
+ text-decoration: none; white-space: nowrap; cursor: pointer;
1438
+ }
1439
+ .universal-card-overflow__item:hover { background: #f5f6f7; }
1440
+
1415
1441
  /* ---- \u8868\u5355\uFF08\u9884\u89C8\uFF1A\u4EC5\u900F\u4F20\uFF0C\u65E0\u8868\u5355\u8BED\u4E49\uFF09 ---- */
1416
1442
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
1417
1443
  .universal-card-form > * { width: 100%; }
@@ -6034,8 +6060,9 @@ function registerBuiltins() {
6034
6060
  const tip = extractText(p.hoverTips);
6035
6061
  const borderNamed = p.border_color ?? p.borderColor;
6036
6062
  const borderCustom = resolveCustomBorderLight(p);
6037
- const borderColor = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
6038
- const hasBorder = borderColor != null || p.hasBorder === true || p.has_border === true;
6063
+ const borderColorResolved = (borderCustom ? resolveColor(borderCustom) : void 0) ?? resolveColor(borderNamed);
6064
+ const hasBorder = p.hasBorder === true || p.has_border === true;
6065
+ const borderColor = hasBorder ? borderColorResolved : void 0;
6039
6066
  const cls = `${CLS}-interactive-container` + (clickable ? ` ${CLS}-interactive-container--action` : "") + (hasBorder && !borderColor ? ` ${CLS}-interactive-container--bordered` : "") + (disabled ? ` ${CLS}-interactive-container--disabled` : "");
6040
6067
  const style = {};
6041
6068
  const bg = propertyBackgroundColor(p);
@@ -6045,6 +6072,18 @@ function registerBuiltins() {
6045
6072
  if (borderColor) style.border = `1px solid ${borderColor}`;
6046
6073
  const radius = resolveCornerRadius(p);
6047
6074
  if (radius != null) style.borderRadius = radius;
6075
+ const horizontalDir = p.direction === "horizontal";
6076
+ if (horizontalDir) style.flexDirection = "row";
6077
+ const hAlign = p.horizontal_align ?? p.horizontalAlign;
6078
+ const vAlign = p.vertical_align ?? p.verticalAlign;
6079
+ const H_ALIGN = { left: "flex-start", center: "center", right: "flex-end" };
6080
+ const V_ALIGN = { top: "flex-start", center: "center", bottom: "flex-end" };
6081
+ const justify = horizontalDir ? H_ALIGN[hAlign] : V_ALIGN[vAlign];
6082
+ const align = horizontalDir ? V_ALIGN[vAlign] : H_ALIGN[hAlign];
6083
+ if (justify) style.justifyContent = justify;
6084
+ if (align) style.alignItems = align;
6085
+ const hGap = p.horizontalSpacingValue?.value ?? p.horizontal_spacing ?? p.horizontalSpacing;
6086
+ if (hGap != null) style.columnGap = horizontalSpacingToPx(hGap) + "px";
6048
6087
  const vGap = p.verticalSpacingValue?.value ?? p.vertical_spacing;
6049
6088
  if (vGap != null) style.rowGap = verticalSpacingToPx(vGap) + "px";
6050
6089
  const icWv = p.widthValue;
@@ -6191,6 +6230,43 @@ function registerBuiltins() {
6191
6230
  };
6192
6231
  return fromList(p.actions) ?? fromList(p.behaviors) ?? (isRecord(p.value) ? p.value : null);
6193
6232
  }
6233
+ registerComponent("overflow", (el, ctx) => {
6234
+ const p = el.property || {};
6235
+ const disabled = p.disabled === true;
6236
+ const options = Array.isArray(p.options) ? p.options : [];
6237
+ const widthTok = p.width ?? p.widthValue?.value;
6238
+ const block = widthTok === "fill";
6239
+ const triggerIcon = renderIcon(
6240
+ { property: { token: "more_outlined", size: "14px" } },
6241
+ ctx
6242
+ );
6243
+ const triggerCls = `${CLS}-button ${CLS}-button--default ${CLS}-button--static ${CLS}-overflow__trigger` + (disabled ? ` ${CLS}-button--disabled` : "") + (block ? ` ${CLS}-button--block` : "");
6244
+ const trigger = triggerIcon ? h("span", { className: triggerCls }, h("span", { className: `${CLS}-button__icon` }, triggerIcon)) : h("span", { className: triggerCls }, "\xB7\xB7\xB7");
6245
+ const items = options.map((raw) => {
6246
+ const opt = isRecord(raw) ? raw : {};
6247
+ const textObj = opt.text;
6248
+ const label = (isRecord(textObj) && typeof textObj.content === "string" ? textObj.content : extractPlainText(textObj)) || extractPlainText(opt);
6249
+ const multi = isRecord(opt.multi_url) ? opt.multi_url : void 0;
6250
+ const href = resolveURL(multi?.url ?? opt.url);
6251
+ const itemCls = `${CLS}-overflow__item`;
6252
+ if (href && !disabled) {
6253
+ return h("a", { className: itemCls, href, target: "_blank", rel: "noopener" }, label);
6254
+ }
6255
+ return h("span", { className: itemCls }, label);
6256
+ });
6257
+ const style = {};
6258
+ if (p.margin) applyOuterMargin(style, p.margin);
6259
+ const styleProp = Object.keys(style).length ? { style } : {};
6260
+ if (!ctx.options.interactive) {
6261
+ return h("span", { className: `${CLS}-overflow ${CLS}-overflow--static`, ...styleProp }, trigger);
6262
+ }
6263
+ return h(
6264
+ "details",
6265
+ { className: `${CLS}-overflow`, ...styleProp },
6266
+ h("summary", { className: `${CLS}-overflow__summary` }, trigger),
6267
+ h("div", { className: `${CLS}-overflow__menu` }, items)
6268
+ );
6269
+ });
6194
6270
  registerComponent("form", (el, ctx) => {
6195
6271
  const children = renderElements(el.property?.elements, ctx);
6196
6272
  const className = `${CLS}-form`;
package/dist/styles.css CHANGED
@@ -443,6 +443,32 @@
443
443
  /* 非跳转按钮:保留外观,但不可点(默认光标、无 hover、不响应点击) */
444
444
  .universal-card-button--static { cursor: default; pointer-events: none; }
445
445
 
446
+ /* ---- overflow 折叠按钮组 ---- */
447
+ /* 真机是一个「…」图标按钮,点击弹出选项菜单。静态预览只画触发器(--static 不可点)。 */
448
+ .universal-card-overflow { display: inline-flex; position: relative; vertical-align: middle; }
449
+ .universal-card-overflow--static { display: inline-flex; }
450
+ .universal-card-overflow__summary {
451
+ list-style: none; cursor: pointer; display: inline-flex;
452
+ }
453
+ /* 去掉 <summary> 的浏览器默认三角标记 */
454
+ .universal-card-overflow__summary::-webkit-details-marker { display: none; }
455
+ .universal-card-overflow__summary::marker { content: ''; }
456
+ /* 菜单:默认隐藏,<details open> 时浮在触发器下方 */
457
+ .universal-card-overflow__menu {
458
+ display: none;
459
+ position: absolute; top: calc(100% + 4px); left: 0; z-index: 20;
460
+ min-width: 120px; padding: 4px 0;
461
+ background: #fff; border-radius: 6px;
462
+ box-shadow: 0 4px 12px rgba(31, 35, 41, 0.15), 0 0 1px rgba(31, 35, 41, 0.2);
463
+ }
464
+ .universal-card-overflow[open] > .universal-card-overflow__menu { display: block; }
465
+ .universal-card-overflow__item {
466
+ display: block; padding: 6px 12px;
467
+ font-size: 14px; line-height: 22px; color: #1f2329;
468
+ text-decoration: none; white-space: nowrap; cursor: pointer;
469
+ }
470
+ .universal-card-overflow__item:hover { background: #f5f6f7; }
471
+
446
472
  /* ---- 表单(预览:仅透传,无表单语义) ---- */
447
473
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
448
474
  .universal-card-form > * { width: 100%; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.2.32",
3
+ "version": "0.3.1",
4
4
  "description": "零框架依赖的飞书卡片渲染引擎:Web / DOM / SSR,输入 RAW 或公开 DSL,输出真实 DOM 或 HTML 字符串",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",