@nocobase/flow-engine 2.0.0-alpha.38 → 2.0.0-alpha.39

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { FC, ReactNode } from 'react';
10
+ interface MobilePopupProps {
11
+ title?: string;
12
+ visible: boolean;
13
+ minHeight?: number | string;
14
+ onClose: () => void;
15
+ children: ReactNode;
16
+ className?: string;
17
+ footer?: ReactNode;
18
+ }
19
+ export declare const MobilePopup: FC<MobilePopupProps>;
20
+ export {};
@@ -0,0 +1,102 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __create = Object.create;
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
+ var MobilePopup_exports = {};
39
+ __export(MobilePopup_exports, {
40
+ MobilePopup: () => MobilePopup
41
+ });
42
+ module.exports = __toCommonJS(MobilePopup_exports);
43
+ var import_antd = require("antd");
44
+ var import_antd_mobile = require("antd-mobile");
45
+ var import_react = __toESM(require("react"));
46
+ var import_antd_mobile_icons = require("antd-mobile-icons");
47
+ var import_MobilePopup = require("./MobilePopup.style");
48
+ var import_react_i18next = require("react-i18next");
49
+ const MobilePopup = /* @__PURE__ */ __name((props) => {
50
+ const { title, visible, onClose: closePopup, children, minHeight, className, footer } = props;
51
+ const { t } = (0, import_react_i18next.useTranslation)();
52
+ const { componentCls, hashId } = (0, import_MobilePopup.useMobileActionDrawerStyle)();
53
+ const style = (0, import_react.useMemo)(() => {
54
+ return {
55
+ minHeight
56
+ };
57
+ }, [minHeight]);
58
+ const theme = (0, import_react.useMemo)(() => {
59
+ return {
60
+ token: {
61
+ paddingPageHorizontal: 8,
62
+ paddingPageVertical: 8,
63
+ marginBlock: 12,
64
+ borderRadiusBlock: 8,
65
+ fontSize: 14
66
+ }
67
+ };
68
+ }, []);
69
+ return /* @__PURE__ */ import_react.default.createElement(import_antd.ConfigProvider, { theme }, /* @__PURE__ */ import_react.default.createElement(
70
+ import_antd_mobile.Popup,
71
+ {
72
+ className: `${componentCls} ${hashId} ${className || ""}`,
73
+ visible,
74
+ onClose: closePopup,
75
+ onMaskClick: closePopup,
76
+ bodyClassName: "nb-mobile-action-drawer-body",
77
+ bodyStyle: {
78
+ padding: 0
79
+ },
80
+ maskStyle: style,
81
+ style,
82
+ destroyOnClose: true
83
+ },
84
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "nb-mobile-action-drawer-header" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "nb-mobile-action-drawer-placeholder" }, /* @__PURE__ */ import_react.default.createElement(import_antd_mobile_icons.CloseOutline, null)), /* @__PURE__ */ import_react.default.createElement("span", null, title), /* @__PURE__ */ import_react.default.createElement(
85
+ "span",
86
+ {
87
+ className: "nb-mobile-action-drawer-close-icon",
88
+ onClick: closePopup,
89
+ role: "button",
90
+ tabIndex: 0,
91
+ "aria-label": t("Close")
92
+ },
93
+ /* @__PURE__ */ import_react.default.createElement(import_antd_mobile_icons.CloseOutline, null)
94
+ )),
95
+ children,
96
+ footer && /* @__PURE__ */ import_react.default.createElement("div", { className: "nb-mobile-action-drawer-footer" }, footer)
97
+ ));
98
+ }, "MobilePopup");
99
+ // Annotate the CommonJS export names for ESM import in node:
100
+ 0 && (module.exports = {
101
+ MobilePopup
102
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { useStyleRegister } from '@ant-design/cssinjs';
10
+ type UseComponentStyleResult = {
11
+ wrapSSR: ReturnType<typeof useStyleRegister>;
12
+ hashId: string;
13
+ componentCls: string;
14
+ rootPrefixCls: string;
15
+ };
16
+ export declare const useMobileActionDrawerStyle: (props?: any) => UseComponentStyleResult;
17
+ export {};
@@ -0,0 +1,186 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var MobilePopup_style_exports = {};
29
+ __export(MobilePopup_style_exports, {
30
+ useMobileActionDrawerStyle: () => useMobileActionDrawerStyle
31
+ });
32
+ module.exports = __toCommonJS(MobilePopup_style_exports);
33
+ var import_cssinjs = require("@ant-design/cssinjs");
34
+ var import_shared = require("@formily/shared");
35
+ var import_react = require("react");
36
+ var import_antd = require("antd");
37
+ const usePrefixCls = /* @__PURE__ */ __name((tag, props) => {
38
+ const { getPrefixCls } = (0, import_react.useContext)(import_antd.ConfigProvider.ConfigContext) || {};
39
+ if ("ConfigContext" in import_antd.ConfigProvider) {
40
+ return (getPrefixCls == null ? void 0 : getPrefixCls(tag, props == null ? void 0 : props.prefixCls)) || "";
41
+ } else {
42
+ const prefix = (props == null ? void 0 : props.prefixCls) ?? "ant-";
43
+ return `${prefix}${tag ?? ""}`;
44
+ }
45
+ }, "usePrefixCls");
46
+ const { ConfigContext } = import_antd.ConfigProvider;
47
+ const useConfig = /* @__PURE__ */ __name(() => (0, import_react.useContext)(ConfigContext), "useConfig");
48
+ const useToken = import_antd.theme.useToken;
49
+ const genCommonStyle = /* @__PURE__ */ __name((token, componentPrefixCls) => {
50
+ const { fontFamily, fontSize } = token;
51
+ const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`;
52
+ return {
53
+ [rootPrefixSelector]: {
54
+ fontFamily,
55
+ fontSize,
56
+ boxSizing: "border-box",
57
+ "&::before, &::after": {
58
+ boxSizing: "border-box"
59
+ },
60
+ [rootPrefixSelector]: {
61
+ boxSizing: "border-box",
62
+ "&::before, &::after": {
63
+ boxSizing: "border-box"
64
+ }
65
+ }
66
+ }
67
+ };
68
+ }, "genCommonStyle");
69
+ const genStyleHook = /* @__PURE__ */ __name((component, styleFn) => {
70
+ return (props) => {
71
+ const { theme: theme2, token, hashId } = useToken();
72
+ const { getPrefixCls, iconPrefixCls } = useConfig();
73
+ const prefixCls = usePrefixCls(component);
74
+ const rootPrefixCls = getPrefixCls();
75
+ const wrapSSR = (0, import_cssinjs.useStyleRegister)(
76
+ {
77
+ theme: theme2,
78
+ token,
79
+ hashId,
80
+ path: ["formily-antd", component, prefixCls, iconPrefixCls]
81
+ },
82
+ () => {
83
+ const componentCls = `.${prefixCls}`;
84
+ const mergedToken = (0, import_shared.merge)(token, {
85
+ componentCls,
86
+ prefixCls,
87
+ iconCls: `.${iconPrefixCls}`,
88
+ antCls: `.${rootPrefixCls}`
89
+ });
90
+ const styleInterpolation = styleFn(mergedToken, props, {
91
+ hashId,
92
+ prefixCls,
93
+ rootPrefixCls,
94
+ iconPrefixCls
95
+ });
96
+ return [genCommonStyle(token, prefixCls), styleInterpolation];
97
+ }
98
+ );
99
+ const memoizedWrapSSR = (0, import_react.useMemo)(() => {
100
+ return wrapSSR;
101
+ }, [theme2, token, hashId, prefixCls, iconPrefixCls, rootPrefixCls, props]);
102
+ return {
103
+ wrapSSR: memoizedWrapSSR,
104
+ hashId,
105
+ componentCls: prefixCls,
106
+ rootPrefixCls
107
+ };
108
+ };
109
+ }, "genStyleHook");
110
+ const useMobileActionDrawerStyle = genStyleHook("nb-mobile-action-drawer", (token) => {
111
+ const { componentCls } = token;
112
+ return {
113
+ [componentCls]: {
114
+ ".nb-mobile-action-drawer-header": {
115
+ height: "var(--nb-mobile-page-header-height)",
116
+ display: "flex",
117
+ alignItems: "center",
118
+ justifyContent: "space-between",
119
+ borderBottom: `1px solid ${token.colorSplit}`,
120
+ position: "sticky",
121
+ top: 0,
122
+ backgroundColor: "white",
123
+ zIndex: 1e3,
124
+ // to match the button named 'Add block'
125
+ "& + .nb-grid-container > .nb-grid > .nb-grid-warp > .ant-btn": {
126
+ margin: 12
127
+ }
128
+ },
129
+ ".nb-mobile-action-drawer-placeholder": {
130
+ display: "inline-block",
131
+ padding: 12,
132
+ visibility: "hidden"
133
+ },
134
+ ".nb-mobile-action-drawer-close-icon": {
135
+ display: "inline-block",
136
+ padding: 12,
137
+ cursor: "pointer"
138
+ },
139
+ ".nb-mobile-action-drawer-body": {
140
+ borderTopLeftRadius: 8,
141
+ borderTopRightRadius: 8,
142
+ maxHeight: "calc(100% - var(--nb-mobile-page-header-height))",
143
+ overflowY: "auto",
144
+ overflowX: "hidden",
145
+ backgroundColor: token.colorBgLayout,
146
+ // 不带 tab 页的半窗
147
+ "& > .nb-grid-container": {
148
+ padding: `${token.paddingPageVertical}px ${token.paddingPageHorizontal}px`
149
+ },
150
+ // 带有 tab 页的半窗
151
+ ".ant-tabs-nav": {
152
+ marginBottom: "0px !important",
153
+ padding: `0 ${token.paddingPageHorizontal + token.borderRadiusBlock / 2}px`,
154
+ backgroundColor: token.colorBgContainer
155
+ },
156
+ // clear the margin-bottom of the last block
157
+ "& > .nb-grid-container > .nb-grid > .nb-grid-warp > .nb-grid-row:nth-last-child(2) .noco-card-item": {
158
+ marginBottom: 0,
159
+ "& > .ant-card": {
160
+ marginBottom: "0 !important"
161
+ }
162
+ }
163
+ },
164
+ ".nb-mobile-action-drawer-footer": {
165
+ padding: "8px var(--nb-mobile-page-tabs-content-padding)",
166
+ display: "flex",
167
+ alignItems: "center",
168
+ justifyContent: "flex-end",
169
+ position: "sticky",
170
+ bottom: 0,
171
+ left: 0,
172
+ right: 0,
173
+ zIndex: 1e3,
174
+ borderTop: `1px solid ${token.colorSplit}`,
175
+ backgroundColor: token.colorBgLayout,
176
+ ".ant-btn": {
177
+ marginLeft: 8
178
+ }
179
+ }
180
+ }
181
+ };
182
+ });
183
+ // Annotate the CommonJS export names for ESM import in node:
184
+ 0 && (module.exports = {
185
+ useMobileActionDrawerStyle
186
+ });
@@ -16,3 +16,4 @@ export * from './FormItem';
16
16
  export * from './settings';
17
17
  export * from './subModel';
18
18
  export * from './variables';
19
+ export * from './MobilePopup';
@@ -33,6 +33,7 @@ __reExport(components_exports, require("./FormItem"), module.exports);
33
33
  __reExport(components_exports, require("./settings"), module.exports);
34
34
  __reExport(components_exports, require("./subModel"), module.exports);
35
35
  __reExport(components_exports, require("./variables"), module.exports);
36
+ __reExport(components_exports, require("./MobilePopup"), module.exports);
36
37
  // Annotate the CommonJS export names for ESM import in node:
37
38
  0 && (module.exports = {
38
39
  ...require("./common/FlowSettingsButton"),
@@ -44,5 +45,6 @@ __reExport(components_exports, require("./variables"), module.exports);
44
45
  ...require("./FormItem"),
45
46
  ...require("./settings"),
46
47
  ...require("./subModel"),
47
- ...require("./variables")
48
+ ...require("./variables"),
49
+ ...require("./MobilePopup")
48
50
  });
@@ -91,6 +91,7 @@ export declare class Collection {
91
91
  dataSource: DataSource;
92
92
  constructor(options?: Record<string, any>);
93
93
  getFilterByTK(record: any): any;
94
+ get titleableFields(): CollectionField[];
94
95
  get hidden(): any;
95
96
  get flowEngine(): FlowEngine;
96
97
  get collectionManager(): CollectionManager;
@@ -149,6 +150,7 @@ export declare class CollectionField {
149
150
  get resourceName(): string;
150
151
  get collectionName(): any;
151
152
  get readonly(): any;
153
+ get titleable(): boolean;
152
154
  get fullpath(): string;
153
155
  get name(): any;
154
156
  get type(): any;
@@ -346,6 +346,9 @@ const _Collection = class _Collection {
346
346
  }
347
347
  return import_lodash.default.pick(record, this.filterTargetKey);
348
348
  }
349
+ get titleableFields() {
350
+ return this.getFields().filter((field) => field.titleable);
351
+ }
349
352
  get hidden() {
350
353
  return this.options.hidden || false;
351
354
  }
@@ -580,6 +583,9 @@ const _CollectionField = class _CollectionField {
580
583
  var _a;
581
584
  return this.options.readonly || ((_a = this.options.uiSchema) == null ? void 0 : _a["x-read-pretty"]) || false;
582
585
  }
586
+ get titleable() {
587
+ return !!(this.options.titleable ?? this.options.titleUsable);
588
+ }
583
589
  get fullpath() {
584
590
  return this.collection.dataSource.key + "." + this.collection.name + "." + this.name;
585
591
  }
@@ -14,5 +14,8 @@ import type { FlowModel } from '../models';
14
14
  * @returns 翻译函数,自动使用 flow-engine 命名空间
15
15
  */
16
16
  export declare function getT(model: FlowModel): (key: string, options?: any) => string;
17
+ export declare function tExpr(text: TFuncKey | TFuncKey[], options?: TOptions): string;
18
+ /**
19
+ * @deprecated use tExpr from `@nocobase/flow-engine` instead
20
+ */
17
21
  export declare function escapeT(text: TFuncKey | TFuncKey[], options?: TOptions): string;
18
- export { escapeT as tExpr };
@@ -29,7 +29,7 @@ var translation_exports = {};
29
29
  __export(translation_exports, {
30
30
  escapeT: () => escapeT,
31
31
  getT: () => getT,
32
- tExpr: () => escapeT
32
+ tExpr: () => tExpr
33
33
  });
34
34
  module.exports = __toCommonJS(translation_exports);
35
35
  var import_constants = require("./constants");
@@ -43,12 +43,16 @@ function getT(model) {
43
43
  return (key) => key;
44
44
  }
45
45
  __name(getT, "getT");
46
- function escapeT(text, options) {
46
+ function tExpr(text, options) {
47
47
  if (options) {
48
48
  return `{{t(${JSON.stringify(text)}, ${JSON.stringify(options)})}}`;
49
49
  }
50
50
  return `{{t(${JSON.stringify(text)})}}`;
51
51
  }
52
+ __name(tExpr, "tExpr");
53
+ function escapeT(text, options) {
54
+ return tExpr(text, options);
55
+ }
52
56
  __name(escapeT, "escapeT");
53
57
  // Annotate the CommonJS export names for ESM import in node:
54
58
  0 && (module.exports = {
@@ -42,9 +42,10 @@ __export(DrawerComponent_exports, {
42
42
  module.exports = __toCommonJS(DrawerComponent_exports);
43
43
  var import_antd = require("antd");
44
44
  var React = __toESM(require("react"));
45
+ var import_MobilePopup = require("../components/MobilePopup");
45
46
  const DrawerComponent = React.forwardRef((props, ref) => {
46
47
  var _a;
47
- const { children, footer: initialFooter, title, extra, hidden, ...drawerProps } = props;
48
+ const { children, footer: initialFooter, title, extra, hidden, isMobile, ...drawerProps } = props;
48
49
  const [open, setOpen] = React.useState(true);
49
50
  const [footer, setFooter] = React.useState(() => initialFooter);
50
51
  const [header, setHeader] = React.useState({ title, extra, ...drawerProps.header });
@@ -75,6 +76,24 @@ const DrawerComponent = React.forwardRef((props, ref) => {
75
76
  const container = React.useMemo(() => {
76
77
  return document.querySelector("#nocobase-app-container");
77
78
  }, []);
79
+ if (isMobile) {
80
+ return /* @__PURE__ */ React.createElement(
81
+ import_MobilePopup.MobilePopup,
82
+ {
83
+ className: hidden ? "nb-hidden" : "",
84
+ visible: open,
85
+ ...drawerProps,
86
+ footer,
87
+ ...header,
88
+ onClose: () => {
89
+ var _a2;
90
+ setOpen(false);
91
+ (_a2 = drawerProps.afterClose) == null ? void 0 : _a2.call(drawerProps);
92
+ }
93
+ },
94
+ children
95
+ );
96
+ }
78
97
  return /* @__PURE__ */ React.createElement(
79
98
  import_antd.Drawer,
80
99
  {
@@ -187,7 +187,8 @@ function useDrawer() {
187
187
  (_a3 = config.onClose) == null ? void 0 : _a3.call(config);
188
188
  resolvePromise == null ? void 0 : resolvePromise(config.result);
189
189
  scopedEngine.unlinkFromStack();
190
- }
190
+ },
191
+ isMobile: ctx.isMobileLayout
191
192
  },
192
193
  content,
193
194
  props.children
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.0.0-alpha.38",
3
+ "version": "2.0.0-alpha.39",
4
4
  "private": false,
5
5
  "description": "A standalone flow engine for NocoBase, managing workflows, models, and actions.",
6
6
  "main": "lib/index.js",
@@ -8,8 +8,8 @@
8
8
  "dependencies": {
9
9
  "@formily/antd-v5": "1.x",
10
10
  "@formily/reactive": "2.x",
11
- "@nocobase/sdk": "2.0.0-alpha.38",
12
- "@nocobase/shared": "2.0.0-alpha.38",
11
+ "@nocobase/sdk": "2.0.0-alpha.39",
12
+ "@nocobase/shared": "2.0.0-alpha.39",
13
13
  "ahooks": "^3.7.2",
14
14
  "dayjs": "^1.11.9",
15
15
  "dompurify": "^3.0.2",
@@ -36,5 +36,5 @@
36
36
  ],
37
37
  "author": "NocoBase Team",
38
38
  "license": "AGPL-3.0",
39
- "gitHead": "30b1723601099007f7641c6ffa4111c880cb44e4"
39
+ "gitHead": "f716c78b4389d45ebe8996bcabcca976b4410aa2"
40
40
  }
@@ -82,6 +82,8 @@ vi.mock('antd', () => {
82
82
  Paragraph: ({ children }: any) => children ?? 'Paragraph',
83
83
  Text: ({ children }: any) => children ?? 'Text',
84
84
  },
85
+ ConfigProvider: ({ children }: any) => children ?? 'ConfigProvider',
86
+ theme: { useToken: () => ({}) },
85
87
  };
86
88
  });
87
89
 
@@ -95,6 +95,8 @@ vi.mock('antd', () => {
95
95
  Paragraph: vi.fn(({ children }: any) => children ?? 'Paragraph'),
96
96
  Text: vi.fn(({ children }: any) => children ?? 'Text'),
97
97
  },
98
+ ConfigProvider: ({ children }: any) => children ?? 'ConfigProvider',
99
+ theme: { useToken: () => ({}) },
98
100
  } as any;
99
101
  });
100
102
 
@@ -0,0 +1,220 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
11
+ import { useStyleRegister } from '@ant-design/cssinjs';
12
+ import { merge } from '@formily/shared';
13
+ import type { ComponentTokenMap } from 'antd/es/theme/interface';
14
+ import { useMemo, useContext } from 'react';
15
+ import { ConfigProvider, theme } from 'antd';
16
+
17
+ const usePrefixCls = (
18
+ tag?: string,
19
+ props?: {
20
+ prefixCls?: string;
21
+ },
22
+ ) => {
23
+ const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) || {};
24
+ if ('ConfigContext' in ConfigProvider) {
25
+ return getPrefixCls?.(tag, props?.prefixCls) || '';
26
+ } else {
27
+ const prefix = props?.prefixCls ?? 'ant-';
28
+ return `${prefix}${tag ?? ''}`;
29
+ }
30
+ };
31
+ const { ConfigContext } = ConfigProvider;
32
+ const useConfig = () => useContext(ConfigContext);
33
+ const useToken = theme.useToken;
34
+
35
+ type OverrideComponent = keyof ComponentTokenMap | string;
36
+
37
+ interface StyleInfo {
38
+ hashId: string;
39
+ prefixCls: string;
40
+ rootPrefixCls: string;
41
+ iconPrefixCls: string;
42
+ }
43
+
44
+ type TokenWithCommonCls<T> = T & {
45
+ /** Wrap component class with `.` prefix */
46
+ componentCls: string;
47
+ /** Origin prefix which do not have `.` prefix */
48
+ prefixCls: string;
49
+ /** Wrap icon class with `.` prefix */
50
+ iconCls: string;
51
+ /** Wrap ant prefixCls class with `.` prefix */
52
+ antCls: string;
53
+ };
54
+
55
+ const genCommonStyle = (token: any, componentPrefixCls: string): CSSObject => {
56
+ const { fontFamily, fontSize } = token;
57
+
58
+ const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`;
59
+
60
+ return {
61
+ [rootPrefixSelector]: {
62
+ fontFamily,
63
+ fontSize,
64
+ boxSizing: 'border-box',
65
+
66
+ '&::before, &::after': {
67
+ boxSizing: 'border-box',
68
+ },
69
+
70
+ [rootPrefixSelector]: {
71
+ boxSizing: 'border-box',
72
+
73
+ '&::before, &::after': {
74
+ boxSizing: 'border-box',
75
+ },
76
+ },
77
+ },
78
+ };
79
+ };
80
+ type UseComponentStyleResult = {
81
+ wrapSSR: ReturnType<typeof useStyleRegister>;
82
+ hashId: string;
83
+ componentCls: string;
84
+ rootPrefixCls: string;
85
+ };
86
+
87
+ const genStyleHook = <ComponentName extends OverrideComponent>(
88
+ component: ComponentName,
89
+ styleFn: (token: TokenWithCommonCls<any>, props: any, info: StyleInfo) => CSSInterpolation,
90
+ ) => {
91
+ return (props?: any): UseComponentStyleResult => {
92
+ const { theme, token, hashId } = useToken();
93
+ const { getPrefixCls, iconPrefixCls } = useConfig();
94
+ const prefixCls = usePrefixCls(component);
95
+ const rootPrefixCls = getPrefixCls();
96
+
97
+ const wrapSSR = useStyleRegister(
98
+ {
99
+ theme: theme as any,
100
+ token,
101
+ hashId,
102
+ path: ['formily-antd', component, prefixCls, iconPrefixCls],
103
+ },
104
+ () => {
105
+ const componentCls = `.${prefixCls}`;
106
+ const mergedToken: TokenWithCommonCls<any> = merge(token, {
107
+ componentCls,
108
+ prefixCls,
109
+ iconCls: `.${iconPrefixCls}`,
110
+ antCls: `.${rootPrefixCls}`,
111
+ });
112
+
113
+ const styleInterpolation = styleFn(mergedToken, props, {
114
+ hashId,
115
+ prefixCls,
116
+ rootPrefixCls,
117
+ iconPrefixCls,
118
+ });
119
+ return [genCommonStyle(token, prefixCls), styleInterpolation];
120
+ },
121
+ );
122
+
123
+ // useStyleRegister 有 BUG,会导致重复渲染,所以这里做了一层缓存
124
+ // 等 https://github.com/ant-design/cssinjs/pull/176 合并后,可以去掉这层缓存
125
+ const memoizedWrapSSR = useMemo(() => {
126
+ return wrapSSR;
127
+ }, [theme, token, hashId, prefixCls, iconPrefixCls, rootPrefixCls, props]);
128
+
129
+ return {
130
+ wrapSSR: memoizedWrapSSR,
131
+ hashId,
132
+ componentCls: prefixCls,
133
+ rootPrefixCls,
134
+ };
135
+ };
136
+ };
137
+
138
+ export const useMobileActionDrawerStyle = genStyleHook('nb-mobile-action-drawer', (token) => {
139
+ const { componentCls } = token;
140
+ return {
141
+ [componentCls]: {
142
+ '.nb-mobile-action-drawer-header': {
143
+ height: 'var(--nb-mobile-page-header-height)',
144
+ display: 'flex',
145
+ alignItems: 'center',
146
+ justifyContent: 'space-between',
147
+ borderBottom: `1px solid ${token.colorSplit}`,
148
+ position: 'sticky',
149
+ top: 0,
150
+ backgroundColor: 'white',
151
+ zIndex: 1000,
152
+
153
+ // to match the button named 'Add block'
154
+ '& + .nb-grid-container > .nb-grid > .nb-grid-warp > .ant-btn': {
155
+ margin: 12,
156
+ },
157
+ },
158
+
159
+ '.nb-mobile-action-drawer-placeholder': {
160
+ display: 'inline-block',
161
+ padding: 12,
162
+ visibility: 'hidden',
163
+ },
164
+
165
+ '.nb-mobile-action-drawer-close-icon': {
166
+ display: 'inline-block',
167
+ padding: 12,
168
+ cursor: 'pointer',
169
+ },
170
+
171
+ '.nb-mobile-action-drawer-body': {
172
+ borderTopLeftRadius: 8,
173
+ borderTopRightRadius: 8,
174
+ maxHeight: 'calc(100% - var(--nb-mobile-page-header-height))',
175
+ overflowY: 'auto',
176
+ overflowX: 'hidden',
177
+ backgroundColor: token.colorBgLayout,
178
+
179
+ // 不带 tab 页的半窗
180
+ '& > .nb-grid-container': {
181
+ padding: `${token.paddingPageVertical}px ${token.paddingPageHorizontal}px`,
182
+ },
183
+
184
+ // 带有 tab 页的半窗
185
+ '.ant-tabs-nav': {
186
+ marginBottom: '0px !important',
187
+ padding: `0 ${token.paddingPageHorizontal + token.borderRadiusBlock / 2}px`,
188
+ backgroundColor: token.colorBgContainer,
189
+ },
190
+
191
+ // clear the margin-bottom of the last block
192
+ '& > .nb-grid-container > .nb-grid > .nb-grid-warp > .nb-grid-row:nth-last-child(2) .noco-card-item': {
193
+ marginBottom: 0,
194
+
195
+ '& > .ant-card': {
196
+ marginBottom: '0 !important',
197
+ },
198
+ },
199
+ },
200
+
201
+ '.nb-mobile-action-drawer-footer': {
202
+ padding: '8px var(--nb-mobile-page-tabs-content-padding)',
203
+ display: 'flex',
204
+ alignItems: 'center',
205
+ justifyContent: 'flex-end',
206
+ position: 'sticky',
207
+ bottom: 0,
208
+ left: 0,
209
+ right: 0,
210
+ zIndex: 1000,
211
+ borderTop: `1px solid ${token.colorSplit}`,
212
+ backgroundColor: token.colorBgLayout,
213
+
214
+ '.ant-btn': {
215
+ marginLeft: 8,
216
+ },
217
+ },
218
+ },
219
+ };
220
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { ConfigProvider } from 'antd';
11
+ import { Popup } from 'antd-mobile';
12
+ import React, { FC, ReactNode, useMemo } from 'react';
13
+ import { CloseOutline } from 'antd-mobile-icons';
14
+ import { useMobileActionDrawerStyle } from './MobilePopup.style';
15
+ import { useTranslation } from 'react-i18next';
16
+
17
+ interface MobilePopupProps {
18
+ title?: string;
19
+ visible: boolean;
20
+ minHeight?: number | string;
21
+ onClose: () => void;
22
+ children: ReactNode;
23
+ className?: string;
24
+ footer?: ReactNode;
25
+ }
26
+
27
+ export const MobilePopup: FC<MobilePopupProps> = (props) => {
28
+ const { title, visible, onClose: closePopup, children, minHeight, className, footer } = props;
29
+ const { t } = useTranslation();
30
+ const { componentCls, hashId } = useMobileActionDrawerStyle();
31
+
32
+ const style = useMemo(() => {
33
+ return {
34
+ minHeight,
35
+ };
36
+ }, [minHeight]);
37
+
38
+ const theme = useMemo(() => {
39
+ return {
40
+ token: {
41
+ paddingPageHorizontal: 8,
42
+ paddingPageVertical: 8,
43
+ marginBlock: 12,
44
+ borderRadiusBlock: 8,
45
+ fontSize: 14,
46
+ },
47
+ };
48
+ }, []);
49
+
50
+ return (
51
+ <ConfigProvider theme={theme}>
52
+ <Popup
53
+ className={`${componentCls} ${hashId} ${className || ''}`}
54
+ visible={visible}
55
+ onClose={closePopup}
56
+ onMaskClick={closePopup}
57
+ bodyClassName="nb-mobile-action-drawer-body"
58
+ bodyStyle={{
59
+ padding: 0,
60
+ }}
61
+ maskStyle={style}
62
+ style={style}
63
+ destroyOnClose
64
+ >
65
+ <div className="nb-mobile-action-drawer-header">
66
+ {/* used to make the title center */}
67
+ <span className="nb-mobile-action-drawer-placeholder">
68
+ <CloseOutline />
69
+ </span>
70
+ <span>{title}</span>
71
+ <span
72
+ className="nb-mobile-action-drawer-close-icon"
73
+ onClick={closePopup}
74
+ role="button"
75
+ tabIndex={0}
76
+ aria-label={t('Close')}
77
+ >
78
+ <CloseOutline />
79
+ </span>
80
+ </div>
81
+ {children}
82
+ {footer && <div className="nb-mobile-action-drawer-footer">{footer}</div>}
83
+ </Popup>
84
+ </ConfigProvider>
85
+ );
86
+ };
@@ -13,7 +13,6 @@ import { render, cleanup, waitFor } from '@testing-library/react';
13
13
  import { App, ConfigProvider } from 'antd';
14
14
  import { FlowEngine } from '../../flowEngine';
15
15
  import { FlowModel, ModelRenderMode } from '../../models/flowModel';
16
- import { DefaultSettingsIcon } from '../settings/wrappers/contextual/DefaultSettingsIcon';
17
16
  import { FlowEngineProvider } from '../../provider';
18
17
  import { FlowModelRenderer } from '../FlowModelRenderer';
19
18
 
@@ -81,6 +80,7 @@ vi.mock('antd', () => {
81
80
  Alert,
82
81
  Skeleton,
83
82
  Spin,
83
+ theme: { useToken: () => ({}) },
84
84
  } as any;
85
85
  });
86
86
 
@@ -17,4 +17,5 @@ export * from './FormItem';
17
17
  export * from './settings';
18
18
  export * from './subModel';
19
19
  export * from './variables';
20
+ export * from './MobilePopup';
20
21
  //
@@ -75,6 +75,7 @@ vi.mock('antd', async (importOriginal) => {
75
75
  Alert,
76
76
  Button,
77
77
  Result,
78
+ theme: { useToken: () => ({}) },
78
79
  };
79
80
  });
80
81
 
@@ -370,6 +370,10 @@ export class Collection {
370
370
  return _.pick(record, this.filterTargetKey);
371
371
  }
372
372
 
373
+ get titleableFields() {
374
+ return this.getFields().filter((field) => field.titleable);
375
+ }
376
+
373
377
  get hidden() {
374
378
  return this.options.hidden || false;
375
379
  }
@@ -640,6 +644,10 @@ export class CollectionField {
640
644
  return this.options.readonly || this.options.uiSchema?.['x-read-pretty'] || false;
641
645
  }
642
646
 
647
+ get titleable() {
648
+ return !!(this.options.titleable ?? this.options.titleUsable);
649
+ }
650
+
643
651
  get fullpath() {
644
652
  return this.collection.dataSource.key + '.' + this.collection.name + '.' + this.name;
645
653
  }
@@ -27,11 +27,16 @@ export function getT(model: FlowModel): (key: string, options?: any) => string {
27
27
  return (key: string) => key;
28
28
  }
29
29
 
30
- export function escapeT(text: TFuncKey | TFuncKey[], options?: TOptions) {
30
+ export function tExpr(text: TFuncKey | TFuncKey[], options?: TOptions) {
31
31
  if (options) {
32
32
  return `{{t(${JSON.stringify(text)}, ${JSON.stringify(options)})}}`;
33
33
  }
34
34
  return `{{t(${JSON.stringify(text)})}}`;
35
35
  }
36
36
 
37
- export { escapeT as tExpr };
37
+ /**
38
+ * @deprecated use tExpr from `@nocobase/flow-engine` instead
39
+ */
40
+ export function escapeT(text: TFuncKey | TFuncKey[], options?: TOptions) {
41
+ return tExpr(text, options);
42
+ }
@@ -10,9 +10,10 @@
10
10
  // components/drawer/useDrawer/DrawerComponent.tsx
11
11
  import { Drawer } from 'antd';
12
12
  import * as React from 'react';
13
+ import { MobilePopup } from '../components/MobilePopup';
13
14
 
14
15
  const DrawerComponent = React.forwardRef((props: any, ref) => {
15
- const { children, footer: initialFooter, title, extra, hidden, ...drawerProps } = props;
16
+ const { children, footer: initialFooter, title, extra, hidden, isMobile, ...drawerProps } = props;
16
17
  const [open, setOpen] = React.useState(true);
17
18
  const [footer, setFooter] = React.useState(() => initialFooter);
18
19
  const [header, setHeader] = React.useState({ title, extra, ...drawerProps.header });
@@ -46,6 +47,24 @@ const DrawerComponent = React.forwardRef((props: any, ref) => {
46
47
  return document.querySelector('#nocobase-app-container');
47
48
  }, []);
48
49
 
50
+ if (isMobile) {
51
+ return (
52
+ <MobilePopup
53
+ className={hidden ? 'nb-hidden' : ''}
54
+ visible={open}
55
+ {...drawerProps}
56
+ footer={footer}
57
+ {...header}
58
+ onClose={() => {
59
+ setOpen(false);
60
+ drawerProps.afterClose?.();
61
+ }}
62
+ >
63
+ {children}
64
+ </MobilePopup>
65
+ );
66
+ }
67
+
49
68
  return (
50
69
  <Drawer
51
70
  rootClassName={hidden ? 'nb-hidden' : ''}
@@ -187,6 +187,7 @@ export function useDrawer() {
187
187
  // 关闭时修正 previous/next 指针
188
188
  scopedEngine.unlinkFromStack();
189
189
  }}
190
+ isMobile={ctx.isMobileLayout}
190
191
  >
191
192
  {content}
192
193
  {props.children}