@nocobase/flow-engine 2.2.0-alpha.1 → 2.2.0-alpha.11

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 (115) hide show
  1. package/lib/JSRunner.d.ts +1 -0
  2. package/lib/JSRunner.js +110 -20
  3. package/lib/acl/Acl.d.ts +2 -1
  4. package/lib/acl/Acl.js +28 -0
  5. package/lib/components/FieldModelRenderer.js +44 -31
  6. package/lib/components/FlowContextSelector.js +55 -12
  7. package/lib/components/FormItem.js +11 -7
  8. package/lib/components/MobilePopup.js +39 -10
  9. package/lib/components/MobilePopup.style.js +27 -6
  10. package/lib/components/dnd/index.js +9 -2
  11. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +86 -32
  12. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +20 -0
  13. package/lib/components/subModel/LazyDropdown.js +41 -26
  14. package/lib/components/variables/VariableHybridInput.d.ts +9 -0
  15. package/lib/components/variables/VariableHybridInput.js +146 -17
  16. package/lib/components/variables/VariableInput.js +19 -7
  17. package/lib/components/variables/VariableTag.js +48 -36
  18. package/lib/components/variables/types.d.ts +21 -0
  19. package/lib/flowContext.d.ts +1 -1
  20. package/lib/flowContext.js +97 -42
  21. package/lib/flowEngine.js +6 -0
  22. package/lib/flowI18n.js +3 -3
  23. package/lib/flowSettings.d.ts +5 -1
  24. package/lib/flowSettings.js +70 -0
  25. package/lib/locale/en-US.json +3 -0
  26. package/lib/locale/index.d.ts +6 -0
  27. package/lib/locale/zh-CN.json +3 -0
  28. package/lib/resources/apiResource.js +2 -1
  29. package/lib/resources/baseRecordResource.js +6 -17
  30. package/lib/resources/multiRecordResource.js +13 -3
  31. package/lib/resources/singleRecordResource.js +7 -2
  32. package/lib/runjs-context/helpers.js +12 -5
  33. package/lib/types.d.ts +15 -1
  34. package/lib/types.js +1 -0
  35. package/lib/utils/dataSourceDirty.d.ts +20 -0
  36. package/lib/utils/dataSourceDirty.js +139 -0
  37. package/lib/utils/dirtyAwareApiClient.d.ts +11 -0
  38. package/lib/utils/dirtyAwareApiClient.js +632 -0
  39. package/lib/utils/index.d.ts +1 -1
  40. package/lib/utils/index.js +11 -11
  41. package/lib/utils/loadedPageCache.d.ts +1 -0
  42. package/lib/utils/loadedPageCache.js +6 -0
  43. package/lib/utils/openViewRouteState.d.ts +28 -0
  44. package/lib/utils/openViewRouteState.js +125 -0
  45. package/lib/utils/parsePathnameToViewParams.d.ts +3 -0
  46. package/lib/utils/parsePathnameToViewParams.js +18 -1
  47. package/lib/utils/resolveRunJSObjectValues.js +3 -2
  48. package/lib/utils/runjsModuleLoader.js +0 -30
  49. package/lib/views/ViewNavigation.js +5 -0
  50. package/package.json +4 -4
  51. package/src/JSRunner.ts +112 -25
  52. package/src/__tests__/JSRunner.test.ts +4 -5
  53. package/src/__tests__/flowContext.test.ts +154 -0
  54. package/src/__tests__/flowEngine.dataSourceDirty.test.ts +51 -0
  55. package/src/__tests__/flowI18n.test.ts +11 -0
  56. package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
  57. package/src/__tests__/flowSettings.test.ts +72 -0
  58. package/src/__tests__/runjsRuntimeFeatures.test.ts +15 -2
  59. package/src/__tests__/viewScopedFlowEngine.test.ts +72 -6
  60. package/src/acl/Acl.tsx +36 -1
  61. package/src/acl/__tests__/Acl.test.tsx +70 -0
  62. package/src/components/FieldModelRenderer.tsx +50 -36
  63. package/src/components/FlowContextSelector.tsx +66 -11
  64. package/src/components/FormItem.tsx +12 -7
  65. package/src/components/MobilePopup.style.ts +34 -7
  66. package/src/components/MobilePopup.tsx +42 -10
  67. package/src/components/__tests__/FieldModelRenderer.test.tsx +165 -0
  68. package/src/components/__tests__/FormItem.test.tsx +17 -2
  69. package/src/components/__tests__/MobilePopup.style.test.tsx +103 -0
  70. package/src/components/__tests__/MobilePopup.test.tsx +150 -0
  71. package/src/components/dnd/index.tsx +11 -2
  72. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +105 -35
  73. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +381 -12
  74. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +28 -0
  75. package/src/components/subModel/LazyDropdown.tsx +44 -26
  76. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +85 -2
  77. package/src/components/variables/VariableHybridInput.tsx +185 -14
  78. package/src/components/variables/VariableInput.tsx +32 -7
  79. package/src/components/variables/VariableTag.tsx +51 -37
  80. package/src/components/variables/__tests__/FlowContextSelector.test.tsx +60 -3
  81. package/src/components/variables/__tests__/VariableHybridInput.test.tsx +212 -0
  82. package/src/components/variables/__tests__/VariableInput.test.tsx +202 -6
  83. package/src/components/variables/__tests__/VariableTag.test.tsx +80 -0
  84. package/src/components/variables/types.ts +21 -0
  85. package/src/flowContext.ts +104 -36
  86. package/src/flowEngine.ts +6 -0
  87. package/src/flowI18n.ts +8 -3
  88. package/src/flowSettings.ts +85 -1
  89. package/src/locale/__tests__/index.test.ts +21 -0
  90. package/src/locale/en-US.json +3 -0
  91. package/src/locale/zh-CN.json +3 -0
  92. package/src/resources/apiResource.ts +2 -1
  93. package/src/resources/baseRecordResource.ts +6 -23
  94. package/src/resources/multiRecordResource.ts +13 -3
  95. package/src/resources/singleRecordResource.ts +6 -1
  96. package/src/runjs-context/helpers.ts +12 -6
  97. package/src/types.ts +16 -0
  98. package/src/utils/__tests__/dirtyAwareApiClient.test.ts +713 -0
  99. package/src/utils/__tests__/openViewRouteState.test.ts +40 -0
  100. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +36 -0
  101. package/src/utils/dataSourceDirty.ts +126 -0
  102. package/src/utils/dirtyAwareApiClient.ts +742 -0
  103. package/src/utils/index.ts +10 -9
  104. package/src/utils/loadedPageCache.ts +7 -0
  105. package/src/utils/openViewRouteState.ts +107 -0
  106. package/src/utils/parsePathnameToViewParams.ts +23 -1
  107. package/src/utils/resolveRunJSObjectValues.ts +5 -2
  108. package/src/utils/runjsModuleLoader.ts +0 -32
  109. package/src/views/ViewNavigation.ts +6 -1
  110. package/src/views/__tests__/ViewNavigation.test.ts +15 -0
  111. package/lib/utils/safeGlobals.d.ts +0 -28
  112. package/lib/utils/safeGlobals.js +0 -367
  113. package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +0 -38
  114. package/src/utils/__tests__/safeGlobals.test.ts +0 -106
  115. package/src/utils/safeGlobals.ts +0 -406
@@ -57,6 +57,10 @@ const EMPTY_COLUMN_UID = "EMPTY_COLUMN";
57
57
  const TOOLBAR_DRAG_ACTIVITY_EVENT = "nb-toolbar-drag-activity";
58
58
  const TOOLBAR_DRAG_ANCHOR_EVENT = "nb-toolbar-drag-anchor";
59
59
  const MENU_SUBMENU_POPUP_SELECTOR = ".ant-menu-submenu-popup";
60
+ const getToolbarModelUidFromNode = /* @__PURE__ */ __name((node) => {
61
+ var _a;
62
+ return ((_a = node == null ? void 0 : node.closest(".nb-toolbar-container[data-model-uid]")) == null ? void 0 : _a.getAttribute("data-model-uid")) || null;
63
+ }, "getToolbarModelUidFromNode");
60
64
  const resolveOverlayAnchorTransform = /* @__PURE__ */ __name(({
61
65
  activeId,
62
66
  active,
@@ -76,7 +80,7 @@ const resolveOverlayAnchorTransform = /* @__PURE__ */ __name(({
76
80
  const resolveDraggableHostNode = /* @__PURE__ */ __name((activatorNode) => {
77
81
  const ownerDocument = activatorNode == null ? void 0 : activatorNode.ownerDocument;
78
82
  const floatToolbarContainer = activatorNode == null ? void 0 : activatorNode.closest(".nb-toolbar-container[data-model-uid]");
79
- const toolbarModelUid = floatToolbarContainer == null ? void 0 : floatToolbarContainer.getAttribute("data-model-uid");
83
+ const toolbarModelUid = getToolbarModelUidFromNode(activatorNode);
80
84
  if (!ownerDocument || !toolbarModelUid) {
81
85
  return activatorNode;
82
86
  }
@@ -99,6 +103,7 @@ const DragHandler = /* @__PURE__ */ __name(({
99
103
  const dragHandlerRef = (0, import_react.useRef)(null);
100
104
  const draggableNodeRef = (0, import_react.useRef)(null);
101
105
  const pointerPressCleanupRef = (0, import_react.useRef)(null);
106
+ const toolbarDragModelUidRef = (0, import_react.useRef)(null);
102
107
  const isDraggingRef = (0, import_react.useRef)(isDragging);
103
108
  const isPointerPressActiveRef = (0, import_react.useRef)(false);
104
109
  const isToolbarDragActiveRef = (0, import_react.useRef)(false);
@@ -128,9 +133,11 @@ const DragHandler = /* @__PURE__ */ __name(({
128
133
  if (!ownerDocument) {
129
134
  return;
130
135
  }
136
+ const toolbarModelUid = getToolbarModelUidFromNode(dragHandlerRef.current) || toolbarDragModelUidRef.current || model.uid;
137
+ toolbarDragModelUidRef.current = active ? toolbarModelUid : null;
131
138
  ownerDocument.dispatchEvent(
132
139
  new CustomEvent(TOOLBAR_DRAG_ACTIVITY_EVENT, {
133
- detail: { active, modelUid: model.uid }
140
+ detail: { active, modelUid: toolbarModelUid }
134
141
  })
135
142
  );
136
143
  },
@@ -44,6 +44,7 @@ var import_react = __toESM(require("react"));
44
44
  var import_react_dom = require("react-dom");
45
45
  var import_antd = require("antd");
46
46
  var import_css = require("@emotion/css");
47
+ var import_ahooks = require("ahooks");
47
48
  var import_hooks = require("../../../../hooks");
48
49
  var import_provider = require("../../../../provider");
49
50
  var import_utils = require("../../../../utils");
@@ -312,6 +313,38 @@ const buildToolbarContainerStyle = /* @__PURE__ */ __name((portalRect, toolbarSt
312
313
  const isModelByIdProps = /* @__PURE__ */ __name((props) => {
313
314
  return "uid" in props && "modelClassName" in props && Boolean(props.uid) && Boolean(props.modelClassName);
314
315
  }, "isModelByIdProps");
316
+ const stopResizeInteractionEvent = /* @__PURE__ */ __name((event) => {
317
+ var _a;
318
+ if (!event) {
319
+ return;
320
+ }
321
+ event.preventDefault();
322
+ event.stopPropagation();
323
+ const nativeEvent = "nativeEvent" in event ? event.nativeEvent : event;
324
+ (_a = nativeEvent.stopImmediatePropagation) == null ? void 0 : _a.call(nativeEvent);
325
+ }, "stopResizeInteractionEvent");
326
+ const pendingResizeClickSuppressions = /* @__PURE__ */ new WeakMap();
327
+ const clearPendingResizeClickSuppression = /* @__PURE__ */ __name((ownerDocument) => {
328
+ const pending = pendingResizeClickSuppressions.get(ownerDocument);
329
+ if (!pending) {
330
+ return;
331
+ }
332
+ ownerDocument.removeEventListener("click", pending.listener, true);
333
+ (ownerDocument.defaultView || window).clearTimeout(pending.timer);
334
+ pendingResizeClickSuppressions.delete(ownerDocument);
335
+ }, "clearPendingResizeClickSuppression");
336
+ const suppressNextResizeClick = /* @__PURE__ */ __name((ownerDocument) => {
337
+ clearPendingResizeClickSuppression(ownerDocument);
338
+ const listener = /* @__PURE__ */ __name((event) => {
339
+ stopResizeInteractionEvent(event);
340
+ clearPendingResizeClickSuppression(ownerDocument);
341
+ }, "listener");
342
+ const timer = (ownerDocument.defaultView || window).setTimeout(() => {
343
+ clearPendingResizeClickSuppression(ownerDocument);
344
+ }, 0);
345
+ pendingResizeClickSuppressions.set(ownerDocument, { listener, timer });
346
+ ownerDocument.addEventListener("click", listener, true);
347
+ }, "suppressNextResizeClick");
315
348
  const FlowsFloatContextMenu = (0, import_reactive.observer)((props) => {
316
349
  const ctx = (0, import__.useFlowContext)();
317
350
  if (!ctx.flowSettingsEnabled) {
@@ -326,44 +359,64 @@ const ResizeHandles = /* @__PURE__ */ __name((props) => {
326
359
  const isDraggingRef = (0, import_react.useRef)(false);
327
360
  const dragTypeRef = (0, import_react.useRef)(null);
328
361
  const dragStartPosRef = (0, import_react.useRef)({ x: 0, y: 0 });
362
+ const dragOwnerDocumentRef = (0, import_react.useRef)(null);
329
363
  const { onDragStart, onDragEnd } = props;
330
- const handleDragMove = (0, import_react.useCallback)(
331
- (e) => {
332
- if (!isDraggingRef.current || !dragTypeRef.current) return;
333
- const deltaX = e.clientX - dragStartPosRef.current.x;
334
- switch (dragTypeRef.current) {
335
- case "left":
336
- props.model.parent.emitter.emit("onResizeLeft", { resizeDistance: -deltaX, model: props.model });
337
- break;
338
- case "right":
339
- props.model.parent.emitter.emit("onResizeRight", { resizeDistance: deltaX, model: props.model });
340
- break;
341
- }
342
- },
343
- [props.model]
344
- );
345
- const handleDragEnd = (0, import_react.useCallback)(() => {
364
+ const handleDragMove = (0, import_ahooks.useMemoizedFn)((e) => {
365
+ if (!isDraggingRef.current || !dragTypeRef.current) return;
366
+ stopResizeInteractionEvent(e);
367
+ const deltaX = e.clientX - dragStartPosRef.current.x;
368
+ switch (dragTypeRef.current) {
369
+ case "left":
370
+ props.model.parent.emitter.emit("onResizeLeft", { resizeDistance: -deltaX, model: props.model });
371
+ break;
372
+ case "right":
373
+ props.model.parent.emitter.emit("onResizeRight", { resizeDistance: deltaX, model: props.model });
374
+ break;
375
+ }
376
+ });
377
+ const handleDragEnd = (0, import_ahooks.useMemoizedFn)((e) => {
378
+ if (!isDraggingRef.current) {
379
+ return;
380
+ }
381
+ stopResizeInteractionEvent(e);
382
+ const ownerDocument = dragOwnerDocumentRef.current || document;
383
+ ownerDocument.removeEventListener("mousemove", handleDragMove, true);
384
+ ownerDocument.removeEventListener("mouseup", handleDragEnd, true);
385
+ suppressNextResizeClick(ownerDocument);
346
386
  isDraggingRef.current = false;
347
387
  dragTypeRef.current = null;
348
388
  dragStartPosRef.current = { x: 0, y: 0 };
349
- document.removeEventListener("mousemove", handleDragMove);
350
- document.removeEventListener("mouseup", handleDragEnd);
389
+ dragOwnerDocumentRef.current = null;
351
390
  props.model.parent.emitter.emit("onResizeEnd");
352
391
  onDragEnd == null ? void 0 : onDragEnd();
353
- }, [handleDragMove, onDragEnd, props.model]);
354
- const handleDragStart = (0, import_react.useCallback)(
355
- (e, type) => {
356
- e.preventDefault();
357
- e.stopPropagation();
358
- isDraggingRef.current = true;
359
- dragTypeRef.current = type;
360
- dragStartPosRef.current = { x: e.clientX, y: e.clientY };
361
- document.addEventListener("mousemove", handleDragMove);
362
- document.addEventListener("mouseup", handleDragEnd);
363
- onDragStart == null ? void 0 : onDragStart();
364
- },
365
- [handleDragMove, handleDragEnd, onDragStart]
366
- );
392
+ });
393
+ (0, import_react.useEffect)(() => {
394
+ return () => {
395
+ const dragOwnerDocument = dragOwnerDocumentRef.current;
396
+ dragOwnerDocument == null ? void 0 : dragOwnerDocument.removeEventListener("mousemove", handleDragMove, true);
397
+ dragOwnerDocument == null ? void 0 : dragOwnerDocument.removeEventListener("mouseup", handleDragEnd, true);
398
+ if (isDraggingRef.current) {
399
+ suppressNextResizeClick(dragOwnerDocument || document);
400
+ props.model.parent.emitter.emit("onResizeEnd");
401
+ onDragEnd == null ? void 0 : onDragEnd();
402
+ }
403
+ isDraggingRef.current = false;
404
+ dragTypeRef.current = null;
405
+ dragStartPosRef.current = { x: 0, y: 0 };
406
+ dragOwnerDocumentRef.current = null;
407
+ };
408
+ }, [handleDragMove, handleDragEnd, onDragEnd, props.model]);
409
+ const handleDragStart = (0, import_ahooks.useMemoizedFn)((e, type) => {
410
+ stopResizeInteractionEvent(e);
411
+ const ownerDocument = e.currentTarget.ownerDocument;
412
+ isDraggingRef.current = true;
413
+ dragTypeRef.current = type;
414
+ dragStartPosRef.current = { x: e.clientX, y: e.clientY };
415
+ dragOwnerDocumentRef.current = ownerDocument;
416
+ ownerDocument.addEventListener("mousemove", handleDragMove, true);
417
+ ownerDocument.addEventListener("mouseup", handleDragEnd, true);
418
+ onDragStart == null ? void 0 : onDragStart();
419
+ });
367
420
  return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
368
421
  "div",
369
422
  {
@@ -466,6 +519,7 @@ const FlowsFloatContextMenuWithModel = (0, import_reactive.observer)(
466
519
  getPopupContainer,
467
520
  handleSettingsMenuOpenChange,
468
521
  model,
522
+ modelUid,
469
523
  settingsMenuLevel,
470
524
  showCopyUidButton,
471
525
  showDeleteButton,
@@ -44,6 +44,17 @@ const getToolbarModelUidFromTarget = /* @__PURE__ */ __name((target) => {
44
44
  }
45
45
  return ((_a = target.closest(".nb-toolbar-container[data-model-uid]")) == null ? void 0 : _a.getAttribute("data-model-uid")) || null;
46
46
  }, "getToolbarModelUidFromTarget");
47
+ const escapeCssAttributeValue = /* @__PURE__ */ __name((value) => {
48
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\a ").replace(/\r/g, "\\d ").replace(/\f/g, "\\c ");
49
+ }, "escapeCssAttributeValue");
50
+ const isToolbarModelUidRendered = /* @__PURE__ */ __name((ownerDocument, modelUid) => {
51
+ if (!ownerDocument) {
52
+ return false;
53
+ }
54
+ return !!ownerDocument.querySelector(
55
+ `.nb-toolbar-container[data-model-uid="${escapeCssAttributeValue(modelUid)}"]`
56
+ );
57
+ }, "isToolbarModelUidRendered");
47
58
  const isNodeWithinDescendantFloatToolbar = /* @__PURE__ */ __name((target, container, currentModelUid) => {
48
59
  const targetModelUid = getToolbarModelUidFromTarget(target);
49
60
  if (!container || !targetModelUid || targetModelUid === currentModelUid) {
@@ -222,6 +233,15 @@ const useFloatToolbarVisibility = /* @__PURE__ */ __name(({
222
233
  const isCurrentHostTarget = !childWithMenu || childWithMenu === containerRef.current;
223
234
  if (isCurrentHostTarget) {
224
235
  clearHideToolbarTimer();
236
+ setActiveChildToolbarIds((prevIds) => {
237
+ var _a;
238
+ if (!prevIds.length) {
239
+ return prevIds;
240
+ }
241
+ const ownerDocument = ((_a = containerRef.current) == null ? void 0 : _a.ownerDocument) ?? null;
242
+ const nextIds = prevIds.filter((id) => isToolbarModelUidRendered(ownerDocument, id));
243
+ return nextIds.length === prevIds.length ? prevIds : nextIds;
244
+ });
225
245
  setHostHovered(true);
226
246
  }
227
247
  setHideMenu(!!childWithMenu && childWithMenu !== containerRef.current);
@@ -347,19 +347,15 @@ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchV
347
347
  e.stopPropagation();
348
348
  },
349
349
  onChange: (e) => {
350
- var _a;
351
350
  e.stopPropagation();
352
351
  const value = e.target.value;
353
352
  if (shouldActivateSearchSubmenu) {
354
353
  activateSearchSubmenu(searchKey);
355
354
  }
356
- if (((_a = e.nativeEvent) == null ? void 0 : _a.isComposing) || searchHandlers.isComposing(searchKey)) {
355
+ if (e.nativeEvent.isComposing || searchHandlers.isComposing(searchKey)) {
357
356
  searchHandlers.updateInputValue(searchKey, value);
358
357
  return;
359
358
  }
360
- if (!value && shouldActivateSearchSubmenu) {
361
- deactivateSearchSubmenu(searchKey);
362
- }
363
359
  searchHandlers.updateSearchValue(searchKey, value);
364
360
  },
365
361
  onCompositionStart: (e) => {
@@ -373,11 +369,7 @@ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchV
373
369
  e.stopPropagation();
374
370
  const value = e.currentTarget.value;
375
371
  if (shouldActivateSearchSubmenu) {
376
- if (value) {
377
- activateSearchSubmenu(searchKey);
378
- } else {
379
- deactivateSearchSubmenu(searchKey);
380
- }
372
+ activateSearchSubmenu(searchKey);
381
373
  }
382
374
  searchHandlers.endComposition(searchKey, value);
383
375
  },
@@ -385,10 +377,20 @@ const createSearchItem = /* @__PURE__ */ __name((item, searchKey, currentSearchV
385
377
  e.stopPropagation();
386
378
  },
387
379
  onKeyDown: (e) => {
380
+ if (e.key === "Escape" || e.key === "Tab") {
381
+ deactivateSearchSubmenu(searchKey);
382
+ return;
383
+ }
384
+ if (shouldActivateSearchSubmenu) {
385
+ activateSearchSubmenu(searchKey);
386
+ }
388
387
  e.stopPropagation();
389
388
  },
390
389
  onMouseDown: (e) => {
391
390
  e.stopPropagation();
391
+ if (shouldActivateSearchSubmenu) {
392
+ activateSearchSubmenu(searchKey);
393
+ }
392
394
  },
393
395
  size: "small",
394
396
  style: {
@@ -409,7 +411,7 @@ const KEEP_OPEN_LABEL_STYLE = {
409
411
  width: "100%"
410
412
  };
411
413
  const DROPDOWN_PERSIST_TTL_MS = 350;
412
- const SUBMENU_CLOSE_DELAY = 0.05;
414
+ const MENU_CLOSE_DELAY = 0.3;
413
415
  const SUBMENU_MOTION_DISABLED = {
414
416
  motionEnter: false,
415
417
  motionLeave: false
@@ -417,13 +419,18 @@ const SUBMENU_MOTION_DISABLED = {
417
419
  const dropdownPersistRegistry = /* @__PURE__ */ new Map();
418
420
  const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
419
421
  const engine = (0, import_provider.useFlowEngine)();
422
+ const { getPrefixCls } = import_react.default.useContext(import_antd.ConfigProvider.ConfigContext);
423
+ const triggerId = import_react.default.useId();
420
424
  const [menuVisible, setMenuVisible] = (0, import_react.useState)(false);
421
425
  const [openKeys, setOpenKeys] = (0, import_react.useState)(/* @__PURE__ */ new Set());
422
- const [activeSearchKey, setActiveSearchKey] = (0, import_react.useState)(null);
423
426
  const [rootItems, setRootItems] = (0, import_react.useState)([]);
424
427
  const [rootLoading, setRootLoading] = (0, import_react.useState)(false);
428
+ const activeSearchKeyRef = (0, import_react.useRef)(null);
425
429
  const closeByOutsideClickRef = (0, import_react.useRef)(false);
426
430
  const skipPreserveActiveSearchRef = (0, import_react.useRef)(false);
431
+ const triggerOpenClassName = `nb-lazy-dropdown-trigger-${triggerId.replace(/[^a-zA-Z0-9_-]/g, "")}`;
432
+ const defaultOpenClassName = `${getPrefixCls("dropdown", props.prefixCls)}-open`;
433
+ const mergedOpenClassName = [props.openClassName ?? defaultOpenClassName, triggerOpenClassName].filter(Boolean).join(" ");
427
434
  const dropdownMaxHeight = useNiceDropdownMaxHeight();
428
435
  const t = engine.translate.bind(engine);
429
436
  const { items: menuItems, keepDropdownOpen, persistKey, stateVersion, refreshKeys, ...dropdownMenuProps } = menu;
@@ -440,12 +447,12 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
440
447
  useSubmenuStyles(menuVisible, dropdownMaxHeight);
441
448
  const closeMenu = (0, import_react.useCallback)(() => {
442
449
  setMenuVisible(false);
443
- setActiveSearchKey(null);
450
+ activeSearchKeyRef.current = null;
444
451
  setOpenKeys(/* @__PURE__ */ new Set());
445
452
  clearAllSearchValues();
446
453
  }, [clearAllSearchValues]);
447
454
  const activateSearchSubmenu = (0, import_react.useCallback)((key) => {
448
- setActiveSearchKey(key);
455
+ activeSearchKeyRef.current = key;
449
456
  setOpenKeys((prev) => {
450
457
  if (prev.has(key)) return prev;
451
458
  const next = new Set(prev);
@@ -454,34 +461,38 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
454
461
  });
455
462
  }, []);
456
463
  const deactivateSearchSubmenu = (0, import_react.useCallback)((key) => {
457
- setActiveSearchKey((prev) => prev === key ? null : prev);
464
+ if (activeSearchKeyRef.current === key) {
465
+ activeSearchKeyRef.current = null;
466
+ }
458
467
  }, []);
459
468
  const closeActiveSearchForPath = (0, import_react.useCallback)(
460
469
  (keyPath) => {
470
+ const activeSearchKey = activeSearchKeyRef.current;
461
471
  if (!activeSearchKey || keyPath === activeSearchKey || keyPath.startsWith(`${activeSearchKey}/`) || activeSearchKey.startsWith(`${keyPath}/`)) {
462
472
  return;
463
473
  }
464
474
  skipPreserveActiveSearchRef.current = true;
465
475
  clearSearchValue(activeSearchKey);
466
- setActiveSearchKey(null);
476
+ activeSearchKeyRef.current = null;
467
477
  setOpenKeys((prev) => {
468
478
  const next = new Set(prev);
469
479
  next.delete(activeSearchKey);
470
480
  return next;
471
481
  });
472
482
  },
473
- [activeSearchKey, clearSearchValue]
483
+ [clearSearchValue]
474
484
  );
475
485
  const handleMenuOpenChange = (0, import_react.useCallback)(
476
486
  (nextOpenKeys) => {
477
487
  var _a, _b;
478
488
  let normalized = normalizeOpenKeys(nextOpenKeys);
479
- if (activeSearchKey && openKeys.has(activeSearchKey) && !normalized.includes(activeSearchKey)) {
480
- if (normalized.length || skipPreserveActiveSearchRef.current) {
489
+ const activeSearchKey = activeSearchKeyRef.current;
490
+ if (activeSearchKey && !normalized.includes(activeSearchKey)) {
491
+ if (skipPreserveActiveSearchRef.current) {
481
492
  clearSearchValue(activeSearchKey);
482
- setActiveSearchKey(null);
493
+ activeSearchKeyRef.current = null;
483
494
  } else {
484
- normalized = [activeSearchKey];
495
+ normalized = Array.from(openKeys);
485
496
  }
486
497
  }
487
498
  if (!normalized.length && shouldPreventClose()) {
@@ -498,13 +509,15 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
498
509
  (_b = dropdownMenuProps.onOpenChange) == null ? void 0 : _b.call(dropdownMenuProps, normalized);
499
510
  skipPreserveActiveSearchRef.current = false;
500
511
  },
501
- [activeSearchKey, clearSearchValue, dropdownMenuProps, openKeys, shouldPreventClose]
512
+ [clearSearchValue, dropdownMenuProps, openKeys, shouldPreventClose]
502
513
  );
503
514
  (0, import_react.useEffect)(() => {
504
515
  if (!menuVisible) return;
505
516
  const markOutsideClick = /* @__PURE__ */ __name((event) => {
506
517
  const target = event.target;
507
- const isOutside = !(target == null ? void 0 : target.closest(".ant-dropdown, .ant-dropdown-menu, .ant-dropdown-menu-submenu-popup"));
518
+ const isInsidePopup = target == null ? void 0 : target.closest(".ant-dropdown, .ant-dropdown-menu, .ant-dropdown-menu-submenu-popup");
519
+ const isInsideCurrentTrigger = target == null ? void 0 : target.closest(`.${triggerOpenClassName}`);
520
+ const isOutside = !isInsidePopup && !isInsideCurrentTrigger;
508
521
  closeByOutsideClickRef.current = isOutside;
509
522
  if (isOutside) {
510
523
  closeMenu();
@@ -516,7 +529,7 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
516
529
  document.removeEventListener("pointerdown", markOutsideClick, true);
517
530
  document.removeEventListener("mousedown", markOutsideClick, true);
518
531
  };
519
- }, [closeMenu, menuVisible]);
532
+ }, [closeMenu, menuVisible, triggerOpenClassName]);
520
533
  (0, import_react.useEffect)(() => {
521
534
  if (!persistKey) return;
522
535
  const until = dropdownPersistRegistry.get(persistKey) || 0;
@@ -734,13 +747,15 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
734
747
  ...props,
735
748
  open: menuVisible,
736
749
  destroyPopupOnHide: true,
750
+ mouseLeaveDelay: props.mouseLeaveDelay ?? MENU_CLOSE_DELAY,
751
+ openClassName: mergedOpenClassName,
737
752
  overlayClassName,
738
753
  placement: "bottomLeft",
739
754
  menu: {
740
755
  ...dropdownMenuProps,
741
756
  openKeys: Array.from(openKeys),
742
757
  items,
743
- subMenuCloseDelay: dropdownMenuProps.subMenuCloseDelay ?? SUBMENU_CLOSE_DELAY,
758
+ subMenuCloseDelay: dropdownMenuProps.subMenuCloseDelay ?? MENU_CLOSE_DELAY,
744
759
  motion: dropdownMenuProps.motion ?? SUBMENU_MOTION_DISABLED,
745
760
  onClick: /* @__PURE__ */ __name(() => {
746
761
  }, "onClick"),
@@ -752,7 +767,7 @@ const LazyDropdown = /* @__PURE__ */ __name(({ menu, ...props }) => {
752
767
  }
753
768
  },
754
769
  onOpenChange: (visible, info) => {
755
- if (!visible && activeSearchKey && (info == null ? void 0 : info.source) === "trigger" && !closeByOutsideClickRef.current) {
770
+ if (!visible && activeSearchKeyRef.current && (info == null ? void 0 : info.source) === "trigger" && !closeByOutsideClickRef.current) {
756
771
  return;
757
772
  }
758
773
  if (!visible && shouldPreventClose()) {
@@ -17,11 +17,20 @@ export interface VariableHybridInputProps {
17
17
  value?: string;
18
18
  onChange?: (value: string) => void;
19
19
  disabled?: boolean;
20
+ readOnly?: boolean;
20
21
  placeholder?: string;
21
22
  addonBefore?: React.ReactNode;
22
23
  metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
23
24
  converters?: VariableHybridInputConverters;
24
25
  style?: React.CSSProperties;
25
26
  className?: string;
27
+ /**
28
+ * Validation status — turns the input border red (`error`) or amber
29
+ * (`warning`). Usually omitted: when rendered inside an antd `Form.Item`, the
30
+ * status is read automatically from `FormItemInputContext`, so dropping this
31
+ * into a `Form.Item` with failing rules colours the border with no extra
32
+ * wiring. An explicit prop wins over the inherited form status.
33
+ */
34
+ status?: 'error' | 'warning';
26
35
  }
27
36
  export declare const VariableHybridInput: React.NamedExoticComponent<VariableHybridInputProps>;