@mojsica/react-mention-input 0.0.0-stage → 0.1.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/README.md +39 -2
- package/dist/MentionInput.d.ts +23 -0
- package/dist/MentionPicker.d.ts +13 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +376 -0
- package/dist/models.d.ts +18 -0
- package/dist/styles.css +1 -0
- package/package.json +54 -4
package/README.md
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @mojsica/react-mention-input
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Contenteditable `@` mention input for React with chips and structured `MentionContentSection[]` value.
|
|
4
|
+
|
|
5
|
+
Shares behavior with [`@mojsica/angular-mention-input`](../../projects/angular-mention-input) via private `@mojsica/mention-core`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @mojsica/react-mention-input
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { MentionInput } from '@mojsica/react-mention-input';
|
|
15
|
+
import '@mojsica/react-mention-input/styles.css';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<MentionInput
|
|
20
|
+
value={sections}
|
|
21
|
+
onChange={setSections}
|
|
22
|
+
users={users}
|
|
23
|
+
loading={loading}
|
|
24
|
+
onMentionQueryChange={onQuery}
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Theming
|
|
29
|
+
|
|
30
|
+
Map design-system tokens to `--mi-*` CSS variables on a parent (same as Angular).
|
|
31
|
+
|
|
32
|
+
## Local demo
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm --filter demo-react dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
import type { MentionContentSection, MentionUser } from './models';
|
|
3
|
+
import './styles.css';
|
|
4
|
+
export type MentionInputProps = {
|
|
5
|
+
value?: MentionContentSection[];
|
|
6
|
+
defaultValue?: MentionContentSection[];
|
|
7
|
+
onChange?: (value: MentionContentSection[]) => void;
|
|
8
|
+
users?: MentionUser[];
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
clientFilter?: boolean;
|
|
11
|
+
emptyText?: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
hasError?: boolean;
|
|
15
|
+
errorMessage?: string | null;
|
|
16
|
+
helperText?: string;
|
|
17
|
+
className?: string;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
onMentionQueryChange?: (query: string) => void;
|
|
20
|
+
onMentionSelected?: (user: MentionUser) => void;
|
|
21
|
+
onMentionOpenChange?: (open: boolean) => void;
|
|
22
|
+
};
|
|
23
|
+
export declare function MentionInput({ value, defaultValue, onChange, users, loading, clientFilter, emptyText, placeholder, disabled, hasError, errorMessage, helperText, className, style, onMentionQueryChange, onMentionSelected, onMentionOpenChange, }: MentionInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MentionUser } from './models';
|
|
2
|
+
export type MentionPickerProps = {
|
|
3
|
+
users: MentionUser[];
|
|
4
|
+
search: string;
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
clientFilter?: boolean;
|
|
7
|
+
emptyText?: string;
|
|
8
|
+
left: number;
|
|
9
|
+
top: number;
|
|
10
|
+
onSelect: (user: MentionUser) => void;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
};
|
|
13
|
+
export declare function MentionPicker({ users, search, loading, clientFilter, emptyText, left, top, onSelect, onClose, }: MentionPickerProps): import("react").JSX.Element;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),f=require("react"),ee=require("react-dom"),_="mi-mention",k="mi-mention-draft";function te(r){const t=window.getSelection();if(!(t!=null&&t.rangeCount))return;const e=t.getRangeAt(0);e.collapsed||e.deleteContents();let i=null;r.forEach(l=>{e.insertNode(l),i=l,e.setStartAfter(l)}),i&&(e.collapse(!0),t.removeAllRanges(),t.addRange(e))}function H(r){const t=[];return r.forEach(e=>{if(e.type==="text"){const i=e.message.split(`
|
|
2
|
+
`);i.forEach((l,a)=>{l&&t.push(document.createTextNode(l)),a<i.length-1&&t.push(document.createElement("br"))})}if(e.type==="mention"){const i=document.createElement("span");i.classList.add(_),i.setAttribute("contenteditable","false"),i.setAttribute("userId",e.userId||""),i.textContent=` @${e.userName} `,t.push(i)}}),t}function W(r){var i;const t=se(r),e=[];return t.childNodes.length===1&&((i=r.childNodes[0])==null?void 0:i.tagName)==="BR"||t.childNodes.forEach(l=>{var a,b;if(l.nodeType===Node.TEXT_NODE){const c=(a=l.textContent)==null?void 0:a.replace(/\u00A0/g," ");c!=null&&c.trim()&&e.push({type:"text",message:c})}if(l.nodeType===Node.ELEMENT_NODE){const c=l;if(c.classList.contains(_)||c.classList.contains("tag-wrap")){const N=c.getAttribute("userId")||"",o=((b=c.textContent)==null?void 0:b.replace(/^\s*@/,"").trim())||"";e.push({type:"mention",userId:N,userName:o})}else c.tagName==="BR"&&e.push({type:"text",message:`
|
|
3
|
+
`})}}),e}function se(r){const t=r.cloneNode(!0),e=i=>{const l=Array.from(i.childNodes);for(const a of l){if(a.nodeType!==Node.ELEMENT_NODE)continue;const b=a,c=b.parentElement;if(!c)continue;const N=b.tagName.toUpperCase();if(N==="DIV"){const o=Array.from(b.childNodes);if(o.length===1&&o[0].nodeType===Node.ELEMENT_NODE&&o[0].tagName==="BR")c.insertBefore(o[0],a),c.removeChild(a),e(c);else{const y=document.createElement("br");for(c.insertBefore(y,a);a.firstChild;)c.insertBefore(a.firstChild,a);c.removeChild(a),e(c)}}else if(N==="SPAN"&&!b.hasAttribute("userId")||N==="FONT"){for(;a.firstChild;)c.insertBefore(a.firstChild,a);c.removeChild(a),e(c)}else if(N==="BR"||N==="SPAN"&&b.hasAttribute("userId")||N==="LIB-MENTION-PICKER")e(a);else{for(;a.firstChild;)c.insertBefore(a.firstChild,a);c.removeChild(a),e(c)}}};return e(t),t}function re(r){const t=window.getSelection();if(!(t!=null&&t.rangeCount))return;const e=t.getRangeAt(0);let i=e.startContainer.childNodes[e.startOffset-1];(i==null?void 0:i.nodeName)==="BR"&&(i=e.startContainer.childNodes[e.startOffset-2]),(i==null?void 0:i.contentEditable)==="false"&&(r.preventDefault(),i.remove())}function ne(r){const t=window.getSelection();if(!(!r||!t))try{const e=document.createRange();e.selectNodeContents(r),e.collapse(!1),t.removeAllRanges(),t.addRange(e)}catch{}}function ie(r){const t=window.getSelection();if(!(t!=null&&t.rangeCount))return;const e=t.getRangeAt(0);e.collapse(!0),e.insertNode(r),e.setStartAfter(r),e.collapse(!0),t.removeAllRanges(),t.addRange(e)}function ce(r){if(!r)return;const t=window.getSelection();t&&requestAnimationFrame(()=>{const e=document.createRange();e.setStartAfter(r),e.collapse(!0),t.removeAllRanges(),t.addRange(e)})}function F(r){var l,a;const t=r.parentNode;if(!t)return;const e=((a=(l=r.childNodes[0])==null?void 0:l.textContent)==null?void 0:a.trim())||"",i=document.createTextNode(e);t.insertBefore(i,r),t.removeChild(r)}function K(r){const t=window.getSelection(),e=(t==null?void 0:t.anchorNode)??null;return e?r===e||r.contains(e):!1}function ae(r){return r.split(/\s+/).filter(Boolean).slice(0,2).map(t=>{var e;return((e=t[0])==null?void 0:e.toUpperCase())??""}).join("")}function V({users:r,search:t,loading:e=!1,clientFilter:i=!0,emptyText:l="No users found",left:a,top:b,onSelect:c,onClose:N}){const o=f.useMemo(()=>{if(!i)return r;const m=t.trim().toLowerCase();return m?r.filter(h=>{var p;return h.name.toLowerCase().includes(m)||(((p=h.email)==null?void 0:p.toLowerCase().includes(m))??!1)}):r},[r,t,i]),[g,y]=f.useState(0);return f.useEffect(()=>{y(0)},[t,o.length]),f.useEffect(()=>{const m=h=>{if(h.key==="Escape"){h.preventDefault(),N();return}o.length&&(h.key==="ArrowDown"?(h.preventDefault(),y(p=>(p+1)%o.length)):h.key==="ArrowUp"?(h.preventDefault(),y(p=>(p-1+o.length)%o.length)):h.key==="Enter"&&(h.preventDefault(),c(o[g])))};return document.addEventListener("keydown",m),()=>document.removeEventListener("keydown",m)},[o,g,N,c]),E.jsx("div",{className:"mi-picker-host",style:{left:a,top:b},role:"listbox",children:E.jsx("div",{className:"mi-picker",children:e?E.jsx("div",{className:"mi-picker__status",children:"Loading…"}):o.length===0?E.jsx("div",{className:"mi-picker__status",children:l}):o.map((m,h)=>E.jsxs("button",{type:"button",className:`mi-user${h===g?" mi-user--active":""}`,role:"option","aria-selected":h===g,onMouseDown:p=>{p.preventDefault(),c(m)},children:[E.jsx("span",{className:"mi-user-avatar","aria-hidden":"true",children:m.avatarUrl?E.jsx("img",{src:m.avatarUrl,alt:""}):ae(m.name)}),E.jsxs("span",{className:"mi-user-meta",children:[E.jsx("span",{className:"mi-user-name",children:m.name}),m.email?E.jsx("span",{className:"mi-user-email",children:m.email}):null]})]},m.id))})})}function J(r,t){return JSON.stringify(r)===JSON.stringify(t)}function le({value:r,defaultValue:t=[],onChange:e,users:i=[],loading:l=!1,clientFilter:a=!0,emptyText:b="No users found",placeholder:c="Write something… use @ to mention",disabled:N=!1,hasError:o=!1,errorMessage:g="",helperText:y="",className:m,style:h,onMentionQueryChange:p,onMentionSelected:j,onMentionOpenChange:w}){const A=f.useRef(null),X=f.useRef(null),L=f.useRef(!1),C=f.useRef([]),[u,T]=f.useState(null),[G,q]=f.useState(!0),$=r!==void 0,B=f.useCallback((n,s)=>{var x;const d=!!s.querySelector(`span.${k}`),v=!!((x=s.textContent)!=null&&x.replace(/\u00A0/g," ").trim());q(n.length===0&&!d&&!v)},[]),D=f.useCallback(()=>{const n=A.current;if(!n)return;const s=W(n);B(s,n),!J(s,C.current)&&(C.current=s,e==null||e(s))},[e,B]),P=f.useCallback(n=>{const s=A.current;if(!s)return;const d=H(n);s.innerHTML="",d.forEach(v=>s.appendChild(v)),C.current=n,B(n,s)},[B]);f.useLayoutEffect(()=>{if(!$){P(t);return}J(r??[],C.current)||P(r??[])},[$,r,t,P]);const I=f.useCallback(n=>{const s=n.getBoundingClientRect(),d=Math.min(Math.max(8,s.left),window.innerWidth-260),v=Math.min(s.bottom+4,window.innerHeight-16);return{left:d,top:Math.max(8,v)}},[]),S=f.useCallback(n=>{if(!L.current)return;L.current=!1;const s=u==null?void 0:u.span;T(null),w==null||w(!1),(n==null?void 0:n.unwrap)!==!1&&(s!=null&&s.isConnected)&&s.classList.contains(k)&&F(s),D()},[u,D,w]),Y=f.useCallback(()=>{if(!A.current||N)return;const s=document.createElement("span");s.className=k,s.contentEditable="true",s.style.position="relative",s.style.display="inline-block",s.appendChild(document.createTextNode("@")),ie(s),ne(s),L.current=!0;const d=I(s);T({span:s,search:"",...d}),q(!1),w==null||w(!0),p==null||p("")},[N,w,p,I]),Z=f.useCallback(n=>{const s=u==null?void 0:u.span;s&&(L.current=!1,s.innerHTML="",s.classList.remove(k),s.classList.add(_),s.contentEditable="false",s.setAttribute("userId",n.id),s.style.position="",s.style.display="",s.appendChild(document.createTextNode(`@${n.name}`)),ce(s),T(null),j==null||j(n),w==null||w(!1),D())},[u,D,w,j]);f.useEffect(()=>{if(!u)return;const n=()=>{if(!L.current)return;if(!K(u.span)){S();return}const x=A.current,R=x==null?void 0:x.querySelector(`span.${k}[contenteditable="true"]`);if(!R){S({unwrap:!1});return}const U=R.innerText??"";if(!U.startsWith("@")){S();return}const z=U.slice(1),M=I(R);T({span:R,search:z,...M}),p==null||p(z)},s=()=>{if(!L.current||!u.span.isConnected)return;const x=window.getSelection();!(x!=null&&x.rangeCount)||!x.anchorNode||K(u.span)||S()},d=()=>{if(!u.span.isConnected)return;const x=I(u.span);T(R=>R&&{...R,...x})},v=A.current;return v==null||v.addEventListener("input",n),document.addEventListener("selectionchange",s),window.addEventListener("resize",d),window.addEventListener("scroll",d,!0),()=>{v==null||v.removeEventListener("input",n),document.removeEventListener("selectionchange",s),window.removeEventListener("resize",d),window.removeEventListener("scroll",d,!0)}},[u,S,p,I]);const O=n=>{var s;if(n.key==="@"){n.preventDefault(),Y();return}if(n.key==="Backspace"){const d=(s=A.current)==null?void 0:s.querySelector(`span.${k}[contenteditable="true"]`);if(d&&d.innerText==="@"){n.preventDefault(),L.current=!1,T(null),w==null||w(!1),F(d),D();return}re(n.nativeEvent)}},Q=n=>{n.preventDefault();const s=n.clipboardData.getData("text/html")||n.clipboardData.getData("text/plain");if(!s)return;const d=document.createElement("div");d.innerHTML=s;const v=W(d),x=H(v);te(x),D()};return E.jsxs("div",{ref:X,className:`mi-editor-wrap${N?" mi-editor-wrap--disabled":""}${m?` ${m}`:""}`,style:h,children:[c&&G?E.jsx("span",{className:"mi-placeholder",children:c}):null,E.jsx("p",{ref:A,className:`mi-editor${o?" mi-editor--error":""}`,contentEditable:!N,suppressContentEditableWarning:!0,onInput:D,onKeyDown:O,onPaste:Q}),o&&g?E.jsx("span",{className:"mi-message mi-message--error",children:g}):y?E.jsx("span",{className:"mi-message",children:y}):null,u?ee.createPortal(E.jsx(V,{users:i,search:u.search,loading:l,clientFilter:a,emptyText:b,left:u.left,top:u.top,onSelect:Z,onClose:()=>S()}),document.body):null]})}exports.MentionInput=le;exports.MentionPicker=V;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { MentionInput } from './MentionInput';
|
|
2
|
+
export type { MentionInputProps } from './MentionInput';
|
|
3
|
+
export { MentionPicker } from './MentionPicker';
|
|
4
|
+
export type { MentionPickerProps } from './MentionPicker';
|
|
5
|
+
export type { MentionContentSection, MentionSection, MentionSectionType, MentionUser, TextSection, } from './models';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { jsx as v, jsxs as q } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo as se, useState as U, useEffect as z, useRef as _, useCallback as D, useLayoutEffect as ne } from "react";
|
|
3
|
+
import { createPortal as ie } from "react-dom";
|
|
4
|
+
const H = "mi-mention", S = "mi-mention-draft";
|
|
5
|
+
function ce(s) {
|
|
6
|
+
const t = window.getSelection();
|
|
7
|
+
if (!(t != null && t.rangeCount))
|
|
8
|
+
return;
|
|
9
|
+
const e = t.getRangeAt(0);
|
|
10
|
+
e.collapsed || e.deleteContents();
|
|
11
|
+
let i = null;
|
|
12
|
+
s.forEach((o) => {
|
|
13
|
+
e.insertNode(o), i = o, e.setStartAfter(o);
|
|
14
|
+
}), i && (e.collapse(!0), t.removeAllRanges(), t.addRange(e));
|
|
15
|
+
}
|
|
16
|
+
function J(s) {
|
|
17
|
+
const t = [];
|
|
18
|
+
return s.forEach((e) => {
|
|
19
|
+
if (e.type === "text") {
|
|
20
|
+
const i = e.message.split(`
|
|
21
|
+
`);
|
|
22
|
+
i.forEach((o, a) => {
|
|
23
|
+
o && t.push(document.createTextNode(o)), a < i.length - 1 && t.push(document.createElement("br"));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (e.type === "mention") {
|
|
27
|
+
const i = document.createElement("span");
|
|
28
|
+
i.classList.add(H), i.setAttribute("contenteditable", "false"), i.setAttribute("userId", e.userId || ""), i.textContent = ` @${e.userName} `, t.push(i);
|
|
29
|
+
}
|
|
30
|
+
}), t;
|
|
31
|
+
}
|
|
32
|
+
function V(s) {
|
|
33
|
+
var i;
|
|
34
|
+
const t = ae(s), e = [];
|
|
35
|
+
return t.childNodes.length === 1 && ((i = s.childNodes[0]) == null ? void 0 : i.tagName) === "BR" || t.childNodes.forEach((o) => {
|
|
36
|
+
var a, g;
|
|
37
|
+
if (o.nodeType === Node.TEXT_NODE) {
|
|
38
|
+
const c = (a = o.textContent) == null ? void 0 : a.replace(/\u00A0/g, " ");
|
|
39
|
+
c != null && c.trim() && e.push({ type: "text", message: c });
|
|
40
|
+
}
|
|
41
|
+
if (o.nodeType === Node.ELEMENT_NODE) {
|
|
42
|
+
const c = o;
|
|
43
|
+
if (c.classList.contains(H) || c.classList.contains("tag-wrap")) {
|
|
44
|
+
const p = c.getAttribute("userId") || "", l = ((g = c.textContent) == null ? void 0 : g.replace(/^\s*@/, "").trim()) || "";
|
|
45
|
+
e.push({ type: "mention", userId: p, userName: l });
|
|
46
|
+
} else c.tagName === "BR" && e.push({ type: "text", message: `
|
|
47
|
+
` });
|
|
48
|
+
}
|
|
49
|
+
}), e;
|
|
50
|
+
}
|
|
51
|
+
function ae(s) {
|
|
52
|
+
const t = s.cloneNode(!0), e = (i) => {
|
|
53
|
+
const o = Array.from(i.childNodes);
|
|
54
|
+
for (const a of o) {
|
|
55
|
+
if (a.nodeType !== Node.ELEMENT_NODE)
|
|
56
|
+
continue;
|
|
57
|
+
const g = a, c = g.parentElement;
|
|
58
|
+
if (!c)
|
|
59
|
+
continue;
|
|
60
|
+
const p = g.tagName.toUpperCase();
|
|
61
|
+
if (p === "DIV") {
|
|
62
|
+
const l = Array.from(g.childNodes);
|
|
63
|
+
if (l.length === 1 && l[0].nodeType === Node.ELEMENT_NODE && l[0].tagName === "BR")
|
|
64
|
+
c.insertBefore(l[0], a), c.removeChild(a), e(c);
|
|
65
|
+
else {
|
|
66
|
+
const A = document.createElement("br");
|
|
67
|
+
for (c.insertBefore(A, a); a.firstChild; )
|
|
68
|
+
c.insertBefore(a.firstChild, a);
|
|
69
|
+
c.removeChild(a), e(c);
|
|
70
|
+
}
|
|
71
|
+
} else if (p === "SPAN" && !g.hasAttribute("userId") || p === "FONT") {
|
|
72
|
+
for (; a.firstChild; )
|
|
73
|
+
c.insertBefore(a.firstChild, a);
|
|
74
|
+
c.removeChild(a), e(c);
|
|
75
|
+
} else if (p === "BR" || p === "SPAN" && g.hasAttribute("userId") || p === "LIB-MENTION-PICKER")
|
|
76
|
+
e(a);
|
|
77
|
+
else {
|
|
78
|
+
for (; a.firstChild; )
|
|
79
|
+
c.insertBefore(a.firstChild, a);
|
|
80
|
+
c.removeChild(a), e(c);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return e(t), t;
|
|
85
|
+
}
|
|
86
|
+
function oe(s) {
|
|
87
|
+
const t = window.getSelection();
|
|
88
|
+
if (!(t != null && t.rangeCount))
|
|
89
|
+
return;
|
|
90
|
+
const e = t.getRangeAt(0);
|
|
91
|
+
let i = e.startContainer.childNodes[e.startOffset - 1];
|
|
92
|
+
(i == null ? void 0 : i.nodeName) === "BR" && (i = e.startContainer.childNodes[e.startOffset - 2]), (i == null ? void 0 : i.contentEditable) === "false" && (s.preventDefault(), i.remove());
|
|
93
|
+
}
|
|
94
|
+
function le(s) {
|
|
95
|
+
const t = window.getSelection();
|
|
96
|
+
if (!(!s || !t))
|
|
97
|
+
try {
|
|
98
|
+
const e = document.createRange();
|
|
99
|
+
e.selectNodeContents(s), e.collapse(!1), t.removeAllRanges(), t.addRange(e);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function de(s) {
|
|
104
|
+
const t = window.getSelection();
|
|
105
|
+
if (!(t != null && t.rangeCount))
|
|
106
|
+
return;
|
|
107
|
+
const e = t.getRangeAt(0);
|
|
108
|
+
e.collapse(!0), e.insertNode(s), e.setStartAfter(s), e.collapse(!0), t.removeAllRanges(), t.addRange(e);
|
|
109
|
+
}
|
|
110
|
+
function ue(s) {
|
|
111
|
+
if (!s)
|
|
112
|
+
return;
|
|
113
|
+
const t = window.getSelection();
|
|
114
|
+
t && requestAnimationFrame(() => {
|
|
115
|
+
const e = document.createRange();
|
|
116
|
+
e.setStartAfter(s), e.collapse(!0), t.removeAllRanges(), t.addRange(e);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function X(s) {
|
|
120
|
+
var o, a;
|
|
121
|
+
const t = s.parentNode;
|
|
122
|
+
if (!t)
|
|
123
|
+
return;
|
|
124
|
+
const e = ((a = (o = s.childNodes[0]) == null ? void 0 : o.textContent) == null ? void 0 : a.trim()) || "", i = document.createTextNode(e);
|
|
125
|
+
t.insertBefore(i, s), t.removeChild(s);
|
|
126
|
+
}
|
|
127
|
+
function G(s) {
|
|
128
|
+
const t = window.getSelection(), e = (t == null ? void 0 : t.anchorNode) ?? null;
|
|
129
|
+
return e ? s === e || s.contains(e) : !1;
|
|
130
|
+
}
|
|
131
|
+
function fe(s) {
|
|
132
|
+
return s.split(/\s+/).filter(Boolean).slice(0, 2).map((t) => {
|
|
133
|
+
var e;
|
|
134
|
+
return ((e = t[0]) == null ? void 0 : e.toUpperCase()) ?? "";
|
|
135
|
+
}).join("");
|
|
136
|
+
}
|
|
137
|
+
function me({
|
|
138
|
+
users: s,
|
|
139
|
+
search: t,
|
|
140
|
+
loading: e = !1,
|
|
141
|
+
clientFilter: i = !0,
|
|
142
|
+
emptyText: o = "No users found",
|
|
143
|
+
left: a,
|
|
144
|
+
top: g,
|
|
145
|
+
onSelect: c,
|
|
146
|
+
onClose: p
|
|
147
|
+
}) {
|
|
148
|
+
const l = se(() => {
|
|
149
|
+
if (!i)
|
|
150
|
+
return s;
|
|
151
|
+
const f = t.trim().toLowerCase();
|
|
152
|
+
return f ? s.filter(
|
|
153
|
+
(N) => {
|
|
154
|
+
var m;
|
|
155
|
+
return N.name.toLowerCase().includes(f) || (((m = N.email) == null ? void 0 : m.toLowerCase().includes(f)) ?? !1);
|
|
156
|
+
}
|
|
157
|
+
) : s;
|
|
158
|
+
}, [s, t, i]), [y, A] = U(0);
|
|
159
|
+
return z(() => {
|
|
160
|
+
A(0);
|
|
161
|
+
}, [t, l.length]), z(() => {
|
|
162
|
+
const f = (N) => {
|
|
163
|
+
if (N.key === "Escape") {
|
|
164
|
+
N.preventDefault(), p();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
l.length && (N.key === "ArrowDown" ? (N.preventDefault(), A((m) => (m + 1) % l.length)) : N.key === "ArrowUp" ? (N.preventDefault(), A((m) => (m - 1 + l.length) % l.length)) : N.key === "Enter" && (N.preventDefault(), c(l[y])));
|
|
168
|
+
};
|
|
169
|
+
return document.addEventListener("keydown", f), () => document.removeEventListener("keydown", f);
|
|
170
|
+
}, [l, y, p, c]), /* @__PURE__ */ v("div", { className: "mi-picker-host", style: { left: a, top: g }, role: "listbox", children: /* @__PURE__ */ v("div", { className: "mi-picker", children: e ? /* @__PURE__ */ v("div", { className: "mi-picker__status", children: "Loading…" }) : l.length === 0 ? /* @__PURE__ */ v("div", { className: "mi-picker__status", children: o }) : l.map((f, N) => /* @__PURE__ */ q(
|
|
171
|
+
"button",
|
|
172
|
+
{
|
|
173
|
+
type: "button",
|
|
174
|
+
className: `mi-user${N === y ? " mi-user--active" : ""}`,
|
|
175
|
+
role: "option",
|
|
176
|
+
"aria-selected": N === y,
|
|
177
|
+
onMouseDown: (m) => {
|
|
178
|
+
m.preventDefault(), c(f);
|
|
179
|
+
},
|
|
180
|
+
children: [
|
|
181
|
+
/* @__PURE__ */ v("span", { className: "mi-user-avatar", "aria-hidden": "true", children: f.avatarUrl ? /* @__PURE__ */ v("img", { src: f.avatarUrl, alt: "" }) : fe(f.name) }),
|
|
182
|
+
/* @__PURE__ */ q("span", { className: "mi-user-meta", children: [
|
|
183
|
+
/* @__PURE__ */ v("span", { className: "mi-user-name", children: f.name }),
|
|
184
|
+
f.email ? /* @__PURE__ */ v("span", { className: "mi-user-email", children: f.email }) : null
|
|
185
|
+
] })
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
f.id
|
|
189
|
+
)) }) });
|
|
190
|
+
}
|
|
191
|
+
function Y(s, t) {
|
|
192
|
+
return JSON.stringify(s) === JSON.stringify(t);
|
|
193
|
+
}
|
|
194
|
+
function Ee({
|
|
195
|
+
value: s,
|
|
196
|
+
defaultValue: t = [],
|
|
197
|
+
onChange: e,
|
|
198
|
+
users: i = [],
|
|
199
|
+
loading: o = !1,
|
|
200
|
+
clientFilter: a = !0,
|
|
201
|
+
emptyText: g = "No users found",
|
|
202
|
+
placeholder: c = "Write something… use @ to mention",
|
|
203
|
+
disabled: p = !1,
|
|
204
|
+
hasError: l = !1,
|
|
205
|
+
errorMessage: y = "",
|
|
206
|
+
helperText: A = "",
|
|
207
|
+
className: f,
|
|
208
|
+
style: N,
|
|
209
|
+
onMentionQueryChange: m,
|
|
210
|
+
onMentionSelected: B,
|
|
211
|
+
onMentionOpenChange: h
|
|
212
|
+
}) {
|
|
213
|
+
const b = _(null), Z = _(null), R = _(!1), C = _([]), [d, T] = U(null), [O, W] = U(!0), j = s !== void 0, P = D((n, r) => {
|
|
214
|
+
var E;
|
|
215
|
+
const u = !!r.querySelector(`span.${S}`), w = !!((E = r.textContent) != null && E.replace(/\u00A0/g, " ").trim());
|
|
216
|
+
W(n.length === 0 && !u && !w);
|
|
217
|
+
}, []), x = D(() => {
|
|
218
|
+
const n = b.current;
|
|
219
|
+
if (!n)
|
|
220
|
+
return;
|
|
221
|
+
const r = V(n);
|
|
222
|
+
P(r, n), !Y(r, C.current) && (C.current = r, e == null || e(r));
|
|
223
|
+
}, [e, P]), $ = D(
|
|
224
|
+
(n) => {
|
|
225
|
+
const r = b.current;
|
|
226
|
+
if (!r)
|
|
227
|
+
return;
|
|
228
|
+
const u = J(n);
|
|
229
|
+
r.innerHTML = "", u.forEach((w) => r.appendChild(w)), C.current = n, P(n, r);
|
|
230
|
+
},
|
|
231
|
+
[P]
|
|
232
|
+
);
|
|
233
|
+
ne(() => {
|
|
234
|
+
if (!j) {
|
|
235
|
+
$(t);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
Y(s ?? [], C.current) || $(s ?? []);
|
|
239
|
+
}, [j, s, t, $]);
|
|
240
|
+
const k = D((n) => {
|
|
241
|
+
const r = n.getBoundingClientRect(), u = Math.min(Math.max(8, r.left), window.innerWidth - 260), w = Math.min(r.bottom + 4, window.innerHeight - 16);
|
|
242
|
+
return { left: u, top: Math.max(8, w) };
|
|
243
|
+
}, []), I = D(
|
|
244
|
+
(n) => {
|
|
245
|
+
if (!R.current)
|
|
246
|
+
return;
|
|
247
|
+
R.current = !1;
|
|
248
|
+
const r = d == null ? void 0 : d.span;
|
|
249
|
+
T(null), h == null || h(!1), (n == null ? void 0 : n.unwrap) !== !1 && (r != null && r.isConnected) && r.classList.contains(S) && X(r), x();
|
|
250
|
+
},
|
|
251
|
+
[d, x, h]
|
|
252
|
+
), Q = D(() => {
|
|
253
|
+
if (!b.current || p)
|
|
254
|
+
return;
|
|
255
|
+
const r = document.createElement("span");
|
|
256
|
+
r.className = S, r.contentEditable = "true", r.style.position = "relative", r.style.display = "inline-block", r.appendChild(document.createTextNode("@")), de(r), le(r), R.current = !0;
|
|
257
|
+
const u = k(r);
|
|
258
|
+
T({ span: r, search: "", ...u }), W(!1), h == null || h(!0), m == null || m("");
|
|
259
|
+
}, [p, h, m, k]), M = D(
|
|
260
|
+
(n) => {
|
|
261
|
+
const r = d == null ? void 0 : d.span;
|
|
262
|
+
r && (R.current = !1, r.innerHTML = "", r.classList.remove(S), r.classList.add(H), r.contentEditable = "false", r.setAttribute("userId", n.id), r.style.position = "", r.style.display = "", r.appendChild(document.createTextNode(`@${n.name}`)), ue(r), T(null), B == null || B(n), h == null || h(!1), x());
|
|
263
|
+
},
|
|
264
|
+
[d, x, h, B]
|
|
265
|
+
);
|
|
266
|
+
z(() => {
|
|
267
|
+
if (!d)
|
|
268
|
+
return;
|
|
269
|
+
const n = () => {
|
|
270
|
+
if (!R.current)
|
|
271
|
+
return;
|
|
272
|
+
if (!G(d.span)) {
|
|
273
|
+
I();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const E = b.current, L = E == null ? void 0 : E.querySelector(
|
|
277
|
+
`span.${S}[contenteditable="true"]`
|
|
278
|
+
);
|
|
279
|
+
if (!L) {
|
|
280
|
+
I({ unwrap: !1 });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const F = L.innerText ?? "";
|
|
284
|
+
if (!F.startsWith("@")) {
|
|
285
|
+
I();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const K = F.slice(1), re = k(L);
|
|
289
|
+
T({ span: L, search: K, ...re }), m == null || m(K);
|
|
290
|
+
}, r = () => {
|
|
291
|
+
if (!R.current || !d.span.isConnected)
|
|
292
|
+
return;
|
|
293
|
+
const E = window.getSelection();
|
|
294
|
+
!(E != null && E.rangeCount) || !E.anchorNode || G(d.span) || I();
|
|
295
|
+
}, u = () => {
|
|
296
|
+
if (!d.span.isConnected)
|
|
297
|
+
return;
|
|
298
|
+
const E = k(d.span);
|
|
299
|
+
T((L) => L && { ...L, ...E });
|
|
300
|
+
}, w = b.current;
|
|
301
|
+
return w == null || w.addEventListener("input", n), document.addEventListener("selectionchange", r), window.addEventListener("resize", u), window.addEventListener("scroll", u, !0), () => {
|
|
302
|
+
w == null || w.removeEventListener("input", n), document.removeEventListener("selectionchange", r), window.removeEventListener("resize", u), window.removeEventListener("scroll", u, !0);
|
|
303
|
+
};
|
|
304
|
+
}, [d, I, m, k]);
|
|
305
|
+
const ee = (n) => {
|
|
306
|
+
var r;
|
|
307
|
+
if (n.key === "@") {
|
|
308
|
+
n.preventDefault(), Q();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (n.key === "Backspace") {
|
|
312
|
+
const u = (r = b.current) == null ? void 0 : r.querySelector(
|
|
313
|
+
`span.${S}[contenteditable="true"]`
|
|
314
|
+
);
|
|
315
|
+
if (u && u.innerText === "@") {
|
|
316
|
+
n.preventDefault(), R.current = !1, T(null), h == null || h(!1), X(u), x();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
oe(n.nativeEvent);
|
|
320
|
+
}
|
|
321
|
+
}, te = (n) => {
|
|
322
|
+
n.preventDefault();
|
|
323
|
+
const r = n.clipboardData.getData("text/html") || n.clipboardData.getData("text/plain");
|
|
324
|
+
if (!r)
|
|
325
|
+
return;
|
|
326
|
+
const u = document.createElement("div");
|
|
327
|
+
u.innerHTML = r;
|
|
328
|
+
const w = V(u), E = J(w);
|
|
329
|
+
ce(E), x();
|
|
330
|
+
};
|
|
331
|
+
return /* @__PURE__ */ q(
|
|
332
|
+
"div",
|
|
333
|
+
{
|
|
334
|
+
ref: Z,
|
|
335
|
+
className: `mi-editor-wrap${p ? " mi-editor-wrap--disabled" : ""}${f ? ` ${f}` : ""}`,
|
|
336
|
+
style: N,
|
|
337
|
+
children: [
|
|
338
|
+
c && O ? /* @__PURE__ */ v("span", { className: "mi-placeholder", children: c }) : null,
|
|
339
|
+
/* @__PURE__ */ v(
|
|
340
|
+
"p",
|
|
341
|
+
{
|
|
342
|
+
ref: b,
|
|
343
|
+
className: `mi-editor${l ? " mi-editor--error" : ""}`,
|
|
344
|
+
contentEditable: !p,
|
|
345
|
+
suppressContentEditableWarning: !0,
|
|
346
|
+
onInput: x,
|
|
347
|
+
onKeyDown: ee,
|
|
348
|
+
onPaste: te
|
|
349
|
+
}
|
|
350
|
+
),
|
|
351
|
+
l && y ? /* @__PURE__ */ v("span", { className: "mi-message mi-message--error", children: y }) : A ? /* @__PURE__ */ v("span", { className: "mi-message", children: A }) : null,
|
|
352
|
+
d ? ie(
|
|
353
|
+
/* @__PURE__ */ v(
|
|
354
|
+
me,
|
|
355
|
+
{
|
|
356
|
+
users: i,
|
|
357
|
+
search: d.search,
|
|
358
|
+
loading: o,
|
|
359
|
+
clientFilter: a,
|
|
360
|
+
emptyText: g,
|
|
361
|
+
left: d.left,
|
|
362
|
+
top: d.top,
|
|
363
|
+
onSelect: M,
|
|
364
|
+
onClose: () => I()
|
|
365
|
+
}
|
|
366
|
+
),
|
|
367
|
+
document.body
|
|
368
|
+
) : null
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
export {
|
|
374
|
+
Ee as MentionInput,
|
|
375
|
+
me as MentionPicker
|
|
376
|
+
};
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type MentionSectionType = 'text' | 'mention';
|
|
2
|
+
export interface TextSection {
|
|
3
|
+
type: 'text';
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MentionSection {
|
|
7
|
+
type: 'mention';
|
|
8
|
+
userId: string;
|
|
9
|
+
userName: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
}
|
|
12
|
+
export type MentionContentSection = TextSection | MentionSection;
|
|
13
|
+
export interface MentionUser {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
avatarUrl?: string;
|
|
18
|
+
}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.mi-editor-wrap{position:relative;display:block}.mi-editor-wrap--disabled{opacity:.65;pointer-events:none}.mi-placeholder{position:absolute;top:12px;left:14px;color:var(--mi-placeholder-color, #9ca3af);pointer-events:none;-webkit-user-select:none;user-select:none;z-index:1}.mi-editor{min-height:var(--mi-editor-min-height, 88px);max-height:180px;overflow:auto;margin:0;padding:12px 14px;border:1px solid var(--mi-editor-border, #d1d5db);border-radius:var(--mi-editor-radius, 8px);background:var(--mi-editor-bg, #fff);outline:none;line-height:1.5;white-space:pre-wrap;word-break:break-word}.mi-editor:focus{border-color:#6366f1;box-shadow:0 0 0 3px #6366f126}.mi-editor--error{border-color:#ef4444}.mi-editor>*{pointer-events:none}.mi-editor .mi-mention{pointer-events:none;background:var(--mi-mention-bg, #e0e7ff);color:var(--mi-mention-color, #3730a3);border-radius:4px;padding:0 2px;font-weight:500}.mi-editor .mi-mention-draft{pointer-events:auto;color:var(--mi-mention-draft-color, #4f46e5);font-weight:500}.mi-message{display:block;margin-top:4px;font-size:12px;color:#6b7280}.mi-message--error{color:#ef4444}.mi-picker-host{position:fixed;z-index:10000;min-width:240px;max-width:320px}.mi-picker{max-height:240px;overflow:auto;border:1px solid var(--mi-editor-border, #d1d5db);border-radius:var(--mi-editor-radius, 8px);background:var(--mi-picker-bg, #fff);box-shadow:var(--mi-picker-shadow, 0 8px 24px rgba(0, 0, 0, .12));padding:4px}.mi-picker__status{padding:10px 12px;color:#6b7280;font-size:13px}.mi-user{display:flex;align-items:center;gap:10px;width:100%;border:0;background:transparent;text-align:left;padding:8px 10px;border-radius:6px;cursor:pointer}.mi-user:hover,.mi-user--active{background:var(--mi-user-active-bg, #eef2ff)}.mi-user-avatar{width:var(--mi-avatar-size, 28px);height:var(--mi-avatar-size, 28px);border-radius:50%;background:#e5e7eb;color:#374151;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;overflow:hidden;flex-shrink:0}.mi-user-avatar img{width:100%;height:100%;object-fit:cover}.mi-user-meta{display:flex;flex-direction:column;min-width:0}.mi-user-name{font-size:13px;color:#111827;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mi-user-email{font-size:12px;color:#6b7280;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
package/package.json
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mojsica/react-mention-input",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Contenteditable @mention input for React with chips and structured sections.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/mojsica/mention-input.git",
|
|
9
|
+
"directory": "packages/react-mention-input"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react",
|
|
13
|
+
"mention",
|
|
14
|
+
"contenteditable",
|
|
15
|
+
"chips"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js",
|
|
25
|
+
"require": "./dist/index.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./styles.css": "./dist/styles.css"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": [
|
|
33
|
+
"**/*.css"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
37
|
+
"dev": "vite build --watch"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=18 <21",
|
|
41
|
+
"react-dom": ">=18 <21"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@mojsica/mention-core": "workspace:*",
|
|
45
|
+
"@types/react": "^18.3.12",
|
|
46
|
+
"@types/react-dom": "^18.3.1",
|
|
47
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
48
|
+
"react": "^18.3.1",
|
|
49
|
+
"react-dom": "^18.3.1",
|
|
50
|
+
"typescript": "~5.9.2",
|
|
51
|
+
"vite": "^6.0.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|