prism-code-editor
Version:
Lightweight, extensible code editor component for the web using Prism
231 lines (230 loc) • 9.99 kB
JavaScript
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