@remyxjs/react 1.0.0-beta
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 +2158 -0
- package/create/index.js +367 -0
- package/create/templates/jsx/index.html +12 -0
- package/create/templates/jsx/src/App.jsx +25 -0
- package/create/templates/jsx/src/main.jsx +9 -0
- package/create/templates/jsx/vite.config.js +6 -0
- package/create/templates/typescript/index.html +12 -0
- package/create/templates/typescript/src/App.tsx +25 -0
- package/create/templates/typescript/src/main.tsx +9 -0
- package/create/templates/typescript/src/vite-env.d.ts +1 -0
- package/create/templates/typescript/tsconfig.json +21 -0
- package/create/templates/typescript/vite.config.ts +6 -0
- package/dist/AttachmentModal--6T-vYJt.js +21 -0
- package/dist/AttachmentModal--6T-vYJt.js.map +1 -0
- package/dist/AttachmentModal-D-uxbXvb.cjs +2 -0
- package/dist/AttachmentModal-D-uxbXvb.cjs.map +1 -0
- package/dist/CommandPalette-DXWyTGOU.js +19 -0
- package/dist/CommandPalette-DXWyTGOU.js.map +1 -0
- package/dist/CommandPalette-t9J8JGNJ.cjs +2 -0
- package/dist/CommandPalette-t9J8JGNJ.cjs.map +1 -0
- package/dist/ContextMenu-B4K3Zbfp.cjs +2 -0
- package/dist/ContextMenu-B4K3Zbfp.cjs.map +1 -0
- package/dist/ContextMenu-D8wNSMc3.js +2 -0
- package/dist/ContextMenu-D8wNSMc3.js.map +1 -0
- package/dist/EmbedModal-Bh2Tcow9.cjs +2 -0
- package/dist/EmbedModal-Bh2Tcow9.cjs.map +1 -0
- package/dist/EmbedModal-cxE4maD2.js +11 -0
- package/dist/EmbedModal-cxE4maD2.js.map +1 -0
- package/dist/ExportModal-Cf1uE4r_.js +13 -0
- package/dist/ExportModal-Cf1uE4r_.js.map +1 -0
- package/dist/ExportModal-DwQVsrZE.cjs +2 -0
- package/dist/ExportModal-DwQVsrZE.cjs.map +1 -0
- package/dist/FindReplaceModal-DYL_2z8U.cjs +2 -0
- package/dist/FindReplaceModal-DYL_2z8U.cjs.map +1 -0
- package/dist/FindReplaceModal-Dgt_MrWb.js +17 -0
- package/dist/FindReplaceModal-Dgt_MrWb.js.map +1 -0
- package/dist/ImageModal-D39ywxqI.cjs +2 -0
- package/dist/ImageModal-D39ywxqI.cjs.map +1 -0
- package/dist/ImageModal-DqScpPrc.js +23 -0
- package/dist/ImageModal-DqScpPrc.js.map +1 -0
- package/dist/ImportDocumentModal-BKqMxO3z.js +22 -0
- package/dist/ImportDocumentModal-BKqMxO3z.js.map +1 -0
- package/dist/ImportDocumentModal-Bev9hp_J.cjs +2 -0
- package/dist/ImportDocumentModal-Bev9hp_J.cjs.map +1 -0
- package/dist/LinkModal-B-igSa-g.cjs +2 -0
- package/dist/LinkModal-B-igSa-g.cjs.map +1 -0
- package/dist/LinkModal-k9IeDtAb.js +15 -0
- package/dist/LinkModal-k9IeDtAb.js.map +1 -0
- package/dist/MenuBar-B-ZAX9rH.cjs +2 -0
- package/dist/MenuBar-B-ZAX9rH.cjs.map +1 -0
- package/dist/MenuBar-DWxJNHmb.js +12 -0
- package/dist/MenuBar-DWxJNHmb.js.map +1 -0
- package/dist/ModalOverlay-BDsGgv3_.cjs +2 -0
- package/dist/ModalOverlay-BDsGgv3_.cjs.map +1 -0
- package/dist/ModalOverlay-CLvRNHmp.js +6 -0
- package/dist/ModalOverlay-CLvRNHmp.js.map +1 -0
- package/dist/SourceModal-BNI_i4iW.cjs +2 -0
- package/dist/SourceModal-BNI_i4iW.cjs.map +1 -0
- package/dist/SourceModal-MdTGK3Uf.js +12 -0
- package/dist/SourceModal-MdTGK3Uf.js.map +1 -0
- package/dist/TablePickerModal-DYODWEA1.js +17 -0
- package/dist/TablePickerModal-DYODWEA1.js.map +1 -0
- package/dist/TablePickerModal-Do1QyoyM.cjs +2 -0
- package/dist/TablePickerModal-Do1QyoyM.cjs.map +1 -0
- package/dist/index-C720tbJA.js +359 -0
- package/dist/index-C720tbJA.js.map +1 -0
- package/dist/index-Dc63uIP0.cjs +2 -0
- package/dist/index-Dc63uIP0.cjs.map +1 -0
- package/dist/remyx-react.cjs +2 -0
- package/dist/remyx-react.cjs.map +1 -0
- package/dist/remyx-react.css +1 -0
- package/dist/remyx-react.js +2 -0
- package/dist/remyx-react.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImportDocumentModal-Bev9hp_J.cjs","sources":["../src/components/Modals/ImportDocumentModal.jsx"],"sourcesContent":["import { useState, useRef } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\nimport { convertDocument, getSupportedExtensions, getSupportedFormatNames } from '@remyxjs/core'\n\nexport function ImportDocumentModal({ open, onClose, engine }) {\n const [file, setFile] = useState(null)\n const [converting, setConverting] = useState(false)\n const [preview, setPreview] = useState('')\n const [error, setError] = useState('')\n const [insertMode, setInsertMode] = useState('insert')\n const fileInputRef = useRef(null)\n\n const handleFileChange = async (e) => {\n const selected = e.target.files[0]\n if (!selected) return\n\n setFile(selected)\n setError('')\n setConverting(true)\n setPreview('')\n\n try {\n const html = await convertDocument(selected)\n setPreview(html)\n } catch (err) {\n setError(err.message || 'Failed to convert document')\n setPreview('')\n } finally {\n setConverting(false)\n }\n }\n\n const handleInsert = () => {\n if (!preview) return\n engine.executeCommand('importDocument', {\n html: preview,\n mode: insertMode,\n })\n handleClose()\n }\n\n const handleClose = () => {\n onClose()\n setFile(null)\n setConverting(false)\n setPreview('')\n setError('')\n setInsertMode('insert')\n if (fileInputRef.current) fileInputRef.current.value = ''\n }\n\n const formats = getSupportedFormatNames().join(', ')\n\n return (\n <ModalOverlay title=\"Import Document\" open={open} onClose={handleClose}>\n <div className=\"rmx-upload-area\">\n <input\n ref={fileInputRef}\n type=\"file\"\n accept={getSupportedExtensions()}\n onChange={handleFileChange}\n className=\"rmx-sr-only\"\n />\n <button\n type=\"button\"\n className=\"rmx-btn rmx-btn-upload\"\n onClick={() => fileInputRef.current?.click()}\n disabled={converting}\n >\n {converting ? (\n <>\n <span className=\"rmx-spinner\" aria-hidden=\"true\" />\n {' Converting\\u2026'}\n </>\n ) : 'Choose Document'}\n </button>\n <p className=\"rmx-upload-hint\">\n Supported: {formats}\n </p>\n {file && !converting && !error && (\n <p className=\"rmx-upload-hint rmx-upload-filename\">\n {file.name}\n </p>\n )}\n </div>\n\n {error && (\n <div className=\"rmx-form-group rmx-form-error\">\n {error}\n </div>\n )}\n\n {preview && (\n <>\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\">Preview</label>\n <div\n className=\"rmx-import-preview\"\n dangerouslySetInnerHTML={{ __html: engine?.sanitizer ? engine.sanitizer.sanitize(preview) : '' }}\n />\n </div>\n\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\">Insert Mode</label>\n <div className=\"rmx-radio-group\">\n <label className=\"rmx-radio-label\">\n <input\n type=\"radio\"\n name=\"insertMode\"\n value=\"insert\"\n checked={insertMode === 'insert'}\n onChange={() => setInsertMode('insert')}\n />\n Insert at cursor\n </label>\n <label className=\"rmx-radio-label\">\n <input\n type=\"radio\"\n name=\"insertMode\"\n value=\"replace\"\n checked={insertMode === 'replace'}\n onChange={() => setInsertMode('replace')}\n />\n Replace all content\n </label>\n </div>\n {insertMode === 'replace' && (\n <p className=\"rmx-replace-warning\">Warning: This will replace all existing content in the editor.</p>\n )}\n </div>\n </>\n )}\n\n <div className=\"rmx-modal-actions\">\n <button type=\"button\" className=\"rmx-btn\" onClick={handleClose}>Cancel</button>\n <button\n type=\"button\"\n className=\"rmx-btn rmx-btn-primary\"\n disabled={!preview || converting}\n onClick={handleInsert}\n >\n {converting ? (\n <>\n <span className=\"rmx-spinner\" aria-hidden=\"true\" />\n {' Converting\\u2026'}\n </>\n ) : 'Insert'}\n </button>\n </div>\n </ModalOverlay>\n )\n}\n\nImportDocumentModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n}\n"],"names":["ImportDocumentModal","open","onClose","engine","file","setFile","useState","converting","setConverting","preview","setPreview","error","setError","insertMode","setInsertMode","fileInputRef","useRef","handleClose","current","value","formats","getSupportedFormatNames","join","ModalOverlay","title","children","jsxs","className","jsx","ref","type","accept","getSupportedExtensions","onChange","async","e","selected","target","files","html","convertDocument","err","message","onClick","click","disabled","Fragment","name","dangerouslySetInnerHTML","__html","sanitizer","sanitize","checked","executeCommand","mode","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),t=require("react"),r=require("./index-Dc63uIP0.cjs"),n=require("./ModalOverlay-BDsGgv3_.cjs"),s=/^\s*(https?|mailto|tel|ftp|ftps):\/?\/?/i,a=/^\s*(\/|#|\?|\.)/;function l({open:r,onClose:l,engine:o,data:i}){const[m,c]=t.useState(""),[x,d]=t.useState(""),[u,p]=t.useState("_blank");t.useEffect((()=>{r&&i&&(c(i.href||""),d(i.text||""),p(i.target||"_blank"))}),[r,i]);return e.jsx(n.ModalOverlay,{title:i?.href?"Edit Link":"Insert Link",open:r,onClose:l,children:e.jsxs("form",{onSubmit:e=>{if(e.preventDefault(),!m.trim())return;let t;try{t=decodeURIComponent(m.trim())}catch{t=m.trim()}(s.test(t)||a.test(t))&&(i?.href?o.executeCommand("editLink",{href:m,text:x,target:u}):o.executeCommand("insertLink",{href:m,text:x,target:u}),l())},className:"rmx-modal-form",children:[e.jsxs("div",{className:"rmx-form-group",children:[e.jsx("label",{className:"rmx-form-label",htmlFor:"rmx-link-url",children:"URL"}),e.jsx("input",{id:"rmx-link-url",type:"text",className:"rmx-form-input",value:m,onChange:e=>c(e.target.value),placeholder:"https://example.com or /about or #section",required:!0,autoFocus:!0})]}),e.jsxs("div",{className:"rmx-form-group",children:[e.jsx("label",{className:"rmx-form-label",htmlFor:"rmx-link-text",children:"Display Text"}),e.jsx("input",{id:"rmx-link-text",type:"text",className:"rmx-form-input",value:x,onChange:e=>d(e.target.value),placeholder:"Link text (optional)"})]}),e.jsx("div",{className:"rmx-form-group",children:e.jsxs("label",{className:"rmx-form-checkbox",children:[e.jsx("input",{type:"checkbox",checked:"_blank"===u,onChange:e=>p(e.target.checked?"_blank":"_self")}),"Open in new tab"]})}),e.jsxs("div",{className:"rmx-modal-actions",children:[i?.href&&e.jsx("button",{type:"button",className:"rmx-btn rmx-btn-danger",onClick:()=>{o.executeCommand("removeLink"),l()},children:"Remove Link"}),e.jsxs("div",{className:"rmx-modal-actions-right",children:[e.jsx("button",{type:"button",className:"rmx-btn",onClick:l,children:"Cancel"}),e.jsx("button",{type:"submit",className:"rmx-btn rmx-btn-primary",children:i?.href?"Update":"Insert"})]})]})]})})}l.propTypes={open:r.PropTypes.bool.isRequired,onClose:r.PropTypes.func.isRequired,engine:r.PropTypes.object,data:r.PropTypes.object},exports.LinkModal=l;
|
|
2
|
+
//# sourceMappingURL=LinkModal-B-igSa-g.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkModal-B-igSa-g.cjs","sources":["../src/components/Modals/LinkModal.jsx"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\n\n// Allowlist-based protocol validation — safer than a blacklist because new\n// dangerous protocols (e.g. mhtml:, ms-*:) are blocked by default.\nconst SAFE_PROTOCOL = /^\\s*(https?|mailto|tel|ftp|ftps):\\/?\\/?/i\nconst RELATIVE_URL = /^\\s*(\\/|#|\\?|\\.)/\n\nexport function LinkModal({ open, onClose, engine, data }) {\n const [href, setHref] = useState('')\n const [text, setText] = useState('')\n const [target, setTarget] = useState('_blank')\n\n useEffect(() => {\n if (open && data) {\n setHref(data.href || '')\n setText(data.text || '')\n setTarget(data.target || '_blank')\n }\n }, [open, data])\n\n const handleSubmit = (e) => {\n e.preventDefault()\n if (!href.trim()) return\n // Decode percent-encoding before validation to prevent bypasses like java%73cript:\n let trimmedHref\n try {\n trimmedHref = decodeURIComponent(href.trim())\n } catch {\n trimmedHref = href.trim()\n }\n // Block any protocol not in the allowlist (blocks javascript:, vbscript:, data:, mhtml:, etc.)\n if (!SAFE_PROTOCOL.test(trimmedHref) && !RELATIVE_URL.test(trimmedHref)) return\n\n if (data?.href) {\n engine.executeCommand('editLink', { href, text, target })\n } else {\n engine.executeCommand('insertLink', { href, text, target })\n }\n onClose()\n }\n\n const handleRemove = () => {\n engine.executeCommand('removeLink')\n onClose()\n }\n\n return (\n <ModalOverlay title={data?.href ? 'Edit Link' : 'Insert Link'} open={open} onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"rmx-modal-form\">\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-link-url\">URL</label>\n <input\n id=\"rmx-link-url\"\n type=\"text\"\n className=\"rmx-form-input\"\n value={href}\n onChange={(e) => setHref(e.target.value)}\n placeholder=\"https://example.com or /about or #section\"\n required\n autoFocus\n />\n </div>\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-link-text\">Display Text</label>\n <input\n id=\"rmx-link-text\"\n type=\"text\"\n className=\"rmx-form-input\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n placeholder=\"Link text (optional)\"\n />\n </div>\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-checkbox\">\n <input\n type=\"checkbox\"\n checked={target === '_blank'}\n onChange={(e) => setTarget(e.target.checked ? '_blank' : '_self')}\n />\n Open in new tab\n </label>\n </div>\n <div className=\"rmx-modal-actions\">\n {data?.href && (\n <button type=\"button\" className=\"rmx-btn rmx-btn-danger\" onClick={handleRemove}>\n Remove Link\n </button>\n )}\n <div className=\"rmx-modal-actions-right\">\n <button type=\"button\" className=\"rmx-btn\" onClick={onClose}>Cancel</button>\n <button type=\"submit\" className=\"rmx-btn rmx-btn-primary\">\n {data?.href ? 'Update' : 'Insert'}\n </button>\n </div>\n </div>\n </form>\n </ModalOverlay>\n )\n}\n\nLinkModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n data: PropTypes.object,\n}\n"],"names":["SAFE_PROTOCOL","RELATIVE_URL","LinkModal","open","onClose","engine","data","href","setHref","useState","text","setText","target","setTarget","useEffect","jsx","ModalOverlay","title","children","jsxs","onSubmit","e","preventDefault","trim","trimmedHref","decodeURIComponent","test","executeCommand","className","htmlFor","id","type","value","onChange","placeholder","required","autoFocus","checked","onClick","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"mNAMMA,EAAgB,2CAChBC,EAAe,mBAEd,SAASC,GAAUC,KAAEA,EAAAC,QAAMA,EAAAC,OAASA,EAAAC,KAAQA,IACjD,MAAOC,EAAMC,GAAWC,EAAAA,SAAS,KAC1BC,EAAMC,GAAWF,EAAAA,SAAS,KAC1BG,EAAQC,GAAaJ,EAAAA,SAAS,UAErCK,EAAAA,WAAU,KACJX,GAAQG,IACVE,EAAQF,EAAKC,MAAQ,IACrBI,EAAQL,EAAKI,MAAQ,IACrBG,EAAUP,EAAKM,QAAU,UAC3B,GACC,CAACT,EAAMG,IA4BV,OACES,EAAAA,IAACC,EAAAA,aAAA,CAAaC,MAAOX,GAAMC,KAAO,YAAc,cAAeJ,OAAYC,UACzEc,WAAAC,KAAC,OAAA,CAAKC,SA5BYC,IAEpB,GADAA,EAAEC,kBACGf,EAAKgB,OAAQ,OAElB,IAAIC,EACJ,IACEA,EAAcC,mBAAmBlB,EAAKgB,OACxC,CAAA,MACEC,EAAcjB,EAAKgB,MACrB,EAEKvB,EAAc0B,KAAKF,IAAiBvB,EAAayB,KAAKF,MAEvDlB,GAAMC,KACRF,EAAOsB,eAAe,WAAY,CAAEpB,OAAMG,OAAME,WAEhDP,EAAOsB,eAAe,aAAc,CAAEpB,OAAMG,OAAME,WAEpDR,IAAA,EAUgCwB,UAAU,iBACtCV,SAAA,GAAAC,KAAC,MAAA,CAAIS,UAAU,iBACbV,SAAA,CAAAH,MAAC,QAAA,CAAMa,UAAU,iBAAiBC,QAAQ,eAAeX,SAAA,QACzDH,EAAAA,IAAC,QAAA,CACCe,GAAG,eACHC,KAAK,OACLH,UAAU,iBACVI,MAAOzB,EACP0B,SAAWZ,GAAMb,EAAQa,EAAET,OAAOoB,OAClCE,YAAY,4CACZC,UAAQ,EACRC,WAAS,SAGbjB,KAAC,MAAA,CAAIS,UAAU,iBACbV,SAAA,CAAAH,MAAC,QAAA,CAAMa,UAAU,iBAAiBC,QAAQ,gBAAgBX,SAAA,iBAC1DH,EAAAA,IAAC,QAAA,CACCe,GAAG,gBACHC,KAAK,OACLH,UAAU,iBACVI,MAAOtB,EACPuB,SAAWZ,GAAMV,EAAQU,EAAET,OAAOoB,OAClCE,YAAY,kCAGf,MAAA,CAAIN,UAAU,iBACbV,SAAAC,EAAAA,KAAC,QAAA,CAAMS,UAAU,oBACfV,SAAA,CAAAH,EAAAA,IAAC,QAAA,CACCgB,KAAK,WACLM,QAAoB,WAAXzB,EACTqB,SAAWZ,GAAMR,EAAUQ,EAAET,OAAOyB,QAAU,SAAW,WACzD,yBAINlB,KAAC,MAAA,CAAIS,UAAU,oBACZV,SAAA,CAAAZ,GAAMC,YACJ,SAAA,CAAOwB,KAAK,SAASH,UAAU,yBAAyBU,QA5C9C,KACnBjC,EAAOsB,eAAe,cACtBvB,GAAA,EA0CwFc,SAAA,kBAIlFC,KAAC,MAAA,CAAIS,UAAU,0BACbV,SAAA,CAAAH,EAAAA,IAAC,UAAOgB,KAAK,SAASH,UAAU,UAAUU,QAASlC,EAASc,SAAA,WAC5DH,EAAAA,IAAC,UAAOgB,KAAK,SAASH,UAAU,0BAC7BV,SAAAZ,GAAMC,KAAO,SAAW,qBAOvC,CAEAL,EAAUqC,UAAY,CACpBpC,KAAMqC,EAAAA,UAAUC,KAAKC,WACrBtC,QAASoC,EAAAA,UAAUG,KAAKD,WACxBrC,OAAQmC,EAAAA,UAAUI,OAClBtC,KAAMkC,EAAAA,UAAUI"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useState as r,useEffect as n}from"react";import{P as a}from"./index-C720tbJA.js";import{M as o}from"./ModalOverlay-CLvRNHmp.js";const l=/^\s*(https?|mailto|tel|ftp|ftps):\/?\/?/i,m=/^\s*(\/|#|\?|\.)/;function i({open:a,onClose:i,engine:c,data:s}){const[d,p]=r(""),[x,u]=r(""),[h,b]=r("_blank");n((()=>{a&&s&&(p(s.href||""),u(s.text||""),b(s.target||"_blank"))}),[a,s]);/* @__PURE__ */
|
|
2
|
+
return e(o,{title:s?.href?"Edit Link":"Insert Link",open:a,onClose:i,children:/* @__PURE__ */t("form",{onSubmit:e=>{if(e.preventDefault(),!d.trim())return;let t;try{t=decodeURIComponent(d.trim())}catch{t=d.trim()}(l.test(t)||m.test(t))&&(s?.href?c.executeCommand("editLink",{href:d,text:x,target:h}):c.executeCommand("insertLink",{href:d,text:x,target:h}),i())},className:"rmx-modal-form",children:[
|
|
3
|
+
/* @__PURE__ */t("div",{className:"rmx-form-group",children:[
|
|
4
|
+
/* @__PURE__ */e("label",{className:"rmx-form-label",htmlFor:"rmx-link-url",children:"URL"}),
|
|
5
|
+
/* @__PURE__ */e("input",{id:"rmx-link-url",type:"text",className:"rmx-form-input",value:d,onChange:e=>p(e.target.value),placeholder:"https://example.com or /about or #section",required:!0,autoFocus:!0})]}),
|
|
6
|
+
/* @__PURE__ */t("div",{className:"rmx-form-group",children:[
|
|
7
|
+
/* @__PURE__ */e("label",{className:"rmx-form-label",htmlFor:"rmx-link-text",children:"Display Text"}),
|
|
8
|
+
/* @__PURE__ */e("input",{id:"rmx-link-text",type:"text",className:"rmx-form-input",value:x,onChange:e=>u(e.target.value),placeholder:"Link text (optional)"})]}),
|
|
9
|
+
/* @__PURE__ */e("div",{className:"rmx-form-group",children:/* @__PURE__ */t("label",{className:"rmx-form-checkbox",children:[
|
|
10
|
+
/* @__PURE__ */e("input",{type:"checkbox",checked:"_blank"===h,onChange:e=>b(e.target.checked?"_blank":"_self")}),"Open in new tab"]})}),
|
|
11
|
+
/* @__PURE__ */t("div",{className:"rmx-modal-actions",children:[s?.href&&/* @__PURE__ */e("button",{type:"button",className:"rmx-btn rmx-btn-danger",onClick:()=>{c.executeCommand("removeLink"),i()},children:"Remove Link"}),
|
|
12
|
+
/* @__PURE__ */t("div",{className:"rmx-modal-actions-right",children:[
|
|
13
|
+
/* @__PURE__ */e("button",{type:"button",className:"rmx-btn",onClick:i,children:"Cancel"}),
|
|
14
|
+
/* @__PURE__ */e("button",{type:"submit",className:"rmx-btn rmx-btn-primary",children:s?.href?"Update":"Insert"})]})]})]})})}i.propTypes={open:a.bool.isRequired,onClose:a.func.isRequired,engine:a.object,data:a.object};export{i as LinkModal};
|
|
15
|
+
//# sourceMappingURL=LinkModal-k9IeDtAb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LinkModal-k9IeDtAb.js","sources":["../src/components/Modals/LinkModal.jsx"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\n\n// Allowlist-based protocol validation — safer than a blacklist because new\n// dangerous protocols (e.g. mhtml:, ms-*:) are blocked by default.\nconst SAFE_PROTOCOL = /^\\s*(https?|mailto|tel|ftp|ftps):\\/?\\/?/i\nconst RELATIVE_URL = /^\\s*(\\/|#|\\?|\\.)/\n\nexport function LinkModal({ open, onClose, engine, data }) {\n const [href, setHref] = useState('')\n const [text, setText] = useState('')\n const [target, setTarget] = useState('_blank')\n\n useEffect(() => {\n if (open && data) {\n setHref(data.href || '')\n setText(data.text || '')\n setTarget(data.target || '_blank')\n }\n }, [open, data])\n\n const handleSubmit = (e) => {\n e.preventDefault()\n if (!href.trim()) return\n // Decode percent-encoding before validation to prevent bypasses like java%73cript:\n let trimmedHref\n try {\n trimmedHref = decodeURIComponent(href.trim())\n } catch {\n trimmedHref = href.trim()\n }\n // Block any protocol not in the allowlist (blocks javascript:, vbscript:, data:, mhtml:, etc.)\n if (!SAFE_PROTOCOL.test(trimmedHref) && !RELATIVE_URL.test(trimmedHref)) return\n\n if (data?.href) {\n engine.executeCommand('editLink', { href, text, target })\n } else {\n engine.executeCommand('insertLink', { href, text, target })\n }\n onClose()\n }\n\n const handleRemove = () => {\n engine.executeCommand('removeLink')\n onClose()\n }\n\n return (\n <ModalOverlay title={data?.href ? 'Edit Link' : 'Insert Link'} open={open} onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"rmx-modal-form\">\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-link-url\">URL</label>\n <input\n id=\"rmx-link-url\"\n type=\"text\"\n className=\"rmx-form-input\"\n value={href}\n onChange={(e) => setHref(e.target.value)}\n placeholder=\"https://example.com or /about or #section\"\n required\n autoFocus\n />\n </div>\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-label\" htmlFor=\"rmx-link-text\">Display Text</label>\n <input\n id=\"rmx-link-text\"\n type=\"text\"\n className=\"rmx-form-input\"\n value={text}\n onChange={(e) => setText(e.target.value)}\n placeholder=\"Link text (optional)\"\n />\n </div>\n <div className=\"rmx-form-group\">\n <label className=\"rmx-form-checkbox\">\n <input\n type=\"checkbox\"\n checked={target === '_blank'}\n onChange={(e) => setTarget(e.target.checked ? '_blank' : '_self')}\n />\n Open in new tab\n </label>\n </div>\n <div className=\"rmx-modal-actions\">\n {data?.href && (\n <button type=\"button\" className=\"rmx-btn rmx-btn-danger\" onClick={handleRemove}>\n Remove Link\n </button>\n )}\n <div className=\"rmx-modal-actions-right\">\n <button type=\"button\" className=\"rmx-btn\" onClick={onClose}>Cancel</button>\n <button type=\"submit\" className=\"rmx-btn rmx-btn-primary\">\n {data?.href ? 'Update' : 'Insert'}\n </button>\n </div>\n </div>\n </form>\n </ModalOverlay>\n )\n}\n\nLinkModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n data: PropTypes.object,\n}\n"],"names":["SAFE_PROTOCOL","RELATIVE_URL","LinkModal","open","onClose","engine","data","href","setHref","useState","text","setText","target","setTarget","useEffect","ModalOverlay","title","children","onSubmit","e","preventDefault","trim","trimmedHref","decodeURIComponent","test","executeCommand","className","jsxs","jsx","htmlFor","id","type","value","onChange","placeholder","required","autoFocus","checked","onClick","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"yLAMA,MAAMA,EAAgB,2CAChBC,EAAe,mBAEd,SAASC,GAAUC,KAAEA,EAAAC,QAAMA,EAAAC,OAASA,EAAAC,KAAQA,IACjD,MAAOC,EAAMC,GAAWC,EAAS,KAC1BC,EAAMC,GAAWF,EAAS,KAC1BG,EAAQC,GAAaJ,EAAS,UAErCK,GAAU,KACJX,GAAQG,IACVE,EAAQF,EAAKC,MAAQ,IACrBI,EAAQL,EAAKI,MAAQ,IACrBG,EAAUP,EAAKM,QAAU,UAC3B,GACC,CAACT,EAAMG;AA4BV,SACGS,EAAA,CAAaC,MAAOV,GAAMC,KAAO,YAAc,cAAeJ,OAAYC,UACzEa,0BAAC,OAAA,CAAKC,SA5BYC,IAEpB,GADAA,EAAEC,kBACGb,EAAKc,OAAQ,OAElB,IAAIC,EACJ,IACEA,EAAcC,mBAAmBhB,EAAKc,OACxC,CAAA,MACEC,EAAcf,EAAKc,MACrB,EAEKrB,EAAcwB,KAAKF,IAAiBrB,EAAauB,KAAKF,MAEvDhB,GAAMC,KACRF,EAAOoB,eAAe,WAAY,CAAElB,OAAMG,OAAME,WAEhDP,EAAOoB,eAAe,aAAc,CAAElB,OAAMG,OAAME,WAEpDR,IAAA,EAUgCsB,UAAU,iBACtCT,SAAA;eAAAU,EAAC,MAAA,CAAID,UAAU,iBACbT,SAAA;eAAAW,EAAC,QAAA,CAAMF,UAAU,iBAAiBG,QAAQ,eAAeZ,SAAA;eACzDW,EAAC,QAAA,CACCE,GAAG,eACHC,KAAK,OACLL,UAAU,iBACVM,MAAOzB,EACP0B,SAAWd,GAAMX,EAAQW,EAAEP,OAAOoB,OAClCE,YAAY,4CACZC,UAAQ,EACRC,WAAS;eAGbT,EAAC,MAAA,CAAID,UAAU,iBACbT,SAAA;eAAAW,EAAC,QAAA,CAAMF,UAAU,iBAAiBG,QAAQ,gBAAgBZ,SAAA;eAC1DW,EAAC,QAAA,CACCE,GAAG,gBACHC,KAAK,OACLL,UAAU,iBACVM,MAAOtB,EACPuB,SAAWd,GAAMR,EAAQQ,EAAEP,OAAOoB,OAClCE,YAAY;iBAGf,MAAA,CAAIR,UAAU,iBACbT,wBAAAU,EAAC,QAAA,CAAMD,UAAU,oBACfT,SAAA;eAAAW,EAAC,QAAA,CACCG,KAAK,WACLM,QAAoB,WAAXzB,EACTqB,SAAWd,GAAMN,EAAUM,EAAEP,OAAOyB,QAAU,SAAW,WACzD;eAINV,EAAC,MAAA,CAAID,UAAU,oBACZT,SAAA,CAAAX,GAAMC,uBACJ,SAAA,CAAOwB,KAAK,SAASL,UAAU,yBAAyBY,QA5C9C,KACnBjC,EAAOoB,eAAe,cACtBrB,GAAA,EA0CwFa,SAAA;eAIlFU,EAAC,MAAA,CAAID,UAAU,0BACbT,SAAA;eAAAW,EAAC,UAAOG,KAAK,SAASL,UAAU,UAAUY,QAASlC,EAASa,SAAA;eAC5DW,EAAC,UAAOG,KAAK,SAASL,UAAU,0BAC7BT,SAAAX,GAAMC,KAAO,SAAW,qBAOvC,CAEAL,EAAUqC,UAAY,CACpBpC,KAAMqC,EAAUC,KAAKC,WACrBtC,QAASoC,EAAUG,KAAKD,WACxBrC,OAAQmC,EAAUI,OAClBtC,KAAMkC,EAAUI"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),n=require("react"),r=require("./index-Dc63uIP0.cjs"),t=require("@remyxjs/core");function a(e,n){if(!e)return;const r=Array.from(e.querySelectorAll(':scope > [role="menuitem"], :scope > .rmx-menubar-submenu-wrapper > [role="menuitem"]')),t=document.activeElement,a=r.indexOf(t);if(-1===a)return;let u;"down"===n?u=a<r.length-1?a+1:0:"up"===n?u=a>0?a-1:r.length-1:"home"===n?u=0:"end"===n&&(u=r.length-1),r[u]?.focus()}const u=n.memo((function({item:n,engine:u,onOpenModal:l,onClose:s}){const c=r.useSelectionContext();if("---"===n)return e.jsx("div",{className:"rmx-menubar-separator",role:"separator"});if("object"==typeof n&&n.label&&n.items)return e.jsx(o,{label:n.label,items:n.items,engine:u,onOpenModal:l,onClose:s});const m="string"==typeof n?n:n.command,i=t.TOOLTIP_MAP[m]||m,b=t.getShortcutLabel(m),p=t.getCommandActiveState(m,c,u),d=r.ICON_MAP[m],f=e=>{e.preventDefault();const n=t.MODAL_COMMANDS[m];n?"link"===m?c.link?l?.("link",c.link):l?.("link",{text:u.selection.getSelectedText()}):l?.(n):u.executeCommand(m),s()};return e.jsxs("button",{className:"rmx-menubar-item "+(p?"rmx-active":""),onClick:f,onKeyDown:e=>{const n=e.currentTarget.closest(".rmx-menubar-menu");switch(e.key){case"ArrowDown":e.preventDefault(),a(n,"down");break;case"ArrowUp":e.preventDefault(),a(n,"up");break;case"Home":e.preventDefault(),a(n,"home");break;case"End":e.preventDefault(),a(n,"end");break;case"Enter":case" ":e.preventDefault(),f(e)}},onMouseDown:e=>e.preventDefault(),type:"button",role:"menuitem",tabIndex:-1,children:[e.jsx("span",{className:"rmx-menubar-item-icon",children:d&&e.jsx(d,{size:16})}),e.jsx("span",{className:"rmx-menubar-item-label",children:i}),b&&e.jsx("span",{className:"rmx-menubar-shortcut",children:b})]})}));function o({label:t,items:o,engine:l,onOpenModal:s,onClose:c}){const[m,i]=n.useState(!1),b=n.useRef(null),p=n.useRef(null),d=n.useCallback((e=>{const n=e.currentTarget.closest(".rmx-menubar-menu");switch(e.key){case"ArrowRight":case"Enter":case" ":e.preventDefault(),i(!0),setTimeout((()=>{const e=b.current?.querySelector(".rmx-menubar-submenu"),n=e?.querySelector('[role="menuitem"]');n?.focus()}),0);break;case"ArrowDown":e.preventDefault(),a(n,"down");break;case"ArrowUp":e.preventDefault(),a(n,"up");break;case"ArrowLeft":e.preventDefault(),i(!1);break;case"Home":e.preventDefault(),a(n,"home");break;case"End":e.preventDefault(),a(n,"end")}}),[]);return n.useEffect((()=>()=>clearTimeout(p.current)),[]),e.jsxs("div",{className:"rmx-menubar-submenu-wrapper",ref:b,onMouseEnter:()=>{clearTimeout(p.current),i(!0)},onMouseLeave:()=>{p.current=setTimeout((()=>i(!1)),150)},children:[e.jsxs("button",{className:"rmx-menubar-item rmx-menubar-submenu-trigger",onMouseDown:e=>e.preventDefault(),onKeyDown:d,type:"button",role:"menuitem","aria-haspopup":"true","aria-expanded":m,tabIndex:-1,children:[e.jsx("span",{className:"rmx-menubar-item-icon"}),e.jsx("span",{className:"rmx-menubar-item-label",children:t}),e.jsx("span",{className:"rmx-menubar-submenu-arrow",children:e.jsx(r.ChevronRightIcon,{size:14})})]}),m&&e.jsx("div",{className:"rmx-menubar-menu rmx-menubar-submenu",role:"menu","aria-label":t,children:o.map(((n,r)=>e.jsx(u,{item:n,engine:l,onOpenModal:s,onClose:c},"string"==typeof n?`${n}-${r}`:n.label||r)))})]})}const l=n.memo((function({config:t,engine:a,onOpenModal:o}){r.useSelectionContext();const[l,s]=n.useState(null),[c,m]=n.useState(!1),i=n.useRef(null),b=n.useRef([]);n.useEffect((()=>{if(null===l)return;const e=e=>{i.current&&!i.current.contains(e.target)&&(s(null),m(!1))},n=e=>{"Escape"===e.key&&(s(null),m(!1),null!==l&&b.current[l]?.focus())};return document.addEventListener("mousedown",e),document.addEventListener("keydown",n),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("keydown",n)}}),[l]);const p=n.useCallback((e=>{l===e?(s(null),m(!1)):(s(e),m(!0))}),[l]),d=n.useCallback((e=>{c&&null!==l&&s(e)}),[c,l]),f=n.useCallback((()=>{s(null),m(!1)}),[]),x=n.useCallback((e=>{const n=b.current.filter(Boolean),r=n.indexOf(document.activeElement);switch(e.key){case"ArrowRight":{e.preventDefault();const t=r<n.length-1?r+1:0;n[t]?.focus(),null!==l&&s(t);break}case"ArrowLeft":{e.preventDefault();const t=r>0?r-1:n.length-1;n[t]?.focus(),null!==l&&s(t);break}case"ArrowDown":{e.preventDefault(),null===l&&r>=0&&(s(r),m(!0));const n=i.current?.querySelectorAll(".rmx-menubar-menu")?.[0];if(n){const e=n.querySelector('[role="menuitem"]');e?.focus()}break}case"Home":e.preventDefault(),n[0]?.focus(),null!==l&&s(0);break;case"End":e.preventDefault(),n[n.length-1]?.focus(),null!==l&&s(n.length-1)}}),[l]);return a&&t?e.jsx("div",{className:"rmx-menubar",ref:i,role:"menubar","aria-label":"Editor menu",onKeyDown:x,children:t.map(((n,r)=>e.jsxs("div",{className:"rmx-menubar-dropdown",children:[e.jsx("button",{ref:e=>{b.current[r]=e},className:"rmx-menubar-trigger "+(l===r?"rmx-open":""),onClick:e=>{e.preventDefault(),p(r)},onMouseEnter:()=>d(r),onMouseDown:e=>e.preventDefault(),type:"button",role:"menuitem","aria-haspopup":"true","aria-expanded":l===r,tabIndex:0===r?0:-1,children:n.label}),l===r&&e.jsx("div",{className:"rmx-menubar-menu",role:"menu","aria-label":n.label,children:n.items.map(((n,r)=>e.jsx(u,{item:n,engine:a,onOpenModal:o,onClose:f},"string"==typeof n?`${n}-${r}`:n.label||n.command||r)))})]},n.label)))}):null}));exports.MenuBar=l;
|
|
2
|
+
//# sourceMappingURL=MenuBar-B-ZAX9rH.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MenuBar-B-ZAX9rH.cjs","sources":["../src/components/MenuBar/MenuItem.jsx","../src/components/MenuBar/MenuBar.jsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport { ICON_MAP, ChevronRightIcon } from '../../icons/index.jsx'\nimport { TOOLTIP_MAP, SHORTCUT_MAP, MODAL_COMMANDS, getShortcutLabel, getCommandActiveState } from '@remyxjs/core'\nimport { useSelectionContext } from '../../config/SelectionContext.js'\n\n/**\n * Navigate focus between sibling menu items within a menu/submenu.\n * Skips separators and non-focusable elements.\n */\nfunction focusMenuSibling(container, direction) {\n if (!container) return\n const items = Array.from(container.querySelectorAll(':scope > [role=\"menuitem\"], :scope > .rmx-menubar-submenu-wrapper > [role=\"menuitem\"]'))\n const current = document.activeElement\n const idx = items.indexOf(current)\n if (idx === -1) return\n let next\n if (direction === 'down') {\n next = idx < items.length - 1 ? idx + 1 : 0\n } else if (direction === 'up') {\n next = idx > 0 ? idx - 1 : items.length - 1\n } else if (direction === 'home') {\n next = 0\n } else if (direction === 'end') {\n next = items.length - 1\n }\n items[next]?.focus()\n}\n\nfunction MenuItemInner({ item, engine, onOpenModal, onClose }) {\n const selectionState = useSelectionContext()\n\n // Separator\n if (item === '---') {\n return <div className=\"rmx-menubar-separator\" role=\"separator\" />\n }\n\n // Submenu\n if (typeof item === 'object' && item.label && item.items) {\n return (\n <SubMenuItem\n label={item.label}\n items={item.items}\n engine={engine}\n onOpenModal={onOpenModal}\n onClose={onClose}\n />\n )\n }\n\n // Regular command item\n const command = typeof item === 'string' ? item : item.command\n const label = TOOLTIP_MAP[command] || command\n const shortcut = getShortcutLabel(command)\n const isActive = getCommandActiveState(command, selectionState, engine)\n const IconComponent = ICON_MAP[command]\n\n const handleClick = (e) => {\n e.preventDefault()\n const modalName = MODAL_COMMANDS[command]\n if (modalName) {\n if (command === 'link') {\n if (selectionState.link) {\n onOpenModal?.('link', selectionState.link)\n } else {\n onOpenModal?.('link', { text: engine.selection.getSelectedText() })\n }\n } else {\n onOpenModal?.(modalName)\n }\n } else {\n engine.executeCommand(command)\n }\n onClose()\n }\n\n const handleKeyDown = (e) => {\n const menu = e.currentTarget.closest('.rmx-menubar-menu')\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n focusMenuSibling(menu, 'down')\n break\n case 'ArrowUp':\n e.preventDefault()\n focusMenuSibling(menu, 'up')\n break\n case 'Home':\n e.preventDefault()\n focusMenuSibling(menu, 'home')\n break\n case 'End':\n e.preventDefault()\n focusMenuSibling(menu, 'end')\n break\n case 'Enter':\n case ' ':\n e.preventDefault()\n handleClick(e)\n break\n }\n }\n\n return (\n <button\n className={`rmx-menubar-item ${isActive ? 'rmx-active' : ''}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onMouseDown={(e) => e.preventDefault()}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={-1}\n >\n <span className=\"rmx-menubar-item-icon\">\n {IconComponent && <IconComponent size={16} />}\n </span>\n <span className=\"rmx-menubar-item-label\">{label}</span>\n {shortcut && <span className=\"rmx-menubar-shortcut\">{shortcut}</span>}\n </button>\n )\n}\n\nexport const MenuItem = React.memo(MenuItemInner)\n\nfunction SubMenuItem({ label, items, engine, onOpenModal, onClose }) {\n const [open, setOpen] = useState(false)\n const ref = useRef(null)\n const timeoutRef = useRef(null)\n\n const handleEnter = () => {\n clearTimeout(timeoutRef.current)\n setOpen(true)\n }\n\n const handleLeave = () => {\n timeoutRef.current = setTimeout(() => setOpen(false), 150)\n }\n\n const handleKeyDown = useCallback((e) => {\n const menu = e.currentTarget.closest('.rmx-menubar-menu')\n switch (e.key) {\n case 'ArrowRight':\n case 'Enter':\n case ' ':\n e.preventDefault()\n setOpen(true)\n // Focus first item in submenu after it renders\n setTimeout(() => {\n const submenu = ref.current?.querySelector('.rmx-menubar-submenu')\n const firstItem = submenu?.querySelector('[role=\"menuitem\"]')\n firstItem?.focus()\n }, 0)\n break\n case 'ArrowDown':\n e.preventDefault()\n focusMenuSibling(menu, 'down')\n break\n case 'ArrowUp':\n e.preventDefault()\n focusMenuSibling(menu, 'up')\n break\n case 'ArrowLeft':\n e.preventDefault()\n setOpen(false)\n break\n case 'Home':\n e.preventDefault()\n focusMenuSibling(menu, 'home')\n break\n case 'End':\n e.preventDefault()\n focusMenuSibling(menu, 'end')\n break\n }\n }, [])\n\n useEffect(() => {\n return () => clearTimeout(timeoutRef.current)\n }, [])\n\n return (\n <div\n className=\"rmx-menubar-submenu-wrapper\"\n ref={ref}\n onMouseEnter={handleEnter}\n onMouseLeave={handleLeave}\n >\n <button\n className=\"rmx-menubar-item rmx-menubar-submenu-trigger\"\n onMouseDown={(e) => e.preventDefault()}\n onKeyDown={handleKeyDown}\n type=\"button\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n aria-expanded={open}\n tabIndex={-1}\n >\n <span className=\"rmx-menubar-item-icon\" />\n <span className=\"rmx-menubar-item-label\">{label}</span>\n <span className=\"rmx-menubar-submenu-arrow\"><ChevronRightIcon size={14} /></span>\n </button>\n {open && (\n <div className=\"rmx-menubar-menu rmx-menubar-submenu\" role=\"menu\" aria-label={label}>\n {items.map((subItem, i) => (\n <MenuItem\n key={typeof subItem === 'string' ? `${subItem}-${i}` : subItem.label || i}\n item={subItem}\n engine={engine}\n onOpenModal={onOpenModal}\n onClose={onClose}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n","import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport { MenuItem } from './MenuItem.jsx'\nimport { useSelectionContext } from '../../config/SelectionContext.js'\n\nfunction MenuBarInner({ config, engine, onOpenModal }) {\n const selectionState = useSelectionContext()\n const [openMenu, setOpenMenu] = useState(null)\n const [hoverMode, setHoverMode] = useState(false)\n const barRef = useRef(null)\n const triggerRefs = useRef([])\n\n // Close on click outside or Escape\n useEffect(() => {\n if (openMenu === null) return\n\n const handleClickOutside = (e) => {\n if (barRef.current && !barRef.current.contains(e.target)) {\n setOpenMenu(null)\n setHoverMode(false)\n }\n }\n\n const handleKeyDown = (e) => {\n if (e.key === 'Escape') {\n setOpenMenu(null)\n setHoverMode(false)\n // Return focus to the trigger that opened this menu\n if (openMenu !== null) {\n triggerRefs.current[openMenu]?.focus()\n }\n }\n }\n\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleKeyDown)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleKeyDown)\n }\n }, [openMenu])\n\n const handleTriggerClick = useCallback((index) => {\n if (openMenu === index) {\n setOpenMenu(null)\n setHoverMode(false)\n } else {\n setOpenMenu(index)\n setHoverMode(true)\n }\n }, [openMenu])\n\n const handleTriggerEnter = useCallback((index) => {\n if (hoverMode && openMenu !== null) {\n setOpenMenu(index)\n }\n }, [hoverMode, openMenu])\n\n const handleCloseMenu = useCallback(() => {\n setOpenMenu(null)\n setHoverMode(false)\n }, [])\n\n const handleBarKeyDown = useCallback((e) => {\n const triggers = triggerRefs.current.filter(Boolean)\n const currentIndex = triggers.indexOf(document.activeElement)\n\n switch (e.key) {\n case 'ArrowRight': {\n e.preventDefault()\n const next = currentIndex < triggers.length - 1 ? currentIndex + 1 : 0\n triggers[next]?.focus()\n if (openMenu !== null) setOpenMenu(next)\n break\n }\n case 'ArrowLeft': {\n e.preventDefault()\n const prev = currentIndex > 0 ? currentIndex - 1 : triggers.length - 1\n triggers[prev]?.focus()\n if (openMenu !== null) setOpenMenu(prev)\n break\n }\n case 'ArrowDown': {\n e.preventDefault()\n if (openMenu === null && currentIndex >= 0) {\n setOpenMenu(currentIndex)\n setHoverMode(true)\n }\n // Focus first item in the open dropdown\n const menuEl = barRef.current?.querySelectorAll('.rmx-menubar-menu')?.[0]\n if (menuEl) {\n const firstItem = menuEl.querySelector('[role=\"menuitem\"]')\n firstItem?.focus()\n }\n break\n }\n case 'Home': {\n e.preventDefault()\n triggers[0]?.focus()\n if (openMenu !== null) setOpenMenu(0)\n break\n }\n case 'End': {\n e.preventDefault()\n triggers[triggers.length - 1]?.focus()\n if (openMenu !== null) setOpenMenu(triggers.length - 1)\n break\n }\n }\n }, [openMenu])\n\n if (!engine || !config) return null\n\n return (\n <div\n className=\"rmx-menubar\"\n ref={barRef}\n role=\"menubar\"\n aria-label=\"Editor menu\"\n onKeyDown={handleBarKeyDown}\n >\n {config.map((menu, index) => (\n <div key={menu.label} className=\"rmx-menubar-dropdown\">\n <button\n ref={(el) => { triggerRefs.current[index] = el }}\n className={`rmx-menubar-trigger ${openMenu === index ? 'rmx-open' : ''}`}\n onClick={(e) => {\n e.preventDefault()\n handleTriggerClick(index)\n }}\n onMouseEnter={() => handleTriggerEnter(index)}\n onMouseDown={(e) => e.preventDefault()}\n type=\"button\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n aria-expanded={openMenu === index}\n tabIndex={index === 0 ? 0 : -1}\n >\n {menu.label}\n </button>\n {openMenu === index && (\n <div className=\"rmx-menubar-menu\" role=\"menu\" aria-label={menu.label}>\n {menu.items.map((item, i) => (\n <MenuItem\n key={typeof item === 'string' ? `${item}-${i}` : item.label || item.command || i}\n item={item}\n engine={engine}\n onOpenModal={onOpenModal}\n onClose={handleCloseMenu}\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n )\n}\n\nexport const MenuBar = React.memo(MenuBarInner)\n\n// Re-export for backward compatibility (actual implementation in separate module\n// to avoid forcing eager load of this component when only the utility is needed)\nexport { collectMenuBarCommands } from './collectMenuBarCommands.js'\n"],"names":["focusMenuSibling","container","direction","items","Array","from","querySelectorAll","current","document","activeElement","idx","indexOf","next","length","focus","MenuItem","React","memo","item","engine","onOpenModal","onClose","selectionState","useSelectionContext","jsx","className","role","label","SubMenuItem","command","TOOLTIP_MAP","shortcut","getShortcutLabel","isActive","getCommandActiveState","IconComponent","ICON_MAP","handleClick","e","preventDefault","modalName","MODAL_COMMANDS","link","text","selection","getSelectedText","executeCommand","jsxs","onClick","onKeyDown","menu","currentTarget","closest","key","onMouseDown","type","tabIndex","children","size","open","setOpen","useState","ref","useRef","timeoutRef","handleKeyDown","useCallback","setTimeout","submenu","querySelector","firstItem","useEffect","clearTimeout","onMouseEnter","onMouseLeave","ChevronRightIcon","map","subItem","i","MenuBar","config","openMenu","setOpenMenu","hoverMode","setHoverMode","barRef","triggerRefs","handleClickOutside","contains","target","addEventListener","removeEventListener","handleTriggerClick","index","handleTriggerEnter","handleCloseMenu","handleBarKeyDown","triggers","filter","Boolean","currentIndex","prev","menuEl","el"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import{jsx as e,jsxs as n}from"react/jsx-runtime";import r,{useState as t,useRef as a,useCallback as o,useEffect as l}from"react";import{u,I as m,C as s}from"./index-C720tbJA.js";import{TOOLTIP_MAP as c,getShortcutLabel as i,getCommandActiveState as p,MODAL_COMMANDS as b}from"@remyxjs/core";function d(e,n){if(!e)return;const r=Array.from(e.querySelectorAll(':scope > [role="menuitem"], :scope > .rmx-menubar-submenu-wrapper > [role="menuitem"]')),t=document.activeElement,a=r.indexOf(t);if(-1===a)return;let o;"down"===n?o=a<r.length-1?a+1:0:"up"===n?o=a>0?a-1:r.length-1:"home"===n?o=0:"end"===n&&(o=r.length-1),r[o]?.focus()}const f=r.memo((function({item:r,engine:t,onOpenModal:a,onClose:o}){const l=u();if("---"===r)/* @__PURE__ */
|
|
2
|
+
return e("div",{className:"rmx-menubar-separator",role:"separator"});if("object"==typeof r&&r.label&&r.items)/* @__PURE__ */
|
|
3
|
+
return e(x,{label:r.label,items:r.items,engine:t,onOpenModal:a,onClose:o});const s="string"==typeof r?r:r.command,f=c[s]||s,v=i(s),w=p(s,l,t),h=m[s],D=e=>{e.preventDefault();const n=b[s];n?"link"===s?l.link?a?.("link",l.link):a?.("link",{text:t.selection.getSelectedText()}):a?.(n):t.executeCommand(s),o()};/* @__PURE__ */
|
|
4
|
+
return n("button",{className:"rmx-menubar-item "+(w?"rmx-active":""),onClick:D,onKeyDown:e=>{const n=e.currentTarget.closest(".rmx-menubar-menu");switch(e.key){case"ArrowDown":e.preventDefault(),d(n,"down");break;case"ArrowUp":e.preventDefault(),d(n,"up");break;case"Home":e.preventDefault(),d(n,"home");break;case"End":e.preventDefault(),d(n,"end");break;case"Enter":case" ":e.preventDefault(),D(e)}},onMouseDown:e=>e.preventDefault(),type:"button",role:"menuitem",tabIndex:-1,children:[
|
|
5
|
+
/* @__PURE__ */e("span",{className:"rmx-menubar-item-icon",children:h&&/* @__PURE__ */e(h,{size:16})}),
|
|
6
|
+
/* @__PURE__ */e("span",{className:"rmx-menubar-item-label",children:f}),v&&/* @__PURE__ */e("span",{className:"rmx-menubar-shortcut",children:v})]})}));function x({label:r,items:u,engine:m,onOpenModal:c,onClose:i}){const[p,b]=t(!1),x=a(null),v=a(null),w=o((e=>{const n=e.currentTarget.closest(".rmx-menubar-menu");switch(e.key){case"ArrowRight":case"Enter":case" ":e.preventDefault(),b(!0),setTimeout((()=>{const e=x.current?.querySelector(".rmx-menubar-submenu"),n=e?.querySelector('[role="menuitem"]');n?.focus()}),0);break;case"ArrowDown":e.preventDefault(),d(n,"down");break;case"ArrowUp":e.preventDefault(),d(n,"up");break;case"ArrowLeft":e.preventDefault(),b(!1);break;case"Home":e.preventDefault(),d(n,"home");break;case"End":e.preventDefault(),d(n,"end")}}),[]);return l((()=>()=>clearTimeout(v.current)),[]),/* @__PURE__ */n("div",{className:"rmx-menubar-submenu-wrapper",ref:x,onMouseEnter:()=>{clearTimeout(v.current),b(!0)},onMouseLeave:()=>{v.current=setTimeout((()=>b(!1)),150)},children:[
|
|
7
|
+
/* @__PURE__ */n("button",{className:"rmx-menubar-item rmx-menubar-submenu-trigger",onMouseDown:e=>e.preventDefault(),onKeyDown:w,type:"button",role:"menuitem","aria-haspopup":"true","aria-expanded":p,tabIndex:-1,children:[
|
|
8
|
+
/* @__PURE__ */e("span",{className:"rmx-menubar-item-icon"}),
|
|
9
|
+
/* @__PURE__ */e("span",{className:"rmx-menubar-item-label",children:r}),
|
|
10
|
+
/* @__PURE__ */e("span",{className:"rmx-menubar-submenu-arrow",children:/* @__PURE__ */e(s,{size:14})})]}),p&&/* @__PURE__ */e("div",{className:"rmx-menubar-menu rmx-menubar-submenu",role:"menu","aria-label":r,children:u.map(((n,r)=>/* @__PURE__ */e(f,{item:n,engine:m,onOpenModal:c,onClose:i},"string"==typeof n?`${n}-${r}`:n.label||r)))})]})}const v=r.memo((function({config:r,engine:m,onOpenModal:s}){u();const[c,i]=t(null),[p,b]=t(!1),d=a(null),x=a([]);l((()=>{if(null===c)return;const e=e=>{d.current&&!d.current.contains(e.target)&&(i(null),b(!1))},n=e=>{"Escape"===e.key&&(i(null),b(!1),null!==c&&x.current[c]?.focus())};return document.addEventListener("mousedown",e),document.addEventListener("keydown",n),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("keydown",n)}}),[c]);const v=o((e=>{c===e?(i(null),b(!1)):(i(e),b(!0))}),[c]),w=o((e=>{p&&null!==c&&i(e)}),[p,c]),h=o((()=>{i(null),b(!1)}),[]),D=o((e=>{const n=x.current.filter(Boolean),r=n.indexOf(document.activeElement);switch(e.key){case"ArrowRight":{e.preventDefault();const t=r<n.length-1?r+1:0;n[t]?.focus(),null!==c&&i(t);break}case"ArrowLeft":{e.preventDefault();const t=r>0?r-1:n.length-1;n[t]?.focus(),null!==c&&i(t);break}case"ArrowDown":{e.preventDefault(),null===c&&r>=0&&(i(r),b(!0));const n=d.current?.querySelectorAll(".rmx-menubar-menu")?.[0];if(n){const e=n.querySelector('[role="menuitem"]');e?.focus()}break}case"Home":e.preventDefault(),n[0]?.focus(),null!==c&&i(0);break;case"End":e.preventDefault(),n[n.length-1]?.focus(),null!==c&&i(n.length-1)}}),[c]);return m&&r?/* @__PURE__ */e("div",{className:"rmx-menubar",ref:d,role:"menubar","aria-label":"Editor menu",onKeyDown:D,children:r.map(((r,t)=>/* @__PURE__ */n("div",{className:"rmx-menubar-dropdown",children:[
|
|
11
|
+
/* @__PURE__ */e("button",{ref:e=>{x.current[t]=e},className:"rmx-menubar-trigger "+(c===t?"rmx-open":""),onClick:e=>{e.preventDefault(),v(t)},onMouseEnter:()=>w(t),onMouseDown:e=>e.preventDefault(),type:"button",role:"menuitem","aria-haspopup":"true","aria-expanded":c===t,tabIndex:0===t?0:-1,children:r.label}),c===t&&/* @__PURE__ */e("div",{className:"rmx-menubar-menu",role:"menu","aria-label":r.label,children:r.items.map(((n,r)=>/* @__PURE__ */e(f,{item:n,engine:m,onOpenModal:s,onClose:h},"string"==typeof n?`${n}-${r}`:n.label||n.command||r)))})]},r.label)))}):null}));export{v as MenuBar};
|
|
12
|
+
//# sourceMappingURL=MenuBar-DWxJNHmb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MenuBar-DWxJNHmb.js","sources":["../src/components/MenuBar/MenuItem.jsx","../src/components/MenuBar/MenuBar.jsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport { ICON_MAP, ChevronRightIcon } from '../../icons/index.jsx'\nimport { TOOLTIP_MAP, SHORTCUT_MAP, MODAL_COMMANDS, getShortcutLabel, getCommandActiveState } from '@remyxjs/core'\nimport { useSelectionContext } from '../../config/SelectionContext.js'\n\n/**\n * Navigate focus between sibling menu items within a menu/submenu.\n * Skips separators and non-focusable elements.\n */\nfunction focusMenuSibling(container, direction) {\n if (!container) return\n const items = Array.from(container.querySelectorAll(':scope > [role=\"menuitem\"], :scope > .rmx-menubar-submenu-wrapper > [role=\"menuitem\"]'))\n const current = document.activeElement\n const idx = items.indexOf(current)\n if (idx === -1) return\n let next\n if (direction === 'down') {\n next = idx < items.length - 1 ? idx + 1 : 0\n } else if (direction === 'up') {\n next = idx > 0 ? idx - 1 : items.length - 1\n } else if (direction === 'home') {\n next = 0\n } else if (direction === 'end') {\n next = items.length - 1\n }\n items[next]?.focus()\n}\n\nfunction MenuItemInner({ item, engine, onOpenModal, onClose }) {\n const selectionState = useSelectionContext()\n\n // Separator\n if (item === '---') {\n return <div className=\"rmx-menubar-separator\" role=\"separator\" />\n }\n\n // Submenu\n if (typeof item === 'object' && item.label && item.items) {\n return (\n <SubMenuItem\n label={item.label}\n items={item.items}\n engine={engine}\n onOpenModal={onOpenModal}\n onClose={onClose}\n />\n )\n }\n\n // Regular command item\n const command = typeof item === 'string' ? item : item.command\n const label = TOOLTIP_MAP[command] || command\n const shortcut = getShortcutLabel(command)\n const isActive = getCommandActiveState(command, selectionState, engine)\n const IconComponent = ICON_MAP[command]\n\n const handleClick = (e) => {\n e.preventDefault()\n const modalName = MODAL_COMMANDS[command]\n if (modalName) {\n if (command === 'link') {\n if (selectionState.link) {\n onOpenModal?.('link', selectionState.link)\n } else {\n onOpenModal?.('link', { text: engine.selection.getSelectedText() })\n }\n } else {\n onOpenModal?.(modalName)\n }\n } else {\n engine.executeCommand(command)\n }\n onClose()\n }\n\n const handleKeyDown = (e) => {\n const menu = e.currentTarget.closest('.rmx-menubar-menu')\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n focusMenuSibling(menu, 'down')\n break\n case 'ArrowUp':\n e.preventDefault()\n focusMenuSibling(menu, 'up')\n break\n case 'Home':\n e.preventDefault()\n focusMenuSibling(menu, 'home')\n break\n case 'End':\n e.preventDefault()\n focusMenuSibling(menu, 'end')\n break\n case 'Enter':\n case ' ':\n e.preventDefault()\n handleClick(e)\n break\n }\n }\n\n return (\n <button\n className={`rmx-menubar-item ${isActive ? 'rmx-active' : ''}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n onMouseDown={(e) => e.preventDefault()}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={-1}\n >\n <span className=\"rmx-menubar-item-icon\">\n {IconComponent && <IconComponent size={16} />}\n </span>\n <span className=\"rmx-menubar-item-label\">{label}</span>\n {shortcut && <span className=\"rmx-menubar-shortcut\">{shortcut}</span>}\n </button>\n )\n}\n\nexport const MenuItem = React.memo(MenuItemInner)\n\nfunction SubMenuItem({ label, items, engine, onOpenModal, onClose }) {\n const [open, setOpen] = useState(false)\n const ref = useRef(null)\n const timeoutRef = useRef(null)\n\n const handleEnter = () => {\n clearTimeout(timeoutRef.current)\n setOpen(true)\n }\n\n const handleLeave = () => {\n timeoutRef.current = setTimeout(() => setOpen(false), 150)\n }\n\n const handleKeyDown = useCallback((e) => {\n const menu = e.currentTarget.closest('.rmx-menubar-menu')\n switch (e.key) {\n case 'ArrowRight':\n case 'Enter':\n case ' ':\n e.preventDefault()\n setOpen(true)\n // Focus first item in submenu after it renders\n setTimeout(() => {\n const submenu = ref.current?.querySelector('.rmx-menubar-submenu')\n const firstItem = submenu?.querySelector('[role=\"menuitem\"]')\n firstItem?.focus()\n }, 0)\n break\n case 'ArrowDown':\n e.preventDefault()\n focusMenuSibling(menu, 'down')\n break\n case 'ArrowUp':\n e.preventDefault()\n focusMenuSibling(menu, 'up')\n break\n case 'ArrowLeft':\n e.preventDefault()\n setOpen(false)\n break\n case 'Home':\n e.preventDefault()\n focusMenuSibling(menu, 'home')\n break\n case 'End':\n e.preventDefault()\n focusMenuSibling(menu, 'end')\n break\n }\n }, [])\n\n useEffect(() => {\n return () => clearTimeout(timeoutRef.current)\n }, [])\n\n return (\n <div\n className=\"rmx-menubar-submenu-wrapper\"\n ref={ref}\n onMouseEnter={handleEnter}\n onMouseLeave={handleLeave}\n >\n <button\n className=\"rmx-menubar-item rmx-menubar-submenu-trigger\"\n onMouseDown={(e) => e.preventDefault()}\n onKeyDown={handleKeyDown}\n type=\"button\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n aria-expanded={open}\n tabIndex={-1}\n >\n <span className=\"rmx-menubar-item-icon\" />\n <span className=\"rmx-menubar-item-label\">{label}</span>\n <span className=\"rmx-menubar-submenu-arrow\"><ChevronRightIcon size={14} /></span>\n </button>\n {open && (\n <div className=\"rmx-menubar-menu rmx-menubar-submenu\" role=\"menu\" aria-label={label}>\n {items.map((subItem, i) => (\n <MenuItem\n key={typeof subItem === 'string' ? `${subItem}-${i}` : subItem.label || i}\n item={subItem}\n engine={engine}\n onOpenModal={onOpenModal}\n onClose={onClose}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n","import React, { useState, useRef, useEffect, useCallback } from 'react'\nimport { MenuItem } from './MenuItem.jsx'\nimport { useSelectionContext } from '../../config/SelectionContext.js'\n\nfunction MenuBarInner({ config, engine, onOpenModal }) {\n const selectionState = useSelectionContext()\n const [openMenu, setOpenMenu] = useState(null)\n const [hoverMode, setHoverMode] = useState(false)\n const barRef = useRef(null)\n const triggerRefs = useRef([])\n\n // Close on click outside or Escape\n useEffect(() => {\n if (openMenu === null) return\n\n const handleClickOutside = (e) => {\n if (barRef.current && !barRef.current.contains(e.target)) {\n setOpenMenu(null)\n setHoverMode(false)\n }\n }\n\n const handleKeyDown = (e) => {\n if (e.key === 'Escape') {\n setOpenMenu(null)\n setHoverMode(false)\n // Return focus to the trigger that opened this menu\n if (openMenu !== null) {\n triggerRefs.current[openMenu]?.focus()\n }\n }\n }\n\n document.addEventListener('mousedown', handleClickOutside)\n document.addEventListener('keydown', handleKeyDown)\n return () => {\n document.removeEventListener('mousedown', handleClickOutside)\n document.removeEventListener('keydown', handleKeyDown)\n }\n }, [openMenu])\n\n const handleTriggerClick = useCallback((index) => {\n if (openMenu === index) {\n setOpenMenu(null)\n setHoverMode(false)\n } else {\n setOpenMenu(index)\n setHoverMode(true)\n }\n }, [openMenu])\n\n const handleTriggerEnter = useCallback((index) => {\n if (hoverMode && openMenu !== null) {\n setOpenMenu(index)\n }\n }, [hoverMode, openMenu])\n\n const handleCloseMenu = useCallback(() => {\n setOpenMenu(null)\n setHoverMode(false)\n }, [])\n\n const handleBarKeyDown = useCallback((e) => {\n const triggers = triggerRefs.current.filter(Boolean)\n const currentIndex = triggers.indexOf(document.activeElement)\n\n switch (e.key) {\n case 'ArrowRight': {\n e.preventDefault()\n const next = currentIndex < triggers.length - 1 ? currentIndex + 1 : 0\n triggers[next]?.focus()\n if (openMenu !== null) setOpenMenu(next)\n break\n }\n case 'ArrowLeft': {\n e.preventDefault()\n const prev = currentIndex > 0 ? currentIndex - 1 : triggers.length - 1\n triggers[prev]?.focus()\n if (openMenu !== null) setOpenMenu(prev)\n break\n }\n case 'ArrowDown': {\n e.preventDefault()\n if (openMenu === null && currentIndex >= 0) {\n setOpenMenu(currentIndex)\n setHoverMode(true)\n }\n // Focus first item in the open dropdown\n const menuEl = barRef.current?.querySelectorAll('.rmx-menubar-menu')?.[0]\n if (menuEl) {\n const firstItem = menuEl.querySelector('[role=\"menuitem\"]')\n firstItem?.focus()\n }\n break\n }\n case 'Home': {\n e.preventDefault()\n triggers[0]?.focus()\n if (openMenu !== null) setOpenMenu(0)\n break\n }\n case 'End': {\n e.preventDefault()\n triggers[triggers.length - 1]?.focus()\n if (openMenu !== null) setOpenMenu(triggers.length - 1)\n break\n }\n }\n }, [openMenu])\n\n if (!engine || !config) return null\n\n return (\n <div\n className=\"rmx-menubar\"\n ref={barRef}\n role=\"menubar\"\n aria-label=\"Editor menu\"\n onKeyDown={handleBarKeyDown}\n >\n {config.map((menu, index) => (\n <div key={menu.label} className=\"rmx-menubar-dropdown\">\n <button\n ref={(el) => { triggerRefs.current[index] = el }}\n className={`rmx-menubar-trigger ${openMenu === index ? 'rmx-open' : ''}`}\n onClick={(e) => {\n e.preventDefault()\n handleTriggerClick(index)\n }}\n onMouseEnter={() => handleTriggerEnter(index)}\n onMouseDown={(e) => e.preventDefault()}\n type=\"button\"\n role=\"menuitem\"\n aria-haspopup=\"true\"\n aria-expanded={openMenu === index}\n tabIndex={index === 0 ? 0 : -1}\n >\n {menu.label}\n </button>\n {openMenu === index && (\n <div className=\"rmx-menubar-menu\" role=\"menu\" aria-label={menu.label}>\n {menu.items.map((item, i) => (\n <MenuItem\n key={typeof item === 'string' ? `${item}-${i}` : item.label || item.command || i}\n item={item}\n engine={engine}\n onOpenModal={onOpenModal}\n onClose={handleCloseMenu}\n />\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n )\n}\n\nexport const MenuBar = React.memo(MenuBarInner)\n\n// Re-export for backward compatibility (actual implementation in separate module\n// to avoid forcing eager load of this component when only the utility is needed)\nexport { collectMenuBarCommands } from './collectMenuBarCommands.js'\n"],"names":["focusMenuSibling","container","direction","items","Array","from","querySelectorAll","current","document","activeElement","idx","indexOf","next","length","focus","MenuItem","React","memo","item","engine","onOpenModal","onClose","selectionState","useSelectionContext","className","role","label","jsx","SubMenuItem","command","TOOLTIP_MAP","shortcut","getShortcutLabel","isActive","getCommandActiveState","IconComponent","ICON_MAP","handleClick","e","preventDefault","modalName","MODAL_COMMANDS","link","text","selection","getSelectedText","executeCommand","jsxs","onClick","onKeyDown","menu","currentTarget","closest","key","onMouseDown","type","tabIndex","children","size","open","setOpen","useState","ref","useRef","timeoutRef","handleKeyDown","useCallback","setTimeout","submenu","querySelector","firstItem","useEffect","clearTimeout","onMouseEnter","onMouseLeave","ChevronRightIcon","map","subItem","i","MenuBar","config","openMenu","setOpenMenu","hoverMode","setHoverMode","barRef","triggerRefs","handleClickOutside","contains","target","addEventListener","removeEventListener","handleTriggerClick","index","handleTriggerEnter","handleCloseMenu","handleBarKeyDown","triggers","filter","Boolean","currentIndex","prev","menuEl","el"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const e=require("react/jsx-runtime"),r=require("react"),t=require("./index-Dc63uIP0.cjs"),n='a[href], button:not([disabled]), textarea, input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';const l=r.memo((function({title:l,open:o,onClose:s,children:c,width:u=420}){const a=r.useRef(null),i=r.useRef(null),d=r.useRef([]);r.useEffect((()=>{o&&(i.current=document.activeElement)}),[o]),r.useEffect((()=>{if(!o&&i.current){const e=i.current;i.current=null,requestAnimationFrame((()=>{e&&"function"==typeof e.focus&&e.focus()}))}}),[o]),r.useEffect((()=>{if(o&&a.current){const e=a.current.querySelector("input, textarea, select")||a.current.querySelector(n);e&&e.focus()}}),[o]),r.useEffect((()=>{o&&a.current&&(d.current=Array.from(a.current.querySelectorAll(n)))}),[o]);const m=r.useCallback((e=>{if("Escape"!==e.key){if("Tab"===e.key&&a.current){const r=d.current.length>0?d.current:Array.from(a.current.querySelectorAll(n));if(0===r.length)return void e.preventDefault();const t=r[0],l=r[r.length-1];e.shiftKey?document.activeElement===t&&(e.preventDefault(),l.focus()):document.activeElement===l&&(e.preventDefault(),t.focus())}}else s()}),[s]);return o?e.jsx("div",{className:"rmx-modal-overlay",onClick:s,onKeyDown:m,children:e.jsxs("div",{ref:a,className:"rmx-modal",style:{maxWidth:u},onClick:e=>e.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":l,children:[e.jsxs("div",{className:"rmx-modal-header",children:[e.jsx("h3",{className:"rmx-modal-title",children:l}),e.jsx("button",{className:"rmx-modal-close",onClick:s,type:"button","aria-label":"Close",children:e.jsx(t.CloseIcon,{size:18})})]}),e.jsx("div",{className:"rmx-modal-body",children:c})]})}):null}));l.propTypes={title:t.PropTypes.string.isRequired,open:t.PropTypes.bool.isRequired,onClose:t.PropTypes.func.isRequired,children:t.PropTypes.node,width:t.PropTypes.number},exports.ModalOverlay=l;
|
|
2
|
+
//# sourceMappingURL=ModalOverlay-BDsGgv3_.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalOverlay-BDsGgv3_.cjs","sources":["../src/components/Modals/ModalOverlay.jsx"],"sourcesContent":["import React, { useEffect, useRef, useCallback } from 'react'\nimport PropTypes from 'prop-types'\nimport { CloseIcon } from '../../icons/index.jsx'\n\nconst FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea, input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n\nfunction ModalOverlayInner({ title, open, onClose, children, width = 420 }) {\n const ref = useRef(null)\n const previousFocusRef = useRef(null)\n const focusableRef = useRef([])\n\n // Save previously focused element when opening\n useEffect(() => {\n if (open) {\n previousFocusRef.current = document.activeElement\n }\n }, [open])\n\n // Restore focus to previously focused element on close\n useEffect(() => {\n if (!open && previousFocusRef.current) {\n const el = previousFocusRef.current\n previousFocusRef.current = null\n // Delay to allow DOM to settle after modal unmount\n requestAnimationFrame(() => {\n if (el && typeof el.focus === 'function') el.focus()\n })\n }\n }, [open])\n\n // Focus the first focusable element on open\n useEffect(() => {\n if (open && ref.current) {\n // Prefer input/textarea/select, fall back to any focusable element\n const firstInput = ref.current.querySelector('input, textarea, select')\n const firstFocusable = firstInput || ref.current.querySelector(FOCUSABLE_SELECTOR)\n if (firstFocusable) firstFocusable.focus()\n }\n }, [open])\n\n // Cache focusable elements when modal opens or content changes\n useEffect(() => {\n if (open && ref.current) {\n focusableRef.current = Array.from(ref.current.querySelectorAll(FOCUSABLE_SELECTOR))\n }\n }, [open])\n\n // Handle Escape and focus trapping (Tab / Shift+Tab)\n const handleKeyDown = useCallback((e) => {\n if (e.key === 'Escape') {\n onClose()\n return\n }\n\n if (e.key === 'Tab' && ref.current) {\n const focusable = focusableRef.current.length > 0 ? focusableRef.current : Array.from(ref.current.querySelectorAll(FOCUSABLE_SELECTOR))\n if (focusable.length === 0) {\n e.preventDefault()\n return\n }\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (e.shiftKey) {\n // Shift+Tab: if focus is on first element, wrap to last\n if (document.activeElement === first) {\n e.preventDefault()\n last.focus()\n }\n } else {\n // Tab: if focus is on last element, wrap to first\n if (document.activeElement === last) {\n e.preventDefault()\n first.focus()\n }\n }\n }\n }, [onClose])\n\n if (!open) return null\n\n return (\n <div className=\"rmx-modal-overlay\" onClick={onClose} onKeyDown={handleKeyDown}>\n <div\n ref={ref}\n className=\"rmx-modal\"\n style={{ maxWidth: width }}\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n >\n <div className=\"rmx-modal-header\">\n <h3 className=\"rmx-modal-title\">{title}</h3>\n <button className=\"rmx-modal-close\" onClick={onClose} type=\"button\" aria-label=\"Close\">\n <CloseIcon size={18} />\n </button>\n </div>\n <div className=\"rmx-modal-body\">\n {children}\n </div>\n </div>\n </div>\n )\n}\n\nexport const ModalOverlay = React.memo(ModalOverlayInner)\n\nModalOverlay.propTypes = {\n title: PropTypes.string.isRequired,\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n children: PropTypes.node,\n width: PropTypes.number,\n}\n"],"names":["FOCUSABLE_SELECTOR","ModalOverlay","React","memo","title","open","onClose","children","width","ref","useRef","previousFocusRef","focusableRef","useEffect","current","document","activeElement","el","requestAnimationFrame","focus","firstFocusable","querySelector","Array","from","querySelectorAll","handleKeyDown","useCallback","e","key","focusable","length","preventDefault","first","last","shiftKey","className","onClick","onKeyDown","jsxs","style","maxWidth","stopPropagation","role","jsx","type","CloseIcon","size","propTypes","PropTypes","string","isRequired","bool","func","node","number"],"mappings":"uGAIMA,EAAqB,4HAuGpB,MAAMC,EAAeC,EAAMC,MArGlC,UAA2BC,MAAEA,EAAAC,KAAOA,EAAAC,QAAMA,WAASC,EAAAC,MAAUA,EAAQ,MACnE,MAAMC,EAAMC,EAAAA,OAAO,MACbC,EAAmBD,EAAAA,OAAO,MAC1BE,EAAeF,EAAAA,OAAO,IAG5BG,EAAAA,WAAU,KACJR,IACFM,EAAiBG,QAAUC,SAASC,cACtC,GACC,CAACX,IAGJQ,EAAAA,WAAU,KACR,IAAKR,GAAQM,EAAiBG,QAAS,CACrC,MAAMG,EAAKN,EAAiBG,QAC5BH,EAAiBG,QAAU,KAE3BI,uBAAsB,KAChBD,GAA0B,mBAAbA,EAAGE,SAAyBA,OAAA,GAEjD,IACC,CAACd,IAGJQ,EAAAA,WAAU,KACR,GAAIR,GAAQI,EAAIK,QAAS,CAEvB,MACMM,EADaX,EAAIK,QAAQO,cAAc,4BACRZ,EAAIK,QAAQO,cAAcrB,GAC3DoB,KAA+BD,OACrC,IACC,CAACd,IAGJQ,EAAAA,WAAU,KACJR,GAAQI,EAAIK,UACdF,EAAaE,QAAUQ,MAAMC,KAAKd,EAAIK,QAAQU,iBAAiBxB,IACjE,GACC,CAACK,IAGJ,MAAMoB,EAAgBC,eAAaC,IACjC,GAAc,WAAVA,EAAEC,KAKN,GAAc,QAAVD,EAAEC,KAAiBnB,EAAIK,QAAS,CAClC,MAAMe,EAAYjB,EAAaE,QAAQgB,OAAS,EAAIlB,EAAaE,QAAUQ,MAAMC,KAAKd,EAAIK,QAAQU,iBAAiBxB,IACnH,GAAyB,IAArB6B,EAAUC,OAEZ,YADAH,EAAEI,iBAIJ,MAAMC,EAAQH,EAAU,GAClBI,EAAOJ,EAAUA,EAAUC,OAAS,GAEtCH,EAAEO,SAEAnB,SAASC,gBAAkBgB,IAC7BL,EAAEI,iBACFE,EAAKd,SAIHJ,SAASC,gBAAkBiB,IAC7BN,EAAEI,iBACFC,EAAMb,QAGZ,OA3BEb,GA2BF,GACC,CAACA,IAEJ,OAAKD,QAGF,MAAA,CAAI8B,UAAU,oBAAoBC,QAAS9B,EAAS+B,UAAWZ,EAC9DlB,SAAA+B,EAAAA,KAAC,MAAA,CACC7B,MACA0B,UAAU,YACVI,MAAO,CAAEC,SAAUhC,GACnB4B,QAAUT,GAAMA,EAAEc,kBAClBC,KAAK,SACL,aAAW,OACX,aAAYtC,EAEZG,SAAA,GAAA+B,KAAC,MAAA,CAAIH,UAAU,mBACb5B,SAAA,CAAAoC,EAAAA,IAAC,KAAA,CAAGR,UAAU,kBAAmB5B,SAAAH,IACjCuC,EAAAA,IAAC,SAAA,CAAOR,UAAU,kBAAkBC,QAAS9B,EAASsC,KAAK,SAAS,aAAW,QAC7ErC,SAAAoC,EAAAA,IAACE,EAAAA,UAAA,CAAUC,KAAM,YAGrBH,IAAC,MAAA,CAAIR,UAAU,iBACZ5B,kBApBS,IAyBpB,IAIAN,EAAa8C,UAAY,CACvB3C,MAAO4C,EAAAA,UAAUC,OAAOC,WACxB7C,KAAM2C,EAAAA,UAAUG,KAAKD,WACrB5C,QAAS0C,EAAAA,UAAUI,KAAKF,WACxB3C,SAAUyC,EAAAA,UAAUK,KACpB7C,MAAOwC,EAAAA,UAAUM"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{jsx as e,jsxs as t}from"react/jsx-runtime";import r,{useRef as n,useEffect as o,useCallback as l}from"react";import{a as c,P as a}from"./index-C720tbJA.js";const i='a[href], button:not([disabled]), textarea, input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';const s=r.memo((function({title:r,open:a,onClose:s,children:u,width:d=420}){const m=n(null),f=n(null),p=n([]);o((()=>{a&&(f.current=document.activeElement)}),[a]),o((()=>{if(!a&&f.current){const e=f.current;f.current=null,requestAnimationFrame((()=>{e&&"function"==typeof e.focus&&e.focus()}))}}),[a]),o((()=>{if(a&&m.current){const e=m.current.querySelector("input, textarea, select")||m.current.querySelector(i);e&&e.focus()}}),[a]),o((()=>{a&&m.current&&(p.current=Array.from(m.current.querySelectorAll(i)))}),[a]);const h=l((e=>{if("Escape"!==e.key){if("Tab"===e.key&&m.current){const t=p.current.length>0?p.current:Array.from(m.current.querySelectorAll(i));if(0===t.length)return void e.preventDefault();const r=t[0],n=t[t.length-1];e.shiftKey?document.activeElement===r&&(e.preventDefault(),n.focus()):document.activeElement===n&&(e.preventDefault(),r.focus())}}else s()}),[s]);return a?/* @__PURE__ */e("div",{className:"rmx-modal-overlay",onClick:s,onKeyDown:h,children:/* @__PURE__ */t("div",{ref:m,className:"rmx-modal",style:{maxWidth:d},onClick:e=>e.stopPropagation(),role:"dialog","aria-modal":"true","aria-label":r,children:[
|
|
2
|
+
/* @__PURE__ */t("div",{className:"rmx-modal-header",children:[
|
|
3
|
+
/* @__PURE__ */e("h3",{className:"rmx-modal-title",children:r}),
|
|
4
|
+
/* @__PURE__ */e("button",{className:"rmx-modal-close",onClick:s,type:"button","aria-label":"Close",children:/* @__PURE__ */e(c,{size:18})})]}),
|
|
5
|
+
/* @__PURE__ */e("div",{className:"rmx-modal-body",children:u})]})}):null}));s.propTypes={title:a.string.isRequired,open:a.bool.isRequired,onClose:a.func.isRequired,children:a.node,width:a.number};export{s as M};
|
|
6
|
+
//# sourceMappingURL=ModalOverlay-CLvRNHmp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModalOverlay-CLvRNHmp.js","sources":["../src/components/Modals/ModalOverlay.jsx"],"sourcesContent":["import React, { useEffect, useRef, useCallback } from 'react'\nimport PropTypes from 'prop-types'\nimport { CloseIcon } from '../../icons/index.jsx'\n\nconst FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea, input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\n\nfunction ModalOverlayInner({ title, open, onClose, children, width = 420 }) {\n const ref = useRef(null)\n const previousFocusRef = useRef(null)\n const focusableRef = useRef([])\n\n // Save previously focused element when opening\n useEffect(() => {\n if (open) {\n previousFocusRef.current = document.activeElement\n }\n }, [open])\n\n // Restore focus to previously focused element on close\n useEffect(() => {\n if (!open && previousFocusRef.current) {\n const el = previousFocusRef.current\n previousFocusRef.current = null\n // Delay to allow DOM to settle after modal unmount\n requestAnimationFrame(() => {\n if (el && typeof el.focus === 'function') el.focus()\n })\n }\n }, [open])\n\n // Focus the first focusable element on open\n useEffect(() => {\n if (open && ref.current) {\n // Prefer input/textarea/select, fall back to any focusable element\n const firstInput = ref.current.querySelector('input, textarea, select')\n const firstFocusable = firstInput || ref.current.querySelector(FOCUSABLE_SELECTOR)\n if (firstFocusable) firstFocusable.focus()\n }\n }, [open])\n\n // Cache focusable elements when modal opens or content changes\n useEffect(() => {\n if (open && ref.current) {\n focusableRef.current = Array.from(ref.current.querySelectorAll(FOCUSABLE_SELECTOR))\n }\n }, [open])\n\n // Handle Escape and focus trapping (Tab / Shift+Tab)\n const handleKeyDown = useCallback((e) => {\n if (e.key === 'Escape') {\n onClose()\n return\n }\n\n if (e.key === 'Tab' && ref.current) {\n const focusable = focusableRef.current.length > 0 ? focusableRef.current : Array.from(ref.current.querySelectorAll(FOCUSABLE_SELECTOR))\n if (focusable.length === 0) {\n e.preventDefault()\n return\n }\n\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n\n if (e.shiftKey) {\n // Shift+Tab: if focus is on first element, wrap to last\n if (document.activeElement === first) {\n e.preventDefault()\n last.focus()\n }\n } else {\n // Tab: if focus is on last element, wrap to first\n if (document.activeElement === last) {\n e.preventDefault()\n first.focus()\n }\n }\n }\n }, [onClose])\n\n if (!open) return null\n\n return (\n <div className=\"rmx-modal-overlay\" onClick={onClose} onKeyDown={handleKeyDown}>\n <div\n ref={ref}\n className=\"rmx-modal\"\n style={{ maxWidth: width }}\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n >\n <div className=\"rmx-modal-header\">\n <h3 className=\"rmx-modal-title\">{title}</h3>\n <button className=\"rmx-modal-close\" onClick={onClose} type=\"button\" aria-label=\"Close\">\n <CloseIcon size={18} />\n </button>\n </div>\n <div className=\"rmx-modal-body\">\n {children}\n </div>\n </div>\n </div>\n )\n}\n\nexport const ModalOverlay = React.memo(ModalOverlayInner)\n\nModalOverlay.propTypes = {\n title: PropTypes.string.isRequired,\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n children: PropTypes.node,\n width: PropTypes.number,\n}\n"],"names":["FOCUSABLE_SELECTOR","ModalOverlay","React","memo","title","open","onClose","children","width","ref","useRef","previousFocusRef","focusableRef","useEffect","current","document","activeElement","el","requestAnimationFrame","focus","firstFocusable","querySelector","Array","from","querySelectorAll","handleKeyDown","useCallback","e","key","focusable","length","preventDefault","first","last","shiftKey","className","onClick","onKeyDown","jsxs","style","maxWidth","stopPropagation","role","type","CloseIcon","size","propTypes","PropTypes","string","isRequired","bool","func","node","number"],"mappings":"mKAIA,MAAMA,EAAqB,4HAuGpB,MAAMC,EAAeC,EAAMC,MArGlC,UAA2BC,MAAEA,EAAAC,KAAOA,EAAAC,QAAMA,WAASC,EAAAC,MAAUA,EAAQ,MACnE,MAAMC,EAAMC,EAAO,MACbC,EAAmBD,EAAO,MAC1BE,EAAeF,EAAO,IAG5BG,GAAU,KACJR,IACFM,EAAiBG,QAAUC,SAASC,cACtC,GACC,CAACX,IAGJQ,GAAU,KACR,IAAKR,GAAQM,EAAiBG,QAAS,CACrC,MAAMG,EAAKN,EAAiBG,QAC5BH,EAAiBG,QAAU,KAE3BI,uBAAsB,KAChBD,GAA0B,mBAAbA,EAAGE,SAAyBA,OAAA,GAEjD,IACC,CAACd,IAGJQ,GAAU,KACR,GAAIR,GAAQI,EAAIK,QAAS,CAEvB,MACMM,EADaX,EAAIK,QAAQO,cAAc,4BACRZ,EAAIK,QAAQO,cAAcrB,GAC3DoB,KAA+BD,OACrC,IACC,CAACd,IAGJQ,GAAU,KACJR,GAAQI,EAAIK,UACdF,EAAaE,QAAUQ,MAAMC,KAAKd,EAAIK,QAAQU,iBAAiBxB,IACjE,GACC,CAACK,IAGJ,MAAMoB,EAAgBC,GAAaC,IACjC,GAAc,WAAVA,EAAEC,KAKN,GAAc,QAAVD,EAAEC,KAAiBnB,EAAIK,QAAS,CAClC,MAAMe,EAAYjB,EAAaE,QAAQgB,OAAS,EAAIlB,EAAaE,QAAUQ,MAAMC,KAAKd,EAAIK,QAAQU,iBAAiBxB,IACnH,GAAyB,IAArB6B,EAAUC,OAEZ,YADAH,EAAEI,iBAIJ,MAAMC,EAAQH,EAAU,GAClBI,EAAOJ,EAAUA,EAAUC,OAAS,GAEtCH,EAAEO,SAEAnB,SAASC,gBAAkBgB,IAC7BL,EAAEI,iBACFE,EAAKd,SAIHJ,SAASC,gBAAkBiB,IAC7BN,EAAEI,iBACFC,EAAMb,QAGZ,OA3BEb,GA2BF,GACC,CAACA,IAEJ,OAAKD,mBAGF,MAAA,CAAI8B,UAAU,oBAAoBC,QAAS9B,EAAS+B,UAAWZ,EAC9DlB,wBAAA+B,EAAC,MAAA,CACC7B,MACA0B,UAAU,YACVI,MAAO,CAAEC,SAAUhC,GACnB4B,QAAUT,GAAMA,EAAEc,kBAClBC,KAAK,SACL,aAAW,OACX,aAAYtC,EAEZG,SAAA;eAAA+B,EAAC,MAAA,CAAIH,UAAU,mBACb5B,SAAA;iBAAC,KAAA,CAAG4B,UAAU,kBAAmB5B,SAAAH;iBAChC,SAAA,CAAO+B,UAAU,kBAAkBC,QAAS9B,EAASqC,KAAK,SAAS,aAAW,QAC7EpC,0BAACqC,EAAA,CAAUC,KAAM;iBAGpB,MAAA,CAAIV,UAAU,iBACZ5B,kBApBS,IAyBpB,IAIAN,EAAa6C,UAAY,CACvB1C,MAAO2C,EAAUC,OAAOC,WACxB5C,KAAM0C,EAAUG,KAAKD,WACrB3C,QAASyC,EAAUI,KAAKF,WACxB1C,SAAUwC,EAAUK,KACpB5C,MAAOuC,EAAUM"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),s=require("react"),t=require("./index-Dc63uIP0.cjs"),n=require("./ModalOverlay-BDsGgv3_.cjs"),l=require("@remyxjs/core");function c(e){const s=[];let t=0;const n=e.length;for(;t<n;){if("<"===e[t]&&e.startsWith("!--",t+1)){const l=e.indexOf("--\x3e",t+4),c=-1===l?n:l+3;s.push({text:e.slice(t,c),className:"rmx-syn-comment"}),t=c;continue}if("<"===e[t]&&e.startsWith("!",t+1)&&/^<![a-zA-Z]/.test(e.slice(t,t+3))){const l=e.indexOf(">",t),c=-1===l?n:l+1;s.push({text:e.slice(t,c),className:"rmx-syn-doctype"}),t=c;continue}if("<"===e[t]&&("/"===e[t+1]||/[a-zA-Z]/.test(e[t+1]||""))){t=a(e,t,s);continue}if("&"===e[t]){const n=e.indexOf(";",t+1);if(-1!==n&&n-t<12&&/^&[#a-zA-Z0-9]+;$/.test(e.slice(t,n+1))){s.push({text:e.slice(t,n+1),className:"rmx-syn-entity"}),t=n+1;continue}}let l=t+1;for(;l<n&&"<"!==e[l]&&"&"!==e[l];)l++;s.push({text:e.slice(t,l),className:null}),t=l}return s}function a(e,s,t){const n=e.length;"/"===e[s+1]?(t.push({text:"</",className:"rmx-syn-tag"}),s+=2):(t.push({text:"<",className:"rmx-syn-tag"}),s+=1);let l=s;for(;s<n&&/[a-zA-Z0-9:-]/.test(e[s]);)s++;for(s>l&&t.push({text:e.slice(l,s),className:"rmx-syn-tag"});s<n;){if(/\s/.test(e[s])){let l=s;for(;s<n&&/\s/.test(e[s]);)s++;t.push({text:e.slice(l,s),className:null});continue}if("/"===e[s]&&">"===e[s+1])return t.push({text:"/>",className:"rmx-syn-tag"}),s+2;if(">"===e[s])return t.push({text:">",className:"rmx-syn-tag"}),s+1;let l=s;for(;s<n&&/[^\s=/>]/.test(e[s]);)s++;for(s>l&&t.push({text:e.slice(l,s),className:"rmx-syn-attr-name"});s<n&&/\s/.test(e[s]);)t.push({text:e[s],className:null}),s++;if(s<n&&"="===e[s]){for(t.push({text:"=",className:null}),s++;s<n&&/\s/.test(e[s]);)t.push({text:e[s],className:null}),s++;if(s<n&&('"'===e[s]||"'"===e[s])){const l=e[s],c=e.indexOf(l,s+1),a=-1===c?n:c+1;t.push({text:e.slice(s,a),className:"rmx-syn-attr-value"}),s=a}else{let l=s;for(;s<n&&/[^\s>]/.test(e[s]);)s++;s>l&&t.push({text:e.slice(l,s),className:"rmx-syn-attr-value"})}}}return s}function r(e){const s=e.split("\n"),t=[];let n=!1;for(let l=0;l<s.length;l++){const e=s[l];if(l>0&&t.push({text:"\n",className:null}),/^```/.test(e.trimStart())){t.push({text:e,className:"rmx-syn-code-fence"}),n=!n;continue}if(n){t.push({text:e,className:"rmx-syn-code"});continue}if(/^#{1,6}\s/.test(e)){t.push({text:e,className:"rmx-syn-heading"});continue}if(/^(\s*)([-*_])\2{2,}\s*$/.test(e)){t.push({text:e,className:"rmx-syn-hr"});continue}if(/^>\s?/.test(e)){const s=e.match(/^(>\s?)/);t.push({text:s[0],className:"rmx-syn-blockquote"}),i(e.slice(s[0].length),t);continue}const c=e.match(/^(\s*[-*+]\s+)/);if(c){t.push({text:c[0],className:"rmx-syn-list-marker"}),i(e.slice(c[0].length),t);continue}const a=e.match(/^(\s*\d+[.)]\s+)/);a?(t.push({text:a[0],className:"rmx-syn-list-marker"}),i(e.slice(a[0].length),t)):/^\|[\s:|-]+\|$/.test(e.trim())?t.push({text:e,className:"rmx-syn-hr"}):i(e,t)}return t}function i(e,s){let t=0;const n=e.length;let l=0;for(;t<n;){if("`"===e[t]){t>l&&s.push({text:e.slice(l,t),className:null});const n=e.indexOf("`",t+1);if(-1!==n){s.push({text:e.slice(t,n+1),className:"rmx-syn-code"}),t=n+1,l=t;continue}}if("!"===e[t]&&"["===e[t+1]){const n=e.indexOf("]",t+2);if(-1!==n&&"("===e[n+1]){const c=e.indexOf(")",n+2);if(-1!==c){t>l&&s.push({text:e.slice(l,t),className:null}),s.push({text:e.slice(t,c+1),className:"rmx-syn-image"}),t=c+1,l=t;continue}}}if("["===e[t]){const n=e.indexOf("]",t+1);if(-1!==n&&"("===e[n+1]){const c=e.indexOf(")",n+2);if(-1!==c){t>l&&s.push({text:e.slice(l,t),className:null}),s.push({text:e.slice(t,n+1),className:"rmx-syn-link"}),s.push({text:e.slice(n+1,c+1),className:"rmx-syn-url"}),t=c+1,l=t;continue}}}if("*"===e[t]&&"*"===e[t+1]||"_"===e[t]&&"_"===e[t+1]){const n=e.slice(t,t+2),c=e.indexOf(n,t+2);if(-1!==c){t>l&&s.push({text:e.slice(l,t),className:null}),s.push({text:e.slice(t,c+2),className:"rmx-syn-bold"}),t=c+2,l=t;continue}}if(("*"===e[t]||"_"===e[t])&&e[t+1]!==e[t]){const n=e[t],c=e.indexOf(n,t+1);if(-1!==c&&c>t+1){t>l&&s.push({text:e.slice(l,t),className:null}),s.push({text:e.slice(t,c+1),className:"rmx-syn-italic"}),t=c+1,l=t;continue}}t++}l<n&&s.push({text:e.slice(l),className:null})}function o({value:t,onChange:n,language:l="html"}){const a=s.useRef(null),i=s.useRef(null),o=s.useRef(null),u=s.useRef(null),[m,x]=s.useState(t||"");s.useEffect((()=>(u.current=setTimeout((()=>{x(t||"")}),150),()=>clearTimeout(u.current))),[t]);const f=s.useMemo((()=>("markdown"===l?r:c)(m)),[m,l]),h=s.useMemo((()=>(t||"").split("\n").length),[t]),d=s.useCallback((()=>{const e=a.current;e&&(i.current&&(i.current.scrollTop=e.scrollTop,i.current.scrollLeft=e.scrollLeft),o.current&&(o.current.scrollTop=e.scrollTop))}),[]),p=s.useCallback((e=>{if("Tab"===e.key){e.preventDefault();const s=e.target,l=s.selectionStart,c=s.selectionEnd;if("function"==typeof s.setRangeText)s.setRangeText(" ",l,c,"end"),s.dispatchEvent(new Event("input",{bubbles:!0}));else{const e=t.substring(0,l)+" "+t.substring(c);n(e),requestAnimationFrame((()=>{s.selectionStart=s.selectionEnd=l+2}))}}}),[t,n]);return e.jsxs("div",{className:"rmx-code-editor",children:[e.jsx("div",{ref:o,className:"rmx-code-gutter","aria-hidden":"true",children:Array.from({length:h},((s,t)=>e.jsx("div",{className:"rmx-code-line-number",children:t+1},t)))}),e.jsxs("div",{className:"rmx-code-body",children:[e.jsx("pre",{ref:i,className:"rmx-code-highlight","aria-hidden":"true",children:e.jsxs("code",{children:[f.map(((s,t)=>s.className?e.jsx("span",{className:s.className,children:s.text},t):s.text)),"\n"]})}),e.jsx("textarea",{ref:a,className:"rmx-code-input",value:t,onChange:e=>n(e.target.value),onScroll:d,onKeyDown:p,spellCheck:!1,autoCapitalize:"off",autoComplete:"off",autoCorrect:"off","data-gramm":"false"})]})]})}function u({open:t,onClose:c,engine:a}){const[r,i]=s.useState(""),[u,m]=s.useState(""),x="markdown"===a?.outputFormat;s.useEffect((()=>{if(t&&a){const e=a.getHTML(),s=x?l.htmlToMarkdown(e):l.formatHTML(e);i(s),m(s)}}),[t,a,x]);const f=s.useCallback((()=>{if(r!==u){if(!window.confirm("You have unsaved changes. Discard them?"))return}c()}),[r,u,c]);return e.jsx(n.ModalOverlay,{title:x?"Markdown Source":"Source Code",open:t,onClose:f,width:750,children:e.jsxs("div",{className:"rmx-modal-form",children:[e.jsx(o,{value:r,onChange:i,language:x?"markdown":"html"}),e.jsxs("div",{className:"rmx-modal-actions",children:[e.jsx("button",{type:"button",className:"rmx-btn",onClick:f,children:"Cancel"}),e.jsx("button",{type:"button",className:"rmx-btn rmx-btn-primary",onClick:()=>{a.history.snapshot();const e=x?l.markdownToHtml(r):r,s=a.sanitizer.sanitize(e);s!==e&&a.eventBus.emit("source:sanitized",{message:"Some HTML elements or attributes were removed for security."}),a.setHTML(s),a.eventBus.emit("content:change"),c()},children:"Apply"})]})]})})}u.propTypes={open:t.PropTypes.bool.isRequired,onClose:t.PropTypes.func.isRequired,engine:t.PropTypes.object},exports.SourceModal=u;
|
|
2
|
+
//# sourceMappingURL=SourceModal-BNI_i4iW.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SourceModal-BNI_i4iW.cjs","sources":["../src/components/Modals/CodeEditor/highlightHTML.js","../src/components/Modals/CodeEditor/highlightMarkdown.js","../src/components/Modals/CodeEditor/CodeEditor.jsx","../src/components/Modals/SourceModal.jsx"],"sourcesContent":["/**\n * Lightweight HTML tokenizer for syntax highlighting.\n * Returns an array of { text, className } tokens.\n */\nexport function highlightHTML(code) {\n const tokens = []\n let i = 0\n const len = code.length\n\n while (i < len) {\n // Comment: <!-- ... -->\n if (code[i] === '<' && code.startsWith('!--', i + 1)) {\n const end = code.indexOf('-->', i + 4)\n const commentEnd = end === -1 ? len : end + 3\n tokens.push({ text: code.slice(i, commentEnd), className: 'rmx-syn-comment' })\n i = commentEnd\n continue\n }\n\n // Doctype: <!DOCTYPE ...>\n if (code[i] === '<' && code.startsWith('!', i + 1) && /^<![a-zA-Z]/.test(code.slice(i, i + 3))) {\n const end = code.indexOf('>', i)\n const doctypeEnd = end === -1 ? len : end + 1\n tokens.push({ text: code.slice(i, doctypeEnd), className: 'rmx-syn-doctype' })\n i = doctypeEnd\n continue\n }\n\n // Tag: < ... >\n if (code[i] === '<' && (code[i + 1] === '/' || /[a-zA-Z]/.test(code[i + 1] || ''))) {\n i = parseTag(code, i, tokens)\n continue\n }\n\n // Entity: &...;\n if (code[i] === '&') {\n const semi = code.indexOf(';', i + 1)\n if (semi !== -1 && semi - i < 12 && /^&[#a-zA-Z0-9]+;$/.test(code.slice(i, semi + 1))) {\n tokens.push({ text: code.slice(i, semi + 1), className: 'rmx-syn-entity' })\n i = semi + 1\n continue\n }\n }\n\n // Plain text until next < or &\n let textEnd = i + 1\n while (textEnd < len && code[textEnd] !== '<' && code[textEnd] !== '&') {\n textEnd++\n }\n tokens.push({ text: code.slice(i, textEnd), className: null })\n i = textEnd\n }\n\n return tokens\n}\n\n/**\n * Parse an HTML tag starting at position i, pushing tokens.\n * Returns the new position after the tag.\n */\nfunction parseTag(code, i, tokens) {\n const len = code.length\n\n // Opening: < or </\n if (code[i + 1] === '/') {\n tokens.push({ text: '</', className: 'rmx-syn-tag' })\n i += 2\n } else {\n tokens.push({ text: '<', className: 'rmx-syn-tag' })\n i += 1\n }\n\n // Tag name\n let nameStart = i\n while (i < len && /[a-zA-Z0-9:-]/.test(code[i])) i++\n if (i > nameStart) {\n tokens.push({ text: code.slice(nameStart, i), className: 'rmx-syn-tag' })\n }\n\n // Attributes and closing\n while (i < len) {\n // Skip whitespace\n if (/\\s/.test(code[i])) {\n let wsStart = i\n while (i < len && /\\s/.test(code[i])) i++\n tokens.push({ text: code.slice(wsStart, i), className: null })\n continue\n }\n\n // Self-close or close: />, >\n if (code[i] === '/' && code[i + 1] === '>') {\n tokens.push({ text: '/>', className: 'rmx-syn-tag' })\n return i + 2\n }\n if (code[i] === '>') {\n tokens.push({ text: '>', className: 'rmx-syn-tag' })\n return i + 1\n }\n\n // Attribute name\n let attrStart = i\n while (i < len && /[^\\s=/>]/.test(code[i])) i++\n if (i > attrStart) {\n tokens.push({ text: code.slice(attrStart, i), className: 'rmx-syn-attr-name' })\n }\n\n // Skip whitespace around =\n while (i < len && /\\s/.test(code[i])) {\n tokens.push({ text: code[i], className: null })\n i++\n }\n\n // = sign\n if (i < len && code[i] === '=') {\n tokens.push({ text: '=', className: null })\n i++\n\n // Skip whitespace after =\n while (i < len && /\\s/.test(code[i])) {\n tokens.push({ text: code[i], className: null })\n i++\n }\n\n // Attribute value\n if (i < len && (code[i] === '\"' || code[i] === \"'\")) {\n const quote = code[i]\n const valEnd = code.indexOf(quote, i + 1)\n const end = valEnd === -1 ? len : valEnd + 1\n tokens.push({ text: code.slice(i, end), className: 'rmx-syn-attr-value' })\n i = end\n } else {\n // Unquoted value\n let valStart = i\n while (i < len && /[^\\s>]/.test(code[i])) i++\n if (i > valStart) {\n tokens.push({ text: code.slice(valStart, i), className: 'rmx-syn-attr-value' })\n }\n }\n }\n }\n\n return i\n}\n","/**\n * Lightweight Markdown tokenizer for syntax highlighting.\n * Operates line-by-line since Markdown is line-oriented.\n * Returns an array of { text, className } tokens.\n */\nexport function highlightMarkdown(code) {\n const lines = code.split('\\n')\n const tokens = []\n let inCodeBlock = false\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx]\n\n // Add newline between lines (not before the first)\n if (lineIdx > 0) {\n tokens.push({ text: '\\n', className: null })\n }\n\n // Code fence toggle: ```\n if (/^```/.test(line.trimStart())) {\n tokens.push({ text: line, className: 'rmx-syn-code-fence' })\n inCodeBlock = !inCodeBlock\n continue\n }\n\n // Inside code block — all code\n if (inCodeBlock) {\n tokens.push({ text: line, className: 'rmx-syn-code' })\n continue\n }\n\n // Heading: # ... ######\n if (/^#{1,6}\\s/.test(line)) {\n tokens.push({ text: line, className: 'rmx-syn-heading' })\n continue\n }\n\n // Horizontal rule: ---, ***, ___\n if (/^(\\s*)([-*_])\\2{2,}\\s*$/.test(line)) {\n tokens.push({ text: line, className: 'rmx-syn-hr' })\n continue\n }\n\n // Blockquote: > text\n if (/^>\\s?/.test(line)) {\n const markerMatch = line.match(/^(>\\s?)/)\n tokens.push({ text: markerMatch[0], className: 'rmx-syn-blockquote' })\n highlightInline(line.slice(markerMatch[0].length), tokens)\n continue\n }\n\n // Unordered list: - item, * item, + item\n const ulMatch = line.match(/^(\\s*[-*+]\\s+)/)\n if (ulMatch) {\n tokens.push({ text: ulMatch[0], className: 'rmx-syn-list-marker' })\n highlightInline(line.slice(ulMatch[0].length), tokens)\n continue\n }\n\n // Ordered list: 1. item, 2) item\n const olMatch = line.match(/^(\\s*\\d+[.)]\\s+)/)\n if (olMatch) {\n tokens.push({ text: olMatch[0], className: 'rmx-syn-list-marker' })\n highlightInline(line.slice(olMatch[0].length), tokens)\n continue\n }\n\n // Table separator: |---|---|\n if (/^\\|[\\s:|-]+\\|$/.test(line.trim())) {\n tokens.push({ text: line, className: 'rmx-syn-hr' })\n continue\n }\n\n // Regular line — process inline formatting\n highlightInline(line, tokens)\n }\n\n return tokens\n}\n\n/**\n * Tokenize inline Markdown formatting within a line.\n * Handles: bold, italic, inline code, links, images.\n */\nfunction highlightInline(text, tokens) {\n let i = 0\n const len = text.length\n let plainStart = 0\n\n while (i < len) {\n // Inline code: `code`\n if (text[i] === '`') {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n const end = text.indexOf('`', i + 1)\n if (end !== -1) {\n tokens.push({ text: text.slice(i, end + 1), className: 'rmx-syn-code' })\n i = end + 1\n plainStart = i\n continue\n }\n }\n\n // Image: \n if (text[i] === '!' && text[i + 1] === '[') {\n const closeBracket = text.indexOf(']', i + 2)\n if (closeBracket !== -1 && text[closeBracket + 1] === '(') {\n const closeParen = text.indexOf(')', closeBracket + 2)\n if (closeParen !== -1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n tokens.push({ text: text.slice(i, closeParen + 1), className: 'rmx-syn-image' })\n i = closeParen + 1\n plainStart = i\n continue\n }\n }\n }\n\n // Link: [text](url)\n if (text[i] === '[') {\n const closeBracket = text.indexOf(']', i + 1)\n if (closeBracket !== -1 && text[closeBracket + 1] === '(') {\n const closeParen = text.indexOf(')', closeBracket + 2)\n if (closeParen !== -1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n // [text]\n tokens.push({ text: text.slice(i, closeBracket + 1), className: 'rmx-syn-link' })\n // (url)\n tokens.push({ text: text.slice(closeBracket + 1, closeParen + 1), className: 'rmx-syn-url' })\n i = closeParen + 1\n plainStart = i\n continue\n }\n }\n }\n\n // Bold: **text** or __text__\n if ((text[i] === '*' && text[i + 1] === '*') || (text[i] === '_' && text[i + 1] === '_')) {\n const marker = text.slice(i, i + 2)\n const end = text.indexOf(marker, i + 2)\n if (end !== -1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n tokens.push({ text: text.slice(i, end + 2), className: 'rmx-syn-bold' })\n i = end + 2\n plainStart = i\n continue\n }\n }\n\n // Italic: *text* or _text_ (single, not preceded/followed by same)\n if ((text[i] === '*' || text[i] === '_') && text[i + 1] !== text[i]) {\n const marker = text[i]\n const end = text.indexOf(marker, i + 1)\n if (end !== -1 && end > i + 1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n tokens.push({ text: text.slice(i, end + 1), className: 'rmx-syn-italic' })\n i = end + 1\n plainStart = i\n continue\n }\n }\n\n i++\n }\n\n // Remaining plain text\n if (plainStart < len) {\n tokens.push({ text: text.slice(plainStart), className: null })\n }\n}\n","import { useRef, useMemo, useCallback, useState, useEffect } from 'react'\nimport { highlightHTML } from './highlightHTML.js'\nimport { highlightMarkdown } from './highlightMarkdown.js'\nimport './CodeEditor.css'\n\nconst HIGHLIGHT_DEBOUNCE_MS = 150\n\nexport function CodeEditor({ value, onChange, language = 'html' }) {\n const textareaRef = useRef(null)\n const preRef = useRef(null)\n const gutterRef = useRef(null)\n const highlightTimerRef = useRef(null)\n\n // Debounced value for highlighting — delays expensive tokenization\n const [debouncedValue, setDebouncedValue] = useState(value || '')\n\n useEffect(() => {\n highlightTimerRef.current = setTimeout(() => {\n setDebouncedValue(value || '')\n }, HIGHLIGHT_DEBOUNCE_MS)\n return () => clearTimeout(highlightTimerRef.current)\n }, [value])\n\n // Tokenize the debounced source for highlighting\n const highlighted = useMemo(() => {\n const highlighter = language === 'markdown' ? highlightMarkdown : highlightHTML\n return highlighter(debouncedValue)\n }, [debouncedValue, language])\n\n // Count lines for the gutter\n const lineCount = useMemo(() => {\n return (value || '').split('\\n').length\n }, [value])\n\n // Sync scroll between textarea, pre, and gutter\n const handleScroll = useCallback(() => {\n const ta = textareaRef.current\n if (!ta) return\n if (preRef.current) {\n preRef.current.scrollTop = ta.scrollTop\n preRef.current.scrollLeft = ta.scrollLeft\n }\n if (gutterRef.current) {\n gutterRef.current.scrollTop = ta.scrollTop\n }\n }, [])\n\n // Tab key inserts 2 spaces\n const handleKeyDown = useCallback((e) => {\n if (e.key === 'Tab') {\n e.preventDefault()\n const ta = e.target\n const start = ta.selectionStart\n const end = ta.selectionEnd\n // Use setRangeText to insert text while preserving undo stack\n if (typeof ta.setRangeText === 'function') {\n ta.setRangeText(' ', start, end, 'end')\n ta.dispatchEvent(new Event('input', { bubbles: true }))\n } else {\n const newValue = value.substring(0, start) + ' ' + value.substring(end)\n onChange(newValue)\n requestAnimationFrame(() => {\n ta.selectionStart = ta.selectionEnd = start + 2\n })\n }\n }\n }, [value, onChange])\n\n return (\n <div className=\"rmx-code-editor\">\n {/* Line number gutter */}\n <div ref={gutterRef} className=\"rmx-code-gutter\" aria-hidden=\"true\">\n {Array.from({ length: lineCount }, (_, i) => (\n <div key={i} className=\"rmx-code-line-number\">{i + 1}</div>\n ))}\n </div>\n\n {/* Editor body: highlighted pre + transparent textarea */}\n <div className=\"rmx-code-body\">\n <pre ref={preRef} className=\"rmx-code-highlight\" aria-hidden=\"true\">\n <code>\n {highlighted.map((token, i) =>\n token.className\n ? <span key={i} className={token.className}>{token.text}</span>\n : token.text\n )}\n {'\\n'}\n </code>\n </pre>\n\n <textarea\n ref={textareaRef}\n className=\"rmx-code-input\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onScroll={handleScroll}\n onKeyDown={handleKeyDown}\n spellCheck={false}\n autoCapitalize=\"off\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n data-gramm=\"false\"\n />\n </div>\n </div>\n )\n}\n","import { useState, useEffect, useRef, useCallback } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\nimport { CodeEditor } from './CodeEditor/CodeEditor.jsx'\nimport { htmlToMarkdown, markdownToHtml, formatHTML } from '@remyxjs/core'\n\nexport function SourceModal({ open, onClose, engine }) {\n const [source, setSource] = useState('')\n const [initialSource, setInitialSource] = useState('')\n const isMarkdown = engine?.outputFormat === 'markdown'\n\n useEffect(() => {\n if (open && engine) {\n const html = engine.getHTML()\n const formatted = isMarkdown ? htmlToMarkdown(html) : formatHTML(html)\n setSource(formatted)\n setInitialSource(formatted)\n }\n }, [open, engine, isMarkdown])\n\n const handleApply = () => {\n engine.history.snapshot()\n const rawHtml = isMarkdown ? markdownToHtml(source) : source\n // Re-sanitize user-edited HTML to prevent XSS injection via source mode\n const htmlToApply = engine.sanitizer.sanitize(rawHtml)\n // Notify if sanitizer modified the input (unsafe content was stripped)\n if (htmlToApply !== rawHtml) {\n engine.eventBus.emit('source:sanitized', {\n message: 'Some HTML elements or attributes were removed for security.',\n })\n }\n engine.setHTML(htmlToApply)\n engine.eventBus.emit('content:change')\n onClose()\n }\n\n // #33: Confirm before discarding unsaved changes\n const handleClose = useCallback(() => {\n if (source !== initialSource) {\n const confirmed = window.confirm('You have unsaved changes. Discard them?')\n if (!confirmed) return\n }\n onClose()\n }, [source, initialSource, onClose])\n\n return (\n <ModalOverlay title={isMarkdown ? 'Markdown Source' : 'Source Code'} open={open} onClose={handleClose} width={750}>\n <div className=\"rmx-modal-form\">\n <CodeEditor\n value={source}\n onChange={setSource}\n language={isMarkdown ? 'markdown' : 'html'}\n />\n <div className=\"rmx-modal-actions\">\n <button type=\"button\" className=\"rmx-btn\" onClick={handleClose}>Cancel</button>\n <button type=\"button\" className=\"rmx-btn rmx-btn-primary\" onClick={handleApply}>Apply</button>\n </div>\n </div>\n </ModalOverlay>\n )\n}\n\nSourceModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n}\n"],"names":["highlightHTML","code","tokens","i","len","length","startsWith","end","indexOf","commentEnd","push","text","slice","className","test","doctypeEnd","parseTag","semi","textEnd","nameStart","wsStart","attrStart","quote","valEnd","valStart","highlightMarkdown","lines","split","inCodeBlock","lineIdx","line","trimStart","markerMatch","match","highlightInline","ulMatch","olMatch","trim","plainStart","closeBracket","closeParen","marker","CodeEditor","value","onChange","language","textareaRef","useRef","preRef","gutterRef","highlightTimerRef","debouncedValue","setDebouncedValue","useState","useEffect","current","setTimeout","clearTimeout","highlighted","useMemo","lineCount","handleScroll","useCallback","ta","scrollTop","scrollLeft","handleKeyDown","e","key","preventDefault","target","start","selectionStart","selectionEnd","setRangeText","dispatchEvent","Event","bubbles","newValue","substring","requestAnimationFrame","jsxs","children","jsx","ref","Array","from","_","map","token","onScroll","onKeyDown","spellCheck","autoCapitalize","autoComplete","autoCorrect","SourceModal","open","onClose","engine","source","setSource","initialSource","setInitialSource","isMarkdown","outputFormat","html","getHTML","formatted","htmlToMarkdown","formatHTML","handleClose","window","confirm","ModalOverlay","title","width","type","onClick","history","snapshot","rawHtml","markdownToHtml","htmlToApply","sanitizer","sanitize","eventBus","emit","message","setHTML","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import{jsxs as e,jsx as t}from"react/jsx-runtime";import{useRef as s,useState as n,useEffect as l,useMemo as c,useCallback as a}from"react";import{P as i}from"./index-C720tbJA.js";import{M as r}from"./ModalOverlay-CLvRNHmp.js";import{htmlToMarkdown as o,formatHTML as m,markdownToHtml as u}from"@remyxjs/core";function x(e){const t=[];let s=0;const n=e.length;for(;s<n;){if("<"===e[s]&&e.startsWith("!--",s+1)){const l=e.indexOf("--\x3e",s+4),c=-1===l?n:l+3;t.push({text:e.slice(s,c),className:"rmx-syn-comment"}),s=c;continue}if("<"===e[s]&&e.startsWith("!",s+1)&&/^<![a-zA-Z]/.test(e.slice(s,s+3))){const l=e.indexOf(">",s),c=-1===l?n:l+1;t.push({text:e.slice(s,c),className:"rmx-syn-doctype"}),s=c;continue}if("<"===e[s]&&("/"===e[s+1]||/[a-zA-Z]/.test(e[s+1]||""))){s=f(e,s,t);continue}if("&"===e[s]){const n=e.indexOf(";",s+1);if(-1!==n&&n-s<12&&/^&[#a-zA-Z0-9]+;$/.test(e.slice(s,n+1))){t.push({text:e.slice(s,n+1),className:"rmx-syn-entity"}),s=n+1;continue}}let l=s+1;for(;l<n&&"<"!==e[l]&&"&"!==e[l];)l++;t.push({text:e.slice(s,l),className:null}),s=l}return t}function f(e,t,s){const n=e.length;"/"===e[t+1]?(s.push({text:"</",className:"rmx-syn-tag"}),t+=2):(s.push({text:"<",className:"rmx-syn-tag"}),t+=1);let l=t;for(;t<n&&/[a-zA-Z0-9:-]/.test(e[t]);)t++;for(t>l&&s.push({text:e.slice(l,t),className:"rmx-syn-tag"});t<n;){if(/\s/.test(e[t])){let l=t;for(;t<n&&/\s/.test(e[t]);)t++;s.push({text:e.slice(l,t),className:null});continue}if("/"===e[t]&&">"===e[t+1])return s.push({text:"/>",className:"rmx-syn-tag"}),t+2;if(">"===e[t])return s.push({text:">",className:"rmx-syn-tag"}),t+1;let l=t;for(;t<n&&/[^\s=/>]/.test(e[t]);)t++;for(t>l&&s.push({text:e.slice(l,t),className:"rmx-syn-attr-name"});t<n&&/\s/.test(e[t]);)s.push({text:e[t],className:null}),t++;if(t<n&&"="===e[t]){for(s.push({text:"=",className:null}),t++;t<n&&/\s/.test(e[t]);)s.push({text:e[t],className:null}),t++;if(t<n&&('"'===e[t]||"'"===e[t])){const l=e[t],c=e.indexOf(l,t+1),a=-1===c?n:c+1;s.push({text:e.slice(t,a),className:"rmx-syn-attr-value"}),t=a}else{let l=t;for(;t<n&&/[^\s>]/.test(e[t]);)t++;t>l&&s.push({text:e.slice(l,t),className:"rmx-syn-attr-value"})}}}return t}function h(e){const t=e.split("\n"),s=[];let n=!1;for(let l=0;l<t.length;l++){const e=t[l];if(l>0&&s.push({text:"\n",className:null}),/^```/.test(e.trimStart())){s.push({text:e,className:"rmx-syn-code-fence"}),n=!n;continue}if(n){s.push({text:e,className:"rmx-syn-code"});continue}if(/^#{1,6}\s/.test(e)){s.push({text:e,className:"rmx-syn-heading"});continue}if(/^(\s*)([-*_])\2{2,}\s*$/.test(e)){s.push({text:e,className:"rmx-syn-hr"});continue}if(/^>\s?/.test(e)){const t=e.match(/^(>\s?)/);s.push({text:t[0],className:"rmx-syn-blockquote"}),p(e.slice(t[0].length),s);continue}const c=e.match(/^(\s*[-*+]\s+)/);if(c){s.push({text:c[0],className:"rmx-syn-list-marker"}),p(e.slice(c[0].length),s);continue}const a=e.match(/^(\s*\d+[.)]\s+)/);a?(s.push({text:a[0],className:"rmx-syn-list-marker"}),p(e.slice(a[0].length),s)):/^\|[\s:|-]+\|$/.test(e.trim())?s.push({text:e,className:"rmx-syn-hr"}):p(e,s)}return s}function p(e,t){let s=0;const n=e.length;let l=0;for(;s<n;){if("`"===e[s]){s>l&&t.push({text:e.slice(l,s),className:null});const n=e.indexOf("`",s+1);if(-1!==n){t.push({text:e.slice(s,n+1),className:"rmx-syn-code"}),s=n+1,l=s;continue}}if("!"===e[s]&&"["===e[s+1]){const n=e.indexOf("]",s+2);if(-1!==n&&"("===e[n+1]){const c=e.indexOf(")",n+2);if(-1!==c){s>l&&t.push({text:e.slice(l,s),className:null}),t.push({text:e.slice(s,c+1),className:"rmx-syn-image"}),s=c+1,l=s;continue}}}if("["===e[s]){const n=e.indexOf("]",s+1);if(-1!==n&&"("===e[n+1]){const c=e.indexOf(")",n+2);if(-1!==c){s>l&&t.push({text:e.slice(l,s),className:null}),t.push({text:e.slice(s,n+1),className:"rmx-syn-link"}),t.push({text:e.slice(n+1,c+1),className:"rmx-syn-url"}),s=c+1,l=s;continue}}}if("*"===e[s]&&"*"===e[s+1]||"_"===e[s]&&"_"===e[s+1]){const n=e.slice(s,s+2),c=e.indexOf(n,s+2);if(-1!==c){s>l&&t.push({text:e.slice(l,s),className:null}),t.push({text:e.slice(s,c+2),className:"rmx-syn-bold"}),s=c+2,l=s;continue}}if(("*"===e[s]||"_"===e[s])&&e[s+1]!==e[s]){const n=e[s],c=e.indexOf(n,s+1);if(-1!==c&&c>s+1){s>l&&t.push({text:e.slice(l,s),className:null}),t.push({text:e.slice(s,c+1),className:"rmx-syn-italic"}),s=c+1,l=s;continue}}s++}l<n&&t.push({text:e.slice(l),className:null})}function d({value:i,onChange:r,language:o="html"}){const m=s(null),u=s(null),f=s(null),p=s(null),[d,N]=n(i||"");l((()=>(p.current=setTimeout((()=>{N(i||"")}),150),()=>clearTimeout(p.current))),[i]);const y=c((()=>("markdown"===o?h:x)(d)),[d,o]),g=c((()=>(i||"").split("\n").length),[i]),v=a((()=>{const e=m.current;e&&(u.current&&(u.current.scrollTop=e.scrollTop,u.current.scrollLeft=e.scrollLeft),f.current&&(f.current.scrollTop=e.scrollTop))}),[]),b=a((e=>{if("Tab"===e.key){e.preventDefault();const t=e.target,s=t.selectionStart,n=t.selectionEnd;if("function"==typeof t.setRangeText)t.setRangeText(" ",s,n,"end"),t.dispatchEvent(new Event("input",{bubbles:!0}));else{const e=i.substring(0,s)+" "+i.substring(n);r(e),requestAnimationFrame((()=>{t.selectionStart=t.selectionEnd=s+2}))}}}),[i,r]);/* @__PURE__ */
|
|
2
|
+
return e("div",{className:"rmx-code-editor",children:[
|
|
3
|
+
/* @__PURE__ */t("div",{ref:f,className:"rmx-code-gutter","aria-hidden":"true",children:Array.from({length:g},((e,s)=>/* @__PURE__ */t("div",{className:"rmx-code-line-number",children:s+1},s)))}),
|
|
4
|
+
/* @__PURE__ */e("div",{className:"rmx-code-body",children:[
|
|
5
|
+
/* @__PURE__ */t("pre",{ref:u,className:"rmx-code-highlight","aria-hidden":"true",children:/* @__PURE__ */e("code",{children:[y.map(((e,s)=>e.className?/* @__PURE__ */t("span",{className:e.className,children:e.text},s):e.text)),"\n"]})}),
|
|
6
|
+
/* @__PURE__ */t("textarea",{ref:m,className:"rmx-code-input",value:i,onChange:e=>r(e.target.value),onScroll:v,onKeyDown:b,spellCheck:!1,autoCapitalize:"off",autoComplete:"off",autoCorrect:"off","data-gramm":"false"})]})]})}function N({open:s,onClose:c,engine:i}){const[x,f]=n(""),[h,p]=n(""),N="markdown"===i?.outputFormat;l((()=>{if(s&&i){const e=i.getHTML(),t=N?o(e):m(e);f(t),p(t)}}),[s,i,N]);const y=a((()=>{if(x!==h){if(!window.confirm("You have unsaved changes. Discard them?"))return}c()}),[x,h,c]);/* @__PURE__ */
|
|
7
|
+
return t(r,{title:N?"Markdown Source":"Source Code",open:s,onClose:y,width:750,children:/* @__PURE__ */e("div",{className:"rmx-modal-form",children:[
|
|
8
|
+
/* @__PURE__ */t(d,{value:x,onChange:f,language:N?"markdown":"html"}),
|
|
9
|
+
/* @__PURE__ */e("div",{className:"rmx-modal-actions",children:[
|
|
10
|
+
/* @__PURE__ */t("button",{type:"button",className:"rmx-btn",onClick:y,children:"Cancel"}),
|
|
11
|
+
/* @__PURE__ */t("button",{type:"button",className:"rmx-btn rmx-btn-primary",onClick:()=>{i.history.snapshot();const e=N?u(x):x,t=i.sanitizer.sanitize(e);t!==e&&i.eventBus.emit("source:sanitized",{message:"Some HTML elements or attributes were removed for security."}),i.setHTML(t),i.eventBus.emit("content:change"),c()},children:"Apply"})]})]})})}N.propTypes={open:i.bool.isRequired,onClose:i.func.isRequired,engine:i.object};export{N as SourceModal};
|
|
12
|
+
//# sourceMappingURL=SourceModal-MdTGK3Uf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SourceModal-MdTGK3Uf.js","sources":["../src/components/Modals/CodeEditor/highlightHTML.js","../src/components/Modals/CodeEditor/highlightMarkdown.js","../src/components/Modals/CodeEditor/CodeEditor.jsx","../src/components/Modals/SourceModal.jsx"],"sourcesContent":["/**\n * Lightweight HTML tokenizer for syntax highlighting.\n * Returns an array of { text, className } tokens.\n */\nexport function highlightHTML(code) {\n const tokens = []\n let i = 0\n const len = code.length\n\n while (i < len) {\n // Comment: <!-- ... -->\n if (code[i] === '<' && code.startsWith('!--', i + 1)) {\n const end = code.indexOf('-->', i + 4)\n const commentEnd = end === -1 ? len : end + 3\n tokens.push({ text: code.slice(i, commentEnd), className: 'rmx-syn-comment' })\n i = commentEnd\n continue\n }\n\n // Doctype: <!DOCTYPE ...>\n if (code[i] === '<' && code.startsWith('!', i + 1) && /^<![a-zA-Z]/.test(code.slice(i, i + 3))) {\n const end = code.indexOf('>', i)\n const doctypeEnd = end === -1 ? len : end + 1\n tokens.push({ text: code.slice(i, doctypeEnd), className: 'rmx-syn-doctype' })\n i = doctypeEnd\n continue\n }\n\n // Tag: < ... >\n if (code[i] === '<' && (code[i + 1] === '/' || /[a-zA-Z]/.test(code[i + 1] || ''))) {\n i = parseTag(code, i, tokens)\n continue\n }\n\n // Entity: &...;\n if (code[i] === '&') {\n const semi = code.indexOf(';', i + 1)\n if (semi !== -1 && semi - i < 12 && /^&[#a-zA-Z0-9]+;$/.test(code.slice(i, semi + 1))) {\n tokens.push({ text: code.slice(i, semi + 1), className: 'rmx-syn-entity' })\n i = semi + 1\n continue\n }\n }\n\n // Plain text until next < or &\n let textEnd = i + 1\n while (textEnd < len && code[textEnd] !== '<' && code[textEnd] !== '&') {\n textEnd++\n }\n tokens.push({ text: code.slice(i, textEnd), className: null })\n i = textEnd\n }\n\n return tokens\n}\n\n/**\n * Parse an HTML tag starting at position i, pushing tokens.\n * Returns the new position after the tag.\n */\nfunction parseTag(code, i, tokens) {\n const len = code.length\n\n // Opening: < or </\n if (code[i + 1] === '/') {\n tokens.push({ text: '</', className: 'rmx-syn-tag' })\n i += 2\n } else {\n tokens.push({ text: '<', className: 'rmx-syn-tag' })\n i += 1\n }\n\n // Tag name\n let nameStart = i\n while (i < len && /[a-zA-Z0-9:-]/.test(code[i])) i++\n if (i > nameStart) {\n tokens.push({ text: code.slice(nameStart, i), className: 'rmx-syn-tag' })\n }\n\n // Attributes and closing\n while (i < len) {\n // Skip whitespace\n if (/\\s/.test(code[i])) {\n let wsStart = i\n while (i < len && /\\s/.test(code[i])) i++\n tokens.push({ text: code.slice(wsStart, i), className: null })\n continue\n }\n\n // Self-close or close: />, >\n if (code[i] === '/' && code[i + 1] === '>') {\n tokens.push({ text: '/>', className: 'rmx-syn-tag' })\n return i + 2\n }\n if (code[i] === '>') {\n tokens.push({ text: '>', className: 'rmx-syn-tag' })\n return i + 1\n }\n\n // Attribute name\n let attrStart = i\n while (i < len && /[^\\s=/>]/.test(code[i])) i++\n if (i > attrStart) {\n tokens.push({ text: code.slice(attrStart, i), className: 'rmx-syn-attr-name' })\n }\n\n // Skip whitespace around =\n while (i < len && /\\s/.test(code[i])) {\n tokens.push({ text: code[i], className: null })\n i++\n }\n\n // = sign\n if (i < len && code[i] === '=') {\n tokens.push({ text: '=', className: null })\n i++\n\n // Skip whitespace after =\n while (i < len && /\\s/.test(code[i])) {\n tokens.push({ text: code[i], className: null })\n i++\n }\n\n // Attribute value\n if (i < len && (code[i] === '\"' || code[i] === \"'\")) {\n const quote = code[i]\n const valEnd = code.indexOf(quote, i + 1)\n const end = valEnd === -1 ? len : valEnd + 1\n tokens.push({ text: code.slice(i, end), className: 'rmx-syn-attr-value' })\n i = end\n } else {\n // Unquoted value\n let valStart = i\n while (i < len && /[^\\s>]/.test(code[i])) i++\n if (i > valStart) {\n tokens.push({ text: code.slice(valStart, i), className: 'rmx-syn-attr-value' })\n }\n }\n }\n }\n\n return i\n}\n","/**\n * Lightweight Markdown tokenizer for syntax highlighting.\n * Operates line-by-line since Markdown is line-oriented.\n * Returns an array of { text, className } tokens.\n */\nexport function highlightMarkdown(code) {\n const lines = code.split('\\n')\n const tokens = []\n let inCodeBlock = false\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx]\n\n // Add newline between lines (not before the first)\n if (lineIdx > 0) {\n tokens.push({ text: '\\n', className: null })\n }\n\n // Code fence toggle: ```\n if (/^```/.test(line.trimStart())) {\n tokens.push({ text: line, className: 'rmx-syn-code-fence' })\n inCodeBlock = !inCodeBlock\n continue\n }\n\n // Inside code block — all code\n if (inCodeBlock) {\n tokens.push({ text: line, className: 'rmx-syn-code' })\n continue\n }\n\n // Heading: # ... ######\n if (/^#{1,6}\\s/.test(line)) {\n tokens.push({ text: line, className: 'rmx-syn-heading' })\n continue\n }\n\n // Horizontal rule: ---, ***, ___\n if (/^(\\s*)([-*_])\\2{2,}\\s*$/.test(line)) {\n tokens.push({ text: line, className: 'rmx-syn-hr' })\n continue\n }\n\n // Blockquote: > text\n if (/^>\\s?/.test(line)) {\n const markerMatch = line.match(/^(>\\s?)/)\n tokens.push({ text: markerMatch[0], className: 'rmx-syn-blockquote' })\n highlightInline(line.slice(markerMatch[0].length), tokens)\n continue\n }\n\n // Unordered list: - item, * item, + item\n const ulMatch = line.match(/^(\\s*[-*+]\\s+)/)\n if (ulMatch) {\n tokens.push({ text: ulMatch[0], className: 'rmx-syn-list-marker' })\n highlightInline(line.slice(ulMatch[0].length), tokens)\n continue\n }\n\n // Ordered list: 1. item, 2) item\n const olMatch = line.match(/^(\\s*\\d+[.)]\\s+)/)\n if (olMatch) {\n tokens.push({ text: olMatch[0], className: 'rmx-syn-list-marker' })\n highlightInline(line.slice(olMatch[0].length), tokens)\n continue\n }\n\n // Table separator: |---|---|\n if (/^\\|[\\s:|-]+\\|$/.test(line.trim())) {\n tokens.push({ text: line, className: 'rmx-syn-hr' })\n continue\n }\n\n // Regular line — process inline formatting\n highlightInline(line, tokens)\n }\n\n return tokens\n}\n\n/**\n * Tokenize inline Markdown formatting within a line.\n * Handles: bold, italic, inline code, links, images.\n */\nfunction highlightInline(text, tokens) {\n let i = 0\n const len = text.length\n let plainStart = 0\n\n while (i < len) {\n // Inline code: `code`\n if (text[i] === '`') {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n const end = text.indexOf('`', i + 1)\n if (end !== -1) {\n tokens.push({ text: text.slice(i, end + 1), className: 'rmx-syn-code' })\n i = end + 1\n plainStart = i\n continue\n }\n }\n\n // Image: \n if (text[i] === '!' && text[i + 1] === '[') {\n const closeBracket = text.indexOf(']', i + 2)\n if (closeBracket !== -1 && text[closeBracket + 1] === '(') {\n const closeParen = text.indexOf(')', closeBracket + 2)\n if (closeParen !== -1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n tokens.push({ text: text.slice(i, closeParen + 1), className: 'rmx-syn-image' })\n i = closeParen + 1\n plainStart = i\n continue\n }\n }\n }\n\n // Link: [text](url)\n if (text[i] === '[') {\n const closeBracket = text.indexOf(']', i + 1)\n if (closeBracket !== -1 && text[closeBracket + 1] === '(') {\n const closeParen = text.indexOf(')', closeBracket + 2)\n if (closeParen !== -1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n // [text]\n tokens.push({ text: text.slice(i, closeBracket + 1), className: 'rmx-syn-link' })\n // (url)\n tokens.push({ text: text.slice(closeBracket + 1, closeParen + 1), className: 'rmx-syn-url' })\n i = closeParen + 1\n plainStart = i\n continue\n }\n }\n }\n\n // Bold: **text** or __text__\n if ((text[i] === '*' && text[i + 1] === '*') || (text[i] === '_' && text[i + 1] === '_')) {\n const marker = text.slice(i, i + 2)\n const end = text.indexOf(marker, i + 2)\n if (end !== -1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n tokens.push({ text: text.slice(i, end + 2), className: 'rmx-syn-bold' })\n i = end + 2\n plainStart = i\n continue\n }\n }\n\n // Italic: *text* or _text_ (single, not preceded/followed by same)\n if ((text[i] === '*' || text[i] === '_') && text[i + 1] !== text[i]) {\n const marker = text[i]\n const end = text.indexOf(marker, i + 1)\n if (end !== -1 && end > i + 1) {\n if (i > plainStart) {\n tokens.push({ text: text.slice(plainStart, i), className: null })\n }\n tokens.push({ text: text.slice(i, end + 1), className: 'rmx-syn-italic' })\n i = end + 1\n plainStart = i\n continue\n }\n }\n\n i++\n }\n\n // Remaining plain text\n if (plainStart < len) {\n tokens.push({ text: text.slice(plainStart), className: null })\n }\n}\n","import { useRef, useMemo, useCallback, useState, useEffect } from 'react'\nimport { highlightHTML } from './highlightHTML.js'\nimport { highlightMarkdown } from './highlightMarkdown.js'\nimport './CodeEditor.css'\n\nconst HIGHLIGHT_DEBOUNCE_MS = 150\n\nexport function CodeEditor({ value, onChange, language = 'html' }) {\n const textareaRef = useRef(null)\n const preRef = useRef(null)\n const gutterRef = useRef(null)\n const highlightTimerRef = useRef(null)\n\n // Debounced value for highlighting — delays expensive tokenization\n const [debouncedValue, setDebouncedValue] = useState(value || '')\n\n useEffect(() => {\n highlightTimerRef.current = setTimeout(() => {\n setDebouncedValue(value || '')\n }, HIGHLIGHT_DEBOUNCE_MS)\n return () => clearTimeout(highlightTimerRef.current)\n }, [value])\n\n // Tokenize the debounced source for highlighting\n const highlighted = useMemo(() => {\n const highlighter = language === 'markdown' ? highlightMarkdown : highlightHTML\n return highlighter(debouncedValue)\n }, [debouncedValue, language])\n\n // Count lines for the gutter\n const lineCount = useMemo(() => {\n return (value || '').split('\\n').length\n }, [value])\n\n // Sync scroll between textarea, pre, and gutter\n const handleScroll = useCallback(() => {\n const ta = textareaRef.current\n if (!ta) return\n if (preRef.current) {\n preRef.current.scrollTop = ta.scrollTop\n preRef.current.scrollLeft = ta.scrollLeft\n }\n if (gutterRef.current) {\n gutterRef.current.scrollTop = ta.scrollTop\n }\n }, [])\n\n // Tab key inserts 2 spaces\n const handleKeyDown = useCallback((e) => {\n if (e.key === 'Tab') {\n e.preventDefault()\n const ta = e.target\n const start = ta.selectionStart\n const end = ta.selectionEnd\n // Use setRangeText to insert text while preserving undo stack\n if (typeof ta.setRangeText === 'function') {\n ta.setRangeText(' ', start, end, 'end')\n ta.dispatchEvent(new Event('input', { bubbles: true }))\n } else {\n const newValue = value.substring(0, start) + ' ' + value.substring(end)\n onChange(newValue)\n requestAnimationFrame(() => {\n ta.selectionStart = ta.selectionEnd = start + 2\n })\n }\n }\n }, [value, onChange])\n\n return (\n <div className=\"rmx-code-editor\">\n {/* Line number gutter */}\n <div ref={gutterRef} className=\"rmx-code-gutter\" aria-hidden=\"true\">\n {Array.from({ length: lineCount }, (_, i) => (\n <div key={i} className=\"rmx-code-line-number\">{i + 1}</div>\n ))}\n </div>\n\n {/* Editor body: highlighted pre + transparent textarea */}\n <div className=\"rmx-code-body\">\n <pre ref={preRef} className=\"rmx-code-highlight\" aria-hidden=\"true\">\n <code>\n {highlighted.map((token, i) =>\n token.className\n ? <span key={i} className={token.className}>{token.text}</span>\n : token.text\n )}\n {'\\n'}\n </code>\n </pre>\n\n <textarea\n ref={textareaRef}\n className=\"rmx-code-input\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n onScroll={handleScroll}\n onKeyDown={handleKeyDown}\n spellCheck={false}\n autoCapitalize=\"off\"\n autoComplete=\"off\"\n autoCorrect=\"off\"\n data-gramm=\"false\"\n />\n </div>\n </div>\n )\n}\n","import { useState, useEffect, useRef, useCallback } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\nimport { CodeEditor } from './CodeEditor/CodeEditor.jsx'\nimport { htmlToMarkdown, markdownToHtml, formatHTML } from '@remyxjs/core'\n\nexport function SourceModal({ open, onClose, engine }) {\n const [source, setSource] = useState('')\n const [initialSource, setInitialSource] = useState('')\n const isMarkdown = engine?.outputFormat === 'markdown'\n\n useEffect(() => {\n if (open && engine) {\n const html = engine.getHTML()\n const formatted = isMarkdown ? htmlToMarkdown(html) : formatHTML(html)\n setSource(formatted)\n setInitialSource(formatted)\n }\n }, [open, engine, isMarkdown])\n\n const handleApply = () => {\n engine.history.snapshot()\n const rawHtml = isMarkdown ? markdownToHtml(source) : source\n // Re-sanitize user-edited HTML to prevent XSS injection via source mode\n const htmlToApply = engine.sanitizer.sanitize(rawHtml)\n // Notify if sanitizer modified the input (unsafe content was stripped)\n if (htmlToApply !== rawHtml) {\n engine.eventBus.emit('source:sanitized', {\n message: 'Some HTML elements or attributes were removed for security.',\n })\n }\n engine.setHTML(htmlToApply)\n engine.eventBus.emit('content:change')\n onClose()\n }\n\n // #33: Confirm before discarding unsaved changes\n const handleClose = useCallback(() => {\n if (source !== initialSource) {\n const confirmed = window.confirm('You have unsaved changes. Discard them?')\n if (!confirmed) return\n }\n onClose()\n }, [source, initialSource, onClose])\n\n return (\n <ModalOverlay title={isMarkdown ? 'Markdown Source' : 'Source Code'} open={open} onClose={handleClose} width={750}>\n <div className=\"rmx-modal-form\">\n <CodeEditor\n value={source}\n onChange={setSource}\n language={isMarkdown ? 'markdown' : 'html'}\n />\n <div className=\"rmx-modal-actions\">\n <button type=\"button\" className=\"rmx-btn\" onClick={handleClose}>Cancel</button>\n <button type=\"button\" className=\"rmx-btn rmx-btn-primary\" onClick={handleApply}>Apply</button>\n </div>\n </div>\n </ModalOverlay>\n )\n}\n\nSourceModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n}\n"],"names":["highlightHTML","code","tokens","i","len","length","startsWith","end","indexOf","commentEnd","push","text","slice","className","test","doctypeEnd","parseTag","semi","textEnd","nameStart","wsStart","attrStart","quote","valEnd","valStart","highlightMarkdown","lines","split","inCodeBlock","lineIdx","line","trimStart","markerMatch","match","highlightInline","ulMatch","olMatch","trim","plainStart","closeBracket","closeParen","marker","CodeEditor","value","onChange","language","textareaRef","useRef","preRef","gutterRef","highlightTimerRef","debouncedValue","setDebouncedValue","useState","useEffect","current","setTimeout","clearTimeout","highlighted","useMemo","lineCount","handleScroll","useCallback","ta","scrollTop","scrollLeft","handleKeyDown","e","key","preventDefault","target","start","selectionStart","selectionEnd","setRangeText","dispatchEvent","Event","bubbles","newValue","substring","requestAnimationFrame","jsxs","children","jsx","ref","Array","from","_","map","token","onScroll","onKeyDown","spellCheck","autoCapitalize","autoComplete","autoCorrect","SourceModal","open","onClose","engine","source","setSource","initialSource","setInitialSource","isMarkdown","outputFormat","html","getHTML","formatted","htmlToMarkdown","formatHTML","handleClose","window","confirm","ModalOverlay","title","width","type","onClick","history","snapshot","rawHtml","markdownToHtml","htmlToApply","sanitizer","sanitize","eventBus","emit","message","setHTML","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"sTAIO,SAASA,EAAcC,GAC5B,MAAMC,EAAS,GACf,IAAIC,EAAI,EACR,MAAMC,EAAMH,EAAKI,OAEjB,KAAOF,EAAIC,GAAK,CAEd,GAAgB,MAAZH,EAAKE,IAAcF,EAAKK,WAAW,MAAOH,EAAI,GAAI,CACpD,MAAMI,EAAMN,EAAKO,QAAQ,SAAOL,EAAI,GAC9BM,GAAqB,IAARF,EAAaH,EAAMG,EAAM,EAC5CL,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMT,EAAGM,GAAaI,UAAW,oBAC1DV,EAAIM,EACJ,QACF,CAGA,GAAgB,MAAZR,EAAKE,IAAcF,EAAKK,WAAW,IAAKH,EAAI,IAAM,cAAcW,KAAKb,EAAKW,MAAMT,EAAGA,EAAI,IAAK,CAC9F,MAAMI,EAAMN,EAAKO,QAAQ,IAAKL,GACxBY,GAAqB,IAARR,EAAaH,EAAMG,EAAM,EAC5CL,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMT,EAAGY,GAAaF,UAAW,oBAC1DV,EAAIY,EACJ,QACF,CAGA,GAAgB,MAAZd,EAAKE,KAA+B,MAAhBF,EAAKE,EAAI,IAAc,WAAWW,KAAKb,EAAKE,EAAI,IAAM,KAAM,CAClFA,EAAIa,EAASf,EAAME,EAAGD,GACtB,QACF,CAGA,GAAgB,MAAZD,EAAKE,GAAY,CACnB,MAAMc,EAAOhB,EAAKO,QAAQ,IAAKL,EAAI,GACnC,IAAa,IAATc,GAAeA,EAAOd,EAAI,IAAM,oBAAoBW,KAAKb,EAAKW,MAAMT,EAAGc,EAAO,IAAK,CACrFf,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMT,EAAGc,EAAO,GAAIJ,UAAW,mBACxDV,EAAIc,EAAO,EACX,QACF,CACF,CAGA,IAAIC,EAAUf,EAAI,EAClB,KAAOe,EAAUd,GAAyB,MAAlBH,EAAKiB,IAAsC,MAAlBjB,EAAKiB,IACpDA,IAEFhB,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMT,EAAGe,GAAUL,UAAW,OACvDV,EAAIe,CACN,CAEA,OAAOhB,CACT,CAMA,SAASc,EAASf,EAAME,EAAGD,GACzB,MAAME,EAAMH,EAAKI,OAGG,MAAhBJ,EAAKE,EAAI,IACXD,EAAOQ,KAAK,CAAEC,KAAM,KAAME,UAAW,gBACrCV,GAAK,IAELD,EAAOQ,KAAK,CAAEC,KAAM,IAAKE,UAAW,gBACpCV,GAAK,GAIP,IAAIgB,EAAYhB,EAChB,KAAOA,EAAIC,GAAO,gBAAgBU,KAAKb,EAAKE,KAAKA,IAMjD,IALIA,EAAIgB,GACNjB,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMO,EAAWhB,GAAIU,UAAW,gBAIpDV,EAAIC,GAAK,CAEd,GAAI,KAAKU,KAAKb,EAAKE,IAAK,CACtB,IAAIiB,EAAUjB,EACd,KAAOA,EAAIC,GAAO,KAAKU,KAAKb,EAAKE,KAAKA,IACtCD,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMQ,EAASjB,GAAIU,UAAW,OACvD,QACF,CAGA,GAAgB,MAAZZ,EAAKE,IAA8B,MAAhBF,EAAKE,EAAI,GAE9B,OADAD,EAAOQ,KAAK,CAAEC,KAAM,KAAME,UAAW,gBAC9BV,EAAI,EAEb,GAAgB,MAAZF,EAAKE,GAEP,OADAD,EAAOQ,KAAK,CAAEC,KAAM,IAAKE,UAAW,gBAC7BV,EAAI,EAIb,IAAIkB,EAAYlB,EAChB,KAAOA,EAAIC,GAAO,WAAWU,KAAKb,EAAKE,KAAKA,IAM5C,IALIA,EAAIkB,GACNnB,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMS,EAAWlB,GAAIU,UAAW,sBAIpDV,EAAIC,GAAO,KAAKU,KAAKb,EAAKE,KAC/BD,EAAOQ,KAAK,CAAEC,KAAMV,EAAKE,GAAIU,UAAW,OACxCV,IAIF,GAAIA,EAAIC,GAAmB,MAAZH,EAAKE,GAAY,CAK9B,IAJAD,EAAOQ,KAAK,CAAEC,KAAM,IAAKE,UAAW,OACpCV,IAGOA,EAAIC,GAAO,KAAKU,KAAKb,EAAKE,KAC/BD,EAAOQ,KAAK,CAAEC,KAAMV,EAAKE,GAAIU,UAAW,OACxCV,IAIF,GAAIA,EAAIC,IAAoB,MAAZH,EAAKE,IAA0B,MAAZF,EAAKE,IAAa,CACnD,MAAMmB,EAAQrB,EAAKE,GACboB,EAAStB,EAAKO,QAAQc,EAAOnB,EAAI,GACjCI,GAAiB,IAAXgB,EAAgBnB,EAAMmB,EAAS,EAC3CrB,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMT,EAAGI,GAAMM,UAAW,uBACnDV,EAAII,CACN,KAAO,CAEL,IAAIiB,EAAWrB,EACf,KAAOA,EAAIC,GAAO,SAASU,KAAKb,EAAKE,KAAKA,IACtCA,EAAIqB,GACNtB,EAAOQ,KAAK,CAAEC,KAAMV,EAAKW,MAAMY,EAAUrB,GAAIU,UAAW,sBAE5D,CACF,CACF,CAEA,OAAOV,CACT,CCzIO,SAASsB,EAAkBxB,GAChC,MAAMyB,EAAQzB,EAAK0B,MAAM,MACnBzB,EAAS,GACf,IAAI0B,GAAc,EAElB,IAAA,IAASC,EAAU,EAAGA,EAAUH,EAAMrB,OAAQwB,IAAW,CACvD,MAAMC,EAAOJ,EAAMG,GAQnB,GALIA,EAAU,GACZ3B,EAAOQ,KAAK,CAAEC,KAAM,KAAME,UAAW,OAInC,OAAOC,KAAKgB,EAAKC,aAAc,CACjC7B,EAAOQ,KAAK,CAAEC,KAAMmB,EAAMjB,UAAW,uBACrCe,GAAeA,EACf,QACF,CAGA,GAAIA,EAAa,CACf1B,EAAOQ,KAAK,CAAEC,KAAMmB,EAAMjB,UAAW,iBACrC,QACF,CAGA,GAAI,YAAYC,KAAKgB,GAAO,CAC1B5B,EAAOQ,KAAK,CAAEC,KAAMmB,EAAMjB,UAAW,oBACrC,QACF,CAGA,GAAI,0BAA0BC,KAAKgB,GAAO,CACxC5B,EAAOQ,KAAK,CAAEC,KAAMmB,EAAMjB,UAAW,eACrC,QACF,CAGA,GAAI,QAAQC,KAAKgB,GAAO,CACtB,MAAME,EAAcF,EAAKG,MAAM,WAC/B/B,EAAOQ,KAAK,CAAEC,KAAMqB,EAAY,GAAInB,UAAW,uBAC/CqB,EAAgBJ,EAAKlB,MAAMoB,EAAY,GAAG3B,QAASH,GACnD,QACF,CAGA,MAAMiC,EAAUL,EAAKG,MAAM,kBAC3B,GAAIE,EAAS,CACXjC,EAAOQ,KAAK,CAAEC,KAAMwB,EAAQ,GAAItB,UAAW,wBAC3CqB,EAAgBJ,EAAKlB,MAAMuB,EAAQ,GAAG9B,QAASH,GAC/C,QACF,CAGA,MAAMkC,EAAUN,EAAKG,MAAM,oBACvBG,GACFlC,EAAOQ,KAAK,CAAEC,KAAMyB,EAAQ,GAAIvB,UAAW,wBAC3CqB,EAAgBJ,EAAKlB,MAAMwB,EAAQ,GAAG/B,QAASH,IAK7C,iBAAiBY,KAAKgB,EAAKO,QAC7BnC,EAAOQ,KAAK,CAAEC,KAAMmB,EAAMjB,UAAW,eAKvCqB,EAAgBJ,EAAM5B,EACxB,CAEA,OAAOA,CACT,CAMA,SAASgC,EAAgBvB,EAAMT,GAC7B,IAAIC,EAAI,EACR,MAAMC,EAAMO,EAAKN,OACjB,IAAIiC,EAAa,EAEjB,KAAOnC,EAAIC,GAAK,CAEd,GAAgB,MAAZO,EAAKR,GAAY,CACfA,EAAImC,GACNpC,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM0B,EAAYnC,GAAIU,UAAW,OAE5D,MAAMN,EAAMI,EAAKH,QAAQ,IAAKL,EAAI,GAClC,IAAY,IAARI,EAAY,CACdL,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAMT,EAAGI,EAAM,GAAIM,UAAW,iBACvDV,EAAII,EAAM,EACV+B,EAAanC,EACb,QACF,CACF,CAGA,GAAgB,MAAZQ,EAAKR,IAA8B,MAAhBQ,EAAKR,EAAI,GAAY,CAC1C,MAAMoC,EAAe5B,EAAKH,QAAQ,IAAKL,EAAI,GAC3C,IAAqB,IAAjBoC,GAAkD,MAA3B5B,EAAK4B,EAAe,GAAY,CACzD,MAAMC,EAAa7B,EAAKH,QAAQ,IAAK+B,EAAe,GACpD,IAAmB,IAAfC,EAAmB,CACjBrC,EAAImC,GACNpC,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM0B,EAAYnC,GAAIU,UAAW,OAE5DX,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAMT,EAAGqC,EAAa,GAAI3B,UAAW,kBAC9DV,EAAIqC,EAAa,EACjBF,EAAanC,EACb,QACF,CACF,CACF,CAGA,GAAgB,MAAZQ,EAAKR,GAAY,CACnB,MAAMoC,EAAe5B,EAAKH,QAAQ,IAAKL,EAAI,GAC3C,IAAqB,IAAjBoC,GAAkD,MAA3B5B,EAAK4B,EAAe,GAAY,CACzD,MAAMC,EAAa7B,EAAKH,QAAQ,IAAK+B,EAAe,GACpD,IAAmB,IAAfC,EAAmB,CACjBrC,EAAImC,GACNpC,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM0B,EAAYnC,GAAIU,UAAW,OAG5DX,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAMT,EAAGoC,EAAe,GAAI1B,UAAW,iBAEhEX,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM2B,EAAe,EAAGC,EAAa,GAAI3B,UAAW,gBAC7EV,EAAIqC,EAAa,EACjBF,EAAanC,EACb,QACF,CACF,CACF,CAGA,GAAiB,MAAZQ,EAAKR,IAA8B,MAAhBQ,EAAKR,EAAI,IAA4B,MAAZQ,EAAKR,IAA8B,MAAhBQ,EAAKR,EAAI,GAAa,CACxF,MAAMsC,EAAS9B,EAAKC,MAAMT,EAAGA,EAAI,GAC3BI,EAAMI,EAAKH,QAAQiC,EAAQtC,EAAI,GACrC,IAAY,IAARI,EAAY,CACVJ,EAAImC,GACNpC,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM0B,EAAYnC,GAAIU,UAAW,OAE5DX,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAMT,EAAGI,EAAM,GAAIM,UAAW,iBACvDV,EAAII,EAAM,EACV+B,EAAanC,EACb,QACF,CACF,CAGA,IAAiB,MAAZQ,EAAKR,IAA0B,MAAZQ,EAAKR,KAAeQ,EAAKR,EAAI,KAAOQ,EAAKR,GAAI,CACnE,MAAMsC,EAAS9B,EAAKR,GACdI,EAAMI,EAAKH,QAAQiC,EAAQtC,EAAI,GACrC,IAAY,IAARI,GAAcA,EAAMJ,EAAI,EAAG,CACzBA,EAAImC,GACNpC,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM0B,EAAYnC,GAAIU,UAAW,OAE5DX,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAMT,EAAGI,EAAM,GAAIM,UAAW,mBACvDV,EAAII,EAAM,EACV+B,EAAanC,EACb,QACF,CACF,CAEAA,GACF,CAGImC,EAAalC,GACfF,EAAOQ,KAAK,CAAEC,KAAMA,EAAKC,MAAM0B,GAAazB,UAAW,MAE3D,CC3KO,SAAS6B,GAAWC,MAAEA,EAAAC,SAAOA,EAAAC,SAAUA,EAAW,SACvD,MAAMC,EAAcC,EAAO,MACrBC,EAASD,EAAO,MAChBE,EAAYF,EAAO,MACnBG,EAAoBH,EAAO,OAG1BI,EAAgBC,GAAqBC,EAASV,GAAS,IAE9DW,GAAU,KACRJ,EAAkBK,QAAUC,YAAW,KACrCJ,EAAkBT,GAAS,GAAE,GAbL,KAenB,IAAMc,aAAaP,EAAkBK,WAC3C,CAACZ,IAGJ,MAAMe,EAAcC,GAAQ,KACO,aAAbd,EAA0BpB,EAAoBzB,GAC/CmD,IAClB,CAACA,EAAgBN,IAGde,EAAYD,GAAQ,KAChBhB,GAAS,IAAIhB,MAAM,MAAMtB,QAChC,CAACsC,IAGEkB,EAAeC,GAAY,KAC/B,MAAMC,EAAKjB,EAAYS,QAClBQ,IACDf,EAAOO,UACTP,EAAOO,QAAQS,UAAYD,EAAGC,UAC9BhB,EAAOO,QAAQU,WAAaF,EAAGE,YAE7BhB,EAAUM,UACZN,EAAUM,QAAQS,UAAYD,EAAGC,WACnC,GACC,IAGGE,EAAgBJ,GAAaK,IACjC,GAAc,QAAVA,EAAEC,IAAe,CACnBD,EAAEE,iBACF,MAAMN,EAAKI,EAAEG,OACPC,EAAQR,EAAGS,eACXjE,EAAMwD,EAAGU,aAEf,GAA+B,mBAApBV,EAAGW,aACZX,EAAGW,aAAa,KAAMH,EAAOhE,EAAK,OAClCwD,EAAGY,cAAc,IAAIC,MAAM,QAAS,CAAEC,SAAS,SAC1C,CACL,MAAMC,EAAWnC,EAAMoC,UAAU,EAAGR,GAAS,KAAO5B,EAAMoC,UAAUxE,GACpEqC,EAASkC,GACTE,uBAAsB,KACpBjB,EAAGS,eAAiBT,EAAGU,aAAeF,EAAQ,CAAA,GAElD,CACF,IACC,CAAC5B,EAAOC;AAEX,OACEqC,EAAC,MAAA,CAAIpE,UAAU,kBAEbqE,SAAA;eAAAC,EAAC,MAAA,CAAIC,IAAKnC,EAAWpC,UAAU,kBAAkB,cAAY,OAC1DqE,SAAAG,MAAMC,KAAK,CAAEjF,OAAQuD,IAAa,CAAC2B,EAAGpF,mBACrCgF,EAAC,MAAA,CAAYtE,UAAU,uBAAwBqE,SAAA/E,EAAI,GAAzCA;eAKd8E,EAAC,MAAA,CAAIpE,UAAU,gBACbqE,SAAA;eAAAC,EAAC,MAAA,CAAIC,IAAKpC,EAAQnC,UAAU,qBAAqB,cAAY,OAC3DqE,0BAAC,OAAA,CACEA,SAAA,CAAAxB,EAAY8B,KAAI,CAACC,EAAOtF,IACvBsF,EAAM5E,2BACD,OAAA,CAAaA,UAAW4E,EAAM5E,UAAYqE,SAAAO,EAAM9E,MAAtCR,GACXsF,EAAM9E,OAEX;eAILwE,EAAC,WAAA,CACCC,IAAKtC,EACLjC,UAAU,iBACV8B,QACAC,SAAWuB,GAAMvB,EAASuB,EAAEG,OAAO3B,OACnC+C,SAAU7B,EACV8B,UAAWzB,EACX0B,YAAY,EACZC,eAAe,MACfC,aAAa,MACbC,YAAY,MACZ,aAAW,eAKrB,CCpGO,SAASC,GAAYC,KAAEA,EAAAC,QAAMA,EAAAC,OAASA,IAC3C,MAAOC,EAAQC,GAAahD,EAAS,KAC9BiD,EAAeC,GAAoBlD,EAAS,IAC7CmD,EAAsC,aAAzBL,GAAQM,aAE3BnD,GAAU,KACR,GAAI2C,GAAQE,EAAQ,CAClB,MAAMO,EAAOP,EAAOQ,UACdC,EAAYJ,EAAaK,EAAeH,GAAQI,EAAWJ,GACjEL,EAAUO,GACVL,EAAiBK,EACnB,IACC,CAACX,EAAME,EAAQK,IAElB,MAiBMO,EAAcjD,GAAY,KAC9B,GAAIsC,IAAWE,EAAe,CAE5B,IADkBU,OAAOC,QAAQ,2CACjB,MAClB,CACAf,GAAA,GACC,CAACE,EAAQE,EAAeJ;AAE3B,SACGgB,EAAA,CAAaC,MAAOX,EAAa,kBAAoB,cAAeP,OAAYC,QAASa,EAAaK,MAAO,IAC5GlC,wBAAAD,EAAC,MAAA,CAAIpE,UAAU,iBACbqE,SAAA;eAAAC,EAACzC,EAAA,CACCC,MAAOyD,EACPxD,SAAUyD,EACVxD,SAAU2D,EAAa,WAAa;eAEtCvB,EAAC,MAAA,CAAIpE,UAAU,oBACbqE,SAAA;eAAAC,EAAC,UAAOkC,KAAK,SAASxG,UAAU,UAAUyG,QAASP,EAAa7B,SAAA;eAChEC,EAAC,UAAOkC,KAAK,SAASxG,UAAU,0BAA0ByG,QAnC9C,KAClBnB,EAAOoB,QAAQC,WACf,MAAMC,EAAUjB,EAAakB,EAAetB,GAAUA,EAEhDuB,EAAcxB,EAAOyB,UAAUC,SAASJ,GAE1CE,IAAgBF,GAClBtB,EAAO2B,SAASC,KAAK,mBAAoB,CACvCC,QAAS,gEAGb7B,EAAO8B,QAAQN,GACfxB,EAAO2B,SAASC,KAAK,kBACrB7B,GAAA,EAsBsFhB,SAAA,iBAK1F,CAEAc,EAAYkC,UAAY,CACtBjC,KAAMkC,EAAUC,KAAKC,WACrBnC,QAASiC,EAAUG,KAAKD,WACxBlC,OAAQgC,EAAUI"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import{jsxs as e,jsx as r}from"react/jsx-runtime";import{useState as a,useCallback as t}from"react";import{P as l}from"./index-C720tbJA.js";import{M as o}from"./ModalOverlay-CLvRNHmp.js";function s({open:l,onClose:s,engine:n}){const[m,i]=a({row:0,col:0}),[c,d]=a(3),[p,x]=a(3),[u,b]=a(null),f=t((e=>{const r=e.target.closest("[data-row]");if(!r)return;const a=parseInt(r.dataset.row,10)+1,t=parseInt(r.dataset.col,10)+1;i({row:a,col:t}),d(a),x(t)}),[]),h=t((e=>{const r=e.target.closest("[data-row]");if(!r)return;const a=parseInt(r.dataset.row,10)+1,t=parseInt(r.dataset.col,10)+1;try{b(null),n.executeCommand("insertTable",{rows:a,cols:t}),s()}catch(l){b(l.message||"Failed to insert table")}}),[n,s]);/* @__PURE__ */
|
|
2
|
+
return e(o,{title:"Insert Table",open:l,onClose:s,width:320,children:[
|
|
3
|
+
/* @__PURE__ */e("div",{className:"rmx-table-picker",children:[
|
|
4
|
+
/* @__PURE__ */r("div",{className:"rmx-table-grid",onMouseOver:f,onClick:h,"aria-hidden":"true",children:Array.from({length:10},((e,a)=>/* @__PURE__ */r("div",{className:"rmx-table-grid-row",children:Array.from({length:10},((e,t)=>/* @__PURE__ */r("div",{"data-row":a,"data-col":t,className:"rmx-table-grid-cell "+(a<m.row&&t<m.col?"rmx-active":"")},t)))},a)))}),
|
|
5
|
+
/* @__PURE__ */e("div",{className:"rmx-table-picker-info",children:[c," x ",p]})]}),
|
|
6
|
+
/* @__PURE__ */e("div",{className:"rmx-modal-form",style:{marginTop:12},children:[
|
|
7
|
+
/* @__PURE__ */e("div",{style:{display:"flex",gap:8,alignItems:"center"},children:[
|
|
8
|
+
/* @__PURE__ */e("div",{className:"rmx-form-group",style:{flex:1},children:[
|
|
9
|
+
/* @__PURE__ */r("label",{className:"rmx-form-label",htmlFor:"rmx-table-rows",children:"Rows"}),
|
|
10
|
+
/* @__PURE__ */r("input",{id:"rmx-table-rows",type:"number",className:"rmx-form-input",value:c,min:1,max:20,onChange:e=>d(parseInt(e.target.value)||1)})]}),
|
|
11
|
+
/* @__PURE__ */e("div",{className:"rmx-form-group",style:{flex:1},children:[
|
|
12
|
+
/* @__PURE__ */r("label",{className:"rmx-form-label",htmlFor:"rmx-table-cols",children:"Columns"}),
|
|
13
|
+
/* @__PURE__ */r("input",{id:"rmx-table-cols",type:"number",className:"rmx-form-input",value:p,min:1,max:20,onChange:e=>x(parseInt(e.target.value)||1)})]})]}),u&&/* @__PURE__ */r("div",{className:"rmx-form-error",style:{color:"#d32f2f",fontSize:13,marginBottom:8},children:u}),
|
|
14
|
+
/* @__PURE__ */e("div",{className:"rmx-modal-actions",children:[
|
|
15
|
+
/* @__PURE__ */r("button",{type:"button",className:"rmx-btn",onClick:s,children:"Cancel"}),
|
|
16
|
+
/* @__PURE__ */r("button",{type:"button",className:"rmx-btn rmx-btn-primary",onClick:()=>{try{b(null),n.executeCommand("insertTable",{rows:c,cols:p}),s()}catch(e){b(e.message||"Failed to insert table")}},children:"Insert"})]})]})]})}s.propTypes={open:l.bool.isRequired,onClose:l.func.isRequired,engine:l.object};export{s as TablePickerModal};
|
|
17
|
+
//# sourceMappingURL=TablePickerModal-DYODWEA1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TablePickerModal-DYODWEA1.js","sources":["../src/components/Modals/TablePickerModal.jsx"],"sourcesContent":["import { useState, useCallback } from 'react'\nimport PropTypes from 'prop-types'\nimport { ModalOverlay } from './ModalOverlay.jsx'\n\nconst MAX_SIZE = 10\n\nexport function TablePickerModal({ open, onClose, engine }) {\n const [hover, setHover] = useState({ row: 0, col: 0 })\n const [rows, setRows] = useState(3)\n const [cols, setCols] = useState(3)\n const [error, setError] = useState(null)\n\n const handleInsert = () => {\n try {\n setError(null)\n engine.executeCommand('insertTable', { rows, cols })\n onClose()\n } catch (err) {\n setError(err.message || 'Failed to insert table')\n }\n }\n\n // Event delegation handlers for the grid container\n const handleGridMouseOver = useCallback((e) => {\n const cell = e.target.closest('[data-row]')\n if (!cell) return\n const r = parseInt(cell.dataset.row, 10) + 1\n const c = parseInt(cell.dataset.col, 10) + 1\n setHover({ row: r, col: c })\n setRows(r)\n setCols(c)\n }, [])\n\n const handleGridClick = useCallback((e) => {\n const cell = e.target.closest('[data-row]')\n if (!cell) return\n const r = parseInt(cell.dataset.row, 10) + 1\n const c = parseInt(cell.dataset.col, 10) + 1\n try {\n setError(null)\n engine.executeCommand('insertTable', { rows: r, cols: c })\n onClose()\n } catch (err) {\n setError(err.message || 'Failed to insert table')\n }\n }, [engine, onClose])\n\n return (\n <ModalOverlay title=\"Insert Table\" open={open} onClose={onClose} width={320}>\n <div className=\"rmx-table-picker\">\n <div className=\"rmx-table-grid\" onMouseOver={handleGridMouseOver} onClick={handleGridClick} aria-hidden=\"true\">\n {Array.from({ length: MAX_SIZE }, (_, r) => (\n <div key={r} className=\"rmx-table-grid-row\">\n {Array.from({ length: MAX_SIZE }, (_, c) => (\n <div\n key={c}\n data-row={r}\n data-col={c}\n className={`rmx-table-grid-cell ${r < hover.row && c < hover.col ? 'rmx-active' : ''}`}\n />\n ))}\n </div>\n ))}\n </div>\n <div className=\"rmx-table-picker-info\">{rows} x {cols}</div>\n </div>\n <div className=\"rmx-modal-form\" style={{ marginTop: 12 }}>\n <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>\n <div className=\"rmx-form-group\" style={{ flex: 1 }}>\n <label className=\"rmx-form-label\" htmlFor=\"rmx-table-rows\">Rows</label>\n <input id=\"rmx-table-rows\" type=\"number\" className=\"rmx-form-input\" value={rows} min={1} max={20}\n onChange={(e) => setRows(parseInt(e.target.value) || 1)} />\n </div>\n <div className=\"rmx-form-group\" style={{ flex: 1 }}>\n <label className=\"rmx-form-label\" htmlFor=\"rmx-table-cols\">Columns</label>\n <input id=\"rmx-table-cols\" type=\"number\" className=\"rmx-form-input\" value={cols} min={1} max={20}\n onChange={(e) => setCols(parseInt(e.target.value) || 1)} />\n </div>\n </div>\n {error && <div className=\"rmx-form-error\" style={{ color: '#d32f2f', fontSize: 13, marginBottom: 8 }}>{error}</div>}\n <div className=\"rmx-modal-actions\">\n <button type=\"button\" className=\"rmx-btn\" onClick={onClose}>Cancel</button>\n <button type=\"button\" className=\"rmx-btn rmx-btn-primary\" onClick={handleInsert}>Insert</button>\n </div>\n </div>\n </ModalOverlay>\n )\n}\n\nTablePickerModal.propTypes = {\n open: PropTypes.bool.isRequired,\n onClose: PropTypes.func.isRequired,\n engine: PropTypes.object,\n}\n"],"names":["TablePickerModal","open","onClose","engine","hover","setHover","useState","row","col","rows","setRows","cols","setCols","error","setError","handleGridMouseOver","useCallback","e","cell","target","closest","r","parseInt","dataset","c","handleGridClick","executeCommand","err","message","ModalOverlay","title","width","children","jsxs","className","jsx","onMouseOver","onClick","Array","from","length","_","style","marginTop","display","gap","alignItems","flex","htmlFor","id","type","value","min","max","onChange","color","fontSize","marginBottom","propTypes","PropTypes","bool","isRequired","func","object"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),r=require("react"),s=require("./index-Dc63uIP0.cjs"),a=require("./ModalOverlay-BDsGgv3_.cjs");function t({open:s,onClose:t,engine:l}){const[o,n]=r.useState({row:0,col:0}),[i,c]=r.useState(3),[m,d]=r.useState(3),[x,u]=r.useState(null),p=r.useCallback((e=>{const r=e.target.closest("[data-row]");if(!r)return;const s=parseInt(r.dataset.row,10)+1,a=parseInt(r.dataset.col,10)+1;n({row:s,col:a}),c(s),d(a)}),[]),b=r.useCallback((e=>{const r=e.target.closest("[data-row]");if(!r)return;const s=parseInt(r.dataset.row,10)+1,a=parseInt(r.dataset.col,10)+1;try{u(null),l.executeCommand("insertTable",{rows:s,cols:a}),t()}catch(o){u(o.message||"Failed to insert table")}}),[l,t]);return e.jsxs(a.ModalOverlay,{title:"Insert Table",open:s,onClose:t,width:320,children:[e.jsxs("div",{className:"rmx-table-picker",children:[e.jsx("div",{className:"rmx-table-grid",onMouseOver:p,onClick:b,"aria-hidden":"true",children:Array.from({length:10},((r,s)=>e.jsx("div",{className:"rmx-table-grid-row",children:Array.from({length:10},((r,a)=>e.jsx("div",{"data-row":s,"data-col":a,className:"rmx-table-grid-cell "+(s<o.row&&a<o.col?"rmx-active":"")},a)))},s)))}),e.jsxs("div",{className:"rmx-table-picker-info",children:[i," x ",m]})]}),e.jsxs("div",{className:"rmx-modal-form",style:{marginTop:12},children:[e.jsxs("div",{style:{display:"flex",gap:8,alignItems:"center"},children:[e.jsxs("div",{className:"rmx-form-group",style:{flex:1},children:[e.jsx("label",{className:"rmx-form-label",htmlFor:"rmx-table-rows",children:"Rows"}),e.jsx("input",{id:"rmx-table-rows",type:"number",className:"rmx-form-input",value:i,min:1,max:20,onChange:e=>c(parseInt(e.target.value)||1)})]}),e.jsxs("div",{className:"rmx-form-group",style:{flex:1},children:[e.jsx("label",{className:"rmx-form-label",htmlFor:"rmx-table-cols",children:"Columns"}),e.jsx("input",{id:"rmx-table-cols",type:"number",className:"rmx-form-input",value:m,min:1,max:20,onChange:e=>d(parseInt(e.target.value)||1)})]})]}),x&&e.jsx("div",{className:"rmx-form-error",style:{color:"#d32f2f",fontSize:13,marginBottom:8},children:x}),e.jsxs("div",{className:"rmx-modal-actions",children:[e.jsx("button",{type:"button",className:"rmx-btn",onClick:t,children:"Cancel"}),e.jsx("button",{type:"button",className:"rmx-btn rmx-btn-primary",onClick:()=>{try{u(null),l.executeCommand("insertTable",{rows:i,cols:m}),t()}catch(e){u(e.message||"Failed to insert table")}},children:"Insert"})]})]})]})}t.propTypes={open:s.PropTypes.bool.isRequired,onClose:s.PropTypes.func.isRequired,engine:s.PropTypes.object},exports.TablePickerModal=t;
|
|
2
|
+
//# sourceMappingURL=TablePickerModal-Do1QyoyM.cjs.map
|