@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 +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +81 -35
- package/dist/index.mjs +64 -21
- package/package.json +2 -2
- package/src/components/global-dialog/__tests__/global-dialog.test.tsx +272 -0
- package/src/components/global-dialog/__tests__/subscribers.test.ts +90 -0
- package/src/components/global-dialog/components/global-dialog.tsx +30 -0
- package/src/components/global-dialog/index.ts +2 -0
- package/src/components/global-dialog/subscribers.ts +36 -0
- package/src/index.ts +1 -1
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/
|
|
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__ */
|
|
302
|
-
|
|
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
|
|
319
|
-
var
|
|
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__ */
|
|
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
|
|
333
|
-
var
|
|
334
|
-
var
|
|
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
|
|
381
|
+
var import_react8 = require("react");
|
|
339
382
|
var useScrollToSelected = ({
|
|
340
383
|
selectedValue,
|
|
341
384
|
items,
|
|
342
385
|
virtualizer
|
|
343
386
|
}) => {
|
|
344
|
-
(0,
|
|
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
|
|
399
|
+
var import_react9 = require("react");
|
|
357
400
|
var useScrollTop = ({ containerRef }) => {
|
|
358
|
-
const [scrollTop, setScrollTop] = (0,
|
|
359
|
-
(0,
|
|
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,
|
|
441
|
+
const containerRef = (0, import_react10.useRef)(null);
|
|
399
442
|
const scrollTop = useScrollTop({ containerRef });
|
|
400
443
|
const MenuListComponent = CustomMenuList || StyledMenuList;
|
|
401
|
-
const stickyIndices = (0,
|
|
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__ */
|
|
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__ */
|
|
452
|
-
|
|
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__ */
|
|
463
|
-
|
|
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,
|
|
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
|
|
533
|
-
var
|
|
575
|
+
var React13 = __toESM(require("react"));
|
|
576
|
+
var import_react11 = require("react");
|
|
534
577
|
var import_icons2 = require("@elementor/icons");
|
|
535
|
-
var
|
|
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,
|
|
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__ */
|
|
548
|
-
|
|
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__ */
|
|
559
|
-
endAdornment: value && /* @__PURE__ */
|
|
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
|
|
609
|
+
var import_react12 = require("react");
|
|
567
610
|
var useEditable = ({ value, onSubmit, validation, onClick, onError }) => {
|
|
568
|
-
const [isEditing, setIsEditing] = (0,
|
|
569
|
-
const [error, setError] = (0,
|
|
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,
|
|
643
|
-
(0,
|
|
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/
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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
|
|
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
|
|
341
|
+
import { useEffect as useEffect4 } from "react";
|
|
302
342
|
var useScrollToSelected = ({
|
|
303
343
|
selectedValue,
|
|
304
344
|
items,
|
|
305
345
|
virtualizer
|
|
306
346
|
}) => {
|
|
307
|
-
|
|
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
|
|
359
|
+
import { useEffect as useEffect5, useState as useState5 } from "react";
|
|
320
360
|
var useScrollTop = ({ containerRef }) => {
|
|
321
|
-
const [scrollTop, setScrollTop] =
|
|
322
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
522
|
-
endAdornment: value && /* @__PURE__ */
|
|
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
|
|
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] =
|
|
532
|
-
const [error, setError] =
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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,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';
|