@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 CHANGED
@@ -1,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # @mojsica/react-mention-input
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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;
@@ -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
+ };
@@ -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
+ }
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }