@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/all.css +10 -14
- package/dist/index.cjs +116 -36
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +116 -36
- package/dist/styles.css +10 -14
- package/package.json +2 -2
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
|
|
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
|
|
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;
|
|
1391
|
-
background: #
|
|
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
|
-
|
|
5999
|
-
"
|
|
6000
|
-
|
|
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
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
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
|
|
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
|
|
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;
|
|
1390
|
-
background: #
|
|
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
|
-
|
|
5998
|
-
"
|
|
5999
|
-
|
|
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
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
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
|
|
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
|
|
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;
|
|
462
|
-
background: #
|
|
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.
|
|
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.
|
|
31
|
+
"@open-feishu-card/adapter": "0.3.17"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"tsup": "^8.3.5",
|