@elementor/editor-ui 3.33.0-116 → 3.33.0-118

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.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React$1 from 'react';
2
- import { ReactNode, PropsWithChildren } from 'react';
2
+ import { ReactNode, PropsWithChildren, ReactElement } from 'react';
3
3
  import { MenuItemProps, AlertProps, InfotipProps, MenuList } from '@elementor/ui';
4
4
  import * as _emotion_styled from '@emotion/styled';
5
5
 
@@ -58,6 +58,14 @@ interface WarningInfotipProps extends PropsWithChildren {
58
58
  }
59
59
  declare const WarningInfotip: React$1.ForwardRefExoticComponent<WarningInfotipProps & React$1.RefAttributes<unknown>>;
60
60
 
61
+ declare const GlobalDialog: () => React$1.JSX.Element | null;
62
+
63
+ type DialogContent = {
64
+ component: ReactElement;
65
+ };
66
+ declare const openDialog: ({ component }: DialogContent) => void;
67
+ declare const closeDialog: () => void;
68
+
61
69
  type PopoverBodyProps = PropsWithChildren<{
62
70
  height?: number | 'auto';
63
71
  width?: number;
@@ -134,4 +142,4 @@ declare const useEditable: ({ value, onSubmit, validation, onClick, onError }: U
134
142
  };
135
143
  };
136
144
 
137
- export { EditableField, EllipsisWithTooltip, ITEM_HEIGHT, InfoAlert, InfoTipCard, IntroductionModal, MenuItemInfotip, MenuListItem, PopoverBody, PopoverHeader, PopoverMenuList, type PopoverMenuListProps, PopoverSearch, StyledMenuList, ThemeProvider, type VirtualizedItem, WarningInfotip, useEditable };
145
+ export { EditableField, EllipsisWithTooltip, GlobalDialog, ITEM_HEIGHT, InfoAlert, InfoTipCard, IntroductionModal, MenuItemInfotip, MenuListItem, PopoverBody, PopoverHeader, PopoverMenuList, type PopoverMenuListProps, PopoverSearch, StyledMenuList, ThemeProvider, type VirtualizedItem, WarningInfotip, closeDialog, openDialog, useEditable };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React$1 from 'react';
2
- import { ReactNode, PropsWithChildren } from 'react';
2
+ import { ReactNode, PropsWithChildren, ReactElement } from 'react';
3
3
  import { MenuItemProps, AlertProps, InfotipProps, MenuList } from '@elementor/ui';
4
4
  import * as _emotion_styled from '@emotion/styled';
5
5
 
@@ -58,6 +58,14 @@ interface WarningInfotipProps extends PropsWithChildren {
58
58
  }
59
59
  declare const WarningInfotip: React$1.ForwardRefExoticComponent<WarningInfotipProps & React$1.RefAttributes<unknown>>;
60
60
 
61
+ declare const GlobalDialog: () => React$1.JSX.Element | null;
62
+
63
+ type DialogContent = {
64
+ component: ReactElement;
65
+ };
66
+ declare const openDialog: ({ component }: DialogContent) => void;
67
+ declare const closeDialog: () => void;
68
+
61
69
  type PopoverBodyProps = PropsWithChildren<{
62
70
  height?: number | 'auto';
63
71
  width?: number;
@@ -134,4 +142,4 @@ declare const useEditable: ({ value, onSubmit, validation, onClick, onError }: U
134
142
  };
135
143
  };
136
144
 
137
- export { EditableField, EllipsisWithTooltip, ITEM_HEIGHT, InfoAlert, InfoTipCard, IntroductionModal, MenuItemInfotip, MenuListItem, PopoverBody, PopoverHeader, PopoverMenuList, type PopoverMenuListProps, PopoverSearch, StyledMenuList, ThemeProvider, type VirtualizedItem, WarningInfotip, useEditable };
145
+ export { EditableField, EllipsisWithTooltip, GlobalDialog, ITEM_HEIGHT, InfoAlert, InfoTipCard, IntroductionModal, MenuItemInfotip, MenuListItem, PopoverBody, PopoverHeader, PopoverMenuList, type PopoverMenuListProps, PopoverSearch, StyledMenuList, ThemeProvider, type VirtualizedItem, WarningInfotip, closeDialog, openDialog, useEditable };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  EditableField: () => EditableField,
34
34
  EllipsisWithTooltip: () => EllipsisWithTooltip,
35
+ GlobalDialog: () => GlobalDialog,
35
36
  ITEM_HEIGHT: () => ITEM_HEIGHT,
36
37
  InfoAlert: () => InfoAlert,
37
38
  InfoTipCard: () => InfoTipCard,
@@ -45,6 +46,8 @@ __export(index_exports, {
45
46
  StyledMenuList: () => StyledMenuList,
46
47
  ThemeProvider: () => ThemeProvider,
47
48
  WarningInfotip: () => WarningInfotip,
49
+ closeDialog: () => closeDialog,
50
+ openDialog: () => openDialog,
48
51
  useEditable: () => useEditable
49
52
  });
50
53
  module.exports = __toCommonJS(index_exports);
@@ -291,15 +294,55 @@ var WarningInfotip = (0, import_react6.forwardRef)(
291
294
  }
292
295
  );
293
296
 
294
- // src/components/popover/body.tsx
297
+ // src/components/global-dialog/components/global-dialog.tsx
298
+ var import_react7 = require("react");
295
299
  var React9 = __toESM(require("react"));
296
300
  var import_ui9 = require("@elementor/ui");
301
+
302
+ // src/components/global-dialog/subscribers.ts
303
+ var currentDialogState = null;
304
+ var stateSubscribers = /* @__PURE__ */ new Set();
305
+ var subscribeToDialogState = (callback) => {
306
+ stateSubscribers.add(callback);
307
+ callback(currentDialogState);
308
+ return () => stateSubscribers.delete(callback);
309
+ };
310
+ var notifySubscribers = () => {
311
+ stateSubscribers.forEach((callback) => callback(currentDialogState));
312
+ };
313
+ var openDialog = ({ component }) => {
314
+ currentDialogState = { component };
315
+ notifySubscribers();
316
+ };
317
+ var closeDialog = () => {
318
+ currentDialogState = null;
319
+ notifySubscribers();
320
+ };
321
+
322
+ // src/components/global-dialog/components/global-dialog.tsx
323
+ var GlobalDialog = () => {
324
+ const [content, setContent] = (0, import_react7.useState)(null);
325
+ (0, import_react7.useEffect)(() => {
326
+ const unsubscribe = subscribeToDialogState(setContent);
327
+ return () => {
328
+ unsubscribe();
329
+ };
330
+ }, []);
331
+ if (!content) {
332
+ return null;
333
+ }
334
+ return /* @__PURE__ */ React9.createElement(ThemeProvider, null, /* @__PURE__ */ React9.createElement(import_ui9.Dialog, { role: "dialog", open: true, onClose: closeDialog, maxWidth: "sm", fullWidth: true }, content.component));
335
+ };
336
+
337
+ // src/components/popover/body.tsx
338
+ var React10 = __toESM(require("react"));
339
+ var import_ui10 = require("@elementor/ui");
297
340
  var SECTION_PADDING_INLINE = 32;
298
341
  var DEFAULT_POPOVER_HEIGHT = 348;
299
342
  var FALLBACK_POPOVER_WIDTH = 220;
300
343
  var PopoverBody = ({ children, height = DEFAULT_POPOVER_HEIGHT, width }) => {
301
- return /* @__PURE__ */ React9.createElement(
302
- import_ui9.Box,
344
+ return /* @__PURE__ */ React10.createElement(
345
+ import_ui10.Box,
303
346
  {
304
347
  display: "flex",
305
348
  flexDirection: "column",
@@ -315,8 +358,8 @@ var PopoverBody = ({ children, height = DEFAULT_POPOVER_HEIGHT, width }) => {
315
358
  };
316
359
 
317
360
  // src/components/popover/header.tsx
318
- var React10 = __toESM(require("react"));
319
- var import_ui10 = require("@elementor/ui");
361
+ var React11 = __toESM(require("react"));
362
+ var import_ui11 = require("@elementor/ui");
320
363
  var SIZE = "tiny";
321
364
  var PopoverHeader = ({ title, onClose, icon, actions }) => {
322
365
  const paddingAndSizing = {
@@ -325,23 +368,23 @@ var PopoverHeader = ({ title, onClose, icon, actions }) => {
325
368
  py: 1.5,
326
369
  maxHeight: 36
327
370
  };
328
- return /* @__PURE__ */ React10.createElement(import_ui10.Stack, { direction: "row", alignItems: "center", ...paddingAndSizing, sx: { columnGap: 0.5 } }, icon, /* @__PURE__ */ React10.createElement(import_ui10.Typography, { variant: "subtitle2", sx: { fontSize: "12px", mt: 0.25 } }, title), /* @__PURE__ */ React10.createElement(import_ui10.Stack, { direction: "row", sx: { ml: "auto" } }, actions, /* @__PURE__ */ React10.createElement(import_ui10.CloseButton, { slotProps: { icon: { fontSize: SIZE } }, sx: { ml: "auto" }, onClick: onClose })));
371
+ return /* @__PURE__ */ React11.createElement(import_ui11.Stack, { direction: "row", alignItems: "center", ...paddingAndSizing, sx: { columnGap: 0.5 } }, icon, /* @__PURE__ */ React11.createElement(import_ui11.Typography, { variant: "subtitle2", sx: { fontSize: "12px", mt: 0.25 } }, title), /* @__PURE__ */ React11.createElement(import_ui11.Stack, { direction: "row", sx: { ml: "auto" } }, actions, /* @__PURE__ */ React11.createElement(import_ui11.CloseButton, { slotProps: { icon: { fontSize: SIZE } }, sx: { ml: "auto" }, onClick: onClose })));
329
372
  };
330
373
 
331
374
  // src/components/popover/menu-list.tsx
332
- var React11 = __toESM(require("react"));
333
- var import_react9 = require("react");
334
- var import_ui11 = require("@elementor/ui");
375
+ var React12 = __toESM(require("react"));
376
+ var import_react10 = require("react");
377
+ var import_ui12 = require("@elementor/ui");
335
378
  var import_react_virtual = require("@tanstack/react-virtual");
336
379
 
337
380
  // src/hooks/use-scroll-to-selected.ts
338
- var import_react7 = require("react");
381
+ var import_react8 = require("react");
339
382
  var useScrollToSelected = ({
340
383
  selectedValue,
341
384
  items,
342
385
  virtualizer
343
386
  }) => {
344
- (0, import_react7.useEffect)(() => {
387
+ (0, import_react8.useEffect)(() => {
345
388
  if (!selectedValue || items.length === 0) {
346
389
  return;
347
390
  }
@@ -353,10 +396,10 @@ var useScrollToSelected = ({
353
396
  };
354
397
 
355
398
  // src/hooks/use-scroll-top.ts
356
- var import_react8 = require("react");
399
+ var import_react9 = require("react");
357
400
  var useScrollTop = ({ containerRef }) => {
358
- const [scrollTop, setScrollTop] = (0, import_react8.useState)(0);
359
- (0, import_react8.useEffect)(() => {
401
+ const [scrollTop, setScrollTop] = (0, import_react9.useState)(0);
402
+ (0, import_react9.useEffect)(() => {
360
403
  const container = containerRef.current;
361
404
  if (!container) {
362
405
  return;
@@ -395,10 +438,10 @@ var PopoverMenuList = ({
395
438
  noResultsComponent,
396
439
  menuListTemplate: CustomMenuList
397
440
  }) => {
398
- const containerRef = (0, import_react9.useRef)(null);
441
+ const containerRef = (0, import_react10.useRef)(null);
399
442
  const scrollTop = useScrollTop({ containerRef });
400
443
  const MenuListComponent = CustomMenuList || StyledMenuList;
401
- const stickyIndices = (0, import_react9.useMemo)(
444
+ const stickyIndices = (0, import_react10.useMemo)(
402
445
  () => items.reduce((categoryIndices, item, index) => {
403
446
  if (item.type === "category") {
404
447
  categoryIndices.push(index);
@@ -430,7 +473,7 @@ var PopoverMenuList = ({
430
473
  });
431
474
  useScrollToSelected({ selectedValue, items, virtualizer });
432
475
  const virtualItems = virtualizer.getVirtualItems();
433
- return /* @__PURE__ */ React11.createElement(import_ui11.Box, { ref: containerRef, sx: { height: "100%", overflowY: "auto" } }, items.length === 0 && noResultsComponent ? noResultsComponent : /* @__PURE__ */ React11.createElement(
476
+ return /* @__PURE__ */ React12.createElement(import_ui12.Box, { ref: containerRef, sx: { height: "100%", overflowY: "auto" } }, items.length === 0 && noResultsComponent ? noResultsComponent : /* @__PURE__ */ React12.createElement(
434
477
  MenuListComponent,
435
478
  {
436
479
  role: "listbox",
@@ -448,8 +491,8 @@ var PopoverMenuList = ({
448
491
  }
449
492
  if (item.type === "category") {
450
493
  const shouldStick = virtualRow.start + MENU_LIST_PADDING_TOP <= scrollTop;
451
- return /* @__PURE__ */ React11.createElement(
452
- import_ui11.MenuSubheader,
494
+ return /* @__PURE__ */ React12.createElement(
495
+ import_ui12.MenuSubheader,
453
496
  {
454
497
  key: virtualRow.key,
455
498
  style: shouldStick ? {} : menuSubHeaderAbsoluteStyling(virtualRow.start),
@@ -459,8 +502,8 @@ var PopoverMenuList = ({
459
502
  );
460
503
  }
461
504
  const isDisabled = item.disabled;
462
- return /* @__PURE__ */ React11.createElement(
463
- import_ui11.ListItem,
505
+ return /* @__PURE__ */ React12.createElement(
506
+ import_ui12.ListItem,
464
507
  {
465
508
  key: virtualRow.key,
466
509
  role: "option",
@@ -498,7 +541,7 @@ var PopoverMenuList = ({
498
541
  })
499
542
  ));
500
543
  };
501
- var StyledMenuList = (0, import_ui11.styled)(import_ui11.MenuList)(({ theme }) => ({
544
+ var StyledMenuList = (0, import_ui12.styled)(import_ui12.MenuList)(({ theme }) => ({
502
545
  "& > li": {
503
546
  height: ITEM_HEIGHT,
504
547
  width: "100%",
@@ -529,14 +572,14 @@ var StyledMenuList = (0, import_ui11.styled)(import_ui11.MenuList)(({ theme }) =
529
572
  }));
530
573
 
531
574
  // src/components/popover/search.tsx
532
- var React12 = __toESM(require("react"));
533
- var import_react10 = require("react");
575
+ var React13 = __toESM(require("react"));
576
+ var import_react11 = require("react");
534
577
  var import_icons2 = require("@elementor/icons");
535
- var import_ui12 = require("@elementor/ui");
578
+ var import_ui13 = require("@elementor/ui");
536
579
  var import_i18n2 = require("@wordpress/i18n");
537
580
  var SIZE2 = "tiny";
538
581
  var PopoverSearch = ({ value, onSearch, placeholder }) => {
539
- const inputRef = (0, import_react10.useRef)(null);
582
+ const inputRef = (0, import_react11.useRef)(null);
540
583
  const handleClear = () => {
541
584
  onSearch("");
542
585
  inputRef.current?.focus();
@@ -544,8 +587,8 @@ var PopoverSearch = ({ value, onSearch, placeholder }) => {
544
587
  const handleInputChange = (event) => {
545
588
  onSearch(event.target.value);
546
589
  };
547
- return /* @__PURE__ */ React12.createElement(import_ui12.Box, { sx: { px: 2, pb: 1.5 } }, /* @__PURE__ */ React12.createElement(
548
- import_ui12.TextField,
590
+ return /* @__PURE__ */ React13.createElement(import_ui13.Box, { sx: { px: 2, pb: 1.5 } }, /* @__PURE__ */ React13.createElement(
591
+ import_ui13.TextField,
549
592
  {
550
593
  autoFocus: true,
551
594
  fullWidth: true,
@@ -555,18 +598,18 @@ var PopoverSearch = ({ value, onSearch, placeholder }) => {
555
598
  onChange: handleInputChange,
556
599
  placeholder,
557
600
  InputProps: {
558
- startAdornment: /* @__PURE__ */ React12.createElement(import_ui12.InputAdornment, { position: "start" }, /* @__PURE__ */ React12.createElement(import_icons2.SearchIcon, { fontSize: SIZE2 })),
559
- endAdornment: value && /* @__PURE__ */ React12.createElement(import_ui12.IconButton, { size: SIZE2, onClick: handleClear, "aria-label": (0, import_i18n2.__)("Clear", "elementor") }, /* @__PURE__ */ React12.createElement(import_icons2.XIcon, { color: "action", fontSize: SIZE2 }))
601
+ startAdornment: /* @__PURE__ */ React13.createElement(import_ui13.InputAdornment, { position: "start" }, /* @__PURE__ */ React13.createElement(import_icons2.SearchIcon, { fontSize: SIZE2 })),
602
+ endAdornment: value && /* @__PURE__ */ React13.createElement(import_ui13.IconButton, { size: SIZE2, onClick: handleClear, "aria-label": (0, import_i18n2.__)("Clear", "elementor") }, /* @__PURE__ */ React13.createElement(import_icons2.XIcon, { color: "action", fontSize: SIZE2 }))
560
603
  }
561
604
  }
562
605
  ));
563
606
  };
564
607
 
565
608
  // src/hooks/use-editable.ts
566
- var import_react11 = require("react");
609
+ var import_react12 = require("react");
567
610
  var useEditable = ({ value, onSubmit, validation, onClick, onError }) => {
568
- const [isEditing, setIsEditing] = (0, import_react11.useState)(false);
569
- const [error, setError] = (0, import_react11.useState)(null);
611
+ const [isEditing, setIsEditing] = (0, import_react12.useState)(false);
612
+ const [error, setError] = (0, import_react12.useState)(null);
570
613
  const ref = useSelection(isEditing);
571
614
  const isDirty = (newValue) => newValue !== value;
572
615
  const openEditMode = () => {
@@ -639,8 +682,8 @@ var useEditable = ({ value, onSubmit, validation, onClick, onError }) => {
639
682
  };
640
683
  };
641
684
  var useSelection = (isEditing) => {
642
- const ref = (0, import_react11.useRef)(null);
643
- (0, import_react11.useEffect)(() => {
685
+ const ref = (0, import_react12.useRef)(null);
686
+ (0, import_react12.useEffect)(() => {
644
687
  if (isEditing) {
645
688
  selectAll(ref.current);
646
689
  }
@@ -661,6 +704,7 @@ var selectAll = (el) => {
661
704
  0 && (module.exports = {
662
705
  EditableField,
663
706
  EllipsisWithTooltip,
707
+ GlobalDialog,
664
708
  ITEM_HEIGHT,
665
709
  InfoAlert,
666
710
  InfoTipCard,
@@ -674,6 +718,8 @@ var selectAll = (el) => {
674
718
  StyledMenuList,
675
719
  ThemeProvider,
676
720
  WarningInfotip,
721
+ closeDialog,
722
+ openDialog,
677
723
  useEditable
678
724
  });
679
725
  //# sourceMappingURL=index.js.map
package/dist/index.mjs CHANGED
@@ -254,14 +254,54 @@ var WarningInfotip = forwardRef5(
254
254
  }
255
255
  );
256
256
 
257
- // src/components/popover/body.tsx
257
+ // src/components/global-dialog/components/global-dialog.tsx
258
+ import { useEffect as useEffect3, useState as useState4 } from "react";
258
259
  import * as React9 from "react";
260
+ import { Dialog as Dialog2 } from "@elementor/ui";
261
+
262
+ // src/components/global-dialog/subscribers.ts
263
+ var currentDialogState = null;
264
+ var stateSubscribers = /* @__PURE__ */ new Set();
265
+ var subscribeToDialogState = (callback) => {
266
+ stateSubscribers.add(callback);
267
+ callback(currentDialogState);
268
+ return () => stateSubscribers.delete(callback);
269
+ };
270
+ var notifySubscribers = () => {
271
+ stateSubscribers.forEach((callback) => callback(currentDialogState));
272
+ };
273
+ var openDialog = ({ component }) => {
274
+ currentDialogState = { component };
275
+ notifySubscribers();
276
+ };
277
+ var closeDialog = () => {
278
+ currentDialogState = null;
279
+ notifySubscribers();
280
+ };
281
+
282
+ // src/components/global-dialog/components/global-dialog.tsx
283
+ var GlobalDialog = () => {
284
+ const [content, setContent] = useState4(null);
285
+ useEffect3(() => {
286
+ const unsubscribe = subscribeToDialogState(setContent);
287
+ return () => {
288
+ unsubscribe();
289
+ };
290
+ }, []);
291
+ if (!content) {
292
+ return null;
293
+ }
294
+ return /* @__PURE__ */ React9.createElement(ThemeProvider, null, /* @__PURE__ */ React9.createElement(Dialog2, { role: "dialog", open: true, onClose: closeDialog, maxWidth: "sm", fullWidth: true }, content.component));
295
+ };
296
+
297
+ // src/components/popover/body.tsx
298
+ import * as React10 from "react";
259
299
  import { Box as Box4 } from "@elementor/ui";
260
300
  var SECTION_PADDING_INLINE = 32;
261
301
  var DEFAULT_POPOVER_HEIGHT = 348;
262
302
  var FALLBACK_POPOVER_WIDTH = 220;
263
303
  var PopoverBody = ({ children, height = DEFAULT_POPOVER_HEIGHT, width }) => {
264
- return /* @__PURE__ */ React9.createElement(
304
+ return /* @__PURE__ */ React10.createElement(
265
305
  Box4,
266
306
  {
267
307
  display: "flex",
@@ -278,7 +318,7 @@ var PopoverBody = ({ children, height = DEFAULT_POPOVER_HEIGHT, width }) => {
278
318
  };
279
319
 
280
320
  // src/components/popover/header.tsx
281
- import * as React10 from "react";
321
+ import * as React11 from "react";
282
322
  import { CloseButton, Stack, Typography as Typography3 } from "@elementor/ui";
283
323
  var SIZE = "tiny";
284
324
  var PopoverHeader = ({ title, onClose, icon, actions }) => {
@@ -288,23 +328,23 @@ var PopoverHeader = ({ title, onClose, icon, actions }) => {
288
328
  py: 1.5,
289
329
  maxHeight: 36
290
330
  };
291
- return /* @__PURE__ */ React10.createElement(Stack, { direction: "row", alignItems: "center", ...paddingAndSizing, sx: { columnGap: 0.5 } }, icon, /* @__PURE__ */ React10.createElement(Typography3, { variant: "subtitle2", sx: { fontSize: "12px", mt: 0.25 } }, title), /* @__PURE__ */ React10.createElement(Stack, { direction: "row", sx: { ml: "auto" } }, actions, /* @__PURE__ */ React10.createElement(CloseButton, { slotProps: { icon: { fontSize: SIZE } }, sx: { ml: "auto" }, onClick: onClose })));
331
+ return /* @__PURE__ */ React11.createElement(Stack, { direction: "row", alignItems: "center", ...paddingAndSizing, sx: { columnGap: 0.5 } }, icon, /* @__PURE__ */ React11.createElement(Typography3, { variant: "subtitle2", sx: { fontSize: "12px", mt: 0.25 } }, title), /* @__PURE__ */ React11.createElement(Stack, { direction: "row", sx: { ml: "auto" } }, actions, /* @__PURE__ */ React11.createElement(CloseButton, { slotProps: { icon: { fontSize: SIZE } }, sx: { ml: "auto" }, onClick: onClose })));
292
332
  };
293
333
 
294
334
  // src/components/popover/menu-list.tsx
295
- import * as React11 from "react";
335
+ import * as React12 from "react";
296
336
  import { useMemo, useRef } from "react";
297
337
  import { Box as Box5, ListItem, MenuList, MenuSubheader, styled as styled2 } from "@elementor/ui";
298
338
  import { useVirtualizer } from "@tanstack/react-virtual";
299
339
 
300
340
  // src/hooks/use-scroll-to-selected.ts
301
- import { useEffect as useEffect3 } from "react";
341
+ import { useEffect as useEffect4 } from "react";
302
342
  var useScrollToSelected = ({
303
343
  selectedValue,
304
344
  items,
305
345
  virtualizer
306
346
  }) => {
307
- useEffect3(() => {
347
+ useEffect4(() => {
308
348
  if (!selectedValue || items.length === 0) {
309
349
  return;
310
350
  }
@@ -316,10 +356,10 @@ var useScrollToSelected = ({
316
356
  };
317
357
 
318
358
  // src/hooks/use-scroll-top.ts
319
- import { useEffect as useEffect4, useState as useState4 } from "react";
359
+ import { useEffect as useEffect5, useState as useState5 } from "react";
320
360
  var useScrollTop = ({ containerRef }) => {
321
- const [scrollTop, setScrollTop] = useState4(0);
322
- useEffect4(() => {
361
+ const [scrollTop, setScrollTop] = useState5(0);
362
+ useEffect5(() => {
323
363
  const container = containerRef.current;
324
364
  if (!container) {
325
365
  return;
@@ -393,7 +433,7 @@ var PopoverMenuList = ({
393
433
  });
394
434
  useScrollToSelected({ selectedValue, items, virtualizer });
395
435
  const virtualItems = virtualizer.getVirtualItems();
396
- return /* @__PURE__ */ React11.createElement(Box5, { ref: containerRef, sx: { height: "100%", overflowY: "auto" } }, items.length === 0 && noResultsComponent ? noResultsComponent : /* @__PURE__ */ React11.createElement(
436
+ return /* @__PURE__ */ React12.createElement(Box5, { ref: containerRef, sx: { height: "100%", overflowY: "auto" } }, items.length === 0 && noResultsComponent ? noResultsComponent : /* @__PURE__ */ React12.createElement(
397
437
  MenuListComponent,
398
438
  {
399
439
  role: "listbox",
@@ -411,7 +451,7 @@ var PopoverMenuList = ({
411
451
  }
412
452
  if (item.type === "category") {
413
453
  const shouldStick = virtualRow.start + MENU_LIST_PADDING_TOP <= scrollTop;
414
- return /* @__PURE__ */ React11.createElement(
454
+ return /* @__PURE__ */ React12.createElement(
415
455
  MenuSubheader,
416
456
  {
417
457
  key: virtualRow.key,
@@ -422,7 +462,7 @@ var PopoverMenuList = ({
422
462
  );
423
463
  }
424
464
  const isDisabled = item.disabled;
425
- return /* @__PURE__ */ React11.createElement(
465
+ return /* @__PURE__ */ React12.createElement(
426
466
  ListItem,
427
467
  {
428
468
  key: virtualRow.key,
@@ -492,7 +532,7 @@ var StyledMenuList = styled2(MenuList)(({ theme }) => ({
492
532
  }));
493
533
 
494
534
  // src/components/popover/search.tsx
495
- import * as React12 from "react";
535
+ import * as React13 from "react";
496
536
  import { useRef as useRef2 } from "react";
497
537
  import { SearchIcon, XIcon } from "@elementor/icons";
498
538
  import { Box as Box6, IconButton, InputAdornment, TextField } from "@elementor/ui";
@@ -507,7 +547,7 @@ var PopoverSearch = ({ value, onSearch, placeholder }) => {
507
547
  const handleInputChange = (event) => {
508
548
  onSearch(event.target.value);
509
549
  };
510
- return /* @__PURE__ */ React12.createElement(Box6, { sx: { px: 2, pb: 1.5 } }, /* @__PURE__ */ React12.createElement(
550
+ return /* @__PURE__ */ React13.createElement(Box6, { sx: { px: 2, pb: 1.5 } }, /* @__PURE__ */ React13.createElement(
511
551
  TextField,
512
552
  {
513
553
  autoFocus: true,
@@ -518,18 +558,18 @@ var PopoverSearch = ({ value, onSearch, placeholder }) => {
518
558
  onChange: handleInputChange,
519
559
  placeholder,
520
560
  InputProps: {
521
- startAdornment: /* @__PURE__ */ React12.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React12.createElement(SearchIcon, { fontSize: SIZE2 })),
522
- endAdornment: value && /* @__PURE__ */ React12.createElement(IconButton, { size: SIZE2, onClick: handleClear, "aria-label": __2("Clear", "elementor") }, /* @__PURE__ */ React12.createElement(XIcon, { color: "action", fontSize: SIZE2 }))
561
+ startAdornment: /* @__PURE__ */ React13.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React13.createElement(SearchIcon, { fontSize: SIZE2 })),
562
+ endAdornment: value && /* @__PURE__ */ React13.createElement(IconButton, { size: SIZE2, onClick: handleClear, "aria-label": __2("Clear", "elementor") }, /* @__PURE__ */ React13.createElement(XIcon, { color: "action", fontSize: SIZE2 }))
523
563
  }
524
564
  }
525
565
  ));
526
566
  };
527
567
 
528
568
  // src/hooks/use-editable.ts
529
- import { useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
569
+ import { useEffect as useEffect6, useRef as useRef3, useState as useState6 } from "react";
530
570
  var useEditable = ({ value, onSubmit, validation, onClick, onError }) => {
531
- const [isEditing, setIsEditing] = useState5(false);
532
- const [error, setError] = useState5(null);
571
+ const [isEditing, setIsEditing] = useState6(false);
572
+ const [error, setError] = useState6(null);
533
573
  const ref = useSelection(isEditing);
534
574
  const isDirty = (newValue) => newValue !== value;
535
575
  const openEditMode = () => {
@@ -603,7 +643,7 @@ var useEditable = ({ value, onSubmit, validation, onClick, onError }) => {
603
643
  };
604
644
  var useSelection = (isEditing) => {
605
645
  const ref = useRef3(null);
606
- useEffect5(() => {
646
+ useEffect6(() => {
607
647
  if (isEditing) {
608
648
  selectAll(ref.current);
609
649
  }
@@ -623,6 +663,7 @@ var selectAll = (el) => {
623
663
  export {
624
664
  EditableField,
625
665
  EllipsisWithTooltip,
666
+ GlobalDialog,
626
667
  ITEM_HEIGHT,
627
668
  InfoAlert,
628
669
  InfoTipCard,
@@ -636,6 +677,8 @@ export {
636
677
  StyledMenuList,
637
678
  ThemeProvider,
638
679
  WarningInfotip,
680
+ closeDialog,
681
+ openDialog,
639
682
  useEditable
640
683
  };
641
684
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-ui",
3
3
  "description": "Elementor Editor UI",
4
- "version": "3.33.0-116",
4
+ "version": "3.33.0-118",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,7 +37,7 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor-v1-adapters": "3.33.0-116",
40
+ "@elementor/editor-v1-adapters": "3.33.0-118",
41
41
  "@elementor/icons": "1.46.0",
42
42
  "@elementor/ui": "1.36.12",
43
43
  "@tanstack/react-virtual": "^3.13.3",
@@ -0,0 +1,272 @@
1
+ import * as React from 'react';
2
+ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+
4
+ import { GlobalDialog } from '../components/global-dialog';
5
+ import { closeDialog, type DialogStateCallback, openDialog } from '../subscribers';
6
+
7
+ // Get mock functions for cleanup
8
+ const mockEventBus = jest.requireMock( '../subscribers' );
9
+
10
+ jest.mock( '../subscribers', () => {
11
+ let currentState: { component: React.ReactElement } | null = null;
12
+ const subscribers = new Set< DialogStateCallback >();
13
+
14
+ const notifySubscribers = () => {
15
+ subscribers.forEach( ( callback ) => callback( currentState ) );
16
+ };
17
+
18
+ return {
19
+ subscribeToDialogState: jest.fn( ( callback: DialogStateCallback ) => {
20
+ subscribers.add( callback );
21
+ // Call callback immediately with current state (matches real implementation)
22
+ callback( currentState );
23
+ return () => subscribers.delete( callback );
24
+ } ),
25
+ openDialog: jest.fn( ( { component }: { component: React.ReactElement } ) => {
26
+ currentState = { component };
27
+ // Notify synchronously (matches real implementation)
28
+ notifySubscribers();
29
+ } ),
30
+ closeDialog: jest.fn( () => {
31
+ currentState = null;
32
+ // Notify synchronously (matches real implementation)
33
+ notifySubscribers();
34
+ } ),
35
+ // For manual control in tests
36
+ __notifySubscribers: notifySubscribers,
37
+ __getCurrentState: () => currentState,
38
+ __getSubscribers: () => subscribers,
39
+ __reset: () => {
40
+ currentState = null;
41
+ subscribers.clear();
42
+ },
43
+ };
44
+ } );
45
+
46
+ // Helper function to render GlobalDialog
47
+ const renderGlobalDialog = () => {
48
+ return render( <GlobalDialog /> );
49
+ };
50
+
51
+ describe( 'GlobalDialog', () => {
52
+ // Reset dialog state before each test to avoid test interference
53
+ beforeEach( () => {
54
+ mockEventBus.__reset();
55
+ } );
56
+
57
+ afterEach( () => {
58
+ // Clean up any open dialogs after each test
59
+ mockEventBus.__reset();
60
+ } );
61
+
62
+ it( 'should not render anything when no dialog is open', () => {
63
+ // Act
64
+ renderGlobalDialog();
65
+
66
+ // Assert
67
+ expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
68
+ } );
69
+
70
+ it( 'should render dialog when openDialog is called', async () => {
71
+ // Arrange
72
+ const TestDialogContent = () => <div>Test Dialog Content</div>;
73
+
74
+ renderGlobalDialog();
75
+
76
+ // Act
77
+ act( () => {
78
+ openDialog( { component: <TestDialogContent /> } );
79
+ } );
80
+
81
+ // Assert - Wait for the dialog to appear
82
+ await waitFor( () => {
83
+ expect( screen.getAllByRole( 'dialog' ) ).toHaveLength( 2 ); // MUI creates 2 dialog elements
84
+ } );
85
+ expect( screen.getByText( 'Test Dialog Content' ) ).toBeInTheDocument();
86
+ } );
87
+
88
+ it( 'should close dialog when closeDialog is called', async () => {
89
+ // Arrange
90
+ const TestDialogContent = () => <div>Test Dialog Content</div>;
91
+
92
+ renderGlobalDialog();
93
+
94
+ // Open dialog first
95
+ act( () => {
96
+ openDialog( { component: <TestDialogContent /> } );
97
+ } );
98
+
99
+ await waitFor( () => {
100
+ expect( screen.getAllByRole( 'dialog' ) ).toHaveLength( 2 );
101
+ } );
102
+
103
+ // Act
104
+ act( () => {
105
+ closeDialog();
106
+ } );
107
+
108
+ // Assert
109
+ await waitFor( () => {
110
+ expect( screen.queryAllByRole( 'dialog' ) ).toHaveLength( 0 );
111
+ } );
112
+ expect( screen.queryByText( 'Test Dialog Content' ) ).not.toBeInTheDocument();
113
+ } );
114
+
115
+ it( 'should close dialog when Dialog onClose is triggered', async () => {
116
+ // Arrange
117
+ const TestDialogContent = () => <div>Test Dialog Content</div>;
118
+
119
+ renderGlobalDialog();
120
+
121
+ // Open dialog first
122
+ act( () => {
123
+ openDialog( { component: <TestDialogContent /> } );
124
+ } );
125
+
126
+ await waitFor( () => {
127
+ expect( screen.getAllByRole( 'dialog' ) ).toHaveLength( 2 );
128
+ } );
129
+ expect( screen.getByText( 'Test Dialog Content' ) ).toBeInTheDocument();
130
+
131
+ // Act - Simulate Dialog's onClose being called (like when user clicks close button)
132
+ act( () => {
133
+ closeDialog();
134
+ } );
135
+
136
+ // Assert
137
+ await waitFor( () => {
138
+ expect( screen.queryAllByRole( 'dialog' ) ).toHaveLength( 0 );
139
+ } );
140
+ expect( screen.queryByText( 'Test Dialog Content' ) ).not.toBeInTheDocument();
141
+ } );
142
+
143
+ it( 'should handle null/undefined dialog content gracefully', () => {
144
+ // This test ensures the component doesn't crash with invalid content
145
+ renderGlobalDialog();
146
+
147
+ // No dialog should be rendered when content is null
148
+ expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
149
+ } );
150
+ it( 'should replace dialog content when a new dialog is opened', async () => {
151
+ // Arrange
152
+ const FirstDialogContent = () => <div>First Dialog</div>;
153
+ const SecondDialogContent = () => <div>Second Dialog</div>;
154
+
155
+ renderGlobalDialog();
156
+
157
+ // Open first dialog
158
+ act( () => {
159
+ openDialog( { component: <FirstDialogContent /> } );
160
+ } );
161
+
162
+ await waitFor( () => {
163
+ expect( screen.getByText( 'First Dialog' ) ).toBeInTheDocument();
164
+ } );
165
+
166
+ // Act - Open second dialog
167
+ act( () => {
168
+ openDialog( { component: <SecondDialogContent /> } );
169
+ } );
170
+
171
+ // Assert
172
+ await waitFor( () => {
173
+ expect( screen.queryByText( 'First Dialog' ) ).not.toBeInTheDocument();
174
+ } );
175
+ expect( screen.getByText( 'Second Dialog' ) ).toBeInTheDocument();
176
+ } );
177
+
178
+ it( 'should subscribe to dialog state on mount', async () => {
179
+ // Arrange
180
+ const TestDialogContent = () => <div>Test Content</div>;
181
+
182
+ // Act
183
+ renderGlobalDialog();
184
+
185
+ // The component should be subscribed, so opening a dialog should work
186
+ act( () => {
187
+ openDialog( { component: <TestDialogContent /> } );
188
+ } );
189
+
190
+ // Assert
191
+ await waitFor( () => {
192
+ expect( screen.getAllByRole( 'dialog' ) ).toHaveLength( 2 );
193
+ } );
194
+ } );
195
+
196
+ it( 'should clean up subscription when component unmounts', async () => {
197
+ // Arrange
198
+ const TestDialogContent = () => <div>Test Dialog</div>;
199
+
200
+ const view = render( <GlobalDialog /> );
201
+ const unmount = view.unmount;
202
+
203
+ // Open dialog to verify subscription is working
204
+ act( () => {
205
+ openDialog( { component: <TestDialogContent /> } );
206
+ } );
207
+
208
+ await waitFor( () => {
209
+ expect( screen.getAllByRole( 'dialog' ) ).toHaveLength( 2 );
210
+ } );
211
+
212
+ // Act - Unmount component
213
+ unmount();
214
+
215
+ // Try to open dialog again - should not affect the unmounted component
216
+ act( () => {
217
+ openDialog( { component: <TestDialogContent /> } );
218
+ } );
219
+
220
+ // Assert - No dialog should be rendered since component is unmounted
221
+ expect( screen.queryAllByRole( 'dialog' ) ).toHaveLength( 0 );
222
+ } );
223
+ it( 'should work with store dispatch patterns like error dialogs', async () => {
224
+ // Arrange - Simulate error dialog pattern from editor-global-classes
225
+ const ErrorDialogContent = ( {
226
+ modifiedLabels,
227
+ }: {
228
+ modifiedLabels: Array< { original: string; modified: string; id: string } >;
229
+ } ) => (
230
+ <div>
231
+ <h3>Duplicate Labels Found</h3>
232
+ <ul>
233
+ { modifiedLabels.map( ( label ) => (
234
+ <li key={ label.id }>
235
+ { label.original } → { label.modified }
236
+ </li>
237
+ ) ) }
238
+ </ul>
239
+ <button onClick={ () => closeDialog() }>Close</button>
240
+ </div>
241
+ );
242
+
243
+ const mockModifiedLabels = [
244
+ { original: 'MyClass', modified: 'DUP_MyClass', id: 'class-1' },
245
+ { original: 'Button', modified: 'DUP_Button', id: 'class-2' },
246
+ ];
247
+
248
+ renderGlobalDialog();
249
+
250
+ // Act - Simulate error dialog opening (like in show-error-dialog.tsx)
251
+ act( () => {
252
+ openDialog( {
253
+ component: <ErrorDialogContent modifiedLabels={ mockModifiedLabels } />,
254
+ } );
255
+ } );
256
+
257
+ // Assert
258
+ await waitFor( () => {
259
+ expect( screen.getAllByRole( 'dialog' ) ).toHaveLength( 2 );
260
+ } );
261
+ expect( screen.getByText( 'Duplicate Labels Found' ) ).toBeInTheDocument();
262
+ expect( screen.getByText( 'MyClass → DUP_MyClass' ) ).toBeInTheDocument();
263
+ expect( screen.getByText( 'Button → DUP_Button' ) ).toBeInTheDocument();
264
+
265
+ // Test closing
266
+ fireEvent.click( screen.getByRole( 'button', { name: 'Close' } ) );
267
+
268
+ await waitFor( () => {
269
+ expect( screen.queryAllByRole( 'dialog' ) ).toHaveLength( 0 );
270
+ } );
271
+ } );
272
+ } );
@@ -0,0 +1,90 @@
1
+ import * as React from 'react';
2
+ import { waitFor } from '@testing-library/react';
3
+
4
+ import { closeDialog, type DialogContent, openDialog, subscribeToDialogState } from '../subscribers';
5
+
6
+ describe( 'subscribers', () => {
7
+ // Clean up state after each test
8
+ afterEach( () => {
9
+ closeDialog();
10
+ } );
11
+
12
+ it( 'should update state when openDialog is called', async () => {
13
+ // Arrange
14
+ const callback = jest.fn();
15
+ const testComponent = React.createElement( 'div', { children: 'Test' } );
16
+ const dialogContent: DialogContent = { component: testComponent };
17
+
18
+ subscribeToDialogState( callback );
19
+ callback.mockClear(); // Clear the initial call
20
+
21
+ // Act
22
+ openDialog( dialogContent );
23
+
24
+ // Assert
25
+ await waitFor( () => {
26
+ expect( callback ).toHaveBeenCalledWith( { component: testComponent } );
27
+ } );
28
+ await waitFor( () => {
29
+ expect( callback ).toHaveBeenCalledTimes( 1 );
30
+ } );
31
+ } );
32
+
33
+ it( 'should reset state to null when closeDialog is called', async () => {
34
+ // Arrange
35
+ const callback = jest.fn();
36
+ const testComponent = React.createElement( 'div', { children: 'Test' } );
37
+
38
+ subscribeToDialogState( callback );
39
+ openDialog( { component: testComponent } );
40
+
41
+ // Wait for the openDialog to complete
42
+ await waitFor( () => {
43
+ expect( callback ).toHaveBeenCalledWith( { component: testComponent } );
44
+ } );
45
+
46
+ callback.mockClear(); // Clear previous calls
47
+
48
+ // Act
49
+ closeDialog();
50
+
51
+ // Assert
52
+ await waitFor( () => {
53
+ expect( callback ).toHaveBeenCalledWith( null );
54
+ } );
55
+ await waitFor( () => {
56
+ expect( callback ).toHaveBeenCalledTimes( 1 );
57
+ } );
58
+ } );
59
+
60
+ it( 'should call callback immediately with current state on subscription', () => {
61
+ // Arrange
62
+ const testComponent = React.createElement( 'div', { children: 'Test' } );
63
+ openDialog( { component: testComponent } );
64
+
65
+ const callback = jest.fn();
66
+
67
+ // Act
68
+ subscribeToDialogState( callback );
69
+
70
+ // Assert
71
+ expect( callback ).toHaveBeenCalledWith( { component: testComponent } );
72
+ expect( callback ).toHaveBeenCalledTimes( 1 );
73
+ } );
74
+
75
+ it( 'should unsubscribe callback when unsubscribe function is called', () => {
76
+ // Arrange
77
+ const callback = jest.fn();
78
+ const testComponent = React.createElement( 'div', { children: 'Test' } );
79
+
80
+ const unsubscribe = subscribeToDialogState( callback );
81
+ callback.mockClear(); // Clear initial call
82
+
83
+ // Act
84
+ unsubscribe();
85
+ openDialog( { component: testComponent } );
86
+
87
+ // Assert
88
+ expect( callback ).not.toHaveBeenCalled();
89
+ } );
90
+ } );
@@ -0,0 +1,30 @@
1
+ import { useEffect, useState } from 'react';
2
+ import * as React from 'react';
3
+ import { Dialog } from '@elementor/ui';
4
+
5
+ import ThemeProvider from '../../theme-provider';
6
+ import { closeDialog, subscribeToDialogState } from '../subscribers';
7
+ import { type DialogContent } from '../subscribers';
8
+
9
+ export const GlobalDialog = () => {
10
+ const [ content, setContent ] = useState< DialogContent | null >( null );
11
+
12
+ useEffect( () => {
13
+ const unsubscribe = subscribeToDialogState( setContent );
14
+ return () => {
15
+ unsubscribe();
16
+ };
17
+ }, [] );
18
+
19
+ if ( ! content ) {
20
+ return null;
21
+ }
22
+
23
+ return (
24
+ <ThemeProvider>
25
+ <Dialog role="dialog" open onClose={ closeDialog } maxWidth="sm" fullWidth>
26
+ { content.component }
27
+ </Dialog>
28
+ </ThemeProvider>
29
+ );
30
+ };
@@ -0,0 +1,2 @@
1
+ export { GlobalDialog } from './components/global-dialog';
2
+ export { openDialog, closeDialog } from './subscribers';
@@ -0,0 +1,36 @@
1
+ import { type ReactElement } from 'react';
2
+
3
+ type DialogState = {
4
+ component: ReactElement;
5
+ } | null;
6
+
7
+ export type DialogStateCallback = ( state: DialogState ) => void;
8
+
9
+ let currentDialogState: DialogState = null;
10
+
11
+ const stateSubscribers = new Set< DialogStateCallback >();
12
+
13
+ export const subscribeToDialogState = ( callback: DialogStateCallback ) => {
14
+ stateSubscribers.add( callback );
15
+
16
+ callback( currentDialogState );
17
+ return () => stateSubscribers.delete( callback );
18
+ };
19
+
20
+ const notifySubscribers = () => {
21
+ stateSubscribers.forEach( ( callback ) => callback( currentDialogState ) );
22
+ };
23
+
24
+ export type DialogContent = {
25
+ component: ReactElement;
26
+ };
27
+
28
+ export const openDialog = ( { component }: DialogContent ) => {
29
+ currentDialogState = { component };
30
+ notifySubscribers();
31
+ };
32
+
33
+ export const closeDialog = () => {
34
+ currentDialogState = null;
35
+ notifySubscribers();
36
+ };
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ export { MenuListItem, MenuItemInfotip } from './components/menu-item';
7
7
  export { InfoTipCard } from './components/infotip-card';
8
8
  export { InfoAlert } from './components/info-alert';
9
9
  export { WarningInfotip } from './components/warning-infotip';
10
+ export { GlobalDialog, openDialog, closeDialog } from './components/global-dialog';
10
11
  export * from './components/popover';
11
-
12
12
  // hooks
13
13
  export { useEditable } from './hooks/use-editable';