@open-feishu-card/renderer 0.2.25 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1127,8 +1127,6 @@ var BASE_STYLES_CSS = `/**
1127
1127
  .universal-card-header {
1128
1128
  width: 100%;
1129
1129
  overflow: hidden;
1130
- border-top-left-radius: 8px;
1131
- border-top-right-radius: 8px;
1132
1130
  }
1133
1131
  .universal-card-header__bg {
1134
1132
  box-sizing: border-box;
@@ -1262,7 +1260,7 @@ var BASE_STYLES_CSS = `/**
1262
1260
  line-height: 22px;
1263
1261
  }
1264
1262
 
1265
- /* ---- div / note ---- */
1263
+ /* ---- div ---- */
1266
1264
  .universal-card-div > * + * { margin-top: 8px; }
1267
1265
  /* div \u5E26\u524D\u7F6E\u56FE\u6807\uFF1Aicon \u5728\u5DE6\u3001\u6587\u672C\u5728\u53F3\u6392\u6210\u4E00\u884C\uFF08\u590D\u523B\u7F51\u9875\u7248 .universal-card-div \u5E03\u5C40\uFF09 */
1268
1266
  .universal-card-div--with-icon {
@@ -1274,10 +1272,6 @@ var BASE_STYLES_CSS = `/**
1274
1272
  display: flex; align-items: center; overflow: hidden;
1275
1273
  }
1276
1274
  .universal-card-div__content { flex: 1; min-width: 0; }
1277
- .universal-card-note {
1278
- font-size: 12px;
1279
- color: #8f959e;
1280
- }
1281
1275
 
1282
1276
  /* ---- \u5E03\u5C40\uFF1Acolumn_set / column ---- */
1283
1277
  .universal-card-column-set { display: flex; }
@@ -1306,6 +1300,7 @@ var BASE_STYLES_CSS = `/**
1306
1300
  display: inline-flex; align-items: center; justify-content: center;
1307
1301
  gap: 4px;
1308
1302
  min-height: 28px; padding: 4px 12px;
1303
+ font-family: inherit;
1309
1304
  font-size: 14px; line-height: 22px; border-radius: 6px;
1310
1305
  border: 1px solid transparent; background: transparent; color: #1f2329;
1311
1306
  cursor: pointer; text-decoration: none; box-sizing: border-box;
@@ -1384,17 +1379,19 @@ var BASE_STYLES_CSS = `/**
1384
1379
  /* ---- \u8868\u5355\uFF08\u9884\u89C8\uFF1A\u4EC5\u900F\u4F20\uFF0C\u65E0\u8868\u5355\u8BED\u4E49\uFF09 ---- */
1385
1380
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
1386
1381
  .universal-card-form > * { width: 100%; }
1387
- /* input / select \u7B49\u8F93\u5165\u63A7\u4EF6\uFF1A\u9884\u89C8\u91CC\u6E32\u67D3\u6210\u53EA\u8BFB\u9759\u6001\u6846\uFF0C\u5C55\u793A placeholder */
1382
+ /* input / select\uFF1A\u9759\u6001\u9884\u89C8\u662F\u53EA\u8BFB\u6846\uFF1Binteractive \u53BB\u6389 --static\uFF0C\u6210\u4E3A\u53EF\u8F93\u5165\u63A7\u4EF6 */
1383
+ .universal-card-input,
1388
1384
  .universal-card-input--static {
1389
1385
  min-height: 32px; padding: 6px 12px; box-sizing: border-box;
1390
- font-size: 14px; line-height: 20px; color: #8f959e;
1391
- background: #f5f6f7; border: 1px solid #dee0e3; border-radius: 6px;
1386
+ font-family: inherit; font-size: 14px; line-height: 20px;
1387
+ background: #fff; border: 1px solid #dee0e3; border-radius: 6px;
1388
+ color: #1f2329; width: 100%;
1389
+ }
1390
+ .universal-card-input--static {
1391
+ color: #8f959e; background: #f5f6f7;
1392
1392
  cursor: default; pointer-events: none;
1393
1393
  }
1394
1394
 
1395
- /* ---- action \u5BB9\u5668 ---- */
1396
- .universal-card-action { display: flex; flex-wrap: wrap; gap: 12px; }
1397
-
1398
1395
  /* ---- checker \u52FE\u9009\u5668\uFF08\u5BF9\u9F50\u7F51\u9875\u7248 universal-card-checker\uFF09 ---- */
1399
1396
  .universal-card-checker {
1400
1397
  display: flex; box-sizing: border-box; overflow: hidden; align-items: flex-start;
@@ -1985,7 +1982,6 @@ var BASE_STYLES_CSS = `/**
1985
1982
  }
1986
1983
  .universal-card-root.theme-dark .universal-card-markdown { color: #d6d6d6; }
1987
1984
  .universal-card-root.theme-dark .universal-card-hr { background-color: #2e2e30; }
1988
- .universal-card-root.theme-dark .universal-card-note { color: #8a8a8a; }
1989
1985
  `;
1990
1986
 
1991
1987
  // src/icon-font.ts
@@ -5612,10 +5608,6 @@ function registerBuiltins() {
5612
5608
  }
5613
5609
  return h("div", { className: `${CLS}-div` }, content);
5614
5610
  });
5615
- registerComponent(
5616
- "note",
5617
- (el, ctx) => h("div", { className: `${CLS}-note` }, renderElements(el.property?.elements, ctx))
5618
- );
5619
5611
  registerComponent("column_set", (el, ctx) => {
5620
5612
  const p = el.property || {};
5621
5613
  const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
@@ -5638,8 +5630,26 @@ function registerBuiltins() {
5638
5630
  const pad = boxToStyle(p.padding);
5639
5631
  if (pad) style.padding = pad;
5640
5632
  const cls = `${CLS}-column-set` + (bg ? ` ${CLS}-column-set--bg` : "");
5633
+ if (isStateHost(el)) {
5634
+ style.position = "absolute";
5635
+ style.width = "0";
5636
+ style.height = "0";
5637
+ style.overflow = "hidden";
5638
+ style.margin = "0";
5639
+ style.padding = "0";
5640
+ }
5641
5641
  return h("div", { className: cls, style }, renderElements(p.columns, ctx));
5642
5642
  });
5643
+ function isStateHost(el) {
5644
+ if (el.id === "card_state") return true;
5645
+ const actions = el.property?.actions;
5646
+ if (!Array.isArray(actions)) return false;
5647
+ return actions.some((item) => {
5648
+ if (!item || typeof item !== "object") return false;
5649
+ const action = item.action;
5650
+ return typeof action?.url === "string" && action.url.includes("msgcard/unsupported_action#s1.");
5651
+ });
5652
+ }
5643
5653
  registerComponent("column", (el, ctx) => {
5644
5654
  const p = el.property || {};
5645
5655
  const style = {
@@ -5989,29 +5999,105 @@ function registerBuiltins() {
5989
5999
  applyOuterMargin(style, p.margin);
5990
6000
  }
5991
6001
  const styleProp = Object.keys(style).length ? { style } : {};
6002
+ const extra = p;
6003
+ const callback = readCallbackValue(extra);
6004
+ const formAction = String(extra.form_action_type ?? extra.formActionType ?? "");
6005
+ if (ctx.options.interactive && callback && !p.disabled && !url) {
6006
+ return h("button", {
6007
+ type: formAction === "reset" ? "reset" : formAction === "submit" ? "submit" : "button",
6008
+ className: base,
6009
+ "data-feishu-action": JSON.stringify(callback),
6010
+ "data-feishu-tag": "button",
6011
+ ...styleProp
6012
+ }, children);
6013
+ }
5992
6014
  if (url && !p.disabled) {
5993
6015
  return h("a", { className: base, href: url, target: "_blank", rel: "noopener", ...styleProp }, children);
5994
6016
  }
5995
6017
  const cls = `${base} ${CLS}-button--static` + (p.disabled ? ` ${CLS}-button--disabled` : "");
5996
6018
  return h("span", { className: cls, ...styleProp }, children);
5997
6019
  });
5998
- registerComponent(
5999
- "form",
6000
- (el, ctx) => h("div", { className: `${CLS}-form` }, renderElements(el.property?.elements, ctx))
6001
- );
6020
+ function isRecord(value) {
6021
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6022
+ }
6023
+ function readCallbackValue(p) {
6024
+ const fromList = (list) => {
6025
+ if (!Array.isArray(list)) return null;
6026
+ for (const item of list) {
6027
+ if (!isRecord(item) || item.type !== "callback") continue;
6028
+ const inner = isRecord(item.action) ? item.action : item;
6029
+ if (isRecord(inner.value)) return inner.value;
6030
+ }
6031
+ return null;
6032
+ };
6033
+ return fromList(p.actions) ?? fromList(p.behaviors) ?? (isRecord(p.value) ? p.value : null);
6034
+ }
6035
+ registerComponent("form", (el, ctx) => {
6036
+ const children = renderElements(el.property?.elements, ctx);
6037
+ const className = `${CLS}-form`;
6038
+ if (!ctx.options.interactive) return h("div", { className }, children);
6039
+ const name = typeof el.property?.name === "string" ? el.property.name : "";
6040
+ return h("form", { className, "data-feishu-form": name }, children);
6041
+ });
6002
6042
  function renderStaticField(el) {
6003
6043
  const p = el.property || {};
6004
6044
  const ph = extractPlainText(p.placeholder) || "";
6005
6045
  return h("div", { className: `${CLS}-input ${CLS}-input--static` }, ph);
6006
6046
  }
6007
- registerComponent("input", (el) => renderStaticField(el));
6008
- registerComponent("multi_select_static", (el) => renderStaticField(el));
6009
- registerComponent("select_static", (el) => renderStaticField(el));
6010
- registerComponent("date_picker", (el) => renderStaticField(el));
6011
- registerComponent("picker_time", (el) => renderStaticField(el));
6012
- registerComponent("picker_datetime", (el) => renderStaticField(el));
6013
- registerComponent("select_person", (el) => renderStaticField(el));
6014
- registerComponent("multi_select_person", (el) => renderStaticField(el));
6047
+ function fieldText(value) {
6048
+ if (typeof value === "string") return value;
6049
+ return extractPlainText(value);
6050
+ }
6051
+ function renderInteractiveField(el) {
6052
+ const p = el.property || {};
6053
+ const ph = extractPlainText(p.placeholder) || "";
6054
+ const name = typeof p.name === "string" ? p.name : void 0;
6055
+ const nameProp = name ? { name } : {};
6056
+ const callback = readCallbackValue(p);
6057
+ const actionProp = callback ? { "data-feishu-action": JSON.stringify(callback), "data-feishu-tag": el.tag } : {};
6058
+ if (el.tag === "select_static" || el.tag === "multi_select_static") {
6059
+ const initial = p.initial_option ?? p.initialOption;
6060
+ const options = Array.isArray(p.options) ? p.options : [];
6061
+ return h("select", {
6062
+ className: `${CLS}-input`,
6063
+ ...nameProp,
6064
+ ...el.tag === "multi_select_static" ? { multiple: true } : {},
6065
+ ...actionProp
6066
+ }, [
6067
+ h("option", { value: "" }, ph),
6068
+ ...options.map((option) => {
6069
+ const item = isRecord(option) ? option : {};
6070
+ const value = typeof item.value === "string" ? item.value : fieldText(item.text ?? item);
6071
+ const label = fieldText(item.text ?? item.label) || value;
6072
+ return h("option", {
6073
+ value,
6074
+ ...value !== "" && value === initial ? { selected: true } : {}
6075
+ }, label);
6076
+ })
6077
+ ]);
6078
+ }
6079
+ const def = p.default_value ?? p.defaultValue;
6080
+ const inputType = el.tag === "date_picker" ? "date" : el.tag === "picker_time" ? "time" : "text";
6081
+ return h("input", {
6082
+ className: `${CLS}-input`,
6083
+ type: inputType,
6084
+ placeholder: ph,
6085
+ value: typeof def === "string" ? def : "",
6086
+ ...nameProp,
6087
+ ...actionProp
6088
+ });
6089
+ }
6090
+ function renderField(el, ctx) {
6091
+ return ctx.options.interactive ? renderInteractiveField(el) : renderStaticField(el);
6092
+ }
6093
+ registerComponent("input", (el, ctx) => renderField(el, ctx));
6094
+ registerComponent("multi_select_static", (el, ctx) => renderField(el, ctx));
6095
+ registerComponent("select_static", (el, ctx) => renderField(el, ctx));
6096
+ registerComponent("date_picker", (el, ctx) => renderField(el, ctx));
6097
+ registerComponent("picker_time", (el, ctx) => renderField(el, ctx));
6098
+ registerComponent("picker_datetime", (el, ctx) => renderField(el, ctx));
6099
+ registerComponent("select_person", (el, ctx) => renderField(el, ctx));
6100
+ registerComponent("multi_select_person", (el, ctx) => renderField(el, ctx));
6015
6101
  registerComponent("checker", (el, ctx) => {
6016
6102
  const p = el.property || {};
6017
6103
  const checked = p.checked === true;
@@ -6083,12 +6169,6 @@ function registerBuiltins() {
6083
6169
  const Tag = overall && !disabled ? "label" : "div";
6084
6170
  return h(Tag, props, checkbox, content, buttonsVNode);
6085
6171
  });
6086
- registerComponent("action", (el, ctx) => {
6087
- const p = el.property || {};
6088
- const layout = p.layout || "flow";
6089
- const style = { display: "flex", flexWrap: "wrap", gap: "12px" };
6090
- return h("div", { className: `${CLS}-action ${CLS}-action--${layout}`, style }, renderElements(p.actions, ctx));
6091
- });
6092
6172
  registerComponent("at", (el, ctx) => {
6093
6173
  const p = el.property || {};
6094
6174
  if (p.userID === "all") {
package/dist/index.d.cts CHANGED
@@ -305,6 +305,12 @@ interface RenderOptions {
305
305
  avatarURLSuffix?: string;
306
306
  /** 自定义头像地址解析:入参为命中的 PersonInfo,返回最终 src(优先级最高) */
307
307
  resolvePersonAvatar?: (person: PersonInfo) => string | null | undefined;
308
+ /**
309
+ * 交互预览。默认关闭:回调按钮是不可点的 span,输入框是静态占位。
310
+ * 打开后,回调按钮变成带 data-feishu-action 的 button,输入框/下拉框变成真实表单控件。
311
+ * 宿主监听这些控件,自己把点击发回卡片服务。
312
+ */
313
+ interactive?: boolean;
308
314
  }
309
315
  /**
310
316
  * 人员信息:person_v1 / person_list / at 元素只带 ID,姓名与头像 key 在这里。
package/dist/index.d.ts CHANGED
@@ -305,6 +305,12 @@ interface RenderOptions {
305
305
  avatarURLSuffix?: string;
306
306
  /** 自定义头像地址解析:入参为命中的 PersonInfo,返回最终 src(优先级最高) */
307
307
  resolvePersonAvatar?: (person: PersonInfo) => string | null | undefined;
308
+ /**
309
+ * 交互预览。默认关闭:回调按钮是不可点的 span,输入框是静态占位。
310
+ * 打开后,回调按钮变成带 data-feishu-action 的 button,输入框/下拉框变成真实表单控件。
311
+ * 宿主监听这些控件,自己把点击发回卡片服务。
312
+ */
313
+ interactive?: boolean;
308
314
  }
309
315
  /**
310
316
  * 人员信息:person_v1 / person_list / at 元素只带 ID,姓名与头像 key 在这里。
package/dist/index.js CHANGED
@@ -1126,8 +1126,6 @@ var BASE_STYLES_CSS = `/**
1126
1126
  .universal-card-header {
1127
1127
  width: 100%;
1128
1128
  overflow: hidden;
1129
- border-top-left-radius: 8px;
1130
- border-top-right-radius: 8px;
1131
1129
  }
1132
1130
  .universal-card-header__bg {
1133
1131
  box-sizing: border-box;
@@ -1261,7 +1259,7 @@ var BASE_STYLES_CSS = `/**
1261
1259
  line-height: 22px;
1262
1260
  }
1263
1261
 
1264
- /* ---- div / note ---- */
1262
+ /* ---- div ---- */
1265
1263
  .universal-card-div > * + * { margin-top: 8px; }
1266
1264
  /* div \u5E26\u524D\u7F6E\u56FE\u6807\uFF1Aicon \u5728\u5DE6\u3001\u6587\u672C\u5728\u53F3\u6392\u6210\u4E00\u884C\uFF08\u590D\u523B\u7F51\u9875\u7248 .universal-card-div \u5E03\u5C40\uFF09 */
1267
1265
  .universal-card-div--with-icon {
@@ -1273,10 +1271,6 @@ var BASE_STYLES_CSS = `/**
1273
1271
  display: flex; align-items: center; overflow: hidden;
1274
1272
  }
1275
1273
  .universal-card-div__content { flex: 1; min-width: 0; }
1276
- .universal-card-note {
1277
- font-size: 12px;
1278
- color: #8f959e;
1279
- }
1280
1274
 
1281
1275
  /* ---- \u5E03\u5C40\uFF1Acolumn_set / column ---- */
1282
1276
  .universal-card-column-set { display: flex; }
@@ -1305,6 +1299,7 @@ var BASE_STYLES_CSS = `/**
1305
1299
  display: inline-flex; align-items: center; justify-content: center;
1306
1300
  gap: 4px;
1307
1301
  min-height: 28px; padding: 4px 12px;
1302
+ font-family: inherit;
1308
1303
  font-size: 14px; line-height: 22px; border-radius: 6px;
1309
1304
  border: 1px solid transparent; background: transparent; color: #1f2329;
1310
1305
  cursor: pointer; text-decoration: none; box-sizing: border-box;
@@ -1383,17 +1378,19 @@ var BASE_STYLES_CSS = `/**
1383
1378
  /* ---- \u8868\u5355\uFF08\u9884\u89C8\uFF1A\u4EC5\u900F\u4F20\uFF0C\u65E0\u8868\u5355\u8BED\u4E49\uFF09 ---- */
1384
1379
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
1385
1380
  .universal-card-form > * { width: 100%; }
1386
- /* input / select \u7B49\u8F93\u5165\u63A7\u4EF6\uFF1A\u9884\u89C8\u91CC\u6E32\u67D3\u6210\u53EA\u8BFB\u9759\u6001\u6846\uFF0C\u5C55\u793A placeholder */
1381
+ /* input / select\uFF1A\u9759\u6001\u9884\u89C8\u662F\u53EA\u8BFB\u6846\uFF1Binteractive \u53BB\u6389 --static\uFF0C\u6210\u4E3A\u53EF\u8F93\u5165\u63A7\u4EF6 */
1382
+ .universal-card-input,
1387
1383
  .universal-card-input--static {
1388
1384
  min-height: 32px; padding: 6px 12px; box-sizing: border-box;
1389
- font-size: 14px; line-height: 20px; color: #8f959e;
1390
- background: #f5f6f7; border: 1px solid #dee0e3; border-radius: 6px;
1385
+ font-family: inherit; font-size: 14px; line-height: 20px;
1386
+ background: #fff; border: 1px solid #dee0e3; border-radius: 6px;
1387
+ color: #1f2329; width: 100%;
1388
+ }
1389
+ .universal-card-input--static {
1390
+ color: #8f959e; background: #f5f6f7;
1391
1391
  cursor: default; pointer-events: none;
1392
1392
  }
1393
1393
 
1394
- /* ---- action \u5BB9\u5668 ---- */
1395
- .universal-card-action { display: flex; flex-wrap: wrap; gap: 12px; }
1396
-
1397
1394
  /* ---- checker \u52FE\u9009\u5668\uFF08\u5BF9\u9F50\u7F51\u9875\u7248 universal-card-checker\uFF09 ---- */
1398
1395
  .universal-card-checker {
1399
1396
  display: flex; box-sizing: border-box; overflow: hidden; align-items: flex-start;
@@ -1984,7 +1981,6 @@ var BASE_STYLES_CSS = `/**
1984
1981
  }
1985
1982
  .universal-card-root.theme-dark .universal-card-markdown { color: #d6d6d6; }
1986
1983
  .universal-card-root.theme-dark .universal-card-hr { background-color: #2e2e30; }
1987
- .universal-card-root.theme-dark .universal-card-note { color: #8a8a8a; }
1988
1984
  `;
1989
1985
 
1990
1986
  // src/icon-font.ts
@@ -5611,10 +5607,6 @@ function registerBuiltins() {
5611
5607
  }
5612
5608
  return h("div", { className: `${CLS}-div` }, content);
5613
5609
  });
5614
- registerComponent(
5615
- "note",
5616
- (el, ctx) => h("div", { className: `${CLS}-note` }, renderElements(el.property?.elements, ctx))
5617
- );
5618
5610
  registerComponent("column_set", (el, ctx) => {
5619
5611
  const p = el.property || {};
5620
5612
  const gapRaw = p.horizontalSpacingValue?.value ?? p.horizontal_spacing;
@@ -5637,8 +5629,26 @@ function registerBuiltins() {
5637
5629
  const pad = boxToStyle(p.padding);
5638
5630
  if (pad) style.padding = pad;
5639
5631
  const cls = `${CLS}-column-set` + (bg ? ` ${CLS}-column-set--bg` : "");
5632
+ if (isStateHost(el)) {
5633
+ style.position = "absolute";
5634
+ style.width = "0";
5635
+ style.height = "0";
5636
+ style.overflow = "hidden";
5637
+ style.margin = "0";
5638
+ style.padding = "0";
5639
+ }
5640
5640
  return h("div", { className: cls, style }, renderElements(p.columns, ctx));
5641
5641
  });
5642
+ function isStateHost(el) {
5643
+ if (el.id === "card_state") return true;
5644
+ const actions = el.property?.actions;
5645
+ if (!Array.isArray(actions)) return false;
5646
+ return actions.some((item) => {
5647
+ if (!item || typeof item !== "object") return false;
5648
+ const action = item.action;
5649
+ return typeof action?.url === "string" && action.url.includes("msgcard/unsupported_action#s1.");
5650
+ });
5651
+ }
5642
5652
  registerComponent("column", (el, ctx) => {
5643
5653
  const p = el.property || {};
5644
5654
  const style = {
@@ -5988,29 +5998,105 @@ function registerBuiltins() {
5988
5998
  applyOuterMargin(style, p.margin);
5989
5999
  }
5990
6000
  const styleProp = Object.keys(style).length ? { style } : {};
6001
+ const extra = p;
6002
+ const callback = readCallbackValue(extra);
6003
+ const formAction = String(extra.form_action_type ?? extra.formActionType ?? "");
6004
+ if (ctx.options.interactive && callback && !p.disabled && !url) {
6005
+ return h("button", {
6006
+ type: formAction === "reset" ? "reset" : formAction === "submit" ? "submit" : "button",
6007
+ className: base,
6008
+ "data-feishu-action": JSON.stringify(callback),
6009
+ "data-feishu-tag": "button",
6010
+ ...styleProp
6011
+ }, children);
6012
+ }
5991
6013
  if (url && !p.disabled) {
5992
6014
  return h("a", { className: base, href: url, target: "_blank", rel: "noopener", ...styleProp }, children);
5993
6015
  }
5994
6016
  const cls = `${base} ${CLS}-button--static` + (p.disabled ? ` ${CLS}-button--disabled` : "");
5995
6017
  return h("span", { className: cls, ...styleProp }, children);
5996
6018
  });
5997
- registerComponent(
5998
- "form",
5999
- (el, ctx) => h("div", { className: `${CLS}-form` }, renderElements(el.property?.elements, ctx))
6000
- );
6019
+ function isRecord(value) {
6020
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6021
+ }
6022
+ function readCallbackValue(p) {
6023
+ const fromList = (list) => {
6024
+ if (!Array.isArray(list)) return null;
6025
+ for (const item of list) {
6026
+ if (!isRecord(item) || item.type !== "callback") continue;
6027
+ const inner = isRecord(item.action) ? item.action : item;
6028
+ if (isRecord(inner.value)) return inner.value;
6029
+ }
6030
+ return null;
6031
+ };
6032
+ return fromList(p.actions) ?? fromList(p.behaviors) ?? (isRecord(p.value) ? p.value : null);
6033
+ }
6034
+ registerComponent("form", (el, ctx) => {
6035
+ const children = renderElements(el.property?.elements, ctx);
6036
+ const className = `${CLS}-form`;
6037
+ if (!ctx.options.interactive) return h("div", { className }, children);
6038
+ const name = typeof el.property?.name === "string" ? el.property.name : "";
6039
+ return h("form", { className, "data-feishu-form": name }, children);
6040
+ });
6001
6041
  function renderStaticField(el) {
6002
6042
  const p = el.property || {};
6003
6043
  const ph = extractPlainText(p.placeholder) || "";
6004
6044
  return h("div", { className: `${CLS}-input ${CLS}-input--static` }, ph);
6005
6045
  }
6006
- registerComponent("input", (el) => renderStaticField(el));
6007
- registerComponent("multi_select_static", (el) => renderStaticField(el));
6008
- registerComponent("select_static", (el) => renderStaticField(el));
6009
- registerComponent("date_picker", (el) => renderStaticField(el));
6010
- registerComponent("picker_time", (el) => renderStaticField(el));
6011
- registerComponent("picker_datetime", (el) => renderStaticField(el));
6012
- registerComponent("select_person", (el) => renderStaticField(el));
6013
- registerComponent("multi_select_person", (el) => renderStaticField(el));
6046
+ function fieldText(value) {
6047
+ if (typeof value === "string") return value;
6048
+ return extractPlainText(value);
6049
+ }
6050
+ function renderInteractiveField(el) {
6051
+ const p = el.property || {};
6052
+ const ph = extractPlainText(p.placeholder) || "";
6053
+ const name = typeof p.name === "string" ? p.name : void 0;
6054
+ const nameProp = name ? { name } : {};
6055
+ const callback = readCallbackValue(p);
6056
+ const actionProp = callback ? { "data-feishu-action": JSON.stringify(callback), "data-feishu-tag": el.tag } : {};
6057
+ if (el.tag === "select_static" || el.tag === "multi_select_static") {
6058
+ const initial = p.initial_option ?? p.initialOption;
6059
+ const options = Array.isArray(p.options) ? p.options : [];
6060
+ return h("select", {
6061
+ className: `${CLS}-input`,
6062
+ ...nameProp,
6063
+ ...el.tag === "multi_select_static" ? { multiple: true } : {},
6064
+ ...actionProp
6065
+ }, [
6066
+ h("option", { value: "" }, ph),
6067
+ ...options.map((option) => {
6068
+ const item = isRecord(option) ? option : {};
6069
+ const value = typeof item.value === "string" ? item.value : fieldText(item.text ?? item);
6070
+ const label = fieldText(item.text ?? item.label) || value;
6071
+ return h("option", {
6072
+ value,
6073
+ ...value !== "" && value === initial ? { selected: true } : {}
6074
+ }, label);
6075
+ })
6076
+ ]);
6077
+ }
6078
+ const def = p.default_value ?? p.defaultValue;
6079
+ const inputType = el.tag === "date_picker" ? "date" : el.tag === "picker_time" ? "time" : "text";
6080
+ return h("input", {
6081
+ className: `${CLS}-input`,
6082
+ type: inputType,
6083
+ placeholder: ph,
6084
+ value: typeof def === "string" ? def : "",
6085
+ ...nameProp,
6086
+ ...actionProp
6087
+ });
6088
+ }
6089
+ function renderField(el, ctx) {
6090
+ return ctx.options.interactive ? renderInteractiveField(el) : renderStaticField(el);
6091
+ }
6092
+ registerComponent("input", (el, ctx) => renderField(el, ctx));
6093
+ registerComponent("multi_select_static", (el, ctx) => renderField(el, ctx));
6094
+ registerComponent("select_static", (el, ctx) => renderField(el, ctx));
6095
+ registerComponent("date_picker", (el, ctx) => renderField(el, ctx));
6096
+ registerComponent("picker_time", (el, ctx) => renderField(el, ctx));
6097
+ registerComponent("picker_datetime", (el, ctx) => renderField(el, ctx));
6098
+ registerComponent("select_person", (el, ctx) => renderField(el, ctx));
6099
+ registerComponent("multi_select_person", (el, ctx) => renderField(el, ctx));
6014
6100
  registerComponent("checker", (el, ctx) => {
6015
6101
  const p = el.property || {};
6016
6102
  const checked = p.checked === true;
@@ -6082,12 +6168,6 @@ function registerBuiltins() {
6082
6168
  const Tag = overall && !disabled ? "label" : "div";
6083
6169
  return h(Tag, props, checkbox, content, buttonsVNode);
6084
6170
  });
6085
- registerComponent("action", (el, ctx) => {
6086
- const p = el.property || {};
6087
- const layout = p.layout || "flow";
6088
- const style = { display: "flex", flexWrap: "wrap", gap: "12px" };
6089
- return h("div", { className: `${CLS}-action ${CLS}-action--${layout}`, style }, renderElements(p.actions, ctx));
6090
- });
6091
6171
  registerComponent("at", (el, ctx) => {
6092
6172
  const p = el.property || {};
6093
6173
  if (p.userID === "all") {
package/dist/styles.css CHANGED
@@ -198,8 +198,6 @@
198
198
  .universal-card-header {
199
199
  width: 100%;
200
200
  overflow: hidden;
201
- border-top-left-radius: 8px;
202
- border-top-right-radius: 8px;
203
201
  }
204
202
  .universal-card-header__bg {
205
203
  box-sizing: border-box;
@@ -333,7 +331,7 @@
333
331
  line-height: 22px;
334
332
  }
335
333
 
336
- /* ---- div / note ---- */
334
+ /* ---- div ---- */
337
335
  .universal-card-div > * + * { margin-top: 8px; }
338
336
  /* div 带前置图标:icon 在左、文本在右排成一行(复刻网页版 .universal-card-div 布局) */
339
337
  .universal-card-div--with-icon {
@@ -345,10 +343,6 @@
345
343
  display: flex; align-items: center; overflow: hidden;
346
344
  }
347
345
  .universal-card-div__content { flex: 1; min-width: 0; }
348
- .universal-card-note {
349
- font-size: 12px;
350
- color: #8f959e;
351
- }
352
346
 
353
347
  /* ---- 布局:column_set / column ---- */
354
348
  .universal-card-column-set { display: flex; }
@@ -377,6 +371,7 @@
377
371
  display: inline-flex; align-items: center; justify-content: center;
378
372
  gap: 4px;
379
373
  min-height: 28px; padding: 4px 12px;
374
+ font-family: inherit;
380
375
  font-size: 14px; line-height: 22px; border-radius: 6px;
381
376
  border: 1px solid transparent; background: transparent; color: #1f2329;
382
377
  cursor: pointer; text-decoration: none; box-sizing: border-box;
@@ -455,17 +450,19 @@
455
450
  /* ---- 表单(预览:仅透传,无表单语义) ---- */
456
451
  .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
457
452
  .universal-card-form > * { width: 100%; }
458
- /* input / select 等输入控件:预览里渲染成只读静态框,展示 placeholder */
453
+ /* input / select:静态预览是只读框;interactive 去掉 --static,成为可输入控件 */
454
+ .universal-card-input,
459
455
  .universal-card-input--static {
460
456
  min-height: 32px; padding: 6px 12px; box-sizing: border-box;
461
- font-size: 14px; line-height: 20px; color: #8f959e;
462
- background: #f5f6f7; border: 1px solid #dee0e3; border-radius: 6px;
457
+ font-family: inherit; font-size: 14px; line-height: 20px;
458
+ background: #fff; border: 1px solid #dee0e3; border-radius: 6px;
459
+ color: #1f2329; width: 100%;
460
+ }
461
+ .universal-card-input--static {
462
+ color: #8f959e; background: #f5f6f7;
463
463
  cursor: default; pointer-events: none;
464
464
  }
465
465
 
466
- /* ---- action 容器 ---- */
467
- .universal-card-action { display: flex; flex-wrap: wrap; gap: 12px; }
468
-
469
466
  /* ---- checker 勾选器(对齐网页版 universal-card-checker) ---- */
470
467
  .universal-card-checker {
471
468
  display: flex; box-sizing: border-box; overflow: hidden; align-items: flex-start;
@@ -1056,4 +1053,3 @@
1056
1053
  }
1057
1054
  .universal-card-root.theme-dark .universal-card-markdown { color: #d6d6d6; }
1058
1055
  .universal-card-root.theme-dark .universal-card-hr { background-color: #2e2e30; }
1059
- .universal-card-root.theme-dark .universal-card-note { color: #8a8a8a; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
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.16"
31
+ "@open-feishu-card/adapter": "0.3.17"
32
32
  },
33
33
  "devDependencies": {
34
34
  "tsup": "^8.3.5",