@open-feishu-card/renderer 0.3.4 → 0.3.5

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
@@ -256,7 +256,7 @@ var HEADER_TEMPLATE = (() => {
256
256
  text: PALETTE[textException[n] || letter + "600"]
257
257
  };
258
258
  }
259
- out.default = { bg: "#f5f6f7", text: "#1f2329" };
259
+ out.default = { bg: "#ffffff", text: "#1f2329" };
260
260
  return out;
261
261
  })();
262
262
  var TEXT_FONT_COLOR_TOKEN = {
@@ -997,6 +997,9 @@ var BASE_STYLES_CSS = `/**
997
997
  width: 100%;
998
998
  max-width: 605px;
999
999
  box-sizing: border-box;
1000
+ /* \u771F\u673A\u6309\u300C\u5361\u7247\u5BB9\u5668\u5BBD\u5EA6 > 320px\u300D\u533A\u5206\u5BBD/\u7A84\u5C4F\u6309\u94AE\u6863\u4F4D\uFF0C\u8FD9\u91CC\u7528\u5BB9\u5668\u67E5\u8BE2\u590D\u523B */
1001
+ container-type: inline-size;
1002
+ container-name: universal-card;
1000
1003
  /* BEGIN PALETTE VARS (auto-generated from constants.ts PALETTE) */
1001
1004
  /* \u6B63\u6587 color:var(--O400) \u7B49\u4F9D\u8D56\u8FD9\u4E9B\u53D8\u91CF\uFF1B\u4E0E\u98DE\u4E66 :root light \u8272\u9636\u5BF9\u9F50 */
1002
1005
  --B50: #f0f4ff;
@@ -1179,6 +1182,8 @@ var BASE_STYLES_CSS = `/**
1179
1182
  /* \u5BF9\u9F50\u98DE\u4E66\uFF1Amax(100%, 605px) 140px\uFF0C\u7A84\u5361\u4E0D\u628A\u9AD8\u5149\u62C9\u7A00 */
1180
1183
  background-size: max(100%, 605px) 140px;
1181
1184
  }
1185
+ /* default/disabled \u6A21\u677F\u771F\u673A\u4E0D\u8BBE\u80CC\u666F\u8272\uFF08bg_color \u4E3A\u7A7A\u4E32\uFF09\uFF0C\u6B64\u65F6 header \u4E0D\u7559\u5E95\u90E8\u5185\u8FB9\u8DDD */
1186
+ .universal-card-header__bg--no-template { padding-bottom: 0; }
1182
1187
  /* \u5185\u5BB9\u884C\uFF1A\u5DE6\u4FA7\u56FE\u6807 + \u53F3\u4FA7\u6807\u9898\u533A\uFF08\u5BF9\u9F50\u7F51\u9875\u7248 header__content = row\uFF09 */
1183
1188
  .universal-card-header__content {
1184
1189
  width: 100%;
@@ -1245,7 +1250,9 @@ var BASE_STYLES_CSS = `/**
1245
1250
  column-gap: 12px;
1246
1251
  align-items: flex-start;
1247
1252
  }
1248
- .universal-card-body > * { width: 100%; box-sizing: border-box; }
1253
+ /* \u4EA4\u4E92\u63A7\u4EF6\u4E0D\u53C2\u4E0E\u300C\u5B50\u5143\u7D20\u6491\u6EE1\u300D\uFF1A\u6309\u94AE\u7684\u5BBD\u5EA6\u7531\u771F\u673A\u89C4\u5219\u51B3\u5B9A\uFF08\u89C1 .universal-card-button--auto\uFF09\uFF0C
1254
+ \u82E5\u88AB\u8FD9\u6761 blanket \u89C4\u5219\u62C9\u5230 100%\uFF0C\u7A84\u5C4F/\u5BBD\u5C4F\u5206\u6863\u5C31\u5931\u6548\u4E86\u3002 */
1255
+ .universal-card-body > *:not(.universal-card-button) { width: 100%; box-sizing: border-box; }
1249
1256
 
1250
1257
  /* ---- markdown \u6BB5\u843D ---- */
1251
1258
  /* \u5BF9\u9F50\u7F51\u9875\u7248\uFF1Aline-height \u9ED8\u8BA4 1.6\uFF1B\u5177\u4F53\u5B57\u53F7/\u884C\u9AD8\u7531\u7EC4\u4EF6\u5185\u8054 style \u6309 text_size \u7ED9\u51FA */
@@ -1365,10 +1372,14 @@ var BASE_STYLES_CSS = `/**
1365
1372
  flex-shrink: 0; height: 1.2em; color: inherit;
1366
1373
  }
1367
1374
  .universal-card-button__icon .universal-card-icon { color: inherit; }
1368
- /* \u5BBD\u5EA6\uFF1A\u5BF9\u9F50\u771F\u673A\u2014\u2014body > * { width:100% }\u3001\u5217 flex stretch \u4F1A\u628A\u6309\u94AE\u6491\u6EE1\u6240\u5728\u884C/\u5217\u3002
1369
- \u4E0D\u518D\u5F3A\u5236 width:auto\uFF08\u90A3\u662F\u7F51\u9875\u7248\u89C2\u611F\uFF1B\u771F\u673A\u72EC\u7ACB\u6210\u884C\u7684\u6309\u94AE\u9ED8\u8BA4\u5360\u6EE1\u6574\u884C\uFF09\u3002
1370
- width:"fill" \u663E\u5F0F\u6302 --block\uFF0C\u4E0E\u7F3A\u7701\u6491\u6EE1\u540C\u5BBD\uFF0C\u5E76\u4F9B margin calc \u8BC6\u522B\u3002 */
1371
- .universal-card-button--block { width: 100%; }
1375
+ /* \u5BBD\u5EA6\uFF1A\u5BF9\u9F50\u771F\u673A ud__button \u7684 isWide \u5206\u6863\uFF08\u771F\u673A\u6309\u300C\u5361\u7247\u5BB9\u5668\u5BBD\u5EA6 > 320px\u300D\u5224\u5B9A\uFF09\uFF1A
1376
+ \u5BBD\u5C4F \u2192 \u81EA\u9002\u5E94\u5185\u5BB9\u5BBD\uFF08--auto\uFF09\uFF1B\u7A84\u5C4F\uFF08\u2264320px\uFF09\u2192 \u6491\u6EE1\u6574\u884C\uFF08\u4E0B\u65B9 @container \u89C4\u5219\u8986\u76D6\uFF09\u3002
1377
+ text \u65CF\u4E0E width:"fill" \u6052\u5B9A\u6491\u6EE1\uFF08--block\uFF09\u3002
1378
+ align-self:flex-start \u2014\u2014 \u7236\u7EA7\uFF08body/form/column\uFF09\u9ED8\u8BA4 align-items:stretch\uFF0C
1379
+ \u53EA\u5199 width:auto \u4ECD\u4F1A\u88AB\u62C9\u4F38\uFF0C\u5FC5\u987B\u540C\u65F6\u963B\u6B62\u62C9\u4F38\u3002 */
1380
+ .universal-card-button { min-width: 58px; align-self: flex-start; }
1381
+ .universal-card-button--block { width: 100%; min-width: unset; }
1382
+ .universal-card-button--auto { width: auto; }
1372
1383
 
1373
1384
  /* \u2014\u2014 outlined \u63CF\u8FB9\u65CF \u2014\u2014 */
1374
1385
  /* default\uFF1A\u7070\u63CF\u8FB9 + \u767D\u5E95 + \u6DF1\u5B57\uFF08\u672A\u52A0\u53D8\u4F53\u7C7B\u7684\u88F8 .universal-card-button \u5DF2\u662F\u767D\u5E95\u6DF1\u5B57\uFF0C
@@ -1395,6 +1406,8 @@ var BASE_STYLES_CSS = `/**
1395
1406
  .universal-card-button--danger_text {
1396
1407
  border-color: transparent; background: transparent; padding: 4px 8px;
1397
1408
  justify-content: flex-start;
1409
+ /* \u771F\u673A text \u65CF\u6309\u94AE\u4E0D\u8BBE\u6700\u5C0F\u5BBD\u5EA6 */
1410
+ min-width: unset;
1398
1411
  }
1399
1412
  .universal-card-button--text { color: #1f2329; }
1400
1413
  .universal-card-button--primary_text { color: #1456f0; }
@@ -1430,6 +1443,8 @@ var BASE_STYLES_CSS = `/**
1430
1443
  .universal-card-overflow--static { display: inline-flex; }
1431
1444
  .universal-card-overflow__summary {
1432
1445
  list-style: none; cursor: pointer; display: inline-flex;
1446
+ /* \u771F\u673A\u6298\u53E0\u6309\u94AE\u7684\u6700\u5C0F\u5BBD\u5EA6\u662F 32px\uFF08\u666E\u901A\u6309\u94AE\u662F 58px\uFF09 */
1447
+ min-width: 32px;
1433
1448
  }
1434
1449
  /* \u53BB\u6389 <summary> \u7684\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0 */
1435
1450
  .universal-card-overflow__summary::-webkit-details-marker { display: none; }
@@ -1451,8 +1466,9 @@ var BASE_STYLES_CSS = `/**
1451
1466
  .universal-card-overflow__item:hover { background: #f5f6f7; }
1452
1467
 
1453
1468
  /* ---- \u8868\u5355\uFF08\u9884\u89C8\uFF1A\u4EC5\u900F\u4F20\uFF0C\u65E0\u8868\u5355\u8BED\u4E49\uFF09 ---- */
1454
- .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
1455
- .universal-card-form > * { width: 100%; }
1469
+ /* row-gap 12px\uFF1A\u5BF9\u9F50\u771F\u673A schema 2.0 \u8868\u5355\u5BB9\u5668 */
1470
+ .universal-card-form { display: flex; flex-direction: column; row-gap: 12px; }
1471
+ .universal-card-form > *:not(.universal-card-button) { width: 100%; }
1456
1472
  /* input / select\uFF1A\u9759\u6001\u9884\u89C8\u662F\u53EA\u8BFB\u6846\uFF1Binteractive \u53BB\u6389 --static\uFF0C\u6210\u4E3A\u53EF\u8F93\u5165\u63A7\u4EF6 */
1457
1473
  .universal-card-input,
1458
1474
  .universal-card-input--static {
@@ -1460,6 +1476,13 @@ var BASE_STYLES_CSS = `/**
1460
1476
  font-family: inherit; font-size: 14px; line-height: 20px;
1461
1477
  background: #fff; border: 1px solid #dee0e3; border-radius: 6px;
1462
1478
  color: #1f2329; width: 100%;
1479
+ /* \u771F\u673A input \u9ED8\u8BA4 max-width:282px\uFF08width:"fill" \u65F6\u7531\u5185\u8054\u6837\u5F0F\u653E\u5F00\uFF09 */
1480
+ max-width: 282px;
1481
+ }
1482
+ /* select type:"text"\uFF1A\u771F\u673A\u662F\u65E0\u8FB9\u6846\u3001\u5185\u5BB9\u81EA\u9002\u5E94\u5BBD\u7684\u4E0B\u62C9 */
1483
+ .universal-card-input--text {
1484
+ border-color: transparent; background: transparent;
1485
+ min-height: 26px; padding: 1px 7px; width: auto;
1463
1486
  }
1464
1487
  .universal-card-input--static {
1465
1488
  color: #8f959e; background: #f5f6f7;
@@ -2127,6 +2150,13 @@ var BASE_STYLES_CSS = `/**
2127
2150
  from { opacity: 1; transform: translateY(0); }
2128
2151
  to { opacity: 0; transform: translateY(-100%); margin-bottom: 0; }
2129
2152
  }
2153
+
2154
+ /* ---- \u7A84\u5C4F\u5206\u6863\uFF08\u771F\u673A isWide\uFF1A\u5361\u7247\u5BB9\u5668\u5BBD\u5EA6 > 320px \u4E3A\u5BBD\u5C4F\uFF09 ----
2155
+ \u771F\u673A\u89C4\u5219\uFF1Adefault/primary/primary_filled/danger/danger_filled/laser \u6309\u94AE\u5728\u7A84\u5C4F\u6491\u6EE1\u6574\u884C\uFF0C
2156
+ \u5BBD\u5C4F\u5219\u81EA\u9002\u5E94\u5185\u5BB9\u5BBD\u3002@container \u4E0D\u589E\u52A0\u9009\u62E9\u5668\u6743\u91CD\uFF0C\u9760\u6E90\u7801\u987A\u5E8F\u5728 --auto \u89C4\u5219\u4E4B\u540E\u8986\u76D6\u3002 */
2157
+ @container universal-card (max-width: 320px) {
2158
+ .universal-card-button--auto { width: 100%; }
2159
+ }
2130
2160
  `;
2131
2161
 
2132
2162
  // src/icon-font.ts
@@ -5694,6 +5724,8 @@ function registerBuiltins() {
5694
5724
  ),
5695
5725
  subtitleVNode
5696
5726
  ];
5727
+ const tplKey2 = p.template || "default";
5728
+ const noTemplate = tplKey2 === "default" || tplKey2 === "disabled";
5697
5729
  return h(
5698
5730
  "div",
5699
5731
  {
@@ -5703,7 +5735,7 @@ function registerBuiltins() {
5703
5735
  h(
5704
5736
  "div",
5705
5737
  {
5706
- className: `${CLS}-header__bg`,
5738
+ className: `${CLS}-header__bg${noTemplate ? ` ${CLS}-header__bg--no-template` : ""}`,
5707
5739
  // 内联 base64 高光纹理;图宽=header 宽使亮带居中;padding 透传 DSL(默认 12px)
5708
5740
  style: (() => {
5709
5741
  const s = { backgroundImage: `url(${HEADER_BG_LIGHT})` };
@@ -6236,12 +6268,26 @@ function registerBuiltins() {
6236
6268
  const url = actionUrl || resolveURL(p.url) || p.behaviors?.[0]?.default_url?.url;
6237
6269
  const wv = p.widthValue;
6238
6270
  const widthTok = p.width ?? wv?.value;
6239
- const block = widthTok === "fill";
6240
- const base = `${CLS}-button ${CLS}-button--${type}${block ? ` ${CLS}-button--block` : ""}`;
6271
+ const isFill = widthTok === "fill";
6272
+ const pxWidth = /^\d+(\.\d+)?px$/i.test(String(widthTok ?? "")) ? String(widthTok) : "";
6273
+ const textFamily = type === "text" || type === "primary_text" || type === "danger_text";
6274
+ const block = isFill || textFamily;
6275
+ const auto = !block && !pxWidth;
6276
+ const base = `${CLS}-button ${CLS}-button--${type}` + (block ? ` ${CLS}-button--block` : "") + (auto ? ` ${CLS}-button--auto` : "");
6241
6277
  const style = {};
6242
- if (p.margin) {
6243
- style.width = "100%";
6244
- applyOuterMargin(style, p.margin);
6278
+ if (pxWidth) {
6279
+ style.width = pxWidth;
6280
+ if (p.margin) applyOuterMargin(style, p.margin);
6281
+ } else if (p.margin) {
6282
+ if (block) {
6283
+ style.width = "100%";
6284
+ applyOuterMargin(style, p.margin);
6285
+ } else {
6286
+ applyOuterMargin(style, p.margin);
6287
+ const { left, right } = horizontalMarginPx(p.margin);
6288
+ const inset = left + right;
6289
+ if (inset > 0) style.maxWidth = `calc(100% - ${inset}px)`;
6290
+ }
6245
6291
  }
6246
6292
  const styleProp = Object.keys(style).length ? { style } : {};
6247
6293
  const extra = p;
@@ -6322,15 +6368,53 @@ function registerBuiltins() {
6322
6368
  const name = typeof el.property?.name === "string" ? el.property.name : "";
6323
6369
  return h("form", { className, "data-feishu-form": name }, children);
6324
6370
  });
6371
+ function fieldWidthStyle(p, kind) {
6372
+ const wv = p.widthValue;
6373
+ const raw = String(p.width ?? wv?.value ?? "");
6374
+ const style = {};
6375
+ if (/^\d+(\.\d+)?px$/i.test(raw)) {
6376
+ style.width = raw;
6377
+ style.maxWidth = "100%";
6378
+ return style;
6379
+ }
6380
+ if (raw === "fill") {
6381
+ style.width = "100%";
6382
+ style.maxWidth = "none";
6383
+ return style;
6384
+ }
6385
+ if (kind === "select") {
6386
+ if (raw === "auto") {
6387
+ style.width = "100%";
6388
+ style.maxWidth = "100%";
6389
+ return style;
6390
+ }
6391
+ if (p.type === "text") {
6392
+ style.width = "auto";
6393
+ style.maxWidth = "100%";
6394
+ return style;
6395
+ }
6396
+ style.width = "282px";
6397
+ style.maxWidth = "100%";
6398
+ return style;
6399
+ }
6400
+ style.width = "100%";
6401
+ style.maxWidth = "282px";
6402
+ return style;
6403
+ }
6325
6404
  function renderStaticField(el) {
6326
6405
  const p = el.property || {};
6327
6406
  const ph = extractPlainText(p.placeholder) || "";
6328
- return h("div", { className: `${CLS}-input ${CLS}-input--static` }, ph);
6407
+ const kind = isSelectTag(el.tag) ? "select" : "input";
6408
+ const cls = `${CLS}-input ${CLS}-input--static` + (kind === "select" && p.type === "text" ? ` ${CLS}-input--text` : "");
6409
+ return h("div", { className: cls, style: fieldWidthStyle(p, kind) }, ph);
6329
6410
  }
6330
6411
  function fieldText(value) {
6331
6412
  if (typeof value === "string") return value;
6332
6413
  return extractPlainText(value);
6333
6414
  }
6415
+ function isSelectTag(tag) {
6416
+ return tag === "select_static" || tag === "multi_select_static" || tag === "select_person" || tag === "multi_select_person";
6417
+ }
6334
6418
  function renderInteractiveField(el) {
6335
6419
  const p = el.property || {};
6336
6420
  const ph = extractPlainText(p.placeholder) || "";
@@ -6338,13 +6422,15 @@ function registerBuiltins() {
6338
6422
  const nameProp = name ? { name } : {};
6339
6423
  const callback = readCallbackValue(p);
6340
6424
  const actionProp = callback ? { "data-feishu-action": JSON.stringify(callback), "data-feishu-tag": el.tag } : {};
6341
- if (el.tag === "select_static" || el.tag === "multi_select_static") {
6425
+ if (isSelectTag(el.tag)) {
6342
6426
  const initial = p.initial_option ?? p.initialOption;
6343
6427
  const options = Array.isArray(p.options) ? p.options : [];
6428
+ const cls = `${CLS}-input` + (p.type === "text" ? ` ${CLS}-input--text` : "");
6344
6429
  return h("select", {
6345
- className: `${CLS}-input`,
6430
+ className: cls,
6431
+ style: fieldWidthStyle(p, "select"),
6346
6432
  ...nameProp,
6347
- ...el.tag === "multi_select_static" ? { multiple: true } : {},
6433
+ ...el.tag === "multi_select_static" || el.tag === "multi_select_person" ? { multiple: true } : {},
6348
6434
  ...actionProp
6349
6435
  }, [
6350
6436
  h("option", { value: "" }, ph),
@@ -6363,6 +6449,7 @@ function registerBuiltins() {
6363
6449
  const inputType = el.tag === "date_picker" ? "date" : el.tag === "picker_time" ? "time" : "text";
6364
6450
  return h("input", {
6365
6451
  className: `${CLS}-input`,
6452
+ style: fieldWidthStyle(p, "input"),
6366
6453
  type: inputType,
6367
6454
  placeholder: ph,
6368
6455
  value: typeof def === "string" ? def : "",
package/dist/index.js CHANGED
@@ -255,7 +255,7 @@ var HEADER_TEMPLATE = (() => {
255
255
  text: PALETTE[textException[n] || letter + "600"]
256
256
  };
257
257
  }
258
- out.default = { bg: "#f5f6f7", text: "#1f2329" };
258
+ out.default = { bg: "#ffffff", text: "#1f2329" };
259
259
  return out;
260
260
  })();
261
261
  var TEXT_FONT_COLOR_TOKEN = {
@@ -996,6 +996,9 @@ var BASE_STYLES_CSS = `/**
996
996
  width: 100%;
997
997
  max-width: 605px;
998
998
  box-sizing: border-box;
999
+ /* \u771F\u673A\u6309\u300C\u5361\u7247\u5BB9\u5668\u5BBD\u5EA6 > 320px\u300D\u533A\u5206\u5BBD/\u7A84\u5C4F\u6309\u94AE\u6863\u4F4D\uFF0C\u8FD9\u91CC\u7528\u5BB9\u5668\u67E5\u8BE2\u590D\u523B */
1000
+ container-type: inline-size;
1001
+ container-name: universal-card;
999
1002
  /* BEGIN PALETTE VARS (auto-generated from constants.ts PALETTE) */
1000
1003
  /* \u6B63\u6587 color:var(--O400) \u7B49\u4F9D\u8D56\u8FD9\u4E9B\u53D8\u91CF\uFF1B\u4E0E\u98DE\u4E66 :root light \u8272\u9636\u5BF9\u9F50 */
1001
1004
  --B50: #f0f4ff;
@@ -1178,6 +1181,8 @@ var BASE_STYLES_CSS = `/**
1178
1181
  /* \u5BF9\u9F50\u98DE\u4E66\uFF1Amax(100%, 605px) 140px\uFF0C\u7A84\u5361\u4E0D\u628A\u9AD8\u5149\u62C9\u7A00 */
1179
1182
  background-size: max(100%, 605px) 140px;
1180
1183
  }
1184
+ /* default/disabled \u6A21\u677F\u771F\u673A\u4E0D\u8BBE\u80CC\u666F\u8272\uFF08bg_color \u4E3A\u7A7A\u4E32\uFF09\uFF0C\u6B64\u65F6 header \u4E0D\u7559\u5E95\u90E8\u5185\u8FB9\u8DDD */
1185
+ .universal-card-header__bg--no-template { padding-bottom: 0; }
1181
1186
  /* \u5185\u5BB9\u884C\uFF1A\u5DE6\u4FA7\u56FE\u6807 + \u53F3\u4FA7\u6807\u9898\u533A\uFF08\u5BF9\u9F50\u7F51\u9875\u7248 header__content = row\uFF09 */
1182
1187
  .universal-card-header__content {
1183
1188
  width: 100%;
@@ -1244,7 +1249,9 @@ var BASE_STYLES_CSS = `/**
1244
1249
  column-gap: 12px;
1245
1250
  align-items: flex-start;
1246
1251
  }
1247
- .universal-card-body > * { width: 100%; box-sizing: border-box; }
1252
+ /* \u4EA4\u4E92\u63A7\u4EF6\u4E0D\u53C2\u4E0E\u300C\u5B50\u5143\u7D20\u6491\u6EE1\u300D\uFF1A\u6309\u94AE\u7684\u5BBD\u5EA6\u7531\u771F\u673A\u89C4\u5219\u51B3\u5B9A\uFF08\u89C1 .universal-card-button--auto\uFF09\uFF0C
1253
+ \u82E5\u88AB\u8FD9\u6761 blanket \u89C4\u5219\u62C9\u5230 100%\uFF0C\u7A84\u5C4F/\u5BBD\u5C4F\u5206\u6863\u5C31\u5931\u6548\u4E86\u3002 */
1254
+ .universal-card-body > *:not(.universal-card-button) { width: 100%; box-sizing: border-box; }
1248
1255
 
1249
1256
  /* ---- markdown \u6BB5\u843D ---- */
1250
1257
  /* \u5BF9\u9F50\u7F51\u9875\u7248\uFF1Aline-height \u9ED8\u8BA4 1.6\uFF1B\u5177\u4F53\u5B57\u53F7/\u884C\u9AD8\u7531\u7EC4\u4EF6\u5185\u8054 style \u6309 text_size \u7ED9\u51FA */
@@ -1364,10 +1371,14 @@ var BASE_STYLES_CSS = `/**
1364
1371
  flex-shrink: 0; height: 1.2em; color: inherit;
1365
1372
  }
1366
1373
  .universal-card-button__icon .universal-card-icon { color: inherit; }
1367
- /* \u5BBD\u5EA6\uFF1A\u5BF9\u9F50\u771F\u673A\u2014\u2014body > * { width:100% }\u3001\u5217 flex stretch \u4F1A\u628A\u6309\u94AE\u6491\u6EE1\u6240\u5728\u884C/\u5217\u3002
1368
- \u4E0D\u518D\u5F3A\u5236 width:auto\uFF08\u90A3\u662F\u7F51\u9875\u7248\u89C2\u611F\uFF1B\u771F\u673A\u72EC\u7ACB\u6210\u884C\u7684\u6309\u94AE\u9ED8\u8BA4\u5360\u6EE1\u6574\u884C\uFF09\u3002
1369
- width:"fill" \u663E\u5F0F\u6302 --block\uFF0C\u4E0E\u7F3A\u7701\u6491\u6EE1\u540C\u5BBD\uFF0C\u5E76\u4F9B margin calc \u8BC6\u522B\u3002 */
1370
- .universal-card-button--block { width: 100%; }
1374
+ /* \u5BBD\u5EA6\uFF1A\u5BF9\u9F50\u771F\u673A ud__button \u7684 isWide \u5206\u6863\uFF08\u771F\u673A\u6309\u300C\u5361\u7247\u5BB9\u5668\u5BBD\u5EA6 > 320px\u300D\u5224\u5B9A\uFF09\uFF1A
1375
+ \u5BBD\u5C4F \u2192 \u81EA\u9002\u5E94\u5185\u5BB9\u5BBD\uFF08--auto\uFF09\uFF1B\u7A84\u5C4F\uFF08\u2264320px\uFF09\u2192 \u6491\u6EE1\u6574\u884C\uFF08\u4E0B\u65B9 @container \u89C4\u5219\u8986\u76D6\uFF09\u3002
1376
+ text \u65CF\u4E0E width:"fill" \u6052\u5B9A\u6491\u6EE1\uFF08--block\uFF09\u3002
1377
+ align-self:flex-start \u2014\u2014 \u7236\u7EA7\uFF08body/form/column\uFF09\u9ED8\u8BA4 align-items:stretch\uFF0C
1378
+ \u53EA\u5199 width:auto \u4ECD\u4F1A\u88AB\u62C9\u4F38\uFF0C\u5FC5\u987B\u540C\u65F6\u963B\u6B62\u62C9\u4F38\u3002 */
1379
+ .universal-card-button { min-width: 58px; align-self: flex-start; }
1380
+ .universal-card-button--block { width: 100%; min-width: unset; }
1381
+ .universal-card-button--auto { width: auto; }
1371
1382
 
1372
1383
  /* \u2014\u2014 outlined \u63CF\u8FB9\u65CF \u2014\u2014 */
1373
1384
  /* default\uFF1A\u7070\u63CF\u8FB9 + \u767D\u5E95 + \u6DF1\u5B57\uFF08\u672A\u52A0\u53D8\u4F53\u7C7B\u7684\u88F8 .universal-card-button \u5DF2\u662F\u767D\u5E95\u6DF1\u5B57\uFF0C
@@ -1394,6 +1405,8 @@ var BASE_STYLES_CSS = `/**
1394
1405
  .universal-card-button--danger_text {
1395
1406
  border-color: transparent; background: transparent; padding: 4px 8px;
1396
1407
  justify-content: flex-start;
1408
+ /* \u771F\u673A text \u65CF\u6309\u94AE\u4E0D\u8BBE\u6700\u5C0F\u5BBD\u5EA6 */
1409
+ min-width: unset;
1397
1410
  }
1398
1411
  .universal-card-button--text { color: #1f2329; }
1399
1412
  .universal-card-button--primary_text { color: #1456f0; }
@@ -1429,6 +1442,8 @@ var BASE_STYLES_CSS = `/**
1429
1442
  .universal-card-overflow--static { display: inline-flex; }
1430
1443
  .universal-card-overflow__summary {
1431
1444
  list-style: none; cursor: pointer; display: inline-flex;
1445
+ /* \u771F\u673A\u6298\u53E0\u6309\u94AE\u7684\u6700\u5C0F\u5BBD\u5EA6\u662F 32px\uFF08\u666E\u901A\u6309\u94AE\u662F 58px\uFF09 */
1446
+ min-width: 32px;
1432
1447
  }
1433
1448
  /* \u53BB\u6389 <summary> \u7684\u6D4F\u89C8\u5668\u9ED8\u8BA4\u4E09\u89D2\u6807\u8BB0 */
1434
1449
  .universal-card-overflow__summary::-webkit-details-marker { display: none; }
@@ -1450,8 +1465,9 @@ var BASE_STYLES_CSS = `/**
1450
1465
  .universal-card-overflow__item:hover { background: #f5f6f7; }
1451
1466
 
1452
1467
  /* ---- \u8868\u5355\uFF08\u9884\u89C8\uFF1A\u4EC5\u900F\u4F20\uFF0C\u65E0\u8868\u5355\u8BED\u4E49\uFF09 ---- */
1453
- .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
1454
- .universal-card-form > * { width: 100%; }
1468
+ /* row-gap 12px\uFF1A\u5BF9\u9F50\u771F\u673A schema 2.0 \u8868\u5355\u5BB9\u5668 */
1469
+ .universal-card-form { display: flex; flex-direction: column; row-gap: 12px; }
1470
+ .universal-card-form > *:not(.universal-card-button) { width: 100%; }
1455
1471
  /* input / select\uFF1A\u9759\u6001\u9884\u89C8\u662F\u53EA\u8BFB\u6846\uFF1Binteractive \u53BB\u6389 --static\uFF0C\u6210\u4E3A\u53EF\u8F93\u5165\u63A7\u4EF6 */
1456
1472
  .universal-card-input,
1457
1473
  .universal-card-input--static {
@@ -1459,6 +1475,13 @@ var BASE_STYLES_CSS = `/**
1459
1475
  font-family: inherit; font-size: 14px; line-height: 20px;
1460
1476
  background: #fff; border: 1px solid #dee0e3; border-radius: 6px;
1461
1477
  color: #1f2329; width: 100%;
1478
+ /* \u771F\u673A input \u9ED8\u8BA4 max-width:282px\uFF08width:"fill" \u65F6\u7531\u5185\u8054\u6837\u5F0F\u653E\u5F00\uFF09 */
1479
+ max-width: 282px;
1480
+ }
1481
+ /* select type:"text"\uFF1A\u771F\u673A\u662F\u65E0\u8FB9\u6846\u3001\u5185\u5BB9\u81EA\u9002\u5E94\u5BBD\u7684\u4E0B\u62C9 */
1482
+ .universal-card-input--text {
1483
+ border-color: transparent; background: transparent;
1484
+ min-height: 26px; padding: 1px 7px; width: auto;
1462
1485
  }
1463
1486
  .universal-card-input--static {
1464
1487
  color: #8f959e; background: #f5f6f7;
@@ -2126,6 +2149,13 @@ var BASE_STYLES_CSS = `/**
2126
2149
  from { opacity: 1; transform: translateY(0); }
2127
2150
  to { opacity: 0; transform: translateY(-100%); margin-bottom: 0; }
2128
2151
  }
2152
+
2153
+ /* ---- \u7A84\u5C4F\u5206\u6863\uFF08\u771F\u673A isWide\uFF1A\u5361\u7247\u5BB9\u5668\u5BBD\u5EA6 > 320px \u4E3A\u5BBD\u5C4F\uFF09 ----
2154
+ \u771F\u673A\u89C4\u5219\uFF1Adefault/primary/primary_filled/danger/danger_filled/laser \u6309\u94AE\u5728\u7A84\u5C4F\u6491\u6EE1\u6574\u884C\uFF0C
2155
+ \u5BBD\u5C4F\u5219\u81EA\u9002\u5E94\u5185\u5BB9\u5BBD\u3002@container \u4E0D\u589E\u52A0\u9009\u62E9\u5668\u6743\u91CD\uFF0C\u9760\u6E90\u7801\u987A\u5E8F\u5728 --auto \u89C4\u5219\u4E4B\u540E\u8986\u76D6\u3002 */
2156
+ @container universal-card (max-width: 320px) {
2157
+ .universal-card-button--auto { width: 100%; }
2158
+ }
2129
2159
  `;
2130
2160
 
2131
2161
  // src/icon-font.ts
@@ -5693,6 +5723,8 @@ function registerBuiltins() {
5693
5723
  ),
5694
5724
  subtitleVNode
5695
5725
  ];
5726
+ const tplKey2 = p.template || "default";
5727
+ const noTemplate = tplKey2 === "default" || tplKey2 === "disabled";
5696
5728
  return h(
5697
5729
  "div",
5698
5730
  {
@@ -5702,7 +5734,7 @@ function registerBuiltins() {
5702
5734
  h(
5703
5735
  "div",
5704
5736
  {
5705
- className: `${CLS}-header__bg`,
5737
+ className: `${CLS}-header__bg${noTemplate ? ` ${CLS}-header__bg--no-template` : ""}`,
5706
5738
  // 内联 base64 高光纹理;图宽=header 宽使亮带居中;padding 透传 DSL(默认 12px)
5707
5739
  style: (() => {
5708
5740
  const s = { backgroundImage: `url(${HEADER_BG_LIGHT})` };
@@ -6235,12 +6267,26 @@ function registerBuiltins() {
6235
6267
  const url = actionUrl || resolveURL(p.url) || p.behaviors?.[0]?.default_url?.url;
6236
6268
  const wv = p.widthValue;
6237
6269
  const widthTok = p.width ?? wv?.value;
6238
- const block = widthTok === "fill";
6239
- const base = `${CLS}-button ${CLS}-button--${type}${block ? ` ${CLS}-button--block` : ""}`;
6270
+ const isFill = widthTok === "fill";
6271
+ const pxWidth = /^\d+(\.\d+)?px$/i.test(String(widthTok ?? "")) ? String(widthTok) : "";
6272
+ const textFamily = type === "text" || type === "primary_text" || type === "danger_text";
6273
+ const block = isFill || textFamily;
6274
+ const auto = !block && !pxWidth;
6275
+ const base = `${CLS}-button ${CLS}-button--${type}` + (block ? ` ${CLS}-button--block` : "") + (auto ? ` ${CLS}-button--auto` : "");
6240
6276
  const style = {};
6241
- if (p.margin) {
6242
- style.width = "100%";
6243
- applyOuterMargin(style, p.margin);
6277
+ if (pxWidth) {
6278
+ style.width = pxWidth;
6279
+ if (p.margin) applyOuterMargin(style, p.margin);
6280
+ } else if (p.margin) {
6281
+ if (block) {
6282
+ style.width = "100%";
6283
+ applyOuterMargin(style, p.margin);
6284
+ } else {
6285
+ applyOuterMargin(style, p.margin);
6286
+ const { left, right } = horizontalMarginPx(p.margin);
6287
+ const inset = left + right;
6288
+ if (inset > 0) style.maxWidth = `calc(100% - ${inset}px)`;
6289
+ }
6244
6290
  }
6245
6291
  const styleProp = Object.keys(style).length ? { style } : {};
6246
6292
  const extra = p;
@@ -6321,15 +6367,53 @@ function registerBuiltins() {
6321
6367
  const name = typeof el.property?.name === "string" ? el.property.name : "";
6322
6368
  return h("form", { className, "data-feishu-form": name }, children);
6323
6369
  });
6370
+ function fieldWidthStyle(p, kind) {
6371
+ const wv = p.widthValue;
6372
+ const raw = String(p.width ?? wv?.value ?? "");
6373
+ const style = {};
6374
+ if (/^\d+(\.\d+)?px$/i.test(raw)) {
6375
+ style.width = raw;
6376
+ style.maxWidth = "100%";
6377
+ return style;
6378
+ }
6379
+ if (raw === "fill") {
6380
+ style.width = "100%";
6381
+ style.maxWidth = "none";
6382
+ return style;
6383
+ }
6384
+ if (kind === "select") {
6385
+ if (raw === "auto") {
6386
+ style.width = "100%";
6387
+ style.maxWidth = "100%";
6388
+ return style;
6389
+ }
6390
+ if (p.type === "text") {
6391
+ style.width = "auto";
6392
+ style.maxWidth = "100%";
6393
+ return style;
6394
+ }
6395
+ style.width = "282px";
6396
+ style.maxWidth = "100%";
6397
+ return style;
6398
+ }
6399
+ style.width = "100%";
6400
+ style.maxWidth = "282px";
6401
+ return style;
6402
+ }
6324
6403
  function renderStaticField(el) {
6325
6404
  const p = el.property || {};
6326
6405
  const ph = extractPlainText(p.placeholder) || "";
6327
- return h("div", { className: `${CLS}-input ${CLS}-input--static` }, ph);
6406
+ const kind = isSelectTag(el.tag) ? "select" : "input";
6407
+ const cls = `${CLS}-input ${CLS}-input--static` + (kind === "select" && p.type === "text" ? ` ${CLS}-input--text` : "");
6408
+ return h("div", { className: cls, style: fieldWidthStyle(p, kind) }, ph);
6328
6409
  }
6329
6410
  function fieldText(value) {
6330
6411
  if (typeof value === "string") return value;
6331
6412
  return extractPlainText(value);
6332
6413
  }
6414
+ function isSelectTag(tag) {
6415
+ return tag === "select_static" || tag === "multi_select_static" || tag === "select_person" || tag === "multi_select_person";
6416
+ }
6333
6417
  function renderInteractiveField(el) {
6334
6418
  const p = el.property || {};
6335
6419
  const ph = extractPlainText(p.placeholder) || "";
@@ -6337,13 +6421,15 @@ function registerBuiltins() {
6337
6421
  const nameProp = name ? { name } : {};
6338
6422
  const callback = readCallbackValue(p);
6339
6423
  const actionProp = callback ? { "data-feishu-action": JSON.stringify(callback), "data-feishu-tag": el.tag } : {};
6340
- if (el.tag === "select_static" || el.tag === "multi_select_static") {
6424
+ if (isSelectTag(el.tag)) {
6341
6425
  const initial = p.initial_option ?? p.initialOption;
6342
6426
  const options = Array.isArray(p.options) ? p.options : [];
6427
+ const cls = `${CLS}-input` + (p.type === "text" ? ` ${CLS}-input--text` : "");
6343
6428
  return h("select", {
6344
- className: `${CLS}-input`,
6429
+ className: cls,
6430
+ style: fieldWidthStyle(p, "select"),
6345
6431
  ...nameProp,
6346
- ...el.tag === "multi_select_static" ? { multiple: true } : {},
6432
+ ...el.tag === "multi_select_static" || el.tag === "multi_select_person" ? { multiple: true } : {},
6347
6433
  ...actionProp
6348
6434
  }, [
6349
6435
  h("option", { value: "" }, ph),
@@ -6362,6 +6448,7 @@ function registerBuiltins() {
6362
6448
  const inputType = el.tag === "date_picker" ? "date" : el.tag === "picker_time" ? "time" : "text";
6363
6449
  return h("input", {
6364
6450
  className: `${CLS}-input`,
6451
+ style: fieldWidthStyle(p, "input"),
6365
6452
  type: inputType,
6366
6453
  placeholder: ph,
6367
6454
  value: typeof def === "string" ? def : "",
package/dist/styles.css CHANGED
@@ -26,6 +26,9 @@
26
26
  width: 100%;
27
27
  max-width: 605px;
28
28
  box-sizing: border-box;
29
+ /* 真机按「卡片容器宽度 > 320px」区分宽/窄屏按钮档位,这里用容器查询复刻 */
30
+ container-type: inline-size;
31
+ container-name: universal-card;
29
32
  /* BEGIN PALETTE VARS (auto-generated from constants.ts PALETTE) */
30
33
  /* 正文 color:var(--O400) 等依赖这些变量;与飞书 :root light 色阶对齐 */
31
34
  --B50: #f0f4ff;
@@ -208,6 +211,8 @@
208
211
  /* 对齐飞书:max(100%, 605px) 140px,窄卡不把高光拉稀 */
209
212
  background-size: max(100%, 605px) 140px;
210
213
  }
214
+ /* default/disabled 模板真机不设背景色(bg_color 为空串),此时 header 不留底部内边距 */
215
+ .universal-card-header__bg--no-template { padding-bottom: 0; }
211
216
  /* 内容行:左侧图标 + 右侧标题区(对齐网页版 header__content = row) */
212
217
  .universal-card-header__content {
213
218
  width: 100%;
@@ -274,7 +279,9 @@
274
279
  column-gap: 12px;
275
280
  align-items: flex-start;
276
281
  }
277
- .universal-card-body > * { width: 100%; box-sizing: border-box; }
282
+ /* 交互控件不参与「子元素撑满」:按钮的宽度由真机规则决定(见 .universal-card-button--auto),
283
+ 若被这条 blanket 规则拉到 100%,窄屏/宽屏分档就失效了。 */
284
+ .universal-card-body > *:not(.universal-card-button) { width: 100%; box-sizing: border-box; }
278
285
 
279
286
  /* ---- markdown 段落 ---- */
280
287
  /* 对齐网页版:line-height 默认 1.6;具体字号/行高由组件内联 style 按 text_size 给出 */
@@ -394,10 +401,14 @@
394
401
  flex-shrink: 0; height: 1.2em; color: inherit;
395
402
  }
396
403
  .universal-card-button__icon .universal-card-icon { color: inherit; }
397
- /* 宽度:对齐真机——body > * { width:100% }、列 flex stretch 会把按钮撑满所在行/列。
398
- 不再强制 width:auto(那是网页版观感;真机独立成行的按钮默认占满整行)。
399
- width:"fill" 显式挂 --block,与缺省撑满同宽,并供 margin calc 识别。 */
400
- .universal-card-button--block { width: 100%; }
404
+ /* 宽度:对齐真机 ud__button 的 isWide 分档(真机按「卡片容器宽度 > 320px」判定):
405
+ 宽屏 → 自适应内容宽(--auto);窄屏(≤320px)→ 撑满整行(下方 @container 规则覆盖)。
406
+ text 族与 width:"fill" 恒定撑满(--block)。
407
+ align-self:flex-start —— 父级(body/form/column)默认 align-items:stretch,
408
+ 只写 width:auto 仍会被拉伸,必须同时阻止拉伸。 */
409
+ .universal-card-button { min-width: 58px; align-self: flex-start; }
410
+ .universal-card-button--block { width: 100%; min-width: unset; }
411
+ .universal-card-button--auto { width: auto; }
401
412
 
402
413
  /* —— outlined 描边族 —— */
403
414
  /* default:灰描边 + 白底 + 深字(未加变体类的裸 .universal-card-button 已是白底深字,
@@ -424,6 +435,8 @@
424
435
  .universal-card-button--danger_text {
425
436
  border-color: transparent; background: transparent; padding: 4px 8px;
426
437
  justify-content: flex-start;
438
+ /* 真机 text 族按钮不设最小宽度 */
439
+ min-width: unset;
427
440
  }
428
441
  .universal-card-button--text { color: #1f2329; }
429
442
  .universal-card-button--primary_text { color: #1456f0; }
@@ -459,6 +472,8 @@
459
472
  .universal-card-overflow--static { display: inline-flex; }
460
473
  .universal-card-overflow__summary {
461
474
  list-style: none; cursor: pointer; display: inline-flex;
475
+ /* 真机折叠按钮的最小宽度是 32px(普通按钮是 58px) */
476
+ min-width: 32px;
462
477
  }
463
478
  /* 去掉 <summary> 的浏览器默认三角标记 */
464
479
  .universal-card-overflow__summary::-webkit-details-marker { display: none; }
@@ -480,8 +495,9 @@
480
495
  .universal-card-overflow__item:hover { background: #f5f6f7; }
481
496
 
482
497
  /* ---- 表单(预览:仅透传,无表单语义) ---- */
483
- .universal-card-form { display: flex; flex-direction: column; row-gap: 8px; }
484
- .universal-card-form > * { width: 100%; }
498
+ /* row-gap 12px:对齐真机 schema 2.0 表单容器 */
499
+ .universal-card-form { display: flex; flex-direction: column; row-gap: 12px; }
500
+ .universal-card-form > *:not(.universal-card-button) { width: 100%; }
485
501
  /* input / select:静态预览是只读框;interactive 去掉 --static,成为可输入控件 */
486
502
  .universal-card-input,
487
503
  .universal-card-input--static {
@@ -489,6 +505,13 @@
489
505
  font-family: inherit; font-size: 14px; line-height: 20px;
490
506
  background: #fff; border: 1px solid #dee0e3; border-radius: 6px;
491
507
  color: #1f2329; width: 100%;
508
+ /* 真机 input 默认 max-width:282px(width:"fill" 时由内联样式放开) */
509
+ max-width: 282px;
510
+ }
511
+ /* select type:"text":真机是无边框、内容自适应宽的下拉 */
512
+ .universal-card-input--text {
513
+ border-color: transparent; background: transparent;
514
+ min-height: 26px; padding: 1px 7px; width: auto;
492
515
  }
493
516
  .universal-card-input--static {
494
517
  color: #8f959e; background: #f5f6f7;
@@ -1156,3 +1179,10 @@
1156
1179
  from { opacity: 1; transform: translateY(0); }
1157
1180
  to { opacity: 0; transform: translateY(-100%); margin-bottom: 0; }
1158
1181
  }
1182
+
1183
+ /* ---- 窄屏分档(真机 isWide:卡片容器宽度 > 320px 为宽屏) ----
1184
+ 真机规则:default/primary/primary_filled/danger/danger_filled/laser 按钮在窄屏撑满整行,
1185
+ 宽屏则自适应内容宽。@container 不增加选择器权重,靠源码顺序在 --auto 规则之后覆盖。 */
1186
+ @container universal-card (max-width: 320px) {
1187
+ .universal-card-button--auto { width: 100%; }
1188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-feishu-card/renderer",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "零框架依赖的飞书卡片渲染引擎:Web / DOM / SSR,输入 RAW 或公开 DSL,输出真实 DOM 或 HTML 字符串",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",