@edux-design/comments 0.2.0 → 0.3.0
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/CHANGELOG.md +11 -0
- package/README.md +2 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +87 -20
- package/dist/index.mjs +73 -6
- package/docs/mention-portals.md +105 -0
- package/package.json +3 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @edux-design/comments
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Resolve mention menus into their containing dialog by default, with a typed portalContainer override and lifecycle-safe observation of reparenting and disconnected containers. Preserve composer values, mentions, external submission and imperative APIs. Require Menus ^1.1.0 for the checked-item and native Tab contract.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @edux-design/menus@1.1.0
|
|
13
|
+
|
|
3
14
|
## 0.2.0
|
|
4
15
|
|
|
5
16
|
### Minor Changes
|
package/README.md
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -14,7 +14,7 @@ type CommentAvatarProps$1 = React.HTMLAttributes<HTMLSpanElement> & React.Button
|
|
|
14
14
|
asButton?: boolean;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
declare function CommentMention({ peopleList, onQueryChange }: CommentMentionProps$1): react_jsx_runtime.JSX.Element;
|
|
17
|
+
declare function CommentMention({ peopleList, onQueryChange, portalContainer, }: CommentMentionProps$1): react_jsx_runtime.JSX.Element;
|
|
18
18
|
type CommentMentionPerson$1 = {
|
|
19
19
|
id: string | number;
|
|
20
20
|
name: string;
|
|
@@ -25,6 +25,7 @@ type CommentMentionProps$1 = {
|
|
|
25
25
|
peopleList?: CommentMentionPerson$1[];
|
|
26
26
|
onQueryChange?: (query: string) => void;
|
|
27
27
|
placeholder?: string;
|
|
28
|
+
portalContainer?: HTMLElement | null;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
declare const CommentComposer: React.ForwardRefExoticComponent<Omit<React.FormHTMLAttributes<HTMLFormElement>, "onChange" | "onSubmit"> & {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ type CommentAvatarProps$1 = React.HTMLAttributes<HTMLSpanElement> & React.Button
|
|
|
14
14
|
asButton?: boolean;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
declare function CommentMention({ peopleList, onQueryChange }: CommentMentionProps$1): react_jsx_runtime.JSX.Element;
|
|
17
|
+
declare function CommentMention({ peopleList, onQueryChange, portalContainer, }: CommentMentionProps$1): react_jsx_runtime.JSX.Element;
|
|
18
18
|
type CommentMentionPerson$1 = {
|
|
19
19
|
id: string | number;
|
|
20
20
|
name: string;
|
|
@@ -25,6 +25,7 @@ type CommentMentionProps$1 = {
|
|
|
25
25
|
peopleList?: CommentMentionPerson$1[];
|
|
26
26
|
onQueryChange?: (query: string) => void;
|
|
27
27
|
placeholder?: string;
|
|
28
|
+
portalContainer?: HTMLElement | null;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
declare const CommentComposer: React.ForwardRefExoticComponent<Omit<React.FormHTMLAttributes<HTMLFormElement>, "onChange" | "onSubmit"> & {
|
package/dist/index.js
CHANGED
|
@@ -558,36 +558,95 @@ function CommentComposerComponent({
|
|
|
558
558
|
var CommentComposer = (0, import_react4.forwardRef)(CommentComposerComponent);
|
|
559
559
|
|
|
560
560
|
// src/elements/CommentMention.jsx
|
|
561
|
-
var
|
|
561
|
+
var import_react7 = __toESM(require("react"));
|
|
562
562
|
var import_menus = require("@edux-design/menus");
|
|
563
563
|
var import_avatars3 = require("@edux-design/avatars");
|
|
564
|
+
|
|
565
|
+
// src/elements/useMentionPortalContainer.js
|
|
566
|
+
var import_react6 = require("react");
|
|
567
|
+
function resolveMentionPortalContainer(anchor, override) {
|
|
568
|
+
const document = anchor?.ownerDocument;
|
|
569
|
+
if (!document || !anchor.isConnected) return void 0;
|
|
570
|
+
if (override === null) return void 0;
|
|
571
|
+
if (override?.isConnected && override.ownerDocument === document)
|
|
572
|
+
return override;
|
|
573
|
+
const dialog = anchor.closest(
|
|
574
|
+
'[role="dialog"], [role="alertdialog"], dialog'
|
|
575
|
+
);
|
|
576
|
+
return dialog?.isConnected ? dialog : void 0;
|
|
577
|
+
}
|
|
578
|
+
function useMentionPortalContainer(anchorRef, override, active) {
|
|
579
|
+
const [container, setContainer] = (0, import_react6.useState)(void 0);
|
|
580
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
581
|
+
let disposed = false;
|
|
582
|
+
let observer;
|
|
583
|
+
let observedDocument;
|
|
584
|
+
const refresh = () => {
|
|
585
|
+
if (disposed) return;
|
|
586
|
+
const anchor = anchorRef?.current;
|
|
587
|
+
const next = resolveMentionPortalContainer(anchor, override);
|
|
588
|
+
setContainer((previous) => previous === next ? previous : next);
|
|
589
|
+
const document = anchor?.ownerDocument;
|
|
590
|
+
if (!active || observedDocument === document) return;
|
|
591
|
+
observer?.disconnect();
|
|
592
|
+
observedDocument = document;
|
|
593
|
+
const MutationObserver = document?.defaultView?.MutationObserver;
|
|
594
|
+
if (MutationObserver && document.documentElement) {
|
|
595
|
+
observer = new MutationObserver(refresh);
|
|
596
|
+
observer.observe(document.documentElement, {
|
|
597
|
+
childList: true,
|
|
598
|
+
subtree: true,
|
|
599
|
+
attributes: true,
|
|
600
|
+
attributeFilter: ["role"]
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
refresh();
|
|
605
|
+
return () => {
|
|
606
|
+
disposed = true;
|
|
607
|
+
observer?.disconnect();
|
|
608
|
+
};
|
|
609
|
+
}, [anchorRef, override, active]);
|
|
610
|
+
return container;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/elements/CommentMention.jsx
|
|
564
614
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
565
|
-
var CommentMention = ({
|
|
615
|
+
var CommentMention = ({
|
|
616
|
+
peopleList = [],
|
|
617
|
+
onQueryChange,
|
|
618
|
+
portalContainer
|
|
619
|
+
}) => {
|
|
566
620
|
const { editor, editorContainerRef, editable = true } = useCommentComposer();
|
|
567
|
-
const [menuOpen, setMenuOpen] = (0,
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
const
|
|
575
|
-
const
|
|
576
|
-
const
|
|
621
|
+
const [menuOpen, setMenuOpen] = (0, import_react7.useState)(false);
|
|
622
|
+
const mentionPortalContainer = useMentionPortalContainer(
|
|
623
|
+
editorContainerRef,
|
|
624
|
+
portalContainer,
|
|
625
|
+
menuOpen
|
|
626
|
+
);
|
|
627
|
+
const [query, setQuery] = (0, import_react7.useState)("");
|
|
628
|
+
const [anchorStyle, setAnchorStyle] = (0, import_react7.useState)({ left: 0, top: 0 });
|
|
629
|
+
const menuListRef = (0, import_react7.useRef)(null);
|
|
630
|
+
const rangeRef = (0, import_react7.useRef)(null);
|
|
631
|
+
const lastQueryRef = (0, import_react7.useRef)("");
|
|
632
|
+
const dismissedRangeRef = (0, import_react7.useRef)(null);
|
|
633
|
+
const focusFrameRef = (0, import_react7.useRef)(null);
|
|
634
|
+
const focusTimeoutRef = (0, import_react7.useRef)(null);
|
|
635
|
+
const dismissMenu = (0, import_react7.useCallback)(() => {
|
|
577
636
|
const activeRange = rangeRef.current;
|
|
578
637
|
dismissedRangeRef.current = activeRange ? `${activeRange.from}:${activeRange.to}` : null;
|
|
579
638
|
setMenuOpen(false);
|
|
580
639
|
if (editor && !editor.isDestroyed && editor.isEditable !== false)
|
|
581
640
|
editor.view.focus();
|
|
582
641
|
}, [editor]);
|
|
583
|
-
const filteredPeople = (0,
|
|
642
|
+
const filteredPeople = (0, import_react7.useMemo)(() => {
|
|
584
643
|
if (!query) return peopleList;
|
|
585
644
|
const lowered = query.toLowerCase();
|
|
586
645
|
return peopleList.filter(
|
|
587
646
|
(person) => person.handle.toLowerCase().startsWith(lowered)
|
|
588
647
|
);
|
|
589
648
|
}, [peopleList, query]);
|
|
590
|
-
const updateMenuState = (0,
|
|
649
|
+
const updateMenuState = (0, import_react7.useCallback)(() => {
|
|
591
650
|
if (!editor || !editable || editor.isEditable === false) {
|
|
592
651
|
setMenuOpen(false);
|
|
593
652
|
rangeRef.current = null;
|
|
@@ -630,7 +689,7 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
630
689
|
onQueryChange("");
|
|
631
690
|
}
|
|
632
691
|
}, [editor, editorContainerRef, onQueryChange, editable]);
|
|
633
|
-
const handleSelectPerson = (0,
|
|
692
|
+
const handleSelectPerson = (0, import_react7.useCallback)(
|
|
634
693
|
(person) => {
|
|
635
694
|
if (!editor || !editable || editor.isEditable === false || !rangeRef.current)
|
|
636
695
|
return;
|
|
@@ -675,14 +734,14 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
675
734
|
},
|
|
676
735
|
[editor, onQueryChange, editable]
|
|
677
736
|
);
|
|
678
|
-
(0,
|
|
737
|
+
(0, import_react7.useEffect)(
|
|
679
738
|
() => () => {
|
|
680
739
|
cancelAnimationFrame(focusFrameRef.current);
|
|
681
740
|
clearTimeout(focusTimeoutRef.current);
|
|
682
741
|
},
|
|
683
742
|
[editor]
|
|
684
743
|
);
|
|
685
|
-
(0,
|
|
744
|
+
(0, import_react7.useEffect)(() => {
|
|
686
745
|
if (!editor) return;
|
|
687
746
|
updateMenuState();
|
|
688
747
|
editor.on("transaction", updateMenuState);
|
|
@@ -692,7 +751,7 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
692
751
|
editor.off("selectionUpdate", updateMenuState);
|
|
693
752
|
};
|
|
694
753
|
}, [editor, updateMenuState]);
|
|
695
|
-
(0,
|
|
754
|
+
(0, import_react7.useEffect)(() => {
|
|
696
755
|
if (!editor) return;
|
|
697
756
|
const handleKeyDown = (event) => {
|
|
698
757
|
if (!menuOpen) return;
|
|
@@ -717,7 +776,7 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
717
776
|
dom?.addEventListener("keydown", handleKeyDown, true);
|
|
718
777
|
return () => dom?.removeEventListener("keydown", handleKeyDown, true);
|
|
719
778
|
}, [editor, menuOpen, dismissMenu]);
|
|
720
|
-
const menuContent = (0,
|
|
779
|
+
const menuContent = (0, import_react7.useMemo)(
|
|
721
780
|
() => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
722
781
|
import_menus.Menu,
|
|
723
782
|
{
|
|
@@ -746,6 +805,7 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
746
805
|
sideOffset: 4,
|
|
747
806
|
avoidCollisions: true,
|
|
748
807
|
collisionPadding: 12,
|
|
808
|
+
container: mentionPortalContainer,
|
|
749
809
|
onCloseAutoFocus: (event) => event.preventDefault()
|
|
750
810
|
},
|
|
751
811
|
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -787,7 +847,14 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
787
847
|
)
|
|
788
848
|
}
|
|
789
849
|
),
|
|
790
|
-
[
|
|
850
|
+
[
|
|
851
|
+
filteredPeople,
|
|
852
|
+
handleSelectPerson,
|
|
853
|
+
menuOpen,
|
|
854
|
+
anchorStyle,
|
|
855
|
+
dismissMenu,
|
|
856
|
+
mentionPortalContainer
|
|
857
|
+
]
|
|
791
858
|
);
|
|
792
859
|
if (!editor) return null;
|
|
793
860
|
return menuContent;
|
package/dist/index.mjs
CHANGED
|
@@ -540,19 +540,78 @@ var CommentComposer = forwardRef2(CommentComposerComponent);
|
|
|
540
540
|
// src/elements/CommentMention.jsx
|
|
541
541
|
import React4, {
|
|
542
542
|
useMemo as useMemo3,
|
|
543
|
-
useState as
|
|
543
|
+
useState as useState4,
|
|
544
544
|
useCallback,
|
|
545
545
|
useEffect as useEffect3,
|
|
546
546
|
useRef as useRef2
|
|
547
547
|
} from "react";
|
|
548
548
|
import { Menu, MenuList, MenuItem } from "@edux-design/menus";
|
|
549
549
|
import { Avatar as Avatar3 } from "@edux-design/avatars";
|
|
550
|
+
|
|
551
|
+
// src/elements/useMentionPortalContainer.js
|
|
552
|
+
import { useLayoutEffect, useState as useState3 } from "react";
|
|
553
|
+
function resolveMentionPortalContainer(anchor, override) {
|
|
554
|
+
const document = anchor?.ownerDocument;
|
|
555
|
+
if (!document || !anchor.isConnected) return void 0;
|
|
556
|
+
if (override === null) return void 0;
|
|
557
|
+
if (override?.isConnected && override.ownerDocument === document)
|
|
558
|
+
return override;
|
|
559
|
+
const dialog = anchor.closest(
|
|
560
|
+
'[role="dialog"], [role="alertdialog"], dialog'
|
|
561
|
+
);
|
|
562
|
+
return dialog?.isConnected ? dialog : void 0;
|
|
563
|
+
}
|
|
564
|
+
function useMentionPortalContainer(anchorRef, override, active) {
|
|
565
|
+
const [container, setContainer] = useState3(void 0);
|
|
566
|
+
useLayoutEffect(() => {
|
|
567
|
+
let disposed = false;
|
|
568
|
+
let observer;
|
|
569
|
+
let observedDocument;
|
|
570
|
+
const refresh = () => {
|
|
571
|
+
if (disposed) return;
|
|
572
|
+
const anchor = anchorRef?.current;
|
|
573
|
+
const next = resolveMentionPortalContainer(anchor, override);
|
|
574
|
+
setContainer((previous) => previous === next ? previous : next);
|
|
575
|
+
const document = anchor?.ownerDocument;
|
|
576
|
+
if (!active || observedDocument === document) return;
|
|
577
|
+
observer?.disconnect();
|
|
578
|
+
observedDocument = document;
|
|
579
|
+
const MutationObserver = document?.defaultView?.MutationObserver;
|
|
580
|
+
if (MutationObserver && document.documentElement) {
|
|
581
|
+
observer = new MutationObserver(refresh);
|
|
582
|
+
observer.observe(document.documentElement, {
|
|
583
|
+
childList: true,
|
|
584
|
+
subtree: true,
|
|
585
|
+
attributes: true,
|
|
586
|
+
attributeFilter: ["role"]
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
refresh();
|
|
591
|
+
return () => {
|
|
592
|
+
disposed = true;
|
|
593
|
+
observer?.disconnect();
|
|
594
|
+
};
|
|
595
|
+
}, [anchorRef, override, active]);
|
|
596
|
+
return container;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// src/elements/CommentMention.jsx
|
|
550
600
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
551
|
-
var CommentMention = ({
|
|
601
|
+
var CommentMention = ({
|
|
602
|
+
peopleList = [],
|
|
603
|
+
onQueryChange,
|
|
604
|
+
portalContainer
|
|
605
|
+
}) => {
|
|
552
606
|
const { editor, editorContainerRef, editable = true } = useCommentComposer();
|
|
553
|
-
const [menuOpen, setMenuOpen] =
|
|
554
|
-
const
|
|
555
|
-
|
|
607
|
+
const [menuOpen, setMenuOpen] = useState4(false);
|
|
608
|
+
const mentionPortalContainer = useMentionPortalContainer(
|
|
609
|
+
editorContainerRef,
|
|
610
|
+
portalContainer,
|
|
611
|
+
menuOpen
|
|
612
|
+
);
|
|
613
|
+
const [query, setQuery] = useState4("");
|
|
614
|
+
const [anchorStyle, setAnchorStyle] = useState4({ left: 0, top: 0 });
|
|
556
615
|
const menuListRef = useRef2(null);
|
|
557
616
|
const rangeRef = useRef2(null);
|
|
558
617
|
const lastQueryRef = useRef2("");
|
|
@@ -732,6 +791,7 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
732
791
|
sideOffset: 4,
|
|
733
792
|
avoidCollisions: true,
|
|
734
793
|
collisionPadding: 12,
|
|
794
|
+
container: mentionPortalContainer,
|
|
735
795
|
onCloseAutoFocus: (event) => event.preventDefault()
|
|
736
796
|
},
|
|
737
797
|
children: /* @__PURE__ */ jsx4(
|
|
@@ -773,7 +833,14 @@ var CommentMention = ({ peopleList = [], onQueryChange }) => {
|
|
|
773
833
|
)
|
|
774
834
|
}
|
|
775
835
|
),
|
|
776
|
-
[
|
|
836
|
+
[
|
|
837
|
+
filteredPeople,
|
|
838
|
+
handleSelectPerson,
|
|
839
|
+
menuOpen,
|
|
840
|
+
anchorStyle,
|
|
841
|
+
dismissMenu,
|
|
842
|
+
mentionPortalContainer
|
|
843
|
+
]
|
|
777
844
|
);
|
|
778
845
|
if (!editor) return null;
|
|
779
846
|
return menuContent;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Mention menus in dialogs
|
|
2
|
+
|
|
3
|
+
`CommentMention` automatically portals its menu into the nearest connected
|
|
4
|
+
`[role="dialog"]`, `[role="alertdialog"]` or native `<dialog>` containing its
|
|
5
|
+
editor. This keeps menu pointer and keyboard interaction inside an owning modal
|
|
6
|
+
Drawer or Modal. Outside a dialog, the menu uses its ordinary document-body
|
|
7
|
+
portal.
|
|
8
|
+
|
|
9
|
+
```jsx
|
|
10
|
+
<Drawer>
|
|
11
|
+
<DrawerTrigger asChild>
|
|
12
|
+
<Button>Open comments</Button>
|
|
13
|
+
</DrawerTrigger>
|
|
14
|
+
<DrawerContent aria-label="Comments" aria-describedby={undefined}>
|
|
15
|
+
<DrawerHeader>
|
|
16
|
+
<ModalTitle>Comments</ModalTitle>
|
|
17
|
+
</DrawerHeader>
|
|
18
|
+
<DrawerBody>
|
|
19
|
+
<CommentComposer
|
|
20
|
+
value={draft}
|
|
21
|
+
valueFormat="text"
|
|
22
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
23
|
+
onValueChange={setSnapshot}
|
|
24
|
+
>
|
|
25
|
+
<CommentMention peopleList={people} onQueryChange={setQuery} />
|
|
26
|
+
</CommentComposer>
|
|
27
|
+
</DrawerBody>
|
|
28
|
+
</DrawerContent>
|
|
29
|
+
</Drawer>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Import the Drawer components from `@edux-design/drawers`, `Button` from
|
|
33
|
+
`@edux-design/buttons`, and the composer and mention components from
|
|
34
|
+
`@edux-design/comments`. `ModalTitle` from `@edux-design/modals` supplies the shared
|
|
35
|
+
Dialog title in the Drawer header. Modal components have the same automatic
|
|
36
|
+
ownership behaviour.
|
|
37
|
+
|
|
38
|
+
## Optional portal override
|
|
39
|
+
|
|
40
|
+
| `portalContainer` | Behaviour |
|
|
41
|
+
| ------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
42
|
+
| Omitted or `undefined` | Find the nearest owning dialog; otherwise use the body portal. |
|
|
43
|
+
| Connected `HTMLElement` in the editor's document | Portal into that element. It takes precedence over automatic ownership. |
|
|
44
|
+
| `null` | Explicitly use the ordinary body portal. |
|
|
45
|
+
| Detached element or element from another document | Ignore the override and use automatic ownership. |
|
|
46
|
+
|
|
47
|
+
Pass the committed element itself, rather than a ref object. A callback ref stored
|
|
48
|
+
in state makes the element available after mount:
|
|
49
|
+
|
|
50
|
+
```jsx
|
|
51
|
+
const [mentionHost, setMentionHost] = React.useState(null);
|
|
52
|
+
|
|
53
|
+
<ModalContent>
|
|
54
|
+
<ModalHeader>
|
|
55
|
+
<ModalTitle>Comments</ModalTitle>
|
|
56
|
+
<ModalDescription>Discuss the current draft.</ModalDescription>
|
|
57
|
+
</ModalHeader>
|
|
58
|
+
<ModalBody>
|
|
59
|
+
<CommentComposer>
|
|
60
|
+
<CommentMention
|
|
61
|
+
peopleList={people}
|
|
62
|
+
portalContainer={mentionHost ?? undefined}
|
|
63
|
+
/>
|
|
64
|
+
</CommentComposer>
|
|
65
|
+
<div ref={setMentionHost} />
|
|
66
|
+
</ModalBody>
|
|
67
|
+
</ModalContent>;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Use `?? undefined` when a not-yet-mounted callback ref should retain automatic
|
|
71
|
+
ownership. Passing its initial `null` directly requests the body portal. Keep a
|
|
72
|
+
custom host inside the active modal's interactive surface. A body portal or a host
|
|
73
|
+
outside that modal remains subject to the modal's pointer and focus restrictions.
|
|
74
|
+
|
|
75
|
+
Ownership resolves after refs commit. While a mention query is active, it follows
|
|
76
|
+
editor reparenting, dialog role changes, and removal of an override element.
|
|
77
|
+
Nested dialogs select the nearest owning dialog. Each composer resolves its own
|
|
78
|
+
owner. Tracking stops when the query closes and when the composer unmounts.
|
|
79
|
+
|
|
80
|
+
## Interaction and values
|
|
81
|
+
|
|
82
|
+
Typing `@query` filters the supplied people by handle and calls `onQueryChange`.
|
|
83
|
+
Consumers continue to own asynchronous lookup, permissions and persistence.
|
|
84
|
+
Pointer activation or ArrowDown/ArrowUp followed by Enter inserts one mention,
|
|
85
|
+
closes the menu, and returns focus to the editor. Escape first dismisses an open
|
|
86
|
+
mention menu and returns focus to the editor; a subsequent Escape can close the
|
|
87
|
+
owning dialog according to that dialog's existing policy.
|
|
88
|
+
|
|
89
|
+
The mention menu retains `trapFocus={false}` and
|
|
90
|
+
`disableOutsidePointerEvents={false}`. The owning Drawer or Modal retains its
|
|
91
|
+
normal focus trap and outside-pointer blocking. Disabled or submitting composers
|
|
92
|
+
do not offer editable mention interaction.
|
|
93
|
+
|
|
94
|
+
Portal ownership does not change the composer model or submission contract.
|
|
95
|
+
`onValueChange` and `composerRef.current.getValue()` continue returning `text`,
|
|
96
|
+
`legacyText`, `html`, `json`, `mentions` and `isEmpty`. Existing `onChange`,
|
|
97
|
+
`onSubmit`, `onCancel`, `externalSubmit`, `submitOnModEnter`, editor labels,
|
|
98
|
+
autofocus, avatar/actions/children, and all composer handle methods remain
|
|
99
|
+
available. See [Composer values, focus and external submission](./composer.md).
|
|
100
|
+
|
|
101
|
+
The Storybook **Content / Comment Mention Portals** stories exercise real modal
|
|
102
|
+
Drawers and Modals, pointer and keyboard selection, Escape ordering, nested and
|
|
103
|
+
changing owners, overrides, independent composers, asynchronous people, locked
|
|
104
|
+
states, and a dark narrow Drawer. They use synthetic people and strict
|
|
105
|
+
accessibility checks.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edux-design/comments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@edux-design/buttons": "*",
|
|
19
19
|
"@edux-design/forms": "*",
|
|
20
20
|
"@edux-design/mentions": "*",
|
|
21
|
-
"@edux-design/menus": "
|
|
21
|
+
"@edux-design/menus": "^1.1.0",
|
|
22
22
|
"@edux-design/utils": "*"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
@@ -33,8 +33,6 @@
|
|
|
33
33
|
"dist",
|
|
34
34
|
"docs",
|
|
35
35
|
"README.md",
|
|
36
|
-
"CHANGELOG.md"
|
|
37
|
-
"LICENSE",
|
|
38
|
-
"LICENSE.md"
|
|
36
|
+
"CHANGELOG.md"
|
|
39
37
|
]
|
|
40
38
|
}
|