@nocobase/flow-engine 2.0.55 → 2.0.57

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.
@@ -253,6 +253,21 @@ const SearchInputWithAutoFocus = /* @__PURE__ */ __name((props) => {
253
253
  return /* @__PURE__ */ import_react.default.createElement(import_antd.Input, { ref: inputRef, ...rest });
254
254
  }, "SearchInputWithAutoFocus");
255
255
  const getKeyPath = /* @__PURE__ */ __name((path, key) => [...path, key].join("/"), "getKeyPath");
256
+ const getLabelSearchText = /* @__PURE__ */ __name((label) => {
257
+ if (label === null || label === void 0 || typeof label === "boolean") {
258
+ return "";
259
+ }
260
+ if (typeof label === "string" || typeof label === "number") {
261
+ return String(label);
262
+ }
263
+ if (Array.isArray(label)) {
264
+ return label.map(getLabelSearchText).join(" ");
265
+ }
266
+ if (import_react.default.isValidElement(label)) {
267
+ return getLabelSearchText(label.props.children);
268
+ }
269
+ return "";
270
+ }, "getLabelSearchText");
256
271
  const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, updateSearchValue) => ({
257
272
  key: `${searchKey}-search`,
258
273
  type: "group",
@@ -349,13 +364,9 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
349
364
  const currentSearchValue = searchValues[searchKey] || "";
350
365
  const filteredChildren = currentSearchValue ? (/* @__PURE__ */ __name(function deepFilter(items2) {
351
366
  const searchText = currentSearchValue.toLowerCase();
352
- const tryString = /* @__PURE__ */ __name((v) => {
353
- if (!v) return "";
354
- return typeof v === "string" ? v : String(v);
355
- }, "tryString");
356
367
  return items2.map((child) => {
357
- const labelStr = tryString(child.label).toLowerCase();
358
- const selfMatch = labelStr.includes(searchText) || child.key && String(child.key).toLowerCase().includes(searchText);
368
+ const labelStr = getLabelSearchText(child.label).toLowerCase();
369
+ const selfMatch = labelStr.includes(searchText);
359
370
  if (child.type === "group" && Array.isArray(child.children)) {
360
371
  const nested = deepFilter(child.children);
361
372
  if (selfMatch || nested.length > 0) {
@@ -172,7 +172,7 @@ function buildSubModelItems(subModelBaseClass, exclude = []) {
172
172
  __name(buildSubModelItems, "buildSubModelItems");
173
173
  function buildSubModelGroups(subModelBaseClasses = []) {
174
174
  return async (ctx) => {
175
- var _a, _b, _c;
175
+ var _a, _b, _c, _d, _e;
176
176
  const items = [];
177
177
  const exclude = [];
178
178
  for (const subModelBaseClass of subModelBaseClasses) {
@@ -203,11 +203,15 @@ function buildSubModelGroups(subModelBaseClasses = []) {
203
203
  const baseKey = typeof subModelBaseClass === "string" ? subModelBaseClass : BaseClass.name;
204
204
  const menuType = ((_b = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _b.menuType) || "group";
205
205
  const groupSort = ((_c = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _c.sort) ?? 1e3;
206
+ const searchable = !!((_d = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _d.searchable);
207
+ const searchPlaceholder = (_e = BaseClass == null ? void 0 : BaseClass.meta) == null ? void 0 : _e.searchPlaceholder;
206
208
  if (menuType === "submenu") {
207
209
  items.push({
208
210
  key: baseKey,
209
211
  label: groupLabel,
210
212
  sort: groupSort,
213
+ searchable,
214
+ searchPlaceholder,
211
215
  children
212
216
  });
213
217
  } else {
@@ -216,6 +220,8 @@ function buildSubModelGroups(subModelBaseClasses = []) {
216
220
  type: "group",
217
221
  label: groupLabel,
218
222
  sort: groupSort,
223
+ searchable,
224
+ searchPlaceholder,
219
225
  children
220
226
  });
221
227
  }
@@ -759,7 +759,7 @@ const _CollectionField = class _CollectionField {
759
759
  {
760
760
  ...import_lodash.default.omit(((_a = this.options.uiSchema) == null ? void 0 : _a["x-component-props"]) || {}, "fieldNames"),
761
761
  options: this.enum.length ? this.enum : void 0,
762
- mode: this.type === "array" ? "multiple" : void 0,
762
+ mode: this.interface === "multipleSelect" ? "multiple" : void 0,
763
763
  multiple: target ? ["belongsToMany", "hasMany", "belongsToArray"].includes(type) : void 0,
764
764
  maxCount: target && !["belongsToMany", "hasMany", "belongsToArray"].includes(type) ? 1 : void 0,
765
765
  target,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/flow-engine",
3
- "version": "2.0.55",
3
+ "version": "2.0.57",
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.55",
12
- "@nocobase/shared": "2.0.55",
11
+ "@nocobase/sdk": "2.0.57",
12
+ "@nocobase/shared": "2.0.57",
13
13
  "ahooks": "^3.7.2",
14
14
  "axios": "^1.7.0",
15
15
  "dayjs": "^1.11.9",
@@ -37,5 +37,5 @@
37
37
  ],
38
38
  "author": "NocoBase Team",
39
39
  "license": "Apache-2.0",
40
- "gitHead": "f8e14bcb84ed1ab8f2915441ef2208218c272ede"
40
+ "gitHead": "629bf05e63bca0cbd60fffb87057d45bda5d9222"
41
41
  }
@@ -358,6 +358,22 @@ const SearchInputWithAutoFocus: FC<InputProps & { visible: boolean }> = (props)
358
358
 
359
359
  const getKeyPath = (path: string[], key: string) => [...path, key].join('/');
360
360
 
361
+ const getLabelSearchText = (label: React.ReactNode): string => {
362
+ if (label === null || label === undefined || typeof label === 'boolean') {
363
+ return '';
364
+ }
365
+ if (typeof label === 'string' || typeof label === 'number') {
366
+ return String(label);
367
+ }
368
+ if (Array.isArray(label)) {
369
+ return label.map(getLabelSearchText).join(' ');
370
+ }
371
+ if (React.isValidElement(label)) {
372
+ return getLabelSearchText(label.props.children);
373
+ }
374
+ return '';
375
+ };
376
+
361
377
  const createSearchItem = (
362
378
  item: Item,
363
379
  searchKey: string,
@@ -498,15 +514,10 @@ const LazyDropdown: React.FC<Omit<DropdownProps, 'menu'> & { menu: LazyDropdownM
498
514
  const filteredChildren = currentSearchValue
499
515
  ? (function deepFilter(items: Item[]): Item[] {
500
516
  const searchText = currentSearchValue.toLowerCase();
501
- const tryString = (v: any) => {
502
- if (!v) return '';
503
- return typeof v === 'string' ? v : String(v);
504
- };
505
517
  return items
506
518
  .map((child) => {
507
- const labelStr = tryString(child.label).toLowerCase();
508
- const selfMatch =
509
- labelStr.includes(searchText) || (child.key && String(child.key).toLowerCase().includes(searchText));
519
+ const labelStr = getLabelSearchText(child.label).toLowerCase();
520
+ const selfMatch = labelStr.includes(searchText);
510
521
  if (child.type === 'group' && Array.isArray(child.children)) {
511
522
  const nested = deepFilter(child.children);
512
523
  if (selfMatch || nested.length > 0) {
@@ -188,6 +188,50 @@ describe('transformItems - searchable flags', () => {
188
188
  expect(submenu.searchPlaceholder).toBe('Search blocks');
189
189
  expect(Array.isArray(submenu.children)).toBe(true);
190
190
  });
191
+
192
+ it('filters searchable field menus by display label instead of item key', async () => {
193
+ const engine = new FlowEngine();
194
+ engine.flowSettings.forceEnable();
195
+ const parent = engine.createModel<FlowModel>({ use: FlowModel });
196
+ const user = userEvent.setup();
197
+
198
+ const items = [
199
+ {
200
+ key: 'fields',
201
+ label: '',
202
+ type: 'group' as const,
203
+ searchable: true,
204
+ searchPlaceholder: 'Search fields',
205
+ children: [
206
+ { key: 'field_name', label: 'Field display name' },
207
+ { key: 'other_field', label: 'Other field' },
208
+ ],
209
+ },
210
+ ];
211
+
212
+ render(
213
+ <FlowEngineProvider engine={engine}>
214
+ <ConfigProvider>
215
+ <App>
216
+ <AddSubModelButton model={parent} items={items as any} subModelKey="items">
217
+ Open
218
+ </AddSubModelButton>
219
+ </App>
220
+ </ConfigProvider>
221
+ </FlowEngineProvider>,
222
+ );
223
+
224
+ await user.click(screen.getByText('Open'));
225
+ const searchInput = await screen.findByPlaceholderText('Search fields');
226
+ expect(screen.getByText('Field display name')).toBeInTheDocument();
227
+
228
+ await user.type(searchInput, 'field_name');
229
+ await waitFor(() => expect(screen.queryByText('Field display name')).not.toBeInTheDocument());
230
+
231
+ await user.clear(searchInput);
232
+ await user.type(searchInput, 'display');
233
+ await waitFor(() => expect(screen.getByText('Field display name')).toBeInTheDocument());
234
+ });
191
235
  });
192
236
 
193
237
  describe('transformItems - hide', () => {
@@ -100,6 +100,30 @@ describe('subModel/utils', () => {
100
100
  expect(groups[0].children).toBeTruthy();
101
101
  });
102
102
 
103
+ it('preserves searchable meta on generated groups', async () => {
104
+ const engine = new FlowEngine();
105
+
106
+ class Base extends FlowModel {}
107
+ Base.define({
108
+ label: 'Base Group',
109
+ searchable: true,
110
+ searchPlaceholder: 'Search fields',
111
+ });
112
+ const BaseDC = attachDefineChildren(Base, async () => [{ key: 'title', label: 'Title' }]);
113
+
114
+ engine.registerModels({ Base: BaseDC });
115
+
116
+ const model = engine.createModel({ use: 'FlowModel' });
117
+ const ctx = model.context;
118
+
119
+ const groupsFactory = buildSubModelGroups([BaseDC]);
120
+ const groups = await groupsFactory(ctx);
121
+
122
+ expect(groups).toHaveLength(1);
123
+ expect(groups[0].searchable).toBe(true);
124
+ expect(groups[0].searchPlaceholder).toBe('Search fields');
125
+ });
126
+
103
127
  it('invokes buildSubModelItems when meta.children is false', async () => {
104
128
  const engine = new FlowEngine();
105
129
 
@@ -196,12 +196,16 @@ export function buildSubModelGroups(subModelBaseClasses: (string | ModelConstruc
196
196
  const baseKey = typeof subModelBaseClass === 'string' ? subModelBaseClass : BaseClass.name;
197
197
  const menuType = BaseClass?.meta?.menuType || 'group';
198
198
  const groupSort = BaseClass?.meta?.sort ?? 1000;
199
+ const searchable = !!BaseClass?.meta?.searchable;
200
+ const searchPlaceholder = BaseClass?.meta?.searchPlaceholder;
199
201
  if (menuType === 'submenu') {
200
202
  // 作为可点击的一级项,展开二级子菜单
201
203
  items.push({
202
204
  key: baseKey,
203
205
  label: groupLabel,
204
206
  sort: groupSort,
207
+ searchable,
208
+ searchPlaceholder,
205
209
  children,
206
210
  });
207
211
  } else {
@@ -211,6 +215,8 @@ export function buildSubModelGroups(subModelBaseClasses: (string | ModelConstruc
211
215
  type: 'group',
212
216
  label: groupLabel,
213
217
  sort: groupSort,
218
+ searchable,
219
+ searchPlaceholder,
214
220
  children,
215
221
  });
216
222
  }
@@ -841,7 +841,7 @@ export class CollectionField {
841
841
  {
842
842
  ..._.omit(this.options.uiSchema?.['x-component-props'] || {}, 'fieldNames'),
843
843
  options: this.enum.length ? this.enum : undefined,
844
- mode: this.type === 'array' ? 'multiple' : undefined,
844
+ mode: this.interface === 'multipleSelect' ? 'multiple' : undefined,
845
845
  multiple: target ? ['belongsToMany', 'hasMany', 'belongsToArray'].includes(type) : undefined,
846
846
  maxCount: target && !['belongsToMany', 'hasMany', 'belongsToArray'].includes(type) ? 1 : undefined,
847
847
  target: target,