@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.45

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.
Files changed (209) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/FlowContextProvider.d.ts +5 -1
  4. package/lib/FlowContextProvider.js +9 -2
  5. package/lib/JSRunner.d.ts +10 -1
  6. package/lib/JSRunner.js +50 -5
  7. package/lib/ViewScopedFlowEngine.js +5 -1
  8. package/lib/components/FieldModelRenderer.js +2 -2
  9. package/lib/components/FlowModelRenderer.d.ts +3 -1
  10. package/lib/components/FlowModelRenderer.js +12 -6
  11. package/lib/components/FormItem.d.ts +6 -0
  12. package/lib/components/FormItem.js +11 -3
  13. package/lib/components/MobilePopup.js +6 -5
  14. package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
  15. package/lib/components/dnd/gridDragPlanner.js +613 -21
  16. package/lib/components/dnd/index.d.ts +31 -2
  17. package/lib/components/dnd/index.js +244 -23
  18. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
  19. package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
  20. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  21. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -11
  22. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
  23. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
  24. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
  25. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  26. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
  27. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  28. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
  29. package/lib/components/subModel/AddSubModelButton.js +27 -1
  30. package/lib/components/subModel/LazyDropdown.js +293 -52
  31. package/lib/components/subModel/index.d.ts +1 -0
  32. package/lib/components/subModel/index.js +19 -0
  33. package/lib/components/subModel/utils.d.ts +1 -1
  34. package/lib/components/subModel/utils.js +9 -3
  35. package/lib/components/variables/VariableHybridInput.d.ts +27 -0
  36. package/lib/components/variables/VariableHybridInput.js +499 -0
  37. package/lib/components/variables/index.d.ts +2 -0
  38. package/lib/components/variables/index.js +3 -0
  39. package/lib/data-source/index.d.ts +84 -0
  40. package/lib/data-source/index.js +259 -5
  41. package/lib/executor/FlowExecutor.js +32 -9
  42. package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
  43. package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
  44. package/lib/flow-registry/index.d.ts +1 -0
  45. package/lib/flow-registry/index.js +3 -1
  46. package/lib/flowContext.d.ts +3 -0
  47. package/lib/flowContext.js +46 -1
  48. package/lib/flowEngine.d.ts +151 -1
  49. package/lib/flowEngine.js +392 -18
  50. package/lib/flowI18n.js +2 -1
  51. package/lib/flowSettings.d.ts +14 -6
  52. package/lib/flowSettings.js +34 -6
  53. package/lib/index.d.ts +2 -0
  54. package/lib/index.js +7 -0
  55. package/lib/lazy-helper.d.ts +14 -0
  56. package/lib/lazy-helper.js +71 -0
  57. package/lib/locale/en-US.json +1 -0
  58. package/lib/locale/index.d.ts +2 -0
  59. package/lib/locale/zh-CN.json +1 -0
  60. package/lib/models/DisplayItemModel.d.ts +1 -1
  61. package/lib/models/EditableItemModel.d.ts +1 -1
  62. package/lib/models/FilterableItemModel.d.ts +1 -1
  63. package/lib/models/flowModel.d.ts +13 -10
  64. package/lib/models/flowModel.js +81 -21
  65. package/lib/provider.js +38 -23
  66. package/lib/reactive/observer.js +46 -16
  67. package/lib/runjs-context/registry.d.ts +1 -1
  68. package/lib/runjs-context/setup.js +20 -12
  69. package/lib/runjs-context/snippets/index.js +13 -2
  70. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  71. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  72. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  73. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  74. package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
  75. package/lib/scheduler/ModelOperationScheduler.js +3 -2
  76. package/lib/types.d.ts +50 -2
  77. package/lib/types.js +1 -0
  78. package/lib/utils/createCollectionContextMeta.js +6 -2
  79. package/lib/utils/index.d.ts +3 -2
  80. package/lib/utils/index.js +7 -0
  81. package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
  82. package/lib/utils/parsePathnameToViewParams.js +29 -5
  83. package/lib/utils/randomId.d.ts +39 -0
  84. package/lib/utils/randomId.js +45 -0
  85. package/lib/utils/runjsTemplateCompat.js +1 -1
  86. package/lib/utils/runjsValue.js +41 -11
  87. package/lib/utils/schema-utils.d.ts +7 -1
  88. package/lib/utils/schema-utils.js +19 -0
  89. package/lib/views/FlowView.d.ts +7 -1
  90. package/lib/views/FlowView.js +11 -1
  91. package/lib/views/PageComponent.js +8 -6
  92. package/lib/views/ViewNavigation.d.ts +12 -2
  93. package/lib/views/ViewNavigation.js +28 -9
  94. package/lib/views/createViewMeta.js +114 -50
  95. package/lib/views/inheritLayoutContext.d.ts +10 -0
  96. package/lib/views/inheritLayoutContext.js +50 -0
  97. package/lib/views/runViewBeforeClose.d.ts +10 -0
  98. package/lib/views/runViewBeforeClose.js +45 -0
  99. package/lib/views/useDialog.d.ts +2 -1
  100. package/lib/views/useDialog.js +22 -3
  101. package/lib/views/useDrawer.d.ts +2 -1
  102. package/lib/views/useDrawer.js +22 -3
  103. package/lib/views/usePage.d.ts +5 -11
  104. package/lib/views/usePage.js +304 -144
  105. package/package.json +6 -5
  106. package/src/FlowContextProvider.tsx +9 -1
  107. package/src/JSRunner.ts +68 -4
  108. package/src/ViewScopedFlowEngine.ts +4 -0
  109. package/src/__tests__/JSRunner.test.ts +27 -1
  110. package/src/__tests__/createViewMeta.popup.test.ts +115 -1
  111. package/src/__tests__/flow-engine.test.ts +166 -0
  112. package/src/__tests__/flowContext.test.ts +82 -1
  113. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  114. package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
  115. package/src/__tests__/flowSettings.test.ts +94 -15
  116. package/src/__tests__/objectVariable.test.ts +24 -0
  117. package/src/__tests__/provider.test.tsx +24 -2
  118. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  119. package/src/__tests__/runjsContext.test.ts +16 -0
  120. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  121. package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
  122. package/src/__tests__/runjsSnippets.test.ts +21 -0
  123. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  124. package/src/components/FieldModelRenderer.tsx +2 -1
  125. package/src/components/FlowModelRenderer.tsx +18 -6
  126. package/src/components/FormItem.tsx +7 -1
  127. package/src/components/MobilePopup.tsx +4 -2
  128. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  129. package/src/components/__tests__/FormItem.test.tsx +25 -0
  130. package/src/components/__tests__/dnd.test.ts +44 -0
  131. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  132. package/src/components/__tests__/gridDragPlanner.test.ts +558 -3
  133. package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
  134. package/src/components/dnd/gridDragPlanner.ts +758 -19
  135. package/src/components/dnd/index.tsx +305 -28
  136. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
  137. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +99 -11
  138. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
  139. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
  140. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +194 -5
  141. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
  142. package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
  143. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
  144. package/src/components/subModel/AddSubModelButton.tsx +32 -2
  145. package/src/components/subModel/LazyDropdown.tsx +332 -56
  146. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +522 -37
  147. package/src/components/subModel/__tests__/utils.test.ts +24 -0
  148. package/src/components/subModel/index.ts +1 -0
  149. package/src/components/subModel/utils.ts +7 -1
  150. package/src/components/variables/VariableHybridInput.tsx +531 -0
  151. package/src/components/variables/index.ts +2 -0
  152. package/src/data-source/__tests__/collection.test.ts +41 -2
  153. package/src/data-source/__tests__/index.test.ts +68 -1
  154. package/src/data-source/index.ts +322 -6
  155. package/src/executor/FlowExecutor.ts +35 -10
  156. package/src/executor/__tests__/flowExecutor.test.ts +85 -0
  157. package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
  158. package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
  159. package/src/flow-registry/index.ts +1 -0
  160. package/src/flowContext.ts +50 -3
  161. package/src/flowEngine.ts +449 -14
  162. package/src/flowI18n.ts +2 -1
  163. package/src/flowSettings.ts +40 -6
  164. package/src/index.ts +2 -0
  165. package/src/lazy-helper.tsx +57 -0
  166. package/src/locale/en-US.json +1 -0
  167. package/src/locale/zh-CN.json +1 -0
  168. package/src/models/DisplayItemModel.tsx +1 -1
  169. package/src/models/EditableItemModel.tsx +1 -1
  170. package/src/models/FilterableItemModel.tsx +1 -1
  171. package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
  172. package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
  173. package/src/models/__tests__/flowModel.test.ts +80 -37
  174. package/src/models/flowModel.tsx +122 -36
  175. package/src/provider.tsx +41 -25
  176. package/src/reactive/__tests__/observer.test.tsx +82 -0
  177. package/src/reactive/observer.tsx +87 -25
  178. package/src/runjs-context/registry.ts +1 -1
  179. package/src/runjs-context/setup.ts +22 -12
  180. package/src/runjs-context/snippets/index.ts +12 -1
  181. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  182. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  183. package/src/scheduler/ModelOperationScheduler.ts +14 -3
  184. package/src/types.ts +62 -0
  185. package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
  186. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +28 -0
  187. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  188. package/src/utils/__tests__/utils.test.ts +62 -0
  189. package/src/utils/createCollectionContextMeta.ts +6 -2
  190. package/src/utils/index.ts +5 -1
  191. package/src/utils/parsePathnameToViewParams.ts +47 -7
  192. package/src/utils/randomId.ts +48 -0
  193. package/src/utils/runjsTemplateCompat.ts +1 -1
  194. package/src/utils/runjsValue.ts +50 -11
  195. package/src/utils/schema-utils.ts +30 -1
  196. package/src/views/FlowView.tsx +22 -2
  197. package/src/views/PageComponent.tsx +7 -4
  198. package/src/views/ViewNavigation.ts +46 -9
  199. package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
  200. package/src/views/__tests__/ViewNavigation.test.ts +52 -0
  201. package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
  202. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  203. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
  204. package/src/views/createViewMeta.ts +106 -34
  205. package/src/views/inheritLayoutContext.ts +26 -0
  206. package/src/views/runViewBeforeClose.ts +19 -0
  207. package/src/views/useDialog.tsx +27 -3
  208. package/src/views/useDrawer.tsx +27 -3
  209. package/src/views/usePage.tsx +367 -179
@@ -169,16 +169,66 @@ const useKeepDropdownOpen = /* @__PURE__ */ __name(() => {
169
169
  }, "useKeepDropdownOpen");
170
170
  const useMenuSearch = /* @__PURE__ */ __name(() => {
171
171
  const [searchValues, setSearchValues] = (0, import_react.useState)({});
172
+ const [inputValues, setInputValues] = (0, import_react.useState)({});
172
173
  const [isSearching, setIsSearching] = (0, import_react.useState)(false);
174
+ const [composingCount, setComposingCount] = (0, import_react.useState)(0);
175
+ const composingKeysRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
173
176
  const searchTimeoutRef = (0, import_react.useRef)(null);
174
- const updateSearchValue = /* @__PURE__ */ __name((key, value) => {
177
+ const updateSearchValue = (0, import_react.useCallback)((key, value) => {
175
178
  setIsSearching(true);
179
+ setInputValues((prev) => ({ ...prev, [key]: value }));
176
180
  setSearchValues((prev) => ({ ...prev, [key]: value }));
177
181
  if (searchTimeoutRef.current) {
178
182
  clearTimeout(searchTimeoutRef.current);
179
183
  }
180
184
  searchTimeoutRef.current = setTimeout(() => setIsSearching(false), 300);
181
- }, "updateSearchValue");
185
+ }, []);
186
+ const startComposition = (0, import_react.useCallback)((key) => {
187
+ composingKeysRef.current.add(key);
188
+ setIsSearching(true);
189
+ setComposingCount(composingKeysRef.current.size);
190
+ if (searchTimeoutRef.current) {
191
+ clearTimeout(searchTimeoutRef.current);
192
+ searchTimeoutRef.current = null;
193
+ }
194
+ }, []);
195
+ const endComposition = (0, import_react.useCallback)(
196
+ (key, value) => {
197
+ composingKeysRef.current.delete(key);
198
+ setComposingCount(composingKeysRef.current.size);
199
+ updateSearchValue(key, value);
200
+ },
201
+ [updateSearchValue]
202
+ );
203
+ const updateInputValue = (0, import_react.useCallback)((key, value) => {
204
+ setInputValues((prev) => ({ ...prev, [key]: value }));
205
+ }, []);
206
+ const clearSearchValue = (0, import_react.useCallback)((key) => {
207
+ composingKeysRef.current.delete(key);
208
+ setComposingCount(composingKeysRef.current.size);
209
+ setInputValues((prev) => {
210
+ if (!(key in prev)) return prev;
211
+ const next = { ...prev };
212
+ delete next[key];
213
+ return next;
214
+ });
215
+ setSearchValues((prev) => {
216
+ if (!(key in prev)) return prev;
217
+ const next = { ...prev };
218
+ delete next[key];
219
+ return next;
220
+ });
221
+ }, []);
222
+ const clearAllSearchValues = (0, import_react.useCallback)(() => {
223
+ composingKeysRef.current.clear();
224
+ setComposingCount(0);
225
+ setInputValues({});
226
+ setSearchValues({});
227
+ setIsSearching(false);
228
+ }, []);
229
+ const isComposing = (0, import_react.useCallback)((key) => {
230
+ return key ? composingKeysRef.current.has(key) : composingKeysRef.current.size > 0;
231
+ }, []);
182
232
  (0, import_react.useEffect)(() => {
183
233
  return () => {
184
234
  if (searchTimeoutRef.current) {
@@ -188,8 +238,15 @@ const useMenuSearch = /* @__PURE__ */ __name(() => {
188
238
  }, []);
189
239
  return {
190
240
  searchValues,
191
- isSearching,
192
- updateSearchValue
241
+ inputValues,
242
+ isSearching: isSearching || composingCount > 0,
243
+ updateSearchValue,
244
+ updateInputValue,
245
+ startComposition,
246
+ endComposition,
247
+ clearSearchValue,
248
+ clearAllSearchValues,
249
+ isComposing
193
250
  };
194
251
  }, "useMenuSearch");
195
252
  const useSubmenuStyles = /* @__PURE__ */ __name((menuVisible, dropdownMaxHeight) => {
@@ -253,10 +310,32 @@ const SearchInputWithAutoFocus = /* @__PURE__ */ __name((props) => {
253
310
  return /* @__PURE__ */ import_react.default.createElement(import_antd.Input, { ref: inputRef, ...rest });
254
311
  }, "SearchInputWithAutoFocus");
255
312
  const getKeyPath = /* @__PURE__ */ __name((path, key) => [...path, key].join("/"), "getKeyPath");
256
- const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, updateSearchValue) => ({
313
+ const normalizeOpenKeys = /* @__PURE__ */ __name((nextOpenKeys) => {
314
+ const latestKey = nextOpenKeys[nextOpenKeys.length - 1];
315
+ if (!latestKey) {
316
+ return [];
317
+ }
318
+ return nextOpenKeys.filter((key) => latestKey === key || latestKey.startsWith(`${key}/`));
319
+ }, "normalizeOpenKeys");
320
+ const getLabelSearchText = /* @__PURE__ */ __name((label) => {
321
+ if (label === null || label === void 0 || typeof label === "boolean") {
322
+ return "";
323
+ }
324
+ if (typeof label === "string" || typeof label === "number") {
325
+ return String(label);
326
+ }
327
+ if (Array.isArray(label)) {
328
+ return label.map(getLabelSearchText).join(" ");
329
+ }
330
+ if (import_react.default.isValidElement(label)) {
331
+ return getLabelSearchText(label.props.children);
332
+ }
333
+ return "";
334
+ }, "getLabelSearchText");
335
+ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchValue, menuVisible, t, searchHandlers, activateSearchSubmenu, deactivateSearchSubmenu) => ({
257
336
  key: `${searchKey}-search`,
258
337
  type: "group",
259
- label: /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement(
338
+ label: /* @__PURE__ */ import_react.default.createElement("div", { onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation() }, /* @__PURE__ */ import_react.default.createElement(
260
339
  SearchInputWithAutoFocus,
261
340
  {
262
341
  visible: menuVisible,
@@ -264,11 +343,44 @@ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchV
264
343
  allowClear: true,
265
344
  placeholder: t(item.searchPlaceholder || "Search"),
266
345
  value: currentSearchValue,
346
+ onFocus: (e) => {
347
+ e.stopPropagation();
348
+ },
267
349
  onChange: (e) => {
350
+ var _a;
351
+ e.stopPropagation();
352
+ const value = e.target.value;
353
+ activateSearchSubmenu(searchKey);
354
+ if (((_a = e.nativeEvent) == null ? void 0 : _a.isComposing) || searchHandlers.isComposing(searchKey)) {
355
+ searchHandlers.updateInputValue(searchKey, value);
356
+ return;
357
+ }
358
+ if (!value) {
359
+ deactivateSearchSubmenu(searchKey);
360
+ }
361
+ searchHandlers.updateSearchValue(searchKey, value);
362
+ },
363
+ onCompositionStart: (e) => {
364
+ e.stopPropagation();
365
+ activateSearchSubmenu(searchKey);
366
+ searchHandlers.startComposition(searchKey);
367
+ },
368
+ onCompositionEnd: (e) => {
369
+ e.stopPropagation();
370
+ const value = e.currentTarget.value;
371
+ if (value) {
372
+ activateSearchSubmenu(searchKey);
373
+ } else {
374
+ deactivateSearchSubmenu(searchKey);
375
+ }
376
+ searchHandlers.endComposition(searchKey, value);
377
+ },
378
+ onClick: (e) => {
379
+ e.stopPropagation();
380
+ },
381
+ onKeyDown: (e) => {
268
382
  e.stopPropagation();
269
- updateSearchValue(searchKey, e.target.value);
270
383
  },
271
- onClick: (e) => e.stopPropagation(),
272
384
  onMouseDown: (e) => {
273
385
  e.stopPropagation();
274
386
  },
@@ -286,14 +398,26 @@ const createEmptyItem = /* @__PURE__ */ __name((itemKey, t) => ({
286
398
  label: /* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "16px", textAlign: "center" } }, /* @__PURE__ */ import_react.default.createElement(import_antd.Empty, { image: import_antd.Empty.PRESENTED_IMAGE_SIMPLE, description: t("No data"), style: { margin: 0 } })),
287
399
  disabled: true
288
400
  }), "createEmptyItem");
401
+ const KEEP_OPEN_LABEL_STYLE = {
402
+ display: "block",
403
+ width: "100%"
404
+ };
289
405
  const DROPDOWN_PERSIST_TTL_MS = 350;
406
+ const SUBMENU_CLOSE_DELAY = 0.05;
407
+ const SUBMENU_MOTION_DISABLED = {
408
+ motionEnter: false,
409
+ motionLeave: false
410
+ };
290
411
  const dropdownPersistRegistry = /* @__PURE__ */ new Map();
291
412
  const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
292
413
  const engine = (0, import_provider.useFlowEngine)();
293
414
  const [menuVisible, setMenuVisible] = (0, import_react.useState)(false);
294
415
  const [openKeys, setOpenKeys] = (0, import_react.useState)(/* @__PURE__ */ new Set());
416
+ const [activeSearchKey, setActiveSearchKey] = (0, import_react.useState)(null);
295
417
  const [rootItems, setRootItems] = (0, import_react.useState)([]);
296
418
  const [rootLoading, setRootLoading] = (0, import_react.useState)(false);
419
+ const closeByOutsideClickRef = (0, import_react.useRef)(false);
420
+ const skipPreserveActiveSearchRef = (0, import_react.useRef)(false);
297
421
  const dropdownMaxHeight = useNiceDropdownMaxHeight();
298
422
  const t = engine.translate.bind(engine);
299
423
  const { items: menuItems, keepDropdownOpen, persistKey, stateVersion, refreshKeys, ...dropdownMenuProps } = menu;
@@ -304,9 +428,89 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
304
428
  openKeys,
305
429
  refreshKeys
306
430
  );
307
- const { searchValues, isSearching, updateSearchValue } = useMenuSearch();
431
+ const searchHandlers = useMenuSearch();
432
+ const { searchValues, inputValues, clearSearchValue, clearAllSearchValues } = searchHandlers;
308
433
  const { requestKeepOpen, shouldPreventClose } = useKeepDropdownOpen();
309
434
  useSubmenuStyles(menuVisible, dropdownMaxHeight);
435
+ const closeMenu = (0, import_react.useCallback)(() => {
436
+ setMenuVisible(false);
437
+ setActiveSearchKey(null);
438
+ setOpenKeys(/* @__PURE__ */ new Set());
439
+ clearAllSearchValues();
440
+ }, [clearAllSearchValues]);
441
+ const activateSearchSubmenu = (0, import_react.useCallback)((key) => {
442
+ setActiveSearchKey(key);
443
+ setOpenKeys((prev) => {
444
+ if (prev.has(key)) return prev;
445
+ const next = new Set(prev);
446
+ next.add(key);
447
+ return next;
448
+ });
449
+ }, []);
450
+ const deactivateSearchSubmenu = (0, import_react.useCallback)((key) => {
451
+ setActiveSearchKey((prev) => prev === key ? null : prev);
452
+ }, []);
453
+ const closeActiveSearchForPath = (0, import_react.useCallback)(
454
+ (keyPath) => {
455
+ if (!activeSearchKey || keyPath === activeSearchKey || keyPath.startsWith(`${activeSearchKey}/`) || activeSearchKey.startsWith(`${keyPath}/`)) {
456
+ return;
457
+ }
458
+ skipPreserveActiveSearchRef.current = true;
459
+ clearSearchValue(activeSearchKey);
460
+ setActiveSearchKey(null);
461
+ setOpenKeys((prev) => {
462
+ const next = new Set(prev);
463
+ next.delete(activeSearchKey);
464
+ return next;
465
+ });
466
+ },
467
+ [activeSearchKey, clearSearchValue]
468
+ );
469
+ const handleMenuOpenChange = (0, import_react.useCallback)(
470
+ (nextOpenKeys) => {
471
+ var _a, _b;
472
+ let normalized = normalizeOpenKeys(nextOpenKeys);
473
+ if (activeSearchKey && openKeys.has(activeSearchKey) && !normalized.includes(activeSearchKey)) {
474
+ if (normalized.length || skipPreserveActiveSearchRef.current) {
475
+ clearSearchValue(activeSearchKey);
476
+ setActiveSearchKey(null);
477
+ } else {
478
+ normalized = [activeSearchKey];
479
+ }
480
+ }
481
+ if (!normalized.length && shouldPreventClose()) {
482
+ (_a = dropdownMenuProps.onOpenChange) == null ? void 0 : _a.call(dropdownMenuProps, Array.from(openKeys));
483
+ skipPreserveActiveSearchRef.current = false;
484
+ return;
485
+ }
486
+ Array.from(openKeys).forEach((key) => {
487
+ if (!normalized.includes(key)) {
488
+ clearSearchValue(key);
489
+ }
490
+ });
491
+ setOpenKeys(new Set(normalized));
492
+ (_b = dropdownMenuProps.onOpenChange) == null ? void 0 : _b.call(dropdownMenuProps, normalized);
493
+ skipPreserveActiveSearchRef.current = false;
494
+ },
495
+ [activeSearchKey, clearSearchValue, dropdownMenuProps, openKeys, shouldPreventClose]
496
+ );
497
+ (0, import_react.useEffect)(() => {
498
+ if (!menuVisible) return;
499
+ const markOutsideClick = /* @__PURE__ */ __name((event) => {
500
+ const target = event.target;
501
+ const isOutside = !(target == null ? void 0 : target.closest(".ant-dropdown, .ant-dropdown-menu, .ant-dropdown-menu-submenu-popup"));
502
+ closeByOutsideClickRef.current = isOutside;
503
+ if (isOutside) {
504
+ closeMenu();
505
+ }
506
+ }, "markOutsideClick");
507
+ document.addEventListener("pointerdown", markOutsideClick, true);
508
+ document.addEventListener("mousedown", markOutsideClick, true);
509
+ return () => {
510
+ document.removeEventListener("pointerdown", markOutsideClick, true);
511
+ document.removeEventListener("mousedown", markOutsideClick, true);
512
+ };
513
+ }, [closeMenu, menuVisible]);
310
514
  (0, import_react.useEffect)(() => {
311
515
  if (!persistKey) return;
312
516
  const until = dropdownPersistRegistry.get(persistKey) || 0;
@@ -326,6 +530,11 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
326
530
  }
327
531
  };
328
532
  }, [persistKey, menuVisible]);
533
+ (0, import_react.useEffect)(() => {
534
+ if (!menuVisible) {
535
+ setOpenKeys(/* @__PURE__ */ new Set());
536
+ }
537
+ }, [menuVisible]);
329
538
  (0, import_react.useEffect)(() => {
330
539
  const loadRootItems = /* @__PURE__ */ __name(async () => {
331
540
  let resolvedItems;
@@ -347,15 +556,12 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
347
556
  function buildSearchChildren(children, item, keyPath, path, menuVisible2, resolve) {
348
557
  const searchKey = keyPath;
349
558
  const currentSearchValue = searchValues[searchKey] || "";
559
+ const currentInputValue = inputValues[searchKey] ?? currentSearchValue;
350
560
  const filteredChildren = currentSearchValue ? (/* @__PURE__ */ __name(function deepFilter(items2) {
351
561
  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
562
  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);
563
+ const labelStr = getLabelSearchText(child.label).toLowerCase();
564
+ const selfMatch = labelStr.includes(searchText);
359
565
  if (child.type === "group" && Array.isArray(child.children)) {
360
566
  const nested = deepFilter(child.children);
361
567
  if (selfMatch || nested.length > 0) {
@@ -367,7 +573,16 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
367
573
  }).filter(Boolean);
368
574
  }, "deepFilter"))(children) : children;
369
575
  const resolvedFiltered = resolve(filteredChildren, [...path, item.key]);
370
- const searchItem = createSearchItem(item, searchKey, currentSearchValue, menuVisible2, t, updateSearchValue);
576
+ const searchItem = createSearchItem(
577
+ item,
578
+ searchKey,
579
+ currentInputValue,
580
+ menuVisible2,
581
+ t,
582
+ searchHandlers,
583
+ activateSearchSubmenu,
584
+ deactivateSearchSubmenu
585
+ );
371
586
  const dividerItem = { key: `${keyPath}-search-divider`, type: "divider" };
372
587
  if (currentSearchValue && resolvedFiltered.length === 0) {
373
588
  return [searchItem, dividerItem, createEmptyItem(keyPath, t)];
@@ -419,51 +634,68 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
419
634
  if (item.type === "divider") {
420
635
  return { type: "divider", key: keyPath };
421
636
  }
637
+ const label = typeof item.label === "string" ? t(item.label) : item.label;
422
638
  if (item.searchable && children) {
423
639
  return {
424
- key: item.key,
425
- label: typeof item.label === "string" ? t(item.label) : item.label,
640
+ key: keyPath,
641
+ label,
426
642
  onClick: /* @__PURE__ */ __name((info) => {
427
643
  }, "onClick"),
428
- onMouseEnter: /* @__PURE__ */ __name(() => {
429
- setOpenKeys((prev) => {
430
- if (prev.has(keyPath)) return prev;
431
- const next = new Set(prev);
432
- next.add(keyPath);
433
- return next;
434
- });
435
- }, "onMouseEnter"),
644
+ onMouseEnter: /* @__PURE__ */ __name(() => closeActiveSearchForPath(keyPath), "onMouseEnter"),
436
645
  children: buildSearchChildren(children, item, keyPath, path, menuVisible, resolveItems)
437
646
  };
438
647
  }
648
+ const itemShouldKeepOpen = !children && (item.keepDropdownOpen ?? keepDropdownOpen ?? false);
649
+ const handleLeafClick = /* @__PURE__ */ __name((info) => {
650
+ var _a;
651
+ if (children) {
652
+ return;
653
+ }
654
+ if (itemShouldKeepOpen) {
655
+ requestKeepOpen();
656
+ }
657
+ const extendedInfo = {
658
+ ...info,
659
+ key: (info == null ? void 0 : info.key) ?? keyPath,
660
+ keyPath: (info == null ? void 0 : info.keyPath) ?? [keyPath],
661
+ item: (info == null ? void 0 : info.item) || item,
662
+ originalItem: item,
663
+ keepDropdownOpen: itemShouldKeepOpen
664
+ };
665
+ (_a = menu.onClick) == null ? void 0 : _a.call(menu, extendedInfo);
666
+ }, "handleLeafClick");
439
667
  return {
440
668
  key: keyPath,
441
- label: typeof item.label === "string" ? t(item.label) : item.label,
669
+ label: itemShouldKeepOpen ? /* @__PURE__ */ import_react.default.createElement(
670
+ "div",
671
+ {
672
+ style: KEEP_OPEN_LABEL_STYLE,
673
+ onMouseDown: (event) => {
674
+ event.stopPropagation();
675
+ requestKeepOpen();
676
+ },
677
+ onClick: (event) => {
678
+ event.stopPropagation();
679
+ handleLeafClick({
680
+ key: keyPath,
681
+ keyPath: [keyPath],
682
+ item,
683
+ domEvent: event
684
+ });
685
+ }
686
+ },
687
+ label
688
+ ) : label,
442
689
  onClick: /* @__PURE__ */ __name((info) => {
443
- var _a;
444
- if (children) {
690
+ if (!itemShouldKeepOpen) handleLeafClick(info);
691
+ }, "onClick"),
692
+ onMouseEnter: /* @__PURE__ */ __name(() => closeActiveSearchForPath(keyPath), "onMouseEnter"),
693
+ onMouseDown: /* @__PURE__ */ __name(() => {
694
+ if (!itemShouldKeepOpen) {
445
695
  return;
446
696
  }
447
- const itemShouldKeepOpen = item.keepDropdownOpen ?? keepDropdownOpen ?? false;
448
- if (itemShouldKeepOpen) {
449
- requestKeepOpen();
450
- }
451
- const extendedInfo = {
452
- ...info,
453
- item: info.item || item,
454
- originalItem: item,
455
- keepDropdownOpen: itemShouldKeepOpen
456
- };
457
- (_a = menu.onClick) == null ? void 0 : _a.call(menu, extendedInfo);
458
- }, "onClick"),
459
- onMouseEnter: /* @__PURE__ */ __name(() => {
460
- setOpenKeys((prev) => {
461
- if (prev.has(keyPath)) return prev;
462
- const next = new Set(prev);
463
- next.add(keyPath);
464
- return next;
465
- });
466
- }, "onMouseEnter"),
697
+ requestKeepOpen();
698
+ }, "onMouseDown"),
467
699
  children: children && children.length > 0 ? resolveItems(children, [...path, item.key]) : children && children.length === 0 ? [createEmptyItem(keyPath, t)] : void 0
468
700
  };
469
701
  });
@@ -498,23 +730,32 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
498
730
  placement: "bottomLeft",
499
731
  menu: {
500
732
  ...dropdownMenuProps,
733
+ openKeys: Array.from(openKeys),
501
734
  items,
735
+ subMenuCloseDelay: dropdownMenuProps.subMenuCloseDelay ?? SUBMENU_CLOSE_DELAY,
736
+ motion: dropdownMenuProps.motion ?? SUBMENU_MOTION_DISABLED,
502
737
  onClick: /* @__PURE__ */ __name(() => {
503
738
  }, "onClick"),
739
+ onOpenChange: handleMenuOpenChange,
504
740
  style: {
505
741
  maxHeight: dropdownMaxHeight,
506
742
  overflowY: "auto",
507
743
  ...dropdownMenuProps == null ? void 0 : dropdownMenuProps.style
508
744
  }
509
745
  },
510
- onOpenChange: (visible) => {
511
- if (!visible && isSearching) {
746
+ onOpenChange: (visible, info) => {
747
+ if (!visible && activeSearchKey && (info == null ? void 0 : info.source) === "trigger" && !closeByOutsideClickRef.current) {
512
748
  return;
513
749
  }
514
750
  if (!visible && shouldPreventClose()) {
515
751
  return;
516
752
  }
517
- setMenuVisible(visible);
753
+ if (!visible) {
754
+ closeMenu();
755
+ } else {
756
+ setMenuVisible(visible);
757
+ }
758
+ closeByOutsideClickRef.current = false;
518
759
  }
519
760
  },
520
761
  props.children
@@ -7,4 +7,5 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  export * from './AddSubModelButton';
10
+ export { default as LazyDropdown } from './LazyDropdown';
10
11
  export * from './utils';
@@ -7,10 +7,16 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
+ var __create = Object.create;
10
11
  var __defProp = Object.defineProperty;
11
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
13
  var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
13
15
  var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
14
20
  var __copyProps = (to, from, except, desc) => {
15
21
  if (from && typeof from === "object" || typeof from === "function") {
16
22
  for (let key of __getOwnPropNames(from))
@@ -20,13 +26,26 @@ var __copyProps = (to, from, except, desc) => {
20
26
  return to;
21
27
  };
22
28
  var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
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
+ ));
23
37
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
24
38
  var subModel_exports = {};
39
+ __export(subModel_exports, {
40
+ LazyDropdown: () => import_LazyDropdown.default
41
+ });
25
42
  module.exports = __toCommonJS(subModel_exports);
26
43
  __reExport(subModel_exports, require("./AddSubModelButton"), module.exports);
44
+ var import_LazyDropdown = __toESM(require("./LazyDropdown"));
27
45
  __reExport(subModel_exports, require("./utils"), module.exports);
28
46
  // Annotate the CommonJS export names for ESM import in node:
29
47
  0 && (module.exports = {
48
+ LazyDropdown,
30
49
  ...require("./AddSubModelButton"),
31
50
  ...require("./utils")
32
51
  });
@@ -29,6 +29,6 @@ export declare function buildWrapperFieldChildren(ctx: FlowModelContext, options
29
29
  label: string;
30
30
  type: "group";
31
31
  searchable: boolean;
32
- searchPlaceholder: any;
32
+ searchPlaceholder: string;
33
33
  children: SubModelItem[];
34
34
  }[];
@@ -44,7 +44,7 @@ __export(utils_exports, {
44
44
  buildWrapperFieldChildren: () => buildWrapperFieldChildren
45
45
  });
46
46
  module.exports = __toCommonJS(utils_exports);
47
- var _ = __toESM(require("lodash"));
47
+ var import_lodash = __toESM(require("lodash"));
48
48
  var import_utils = require("../../utils");
49
49
  async function callHideFunction(hide, ctx) {
50
50
  if (typeof hide === "function") {
@@ -107,7 +107,7 @@ function buildSubModelChildren(M, ctx) {
107
107
  const extraArg = args && args.length > 0 ? args[args.length - 1] : void 0;
108
108
  const defaultOpts = await (0, import_utils.resolveCreateModelOptions)(meta == null ? void 0 : meta.createModelOptions, ctx, extraArg);
109
109
  const childOpts = await (0, import_utils.resolveCreateModelOptions)(src, ctx, extraArg);
110
- return _.merge({}, _.cloneDeep(defaultOpts), childOpts);
110
+ return import_lodash.default.merge({}, import_lodash.default.cloneDeep(defaultOpts), childOpts);
111
111
  };
112
112
  }
113
113
  return node;
@@ -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
  }
@@ -0,0 +1,27 @@
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 React from 'react';
10
+ import type { MetaTreeNode } from '../../flowContext';
11
+ export interface VariableHybridInputConverters {
12
+ formatPathToValue?: (item?: MetaTreeNode) => string | undefined;
13
+ parseValueToPath?: (value?: string) => string[] | undefined;
14
+ variableRegExp?: RegExp;
15
+ }
16
+ export interface VariableHybridInputProps {
17
+ value?: string;
18
+ onChange?: (value: string) => void;
19
+ disabled?: boolean;
20
+ placeholder?: string;
21
+ addonBefore?: React.ReactNode;
22
+ metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
23
+ converters?: VariableHybridInputConverters;
24
+ style?: React.CSSProperties;
25
+ className?: string;
26
+ }
27
+ export declare const VariableHybridInput: React.NamedExoticComponent<VariableHybridInputProps>;