@nocobase/flow-engine 2.1.0-beta.9 → 2.1.1

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 (215) hide show
  1. package/lib/FlowContextProvider.d.ts +5 -1
  2. package/lib/FlowContextProvider.js +9 -2
  3. package/lib/components/FieldModelRenderer.js +2 -2
  4. package/lib/components/FlowModelRenderer.d.ts +3 -1
  5. package/lib/components/FlowModelRenderer.js +12 -6
  6. package/lib/components/FormItem.d.ts +6 -0
  7. package/lib/components/FormItem.js +11 -3
  8. package/lib/components/MobilePopup.js +6 -5
  9. package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
  10. package/lib/components/dnd/gridDragPlanner.js +607 -19
  11. package/lib/components/dnd/index.d.ts +31 -2
  12. package/lib/components/dnd/index.js +244 -23
  13. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
  14. package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
  15. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  16. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +152 -42
  17. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
  18. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
  19. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  20. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
  21. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  22. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
  23. package/lib/components/subModel/AddSubModelButton.js +12 -1
  24. package/lib/components/subModel/LazyDropdown.js +301 -52
  25. package/lib/components/subModel/index.d.ts +1 -0
  26. package/lib/components/subModel/index.js +19 -0
  27. package/lib/components/subModel/utils.d.ts +2 -1
  28. package/lib/components/subModel/utils.js +15 -5
  29. package/lib/components/variables/VariableHybridInput.d.ts +27 -0
  30. package/lib/components/variables/VariableHybridInput.js +499 -0
  31. package/lib/components/variables/index.d.ts +2 -0
  32. package/lib/components/variables/index.js +3 -0
  33. package/lib/data-source/index.d.ts +84 -0
  34. package/lib/data-source/index.js +269 -7
  35. package/lib/executor/FlowExecutor.js +6 -3
  36. package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
  37. package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
  38. package/lib/flow-registry/index.d.ts +1 -0
  39. package/lib/flow-registry/index.js +3 -1
  40. package/lib/flowContext.d.ts +9 -1
  41. package/lib/flowContext.js +77 -6
  42. package/lib/flowEngine.d.ts +136 -4
  43. package/lib/flowEngine.js +429 -51
  44. package/lib/flowI18n.js +2 -1
  45. package/lib/flowSettings.d.ts +14 -6
  46. package/lib/flowSettings.js +34 -6
  47. package/lib/index.d.ts +2 -0
  48. package/lib/index.js +7 -0
  49. package/lib/lazy-helper.d.ts +14 -0
  50. package/lib/lazy-helper.js +71 -0
  51. package/lib/locale/en-US.json +1 -0
  52. package/lib/locale/index.d.ts +2 -0
  53. package/lib/locale/zh-CN.json +1 -0
  54. package/lib/models/DisplayItemModel.d.ts +1 -1
  55. package/lib/models/EditableItemModel.d.ts +1 -1
  56. package/lib/models/FilterableItemModel.d.ts +1 -1
  57. package/lib/models/flowModel.d.ts +13 -10
  58. package/lib/models/flowModel.js +126 -34
  59. package/lib/provider.js +38 -23
  60. package/lib/reactive/observer.js +46 -16
  61. package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
  62. package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
  63. package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
  64. package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
  65. package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
  66. package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
  67. package/lib/runjs-context/contexts/base.js +464 -29
  68. package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
  69. package/lib/runjs-context/contexts/elementDoc.js +152 -0
  70. package/lib/runjs-context/setup.js +1 -0
  71. package/lib/runjs-context/snippets/index.js +13 -2
  72. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  73. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  74. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  75. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  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/loadedPageCache.d.ts +24 -0
  82. package/lib/utils/loadedPageCache.js +139 -0
  83. package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
  84. package/lib/utils/parsePathnameToViewParams.js +28 -4
  85. package/lib/utils/randomId.d.ts +39 -0
  86. package/lib/utils/randomId.js +45 -0
  87. package/lib/utils/runjsTemplateCompat.js +1 -1
  88. package/lib/utils/runjsValue.js +41 -11
  89. package/lib/utils/schema-utils.d.ts +7 -1
  90. package/lib/utils/schema-utils.js +19 -0
  91. package/lib/views/FlowView.d.ts +7 -1
  92. package/lib/views/FlowView.js +11 -1
  93. package/lib/views/PageComponent.js +8 -6
  94. package/lib/views/ViewNavigation.d.ts +12 -2
  95. package/lib/views/ViewNavigation.js +28 -9
  96. package/lib/views/createViewMeta.js +114 -50
  97. package/lib/views/inheritLayoutContext.d.ts +10 -0
  98. package/lib/views/inheritLayoutContext.js +50 -0
  99. package/lib/views/runViewBeforeClose.d.ts +10 -0
  100. package/lib/views/runViewBeforeClose.js +45 -0
  101. package/lib/views/useDialog.d.ts +2 -1
  102. package/lib/views/useDialog.js +12 -3
  103. package/lib/views/useDrawer.d.ts +2 -1
  104. package/lib/views/useDrawer.js +12 -3
  105. package/lib/views/usePage.d.ts +5 -11
  106. package/lib/views/usePage.js +304 -144
  107. package/package.json +5 -4
  108. package/src/FlowContextProvider.tsx +9 -1
  109. package/src/__tests__/createViewMeta.popup.test.ts +115 -1
  110. package/src/__tests__/flow-engine.test.ts +166 -0
  111. package/src/__tests__/flowContext.test.ts +105 -1
  112. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  113. package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
  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 +21 -0
  120. package/src/__tests__/runjsContextImplementations.test.ts +9 -2
  121. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  122. package/src/__tests__/runjsLocales.test.ts +6 -5
  123. package/src/__tests__/runjsSnippets.test.ts +21 -0
  124. package/src/__tests__/viewScopedFlowEngine.test.ts +136 -3
  125. package/src/components/FieldModelRenderer.tsx +2 -1
  126. package/src/components/FlowModelRenderer.tsx +18 -6
  127. package/src/components/FormItem.tsx +7 -1
  128. package/src/components/MobilePopup.tsx +4 -2
  129. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  130. package/src/components/__tests__/FormItem.test.tsx +25 -0
  131. package/src/components/__tests__/dnd.test.ts +44 -0
  132. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  133. package/src/components/__tests__/gridDragPlanner.test.ts +472 -5
  134. package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
  135. package/src/components/dnd/gridDragPlanner.ts +750 -17
  136. package/src/components/dnd/index.tsx +305 -28
  137. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
  138. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +178 -48
  139. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
  140. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +344 -8
  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 +16 -2
  145. package/src/components/subModel/LazyDropdown.tsx +341 -56
  146. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +524 -38
  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 +13 -2
  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 +69 -2
  154. package/src/data-source/index.ts +332 -8
  155. package/src/executor/FlowExecutor.ts +6 -3
  156. package/src/executor/__tests__/flowExecutor.test.ts +57 -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 +85 -6
  161. package/src/flowEngine.ts +484 -45
  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__/flowEngine.resolveUse.test.ts +0 -15
  172. package/src/models/__tests__/flowModel.test.ts +65 -37
  173. package/src/models/flowModel.tsx +184 -65
  174. package/src/provider.tsx +41 -25
  175. package/src/reactive/__tests__/observer.test.tsx +82 -0
  176. package/src/reactive/observer.tsx +87 -25
  177. package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +4 -3
  178. package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
  179. package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
  180. package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
  181. package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
  182. package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
  183. package/src/runjs-context/contexts/base.ts +467 -31
  184. package/src/runjs-context/contexts/elementDoc.ts +130 -0
  185. package/src/runjs-context/setup.ts +1 -0
  186. package/src/runjs-context/snippets/index.ts +12 -1
  187. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  188. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  189. package/src/types.ts +62 -0
  190. package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
  191. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -0
  192. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  193. package/src/utils/__tests__/utils.test.ts +62 -0
  194. package/src/utils/createCollectionContextMeta.ts +6 -2
  195. package/src/utils/index.ts +5 -1
  196. package/src/utils/loadedPageCache.ts +147 -0
  197. package/src/utils/parsePathnameToViewParams.ts +45 -5
  198. package/src/utils/randomId.ts +48 -0
  199. package/src/utils/runjsTemplateCompat.ts +1 -1
  200. package/src/utils/runjsValue.ts +50 -11
  201. package/src/utils/schema-utils.ts +30 -1
  202. package/src/views/FlowView.tsx +22 -2
  203. package/src/views/PageComponent.tsx +7 -4
  204. package/src/views/ViewNavigation.ts +46 -9
  205. package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
  206. package/src/views/__tests__/ViewNavigation.test.ts +52 -0
  207. package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
  208. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  209. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +12 -12
  210. package/src/views/createViewMeta.ts +106 -34
  211. package/src/views/inheritLayoutContext.ts +26 -0
  212. package/src/views/runViewBeforeClose.ts +19 -0
  213. package/src/views/useDialog.tsx +13 -3
  214. package/src/views/useDrawer.tsx +13 -3
  215. package/src/views/usePage.tsx +367 -180
@@ -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, shouldActivateSearchSubmenu) => ({
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,50 @@ 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
+ if (shouldActivateSearchSubmenu) {
354
+ activateSearchSubmenu(searchKey);
355
+ }
356
+ if (((_a = e.nativeEvent) == null ? void 0 : _a.isComposing) || searchHandlers.isComposing(searchKey)) {
357
+ searchHandlers.updateInputValue(searchKey, value);
358
+ return;
359
+ }
360
+ if (!value && shouldActivateSearchSubmenu) {
361
+ deactivateSearchSubmenu(searchKey);
362
+ }
363
+ searchHandlers.updateSearchValue(searchKey, value);
364
+ },
365
+ onCompositionStart: (e) => {
366
+ e.stopPropagation();
367
+ if (shouldActivateSearchSubmenu) {
368
+ activateSearchSubmenu(searchKey);
369
+ }
370
+ searchHandlers.startComposition(searchKey);
371
+ },
372
+ onCompositionEnd: (e) => {
373
+ e.stopPropagation();
374
+ const value = e.currentTarget.value;
375
+ if (shouldActivateSearchSubmenu) {
376
+ if (value) {
377
+ activateSearchSubmenu(searchKey);
378
+ } else {
379
+ deactivateSearchSubmenu(searchKey);
380
+ }
381
+ }
382
+ searchHandlers.endComposition(searchKey, value);
383
+ },
384
+ onClick: (e) => {
385
+ e.stopPropagation();
386
+ },
387
+ onKeyDown: (e) => {
268
388
  e.stopPropagation();
269
- updateSearchValue(searchKey, e.target.value);
270
389
  },
271
- onClick: (e) => e.stopPropagation(),
272
390
  onMouseDown: (e) => {
273
391
  e.stopPropagation();
274
392
  },
@@ -286,14 +404,26 @@ const createEmptyItem = /* @__PURE__ */ __name((itemKey, t) => ({
286
404
  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
405
  disabled: true
288
406
  }), "createEmptyItem");
407
+ const KEEP_OPEN_LABEL_STYLE = {
408
+ display: "block",
409
+ width: "100%"
410
+ };
289
411
  const DROPDOWN_PERSIST_TTL_MS = 350;
412
+ const SUBMENU_CLOSE_DELAY = 0.05;
413
+ const SUBMENU_MOTION_DISABLED = {
414
+ motionEnter: false,
415
+ motionLeave: false
416
+ };
290
417
  const dropdownPersistRegistry = /* @__PURE__ */ new Map();
291
418
  const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
292
419
  const engine = (0, import_provider.useFlowEngine)();
293
420
  const [menuVisible, setMenuVisible] = (0, import_react.useState)(false);
294
421
  const [openKeys, setOpenKeys] = (0, import_react.useState)(/* @__PURE__ */ new Set());
422
+ const [activeSearchKey, setActiveSearchKey] = (0, import_react.useState)(null);
295
423
  const [rootItems, setRootItems] = (0, import_react.useState)([]);
296
424
  const [rootLoading, setRootLoading] = (0, import_react.useState)(false);
425
+ const closeByOutsideClickRef = (0, import_react.useRef)(false);
426
+ const skipPreserveActiveSearchRef = (0, import_react.useRef)(false);
297
427
  const dropdownMaxHeight = useNiceDropdownMaxHeight();
298
428
  const t = engine.translate.bind(engine);
299
429
  const { items: menuItems, keepDropdownOpen, persistKey, stateVersion, refreshKeys, ...dropdownMenuProps } = menu;
@@ -304,9 +434,89 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
304
434
  openKeys,
305
435
  refreshKeys
306
436
  );
307
- const { searchValues, isSearching, updateSearchValue } = useMenuSearch();
437
+ const searchHandlers = useMenuSearch();
438
+ const { searchValues, inputValues, clearSearchValue, clearAllSearchValues } = searchHandlers;
308
439
  const { requestKeepOpen, shouldPreventClose } = useKeepDropdownOpen();
309
440
  useSubmenuStyles(menuVisible, dropdownMaxHeight);
441
+ const closeMenu = (0, import_react.useCallback)(() => {
442
+ setMenuVisible(false);
443
+ setActiveSearchKey(null);
444
+ setOpenKeys(/* @__PURE__ */ new Set());
445
+ clearAllSearchValues();
446
+ }, [clearAllSearchValues]);
447
+ const activateSearchSubmenu = (0, import_react.useCallback)((key) => {
448
+ setActiveSearchKey(key);
449
+ setOpenKeys((prev) => {
450
+ if (prev.has(key)) return prev;
451
+ const next = new Set(prev);
452
+ next.add(key);
453
+ return next;
454
+ });
455
+ }, []);
456
+ const deactivateSearchSubmenu = (0, import_react.useCallback)((key) => {
457
+ setActiveSearchKey((prev) => prev === key ? null : prev);
458
+ }, []);
459
+ const closeActiveSearchForPath = (0, import_react.useCallback)(
460
+ (keyPath) => {
461
+ if (!activeSearchKey || keyPath === activeSearchKey || keyPath.startsWith(`${activeSearchKey}/`) || activeSearchKey.startsWith(`${keyPath}/`)) {
462
+ return;
463
+ }
464
+ skipPreserveActiveSearchRef.current = true;
465
+ clearSearchValue(activeSearchKey);
466
+ setActiveSearchKey(null);
467
+ setOpenKeys((prev) => {
468
+ const next = new Set(prev);
469
+ next.delete(activeSearchKey);
470
+ return next;
471
+ });
472
+ },
473
+ [activeSearchKey, clearSearchValue]
474
+ );
475
+ const handleMenuOpenChange = (0, import_react.useCallback)(
476
+ (nextOpenKeys) => {
477
+ var _a, _b;
478
+ let normalized = normalizeOpenKeys(nextOpenKeys);
479
+ if (activeSearchKey && openKeys.has(activeSearchKey) && !normalized.includes(activeSearchKey)) {
480
+ if (normalized.length || skipPreserveActiveSearchRef.current) {
481
+ clearSearchValue(activeSearchKey);
482
+ setActiveSearchKey(null);
483
+ } else {
484
+ normalized = [activeSearchKey];
485
+ }
486
+ }
487
+ if (!normalized.length && shouldPreventClose()) {
488
+ (_a = dropdownMenuProps.onOpenChange) == null ? void 0 : _a.call(dropdownMenuProps, Array.from(openKeys));
489
+ skipPreserveActiveSearchRef.current = false;
490
+ return;
491
+ }
492
+ Array.from(openKeys).forEach((key) => {
493
+ if (!normalized.includes(key)) {
494
+ clearSearchValue(key);
495
+ }
496
+ });
497
+ setOpenKeys(new Set(normalized));
498
+ (_b = dropdownMenuProps.onOpenChange) == null ? void 0 : _b.call(dropdownMenuProps, normalized);
499
+ skipPreserveActiveSearchRef.current = false;
500
+ },
501
+ [activeSearchKey, clearSearchValue, dropdownMenuProps, openKeys, shouldPreventClose]
502
+ );
503
+ (0, import_react.useEffect)(() => {
504
+ if (!menuVisible) return;
505
+ const markOutsideClick = /* @__PURE__ */ __name((event) => {
506
+ const target = event.target;
507
+ const isOutside = !(target == null ? void 0 : target.closest(".ant-dropdown, .ant-dropdown-menu, .ant-dropdown-menu-submenu-popup"));
508
+ closeByOutsideClickRef.current = isOutside;
509
+ if (isOutside) {
510
+ closeMenu();
511
+ }
512
+ }, "markOutsideClick");
513
+ document.addEventListener("pointerdown", markOutsideClick, true);
514
+ document.addEventListener("mousedown", markOutsideClick, true);
515
+ return () => {
516
+ document.removeEventListener("pointerdown", markOutsideClick, true);
517
+ document.removeEventListener("mousedown", markOutsideClick, true);
518
+ };
519
+ }, [closeMenu, menuVisible]);
310
520
  (0, import_react.useEffect)(() => {
311
521
  if (!persistKey) return;
312
522
  const until = dropdownPersistRegistry.get(persistKey) || 0;
@@ -326,6 +536,11 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
326
536
  }
327
537
  };
328
538
  }, [persistKey, menuVisible]);
539
+ (0, import_react.useEffect)(() => {
540
+ if (!menuVisible) {
541
+ setOpenKeys(/* @__PURE__ */ new Set());
542
+ }
543
+ }, [menuVisible]);
329
544
  (0, import_react.useEffect)(() => {
330
545
  const loadRootItems = /* @__PURE__ */ __name(async () => {
331
546
  let resolvedItems;
@@ -347,15 +562,13 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
347
562
  function buildSearchChildren(children, item, keyPath, path, menuVisible2, resolve) {
348
563
  const searchKey = keyPath;
349
564
  const currentSearchValue = searchValues[searchKey] || "";
565
+ const currentInputValue = inputValues[searchKey] ?? currentSearchValue;
566
+ const shouldActivateSearchSubmenu = !(item.type === "group" && path.length === 0);
350
567
  const filteredChildren = currentSearchValue ? (/* @__PURE__ */ __name(function deepFilter(items2) {
351
568
  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
569
  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);
570
+ const labelStr = getLabelSearchText(child.label).toLowerCase();
571
+ const selfMatch = labelStr.includes(searchText);
359
572
  if (child.type === "group" && Array.isArray(child.children)) {
360
573
  const nested = deepFilter(child.children);
361
574
  if (selfMatch || nested.length > 0) {
@@ -367,7 +580,17 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
367
580
  }).filter(Boolean);
368
581
  }, "deepFilter"))(children) : children;
369
582
  const resolvedFiltered = resolve(filteredChildren, [...path, item.key]);
370
- const searchItem = createSearchItem(item, searchKey, currentSearchValue, menuVisible2, t, updateSearchValue);
583
+ const searchItem = createSearchItem(
584
+ item,
585
+ searchKey,
586
+ currentInputValue,
587
+ menuVisible2,
588
+ t,
589
+ searchHandlers,
590
+ activateSearchSubmenu,
591
+ deactivateSearchSubmenu,
592
+ shouldActivateSearchSubmenu
593
+ );
371
594
  const dividerItem = { key: `${keyPath}-search-divider`, type: "divider" };
372
595
  if (currentSearchValue && resolvedFiltered.length === 0) {
373
596
  return [searchItem, dividerItem, createEmptyItem(keyPath, t)];
@@ -419,51 +642,68 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
419
642
  if (item.type === "divider") {
420
643
  return { type: "divider", key: keyPath };
421
644
  }
645
+ const label = typeof item.label === "string" ? t(item.label) : item.label;
422
646
  if (item.searchable && children) {
423
647
  return {
424
- key: item.key,
425
- label: typeof item.label === "string" ? t(item.label) : item.label,
648
+ key: keyPath,
649
+ label,
426
650
  onClick: /* @__PURE__ */ __name((info) => {
427
651
  }, "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"),
652
+ onMouseEnter: /* @__PURE__ */ __name(() => closeActiveSearchForPath(keyPath), "onMouseEnter"),
436
653
  children: buildSearchChildren(children, item, keyPath, path, menuVisible, resolveItems)
437
654
  };
438
655
  }
656
+ const itemShouldKeepOpen = !children && (item.keepDropdownOpen ?? keepDropdownOpen ?? false);
657
+ const handleLeafClick = /* @__PURE__ */ __name((info) => {
658
+ var _a;
659
+ if (children) {
660
+ return;
661
+ }
662
+ if (itemShouldKeepOpen) {
663
+ requestKeepOpen();
664
+ }
665
+ const extendedInfo = {
666
+ ...info,
667
+ key: (info == null ? void 0 : info.key) ?? keyPath,
668
+ keyPath: (info == null ? void 0 : info.keyPath) ?? [keyPath],
669
+ item: (info == null ? void 0 : info.item) || item,
670
+ originalItem: item,
671
+ keepDropdownOpen: itemShouldKeepOpen
672
+ };
673
+ (_a = menu.onClick) == null ? void 0 : _a.call(menu, extendedInfo);
674
+ }, "handleLeafClick");
439
675
  return {
440
676
  key: keyPath,
441
- label: typeof item.label === "string" ? t(item.label) : item.label,
677
+ label: itemShouldKeepOpen ? /* @__PURE__ */ import_react.default.createElement(
678
+ "div",
679
+ {
680
+ style: KEEP_OPEN_LABEL_STYLE,
681
+ onMouseDown: (event) => {
682
+ event.stopPropagation();
683
+ requestKeepOpen();
684
+ },
685
+ onClick: (event) => {
686
+ event.stopPropagation();
687
+ handleLeafClick({
688
+ key: keyPath,
689
+ keyPath: [keyPath],
690
+ item,
691
+ domEvent: event
692
+ });
693
+ }
694
+ },
695
+ label
696
+ ) : label,
442
697
  onClick: /* @__PURE__ */ __name((info) => {
443
- var _a;
444
- if (children) {
698
+ if (!itemShouldKeepOpen) handleLeafClick(info);
699
+ }, "onClick"),
700
+ onMouseEnter: /* @__PURE__ */ __name(() => closeActiveSearchForPath(keyPath), "onMouseEnter"),
701
+ onMouseDown: /* @__PURE__ */ __name(() => {
702
+ if (!itemShouldKeepOpen) {
445
703
  return;
446
704
  }
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"),
705
+ requestKeepOpen();
706
+ }, "onMouseDown"),
467
707
  children: children && children.length > 0 ? resolveItems(children, [...path, item.key]) : children && children.length === 0 ? [createEmptyItem(keyPath, t)] : void 0
468
708
  };
469
709
  });
@@ -498,23 +738,32 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
498
738
  placement: "bottomLeft",
499
739
  menu: {
500
740
  ...dropdownMenuProps,
741
+ openKeys: Array.from(openKeys),
501
742
  items,
743
+ subMenuCloseDelay: dropdownMenuProps.subMenuCloseDelay ?? SUBMENU_CLOSE_DELAY,
744
+ motion: dropdownMenuProps.motion ?? SUBMENU_MOTION_DISABLED,
502
745
  onClick: /* @__PURE__ */ __name(() => {
503
746
  }, "onClick"),
747
+ onOpenChange: handleMenuOpenChange,
504
748
  style: {
505
749
  maxHeight: dropdownMaxHeight,
506
750
  overflowY: "auto",
507
751
  ...dropdownMenuProps == null ? void 0 : dropdownMenuProps.style
508
752
  }
509
753
  },
510
- onOpenChange: (visible) => {
511
- if (!visible && isSearching) {
754
+ onOpenChange: (visible, info) => {
755
+ if (!visible && activeSearchKey && (info == null ? void 0 : info.source) === "trigger" && !closeByOutsideClickRef.current) {
512
756
  return;
513
757
  }
514
758
  if (!visible && shouldPreventClose()) {
515
759
  return;
516
760
  }
517
- setMenuVisible(visible);
761
+ if (!visible) {
762
+ closeMenu();
763
+ } else {
764
+ setMenuVisible(visible);
765
+ }
766
+ closeByOutsideClickRef.current = false;
518
767
  }
519
768
  },
520
769
  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
  });
@@ -19,6 +19,7 @@ export interface BuildFieldChildrenOptions {
19
19
  fieldUseModel?: string | ((field: any) => string);
20
20
  collection?: Collection;
21
21
  associationPathName?: string;
22
+ maxAssociationFieldDepth?: number;
22
23
  /**
23
24
  * 点击这些子项后,除自身路径外,还需要联动刷新的其他菜单路径前缀
24
25
  */
@@ -29,6 +30,6 @@ export declare function buildWrapperFieldChildren(ctx: FlowModelContext, options
29
30
  label: string;
30
31
  type: "group";
31
32
  searchable: boolean;
32
- searchPlaceholder: any;
33
+ searchPlaceholder: string;
33
34
  children: SubModelItem[];
34
35
  }[];
@@ -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
  }
@@ -225,14 +231,18 @@ function buildSubModelGroups(subModelBaseClasses = []) {
225
231
  }
226
232
  __name(buildSubModelGroups, "buildSubModelGroups");
227
233
  function buildWrapperFieldChildren(ctx, options) {
228
- var _a;
229
- const { useModel, fieldUseModel, associationPathName, refreshTargets } = options;
234
+ var _a, _b;
235
+ const { useModel, fieldUseModel, associationPathName, refreshTargets, maxAssociationFieldDepth = 2 } = options;
230
236
  const collection = options.collection || ctx.model["collection"] || ctx.collection;
231
237
  const fields = collection.getFields();
232
238
  const defaultItemKeys = ["fieldSettings", "init"];
233
239
  const children = [];
240
+ const associationDepth = associationPathName ? associationPathName.split(".").filter(Boolean).length : 0;
234
241
  for (const f of fields) {
235
242
  if (!((_a = f == null ? void 0 : f.options) == null ? void 0 : _a.interface)) continue;
243
+ if (associationDepth >= maxAssociationFieldDepth && (((_b = f.isAssociationField) == null ? void 0 : _b.call(f)) || f.target || f.targetCollection)) {
244
+ continue;
245
+ }
236
246
  const fieldPath = associationPathName ? `${associationPathName}.${f.name}` : f.name;
237
247
  const childUse = typeof fieldUseModel === "function" ? fieldUseModel(f) : fieldUseModel ?? "FieldModel";
238
248
  if (childUse) {
@@ -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>;