UNPKG

modern-text

Version:

Measure and render text in a way that describes the DOM.

722 lines (711 loc) 34.3 kB
import { diffChars } from 'diff'; import { isCRLF, textContentToString, normalizeCRLF, normalizeTextContent, property } from 'modern-idoc'; import { T as Text } from '../shared/modern-text.Dd6yqc4a.mjs'; import 'modern-font'; import 'modern-path2d'; import '../shared/modern-text.C0w4dLp7.mjs'; var __defProp = Object.defineProperty; var __decorateClass = (decorators, target, key, kind) => { var result = void 0 ; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (decorator(target, key, result) ) || result; if (result) __defProp(target, key, result); return result; }; function normalizeStyle(style) { const newStyle = {}; for (const key in style) { if (key !== "id" && style[key] !== void 0 && style[key] !== "") { newStyle[key] = style[key]; } } return newStyle; } function textContentToCharStyles(textContent) { return textContent.flatMap((p) => { const { fragments } = p; const res = fragments.flatMap((f) => { const { content, ...fStyle } = f; return Array.from(normalizeCRLF(content)).map(() => ({ ...fStyle })); }); if (isCRLF(normalizeCRLF(fragments[fragments.length - 1]?.content ?? ""))) { return res; } return [...res, {}]; }); } function isEqualStyle(style1, style2) { const keys1 = Object.keys(style1); const keys2 = Object.keys(style2); const keys = Array.from(/* @__PURE__ */ new Set([...keys1, ...keys2])); return !keys.length || keys.every((key) => style1[key] === style2[key]); } const emojiRE = /[#*0-9]\uFE0F?\u20E3|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26AA\u26B0\u26B1\u26BD\u26BE\u26C4\u26C8\u26CF\u26D1\u26E9\u26F0-\u26F5\u26F7\u26F8\u26FA\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B55\u3030\u303D\u3297\u3299]\uFE0F?|[\u261D\u270C\u270D](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\u270A\u270B](?:\uD83C[\uDFFB-\uDFFF])?|[\u23E9-\u23EC\u23F0\u23F3\u25FD\u2693\u26A1\u26AB\u26C5\u26CE\u26D4\u26EA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2795-\u2797\u27B0\u27BF\u2B50]|\u26D3\uFE0F?(?:\u200D\uD83D\uDCA5)?|\u26F9(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\u2764\uFE0F?(?:\u200D(?:\uD83D\uDD25|\uD83E\uDE79))?|\uD83C(?:[\uDC04\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]\uFE0F?|[\uDF85\uDFC2\uDFC7](?:\uD83C[\uDFFB-\uDFFF])?|[\uDFC4\uDFCA](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDFCB\uDFCC](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF43\uDF45-\uDF4A\uDF4C-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|\uDF44(?:\u200D\uD83D\uDFEB)?|\uDF4B(?:\u200D\uD83D\uDFE9)?|\uDFC3(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDFF3\uFE0F?(?:\u200D(?:\u26A7\uFE0F?|\uD83C\uDF08))?|\uDFF4(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDC73\uDB40\uDC63\uDB40\uDC74|\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F)?)|\uD83D(?:[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3]\uFE0F?|[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4\uDEB5](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD74\uDD90](?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?|[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC25\uDC27-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE41\uDE43\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEDC-\uDEDF\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB\uDFF0]|\uDC08(?:\u200D\u2B1B)?|\uDC15(?:\u200D\uD83E\uDDBA)?|\uDC26(?:\u200D(?:\u2B1B|\uD83D\uDD25))?|\uDC3B(?:\u200D\u2744\uFE0F?)?|\uDC41\uFE0F?(?:\u200D\uD83D\uDDE8\uFE0F?)?|\uDC68(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDC68\uDC69]\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?\uDC68\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D\uDC68\uD83C[\uDFFB-\uDFFE])))?))?|\uDC69(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:\uDC8B\u200D\uD83D)?[\uDC68\uDC69]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D(?:[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?))|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFC-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFD-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFD\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D\uD83D(?:[\uDC68\uDC69]|\uDC8B\u200D\uD83D[\uDC68\uDC69])\uD83C[\uDFFB-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83D[\uDC68\uDC69]\uD83C[\uDFFB-\uDFFE])))?))?|\uDC6F(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDD75(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F)?(?:\u200D[\u2640\u2642]\uFE0F?)?|\uDE2E(?:\u200D\uD83D\uDCA8)?|\uDE35(?:\u200D\uD83D\uDCAB)?|\uDE36(?:\u200D\uD83C\uDF2B\uFE0F?)?|\uDE42(?:\u200D[\u2194\u2195]\uFE0F?)?|\uDEB6(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?)|\uD83E(?:[\uDD0C\uDD0F\uDD18-\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5\uDEC3-\uDEC5\uDEF0\uDEF2-\uDEF8](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD\uDDCF\uDDD4\uDDD6-\uDDDD](?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDDDE\uDDDF](?:\u200D[\u2640\u2642]\uFE0F?)?|[\uDD0D\uDD0E\uDD10-\uDD17\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCC\uDDD0\uDDE0-\uDDFF\uDE70-\uDE7C\uDE80-\uDE89\uDE8F-\uDEC2\uDEC6\uDECE-\uDEDC\uDEDF-\uDEE9]|\uDD3C(?:\u200D[\u2640\u2642]\uFE0F?|\uD83C[\uDFFB-\uDFFF])?|\uDDCE(?:\uD83C[\uDFFB-\uDFFF])?(?:\u200D(?:[\u2640\u2642]\uFE0F?(?:\u200D\u27A1\uFE0F?)?|\u27A1\uFE0F?))?|\uDDD1(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1|\uDDD1\u200D\uD83E\uDDD2(?:\u200D\uD83E\uDDD2)?|\uDDD2(?:\u200D\uD83E\uDDD2)?))|\uD83C(?:\uDFFB(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFC-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFC(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFD-\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFD(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFE(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFD\uDFFF]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?|\uDFFF(?:\u200D(?:[\u2695\u2696\u2708]\uFE0F?|\u2764\uFE0F?\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1\uD83C[\uDFFB-\uDFFE]|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E(?:[\uDDAF\uDDBC\uDDBD](?:\u200D\u27A1\uFE0F?)?|[\uDDB0-\uDDB3]|\uDD1D\u200D\uD83E\uDDD1\uD83C[\uDFFB-\uDFFF])))?))?|\uDEF1(?:\uD83C(?:\uDFFB(?:\u200D\uD83E\uDEF2\uD83C[\uDFFC-\uDFFF])?|\uDFFC(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFD-\uDFFF])?|\uDFFD(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])?|\uDFFE(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFD\uDFFF])?|\uDFFF(?:\u200D\uD83E\uDEF2\uD83C[\uDFFB-\uDFFE])?))?)/g; function parseHTML(html) { const template = document.createElement("template"); template.innerHTML = html; return template.content.cloneNode(true); } const SUPPORTS_POINTER_EVENTS = "PointerEvent" in globalThis; class TextEditor extends HTMLElement { _prevSelection = [0, 0]; _composition = false; get composition() { return this._composition; } static _defined = false; static register() { if (!this._defined) { this._defined = true; customElements.define("text-editor", this); } } _text = new Text(); get text() { return this._text; } set text(newText) { if (newText) { this._text?.off("update", this._update); this.reset(); newText.on("update", this._update); this._text = newText; this._setTextInput(this.getPlaintext()); this.text.update(); this._update(); } } _oldText = ""; _container; _selection; _textarea; _cursor; constructor() { super(); const shadowRoot = this.attachShadow({ mode: "open" }); shadowRoot.appendChild( parseHTML(` <style> :host { position: absolute; left: 0; top: 0; width: 0; height: 0; z-index: 1; } .container { position: absolute; } textarea { position: absolute; opacity: 0; caret-color: transparent; left: 0; top: 0; width: 100%; height: 100%; padding: 0; border: 0; white-space: pre; resize: none; overflow: hidden; } .selection { position: absolute; left: 0; top: 0; pointer-events: none; } .selection > * { position: absolute; left: 0; top: 0; background: rgba(var(--color, 0, 0, 0), 0.4); } .cursor { position: absolute; left: 0; top: 0; pointer-events: none; } .cursor.blink { animation: cursor-blink 1s steps(2, start) infinite; } @keyframes cursor-blink { 100% { visibility: hidden; } } </style> <div class="container"> <textarea></textarea> <div class="selection"></div> <div class="cursor blink"></div> </div> `) ); this._container = shadowRoot.querySelector(".container"); this._selection = shadowRoot.querySelector(".selection"); this._textarea = shadowRoot.querySelector("textarea"); this._cursor = shadowRoot.querySelector(".cursor"); this._bindEventListeners(); this._update = this._update.bind(this); } connectedCallback() { this._emit("init"); } moveToDom(target) { const style = getComputedStyle(target); const host = this.shadowRoot.host; host.style.width = `${target.clientWidth}px`; host.style.height = `${target.clientHeight}px`; host.style.transform = ""; const hostRect = host.getBoundingClientRect(); const hostCenter = { x: hostRect.x + hostRect.width / 2, y: hostRect.y + hostRect.height / 2 }; const targetRect = target.getBoundingClientRect(); const targetCenter = { x: targetRect.x + targetRect.width / 2, y: targetRect.y + targetRect.height / 2 }; this.left = targetCenter.x - hostCenter.x; this.top = targetCenter.y - hostCenter.y; const m = new DOMMatrixReadOnly(style.transform); this.rotate = Math.atan2(m.b, m.a) * (180 / Math.PI); this._update(); } set(text) { this.text = text; } onUpdateProperty(key, _newValue, _oldValue) { switch (key) { case "selection": this._selectionMinMax = { min: Math.min(...this.selection), max: Math.max(...this.selection) }; break; case "_selectionMinMax": case "_chars": this._updateSelectedChars(); this._updateCursorPosition(); break; case "_showCursor": case "_cursorPosition": this._renderCursor(); break; case "_selectedChars": this._renderCursor(); this._renderSelectRange(); break; case "left": case "top": case "rotate": this._update(); break; } } _updateChars() { const paragraphs = []; this.text.paragraphs.forEach((p, paragraphIndex) => { p.fragments.forEach((f, fragmentIndex) => { f.characters.forEach((c) => { if (!paragraphs[paragraphIndex]) paragraphs[paragraphIndex] = []; if (!paragraphs[paragraphIndex][fragmentIndex]) paragraphs[paragraphIndex][fragmentIndex] = []; paragraphs[paragraphIndex][fragmentIndex].push(c); }); }); }); const toIndexChar = (c) => { return { paragraphIndex: -1, fragmentIndex: -1, charIndex: -1, color: c.computedStyle.color, left: c.inlineBox.left, top: c.inlineBox.top, width: c.inlineBox.width, height: c.inlineBox.height, content: c.content }; }; const chars = []; let pos = 0; paragraphs.forEach((p, paragraphIndex) => { if (p.length === 1 && p[0].length === 1 && isCRLF(p[0][0].content)) { const c = p[0][0]; chars[pos] = { ...toIndexChar(c), paragraphIndex, fragmentIndex: 0, charIndex: 0, isCrlf: true }; } else { p.forEach((f, fragmentIndex) => { f.forEach((c, charIndex) => { const char = toIndexChar(c); chars[pos] = { ...char, paragraphIndex, fragmentIndex, charIndex }; pos++; if (!isCRLF(c.content)) { chars[pos] = { ...char, paragraphIndex, fragmentIndex, charIndex, content: " ", isLastSelected: true }; } }); }); } pos++; }); if (chars[0]) { chars[0].isFirst = true; } if (chars[chars.length - 1]) { chars[chars.length - 1].isLast = true; } this._chars = chars; } _updateSelectedChars() { this._selectedChars = this._chars.filter((_char, index) => { return index >= this._selectionMinMax.min && index < this._selectionMinMax.max; }); this._emit("selected", [ this._chars[this._selectionMinMax.min], this._chars[this._selectionMinMax.max] ]); } _updateCursorPosition() { let left = 0; let top = 0; const char = this._chars[this._selectionMinMax.min]; if (char?.isLastSelected) { if (this.text.isVertical) { top += char?.height ?? 0; } else { left += char?.width ?? 0; } } left += char?.left ?? 0; top += char?.top ?? 0; this._cursorPosition = { color: char?.color, left, top, height: char?.height ?? 0, width: char?.width ?? 0 }; } getPlaintext() { return textContentToString( this._getNewContent( this.text.content ) ); } _getNewContent(content, newString = textContentToString(content), oldString = newString) { newString = normalizeCRLF(newString); newString = newString.replace(emojiRE, (emoji) => { if (Array.from(emoji).length > 1) { return "?"; } return emoji; }); oldString = normalizeCRLF(oldString); const oldStyles = textContentToCharStyles(content); const styles = []; let styleIndex = 0; let oldStyleIndex = 0; let prevOldStyle = {}; const changes = diffChars(oldString, newString); changes.forEach((change) => { const chars = Array.from(change.value); if (change.removed) { oldStyleIndex += chars.length; } else { chars.forEach(() => { if (change.added) { styles[styleIndex] = { ...prevOldStyle }; } else { prevOldStyle = normalizeStyle(oldStyles[oldStyleIndex]); styles[styleIndex] = { ...prevOldStyle }; oldStyleIndex++; } styleIndex++; }); } }); let charIndex = 0; const newContents = []; normalizeTextContent(newString).forEach((p, pI) => { const { fragments: _, ...pStyle } = content[pI] ?? {}; let newParagraph = { ...pStyle, fragments: [] }; let newFragment; p.fragments.forEach((f) => { Array.from(f.content).forEach((char) => { const style = styles[charIndex] ?? {}; if (newFragment) { const { content: _2, ..._style } = newFragment; if (isEqualStyle(style, _style)) { newFragment.content += char; } else { newParagraph.fragments.push(newFragment); newFragment = { ...style, content: char }; } } else { newFragment = { ...style, content: char }; } charIndex++; }); }); if (!isCRLF(p.fragments[p.fragments.length - 1]?.content ?? "")) { charIndex++; } if (newFragment) { newParagraph.fragments.push(newFragment); } if (newParagraph.fragments.length) { newContents.push(newParagraph); newParagraph = { ...pStyle, fragments: [] }; } }); return newContents; } _setTextInput(newText) { this._textarea.value = newText; this._oldText = newText; } _onInput() { const newText = this._textarea.value; this.text.content = this._getNewContent( this.text.content, newText, this._oldText ); this._oldText = newText; this.text.update(); this._emit("update"); } _timer; _onKeydown(e) { e.stopPropagation(); switch (e.key) { case "Escape": return this._textarea.blur(); } this._updateSelectionByDom(); setTimeout(() => this._updateSelectionByDom(), 0); setTimeout(() => this._updateSelectionByDom(), 100); } _onFocus() { this._showCursor = true; } _onBlur() { this._showCursor = false; } _findNearest(options) { const isVertical = this.text.isVertical; const { boundingBox } = this.text; const { xWeight = 1, yWeight = 1 } = options; const x = options.x + boundingBox.left; const y = options.y + boundingBox.top; const char = this._chars.reduce( (prev, current, index) => { const diff = Math.abs(current.left + current.width / 2 - x) * xWeight + Math.abs(current.top + current.height / 2 - y) * yWeight; if (diff < prev.diff) { return { diff, index, value: current }; } return prev; }, { diff: Number.MAX_SAFE_INTEGER, index: -1, value: void 0 } ); if (char?.value) { if ((isVertical ? y > char.value.top + char.value.height / 2 : x > char.value.left + char.value.width / 2) && !char.value.isCrlf && !char.value.isLastSelected) { return char.index + 1; } return char.index; } return -1; } _updateSelectionByDom() { if (this._composition) { this.selection = this._prevSelection; } else { let count = 0; const _selection = []; this._chars.forEach((char, index) => { if (count <= this._textarea.selectionStart) { _selection[0] = index; } else if (count <= this._textarea.selectionEnd) { _selection[1] = index; } count += char.content.length; }); const oldSelection = this.selection; this.selection = _selection; this._prevSelection = oldSelection; } } _updateDomSelection() { let start = 0; let end = 0; this._chars.forEach((char, index) => { if (index < this._selectionMinMax.min) { start += char.content.length; end = start; } else if (index < this._selectionMinMax.max) { end += char.content.length; } }); this._textarea.selectionStart = start; this._textarea.selectionEnd = end; } _update() { this._updateChars(); const { boundingBox, lineBox } = this.text; const host = this.shadowRoot.host; const radian = this.rotate * Math.PI / 180; const cos = Math.cos(radian); const sin = Math.sin(radian); host.style.transform = `matrix(${cos}, ${sin}, ${-sin}, ${cos}, ${this.left}, ${this.top})`; host.style.width = `${boundingBox.width}px`; host.style.height = `${boundingBox.height}px`; this._container.style.left = `${-boundingBox.left}px`; this._container.style.top = `${-boundingBox.top}px`; this._container.style.width = `${lineBox.width}px`; this._container.style.height = `${lineBox.height}px`; this._textarea.style.fontSize = `${this.text.computedStyle.fontSize}px`; this._textarea.style.writingMode = this.text.computedStyle.writingMode; this._renderSelectRange(); this._renderCursor(); } _bindEventListeners() { this._textarea.addEventListener("compositionstart", () => this._composition = true); this._textarea.addEventListener("compositionend", () => this._composition = false); this._textarea.addEventListener("keydown", this._onKeydown.bind(this)); this._textarea.addEventListener("input", this._onInput.bind(this)); this._textarea.addEventListener("focus", this._onFocus.bind(this)); this._textarea.addEventListener("blur", this._onBlur.bind(this)); if (SUPPORTS_POINTER_EVENTS) { this._textarea.addEventListener("pointerdown", this.pointerDown.bind(this)); } else { this._textarea.addEventListener("mousedown", this.pointerDown.bind(this)); } ["keyup", "mouseup", "input", "paste", "cut"].forEach((key) => { this._textarea.addEventListener(key, () => { this._updateSelectionByDom(); }); }); } pointerDown(e) { if (e && e.button !== 0) { e.preventDefault(); e.stopPropagation(); return false; } const isVertical = this.text.isVertical; const host = this.shadowRoot.host; const clientRect = host.getBoundingClientRect(); const { clientWidth: width, clientHeight: height } = host; const scaleX = clientRect.width / width; const scaleY = clientRect.height / height; const radian = this.rotate * Math.PI / 180; const cos = Math.cos(radian); const sin = Math.sin(radian); const m = new DOMMatrixReadOnly([cos, sin, -sin, cos, 0, 0]).inverse(); const clientCenter = { x: clientRect.x + clientRect.width / 2, y: clientRect.y + clientRect.height / 2 }; const center = { x: clientCenter.x / scaleX, y: clientCenter.y / scaleY }; let x = 0; let y = 0; const getXy = (e2) => { const _p = m.transformPoint({ x: e2.clientX - clientCenter.x, y: e2.clientY - clientCenter.y }); const p = { x: (_p.x + clientCenter.x) / scaleX, y: (_p.y + clientCenter.y) / scaleY }; if (isVertical) { return { x: center.x + width / 2 - p.x, y: p.y - (center.y - height / 2) }; } else { return { x: p.x - (center.x - width / 2), y: p.y - (center.y - height / 2) }; } }; if (e) { ({ x, y } = getXy(e)); e.preventDefault(); e.stopPropagation(); } const index = this._findNearest({ x, y }); this.selection = [index, index]; this._updateDomSelection(); if (e && ["mousedown", "pointerdown"].includes(e.type)) { const onMove = (e2) => { this.selection = [ this.selection[0], this._findNearest(getXy(e2)) ]; this._updateDomSelection(); }; const onUp = () => { if (SUPPORTS_POINTER_EVENTS) { document.removeEventListener("pointermove", onMove); document.removeEventListener("pointerup", onUp); } else { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); } }; if (SUPPORTS_POINTER_EVENTS) { document.addEventListener("pointermove", onMove); document.addEventListener("pointerup", onUp); } else { document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); } } this._textarea.focus(); return true; } selectAll() { this._textarea.focus(); this._textarea.select(); this._updateSelectionByDom(); } attributeChangedCallback(name, _oldValue, newValue) { this[name] = newValue; } _emit(type, detail) { return this.dispatchEvent( new CustomEvent(type, { bubbles: true, cancelable: true, composed: true, detail }) ); } _renderSelectRange() { const isVertical = this.text.isVertical; const boxesGroupsMap = {}; this._selectedChars.forEach((char) => { if (char.isLastSelected) { return; } const key = isVertical ? char.left : char.top; if (!boxesGroupsMap[key]) { boxesGroupsMap[key] = []; } boxesGroupsMap[key].push({ x: char.left, y: char.top, w: char.width, h: char.height }); }); const boxesGroups = Object.values(boxesGroupsMap); const sourceLen = this._selection.children.length; const targetLen = boxesGroups.length; const len = Math.max(sourceLen, targetLen); const deleted = []; for (let i = 0; i < len; i++) { let element = this._selection.children.item(i); const boxes = boxesGroups[i]; if (!boxes) { deleted.push(element); continue; } else if (!element) { element = document.createElement("div"); this._selection.append(element); } const min = { x: Math.min(...boxes.map((v) => v.x)), y: Math.min(...boxes.map((v) => v.y)) }; const max = { x: Math.max(...boxes.map((v) => v.x + v.w)), y: Math.max(...boxes.map((v) => v.y + v.h)) }; element.style.width = `${max.x - min.x}px`; element.style.height = `${max.y - min.y}px`; element.style.transform = `translate(${min.x}px, ${min.y}px)`; } deleted.forEach((el) => el?.remove()); } _renderCursor() { if (this._showCursor && this._cursorPosition && this._selectedChars.length === 0) { const _cursorPosition = this._cursorPosition; this._cursor.style.display = "block"; this._cursor.style.backgroundColor = _cursorPosition.color ?? "rgba(var(--color))"; this._cursor.style.left = `${_cursorPosition.left}px`; this._cursor.style.top = `${_cursorPosition.top}px`; this._cursor.style.height = this.text.isVertical ? "1px" : `${_cursorPosition.height}px`; this._cursor.style.width = this.text.isVertical ? `${_cursorPosition.width}px` : "1px"; } else { this._cursor.style.display = "none"; } this._cursor.classList.remove("blink"); if (this._timer) { clearTimeout(this._timer); } this._timer = setTimeout(() => this._cursor.classList.add("blink"), 500); } reset() { this.selection = []; } } __decorateClass([ property({ fallback: 0 }) ], TextEditor.prototype, "left"); __decorateClass([ property({ fallback: 0 }) ], TextEditor.prototype, "top"); __decorateClass([ property({ fallback: 0 }) ], TextEditor.prototype, "rotate"); __decorateClass([ property({ fallback: () => [0, 0] }) ], TextEditor.prototype, "selection"); __decorateClass([ property({ internal: true, fallback: () => ({ min: 0, max: 0 }) }) ], TextEditor.prototype, "_selectionMinMax"); __decorateClass([ property({ internal: true, fallback: () => [] }) ], TextEditor.prototype, "_chars"); __decorateClass([ property({ internal: true, fallback: () => [] }) ], TextEditor.prototype, "_selectedChars"); __decorateClass([ property({ internal: true }) ], TextEditor.prototype, "_cursorPosition"); __decorateClass([ property({ internal: true, fallback: false }) ], TextEditor.prototype, "_showCursor"); export { TextEditor };