UNPKG

prism-code-editor

Version:

Lightweight, extensible code editor component for the web using Prism

231 lines (230 loc) 9.99 kB
import { a as createTemplate, i as isMac, b as addTextareaListener, d as isChrome, p as preventDefault, e as isWebKit, n as numLines } from "./index-Bb4AMnd0.js"; import { h as addListener, a as getModifierCode, d as getLineStart, e as getLineEnd, r as regexEscape } from "./index-2teoWRgh.js"; import { createReplaceAPI } from "./extensions/search/api.js"; const shortcut = ` (Alt+${isMac ? "Cmd+" : ""}`; const template = createTemplate( `<div class=prism-search-container style=display:none;align-items:flex-start;justify-content:flex-end><div dir=ltr class=prism-search><button type=button aria-expanded=false title="Toggle Replace" class=pce-expand></button><div spellcheck=false><div><div class="pce-input pce-find"><input autocorrect=off autocapitalize=off placeholder=Find aria-label=Find><button type=button class=prev-match title="Previous Match (Shift+Enter)"></button><button type=button class=next-match title="Next Match (Enter)"></button><div class=search-error></div></div><button type=button class=pce-close title="Close (Esc)"></button></div><div class="pce-input pce-replace"><input autocorrect=off autocapitalize=off placeholder=Replace aria-label=Replace><button type=button title=(Enter)>Replace</button><button type=button title=(${isMac ? "Cmd" : "Ctrl+Alt"}+Enter)>All</button></div><div class=pce-options><div class=pce-match-count>0<span> of </span>0</div><button type=button aria-pressed=false class=pce-regex title="RegExp Search${shortcut}R)"><span aria-hidden=true></span></button><button type=button aria-pressed=false title="Preserve Case${shortcut}P)"><span aria-hidden=true>Aa</span></button><button type=button aria-pressed=false class=pce-whole title="Match Whole Word${shortcut}W)"><span aria-hidden=true>ab</span></button><button type=button aria-pressed=false class=pce-in-selection title="Find in Selection${shortcut}L)">` ); const toggleAttr = (el, name) => el.setAttribute(name, el.getAttribute(name) == "false"); const getStyleValue = (el, prop) => parseFloat(getComputedStyle(el)[prop]); const searchWidget = () => { let prevLength, useRegExp, matchCase, wholeWord, searchSelection, isOpen, currentSelection, prevUserSelection, prevMargin, selectNext = false, marginTop, removeUpdateHandler, removeSelectionHandler; const self = (editor) => { editor.extensions.searchWidget = self; const { textarea, wrapper, overlays, scrollContainer, getSelection } = editor; const replaceAPI = createReplaceAPI(editor); const startSearch = (selectMatch) => { if (selectMatch && !isWebKit) textarea.setSelectionRange(...prevUserSelection); const error = replaceAPI.search( findInput.value, matchCase, wholeWord, useRegExp, searchSelection ); const index = error ? -1 : selectNext ? replaceAPI.next() : replaceAPI.closest(); current.data = index + 1; total.data = replaceAPI.matches.length; findContainer.classList.toggle("pce-error", !!error); if (error) errorEl.textContent = error; else if (selectMatch || selectNext) replaceAPI.selectMatch(index, prevMargin); }; const keydown = (e) => { if (e.keyCode >> 1 == 35 && getModifierCode(e) == (isMac ? 4 : 2)) { preventDefault(e); open(); let [start, end] = getSelection(), value = editor.value, word = value.slice(start, end) || value.slice(0, start).match(/[_\p{N}\p{L}]*$/u)[0] + value.slice(start).match(/^[_\p{N}\p{L}]*/u)[0]; if (/^$|\n/.test(word)) startSearch(); else { if (useRegExp) word = regexEscape(word); document.execCommand("insertText", false, word); findInput.select(); } } }; const selectionChange = (selection) => { if (editor.focused) prevUserSelection = selection; }; const beforeinput = () => { if (searchSelection) currentSelection = getSelection(); }; const input = () => { if (searchSelection && currentSelection) { const diff = prevLength - (prevLength = editor.value.length); const [, end] = currentSelection; const [searchStart, searchEnd] = searchSelection; if (end <= searchEnd) { searchSelection[1] -= diff; if (end <= searchStart - +(diff < 0)) searchSelection[0] -= diff; } } startSearch(); }; const open = (focusInput = true) => { if (!isOpen) { isOpen = true; if (marginTop == null) prevMargin = marginTop = getStyleValue(wrapper, "marginTop"); removeUpdateHandler = addListener(editor, "update", input); removeSelectionHandler = addListener(editor, "selectionChange", selectionChange); prevUserSelection = getSelection(); addTextareaListener(editor, "beforeinput", beforeinput); container.style.display = "flex"; updateMargin(); resize(); observer?.observe(scrollContainer); } if (focusInput) findInput.select(); }; const close = self.close = (focusTextarea = true) => { if (isOpen) { isOpen = false; replaceAPI.stopSearch(); removeUpdateHandler(); removeSelectionHandler(); textarea.removeEventListener("beforeinput", beforeinput); container.style.display = "none"; updateMargin(); observer?.disconnect(); focusTextarea && textarea.focus(); } }; const move = (next) => { if (replaceAPI.matches[0]) { const index = replaceAPI[next ? "next" : "prev"](); replaceAPI.selectMatch(index, prevMargin); current.data = index + 1; } }; const updateMargin = () => { const newMargin = isOpen ? getStyleValue(search, "top") + getStyleValue(search, "height") : marginTop; const newScroll = scrollContainer.scrollTop + newMargin - prevMargin; wrapper.style.marginTop = newMargin + "px"; scrollContainer.scrollTop = newScroll; prevMargin = newMargin; }; const resize = () => div.style.setProperty( "--search-width", `min(${scrollContainer.clientWidth - 2}px - 2.4em - var(--padding-left),20em)` ); const observer = window.ResizeObserver && new ResizeObserver(resize); const replace = () => { selectNext = true; const index = replaceAPI.replace(replaceInput.value); if (index != null) { current.data = index + 1; replaceAPI.selectMatch(index, prevMargin); } selectNext = false; }; const replaceAll = () => { replaceAPI.replaceAll(replaceInput.value); }; const keyCodeButtonMap = { 80: matchCaseEl, 87: wholeWordEl, 82: useRegExpEl, 76: inSelectionEl }; const elementHandlerMap = /* @__PURE__ */ new Map([ [nextEl, () => move(true)], [prevEl, move], [closeEl, close], [replaceEl, replace], [replaceAllEl, replaceAll], [ toggle, () => { toggleAttr(toggle, "aria-expanded"); updateMargin(); } ], [matchCaseEl, () => matchCase = !matchCase], [useRegExpEl, () => useRegExp = !useRegExp], [wholeWordEl, () => wholeWord = !wholeWord], [ inSelectionEl, () => { const value = editor.value; if (searchSelection) searchSelection = void 0; else { searchSelection = getSelection().slice(0, 2); if (numLines(value, ...searchSelection) > 1) { searchSelection = [ getLineStart(value, searchSelection[0]), getLineEnd(value, searchSelection[1]) ]; } } prevLength = value.length; } ] ]); addTextareaListener(editor, "keydown", keydown); isChrome && container.addEventListener("focusin", (e) => { if (!container.contains(e.relatedTarget)) { findInput.focus(); e.target.focus(); } }); container.addEventListener("click", (e) => { const target = e.target; const remove = addListener(editor, "update", () => target.focus()); elementHandlerMap.get(target)?.(); if (target.matches(".pce-options>button")) { toggleAttr(target, "aria-pressed"); startSearch(true); } remove(); }); findInput.oninput = () => isOpen && startSearch(true); container.addEventListener("keydown", (e) => { const shortcut2 = getModifierCode(e); const target = e.target; const keyCode = e.keyCode; const isFind = target == findInput; if (shortcut2 == (isMac ? 5 : 1)) { if (keyCodeButtonMap[keyCode]) { preventDefault(e); keyCodeButtonMap[keyCode].click(); } } else if (keyCode == 13 && target.tagName == "INPUT") { preventDefault(e); if (!shortcut2) isFind ? move(true) : replaceEl.click(); else if (shortcut2 == 8 && isFind) move(); else if (shortcut2 == (isMac ? 4 : 3) && !isFind) replaceAllEl.click(); target.focus(); } else if (!shortcut2 && keyCode == 27) close(); else keydown(e); }); self.open = (focusInput) => { open(focusInput); startSearch(); }; overlays.append(container); replaceAPI.container.className = "pce-matches"; }; const container = template(), search = self.element = container.firstChild, [toggle, div] = search.children, rows = div.children, [findContainer, closeEl] = rows[0].children, [findInput, prevEl, nextEl, errorEl] = findContainer.children, [replaceInput, replaceEl, replaceAllEl] = rows[1].children, [matchCount, useRegExpEl, matchCaseEl, wholeWordEl, inSelectionEl] = rows[2].children, [current, , total] = matchCount.childNodes; self.open = self.close = () => { }; return self; }; export { searchWidget as s }; //# sourceMappingURL=widget-DYgH2uGJ.js.map