UNPKG

@freedom-editor/lighterhtml-paragraph-block

Version:
1,366 lines (1,266 loc) 40.3 kB
'use strict'; /*! (c) Andrea Giammarchi - ISC */ var self = {}; try { self.WeakMap = WeakMap; } catch (WeakMap) { // this could be better but 90% of the time // it's everything developers need as fallback self.WeakMap = (function (id, Object) { var dP = Object.defineProperty; var hOP = Object.hasOwnProperty; var proto = WeakMap.prototype; proto.delete = function (key) { return this.has(key) && delete key[this._]; }; proto.get = function (key) { return this.has(key) ? key[this._] : void 0; }; proto.has = function (key) { return hOP.call(key, this._); }; proto.set = function (key, value) { dP(key, this._, {configurable: true, value: value}); return this; }; return WeakMap; function WeakMap(iterable) { dP(this, '_', {value: '_@ungap/weakmap' + id++}); if (iterable) iterable.forEach(add, this); } function add(pair) { this.set(pair[0], pair[1]); } }(Math.random(), Object)); } var WeakMap$1 = self.WeakMap; /*! (c) Andrea Giammarchi - ISC */ // Custom var UID = '-' + Math.random().toFixed(6) + '%'; // Edge issue! var UID_IE = false; try { if (!(function (template, content, tabindex) { return content in template && ( (template.innerHTML = '<p ' + tabindex + '="' + UID + '"></p>'), template[content].childNodes[0].getAttribute(tabindex) == UID ); }(document.createElement('template'), 'content', 'tabindex'))) { UID = '_dt: ' + UID.slice(1, -1) + ';'; UID_IE = true; } } catch(meh) {} var UIDC = '<!--' + UID + '-->'; // DOM var COMMENT_NODE = 8; var ELEMENT_NODE = 1; var TEXT_NODE = 3; var SHOULD_USE_TEXT_CONTENT = /^(?:style|textarea)$/i; var VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i; /*! (c) Andrea Giammarchi - ISC */ function domsanitizer (template) { return template.join(UIDC) .replace(selfClosing, fullClosing) .replace(attrSeeker, attrReplacer); } var spaces = ' \\f\\n\\r\\t'; var almostEverything = '[^' + spaces + '\\/>"\'=]+'; var attrName = '[' + spaces + ']+' + almostEverything; var tagName = '<([A-Za-z]+[A-Za-z0-9:._-]*)((?:'; var attrPartials = '(?:\\s*=\\s*(?:\'[^\']*?\'|"[^"]*?"|<[^>]*?>|' + almostEverything.replace('\\/', '') + '))?)'; var attrSeeker = new RegExp(tagName + attrName + attrPartials + '+)([' + spaces + ']*/?>)', 'g'); var selfClosing = new RegExp(tagName + attrName + attrPartials + '*)([' + spaces + ']*/>)', 'g'); var findAttributes = new RegExp('(' + attrName + '\\s*=\\s*)([\'"]?)' + UIDC + '\\2', 'gi'); function attrReplacer($0, $1, $2, $3) { return '<' + $1 + $2.replace(findAttributes, replaceAttributes) + $3; } function replaceAttributes($0, $1, $2) { return $1 + ($2 || '"') + UID + ($2 || '"'); } function fullClosing($0, $1, $2) { return VOID_ELEMENTS.test($1) ? $0 : ('<' + $1 + $2 + '></' + $1 + '>'); } const {isArray} = Array; const {indexOf, slice} = []; var umap = _ => ({ // About: get: _.get.bind(_) // It looks like WebKit/Safari didn't optimize bind at all, // so that using bind slows it down by 60%. // Firefox and Chrome are just fine in both cases, // so let's use the approach that works fast everywhere 👍 get: key => _.get(key), set: (key, value) => (_.set(key, value), value) }); const ELEMENT_NODE$1 = 1; const nodeType = 111; const remove = ({firstChild, lastChild}) => { const range = document.createRange(); range.setStartAfter(firstChild); range.setEndAfter(lastChild); range.deleteContents(); return firstChild; }; const diffable = (node, operation) => node.nodeType === nodeType ? ((1 / operation) < 0 ? (operation ? remove(node) : node.lastChild) : (operation ? node.valueOf() : node.firstChild)) : node ; const persistent = fragment => { const {childNodes} = fragment; const {length} = childNodes; // If the fragment has no content // it should return undefined and break if (length < 2) return childNodes[0]; const nodes = slice.call(childNodes, 0); const firstChild = nodes[0]; const lastChild = nodes[length - 1]; return { ELEMENT_NODE: ELEMENT_NODE$1, nodeType, firstChild, lastChild, valueOf() { if (childNodes.length !== length) { let i = 0; while (i < length) fragment.appendChild(nodes[i++]); } return fragment; } }; }; /*! (c) Andrea Giammarchi - ISC */ var createContent = (function (document) { var FRAGMENT = 'fragment'; var TEMPLATE = 'template'; var HAS_CONTENT = 'content' in create(TEMPLATE); var createHTML = HAS_CONTENT ? function (html) { var template = create(TEMPLATE); template.innerHTML = html; return template.content; } : function (html) { var content = create(FRAGMENT); var template = create(TEMPLATE); var childNodes = null; if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { var selector = RegExp.$1; template.innerHTML = '<table>' + html + '</table>'; childNodes = template.querySelectorAll(selector); } else { template.innerHTML = html; childNodes = template.childNodes; } append(content, childNodes); return content; }; return function createContent(markup, type) { return (type === 'svg' ? createSVG : createHTML)(markup); }; function append(root, childNodes) { var length = childNodes.length; while (length--) root.appendChild(childNodes[0]); } function create(element) { return element === FRAGMENT ? document.createDocumentFragment() : document.createElementNS('http://www.w3.org/1999/xhtml', element); } // it could use createElementNS when hasNode is there // but this fallback is equally fast and easier to maintain // it is also battle tested already in all IE function createSVG(svg) { var content = create(FRAGMENT); var template = create('div'); template.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + svg + '</svg>'; append(content, template.firstChild.childNodes); return content; } }(document)); /** * ISC License * * Copyright (c) 2020, Andrea Giammarchi, @WebReflection * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /** * @param {Node} parentNode The container where children live * @param {Node[]} a The list of current/live children * @param {Node[]} b The list of future children * @param {(entry: Node, action: number) => Node} get * The callback invoked per each entry related DOM operation. * @param {Node} [before] The optional node used as anchor to insert before. * @returns {Node[]} The same list of future children. */ var udomdiff = (parentNode, a, b, get, before) => { const bLength = b.length; let aEnd = a.length; let bEnd = bLength; let aStart = 0; let bStart = 0; let map = null; while (aStart < aEnd || bStart < bEnd) { // append head, tail, or nodes in between: fast path if (aEnd === aStart) { // we could be in a situation where the rest of nodes that // need to be added are not at the end, and in such case // the node to `insertBefore`, if the index is more than 0 // must be retrieved, otherwise it's gonna be the first item. const node = bEnd < bLength ? (bStart ? (get(b[bStart - 1], -0).nextSibling) : get(b[bEnd - bStart], 0)) : before; while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node); } // remove head or tail: fast path else if (bEnd === bStart) { while (aStart < aEnd) { // remove the node only if it's unknown or not live if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1)); aStart++; } } // same node: fast path else if (a[aStart] === b[bStart]) { aStart++; bStart++; } // same tail: fast path else if (a[aEnd - 1] === b[bEnd - 1]) { aEnd--; bEnd--; } // The once here single last swap "fast path" has been removed in v1.1.0 // https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85 // reverse swap: also fast path else if ( a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1] ) { // this is a "shrink" operation that could happen in these cases: // [1, 2, 3, 4, 5] // [1, 4, 3, 2, 5] // or asymmetric too // [1, 2, 3, 4, 5] // [1, 2, 3, 5, 6, 4] const node = get(a[--aEnd], -1).nextSibling; parentNode.insertBefore( get(b[bStart++], 1), get(a[aStart++], -1).nextSibling ); parentNode.insertBefore(get(b[--bEnd], 1), node); // mark the future index as identical (yeah, it's dirty, but cheap 👍) // The main reason to do this, is that when a[aEnd] will be reached, // the loop will likely be on the fast path, as identical to b[bEnd]. // In the best case scenario, the next loop will skip the tail, // but in the worst one, this node will be considered as already // processed, bailing out pretty quickly from the map index check a[aEnd] = b[bEnd]; } // map based fallback, "slow" path else { // the map requires an O(bEnd - bStart) operation once // to store all future nodes indexes for later purposes. // In the worst case scenario, this is a full O(N) cost, // and such scenario happens at least when all nodes are different, // but also if both first and last items of the lists are different if (!map) { map = new Map; let i = bStart; while (i < bEnd) map.set(b[i], i++); } // if it's a future node, hence it needs some handling if (map.has(a[aStart])) { // grab the index of such node, 'cause it might have been processed const index = map.get(a[aStart]); // if it's not already processed, look on demand for the next LCS if (bStart < index && index < bEnd) { let i = aStart; // counts the amount of nodes that are the same in the future let sequence = 1; while (++i < aEnd && i < bEnd && map.get(a[i]) === (index + sequence)) sequence++; // effort decision here: if the sequence is longer than replaces // needed to reach such sequence, which would brings again this loop // to the fast path, prepend the difference before a sequence, // and move only the future list index forward, so that aStart // and bStart will be aligned again, hence on the fast path. // An example considering aStart and bStart are both 0: // a: [1, 2, 3, 4] // b: [7, 1, 2, 3, 6] // this would place 7 before 1 and, from that time on, 1, 2, and 3 // will be processed at zero cost if (sequence > (index - bStart)) { const node = get(a[aStart], 0); while (bStart < index) parentNode.insertBefore(get(b[bStart++], 1), node); } // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { parentNode.replaceChild( get(b[bStart++], 1), get(a[aStart++], -1) ); } } // otherwise move the source forward, 'cause there's nothing to do else aStart++; } // this node has no meaning in the future list, so it's more than safe // to remove it, and check the next live node out instead, meaning // that only the live list index should be forwarded else parentNode.removeChild(get(a[aStart++], -1)); } } return b; }; /*! (c) Andrea Giammarchi - ISC */ var self$1 = undefined || /* istanbul ignore next */ {}; try { self$1.WeakMap = WeakMap; } catch (WeakMap) { // this could be better but 90% of the time // it's everything developers need as fallback self$1.WeakMap = (function (id, Object) { var dP = Object.defineProperty; var hOP = Object.hasOwnProperty; var proto = WeakMap.prototype; proto.delete = function (key) { return this.has(key) && delete key[this._]; }; proto.get = function (key) { return this.has(key) ? key[this._] : void 0; }; proto.has = function (key) { return hOP.call(key, this._); }; proto.set = function (key, value) { dP(key, this._, {configurable: true, value: value}); return this; }; return WeakMap; function WeakMap(iterable) { dP(this, '_', {value: '_@ungap/weakmap' + id++}); if (iterable) iterable.forEach(add, this); } function add(pair) { this.set(pair[0], pair[1]); } }(Math.random(), Object)); } var WeakMap$2 = self$1.WeakMap; /*! (c) Andrea Giammarchi - ISC */ var createContent$1 = (function (document) { var FRAGMENT = 'fragment'; var TEMPLATE = 'template'; var HAS_CONTENT = 'content' in create(TEMPLATE); var createHTML = HAS_CONTENT ? function (html) { var template = create(TEMPLATE); template.innerHTML = html; return template.content; } : function (html) { var content = create(FRAGMENT); var template = create(TEMPLATE); var childNodes = null; if (/^[^\S]*?<(col(?:group)?|t(?:head|body|foot|r|d|h))/i.test(html)) { var selector = RegExp.$1; template.innerHTML = '<table>' + html + '</table>'; childNodes = template.querySelectorAll(selector); } else { template.innerHTML = html; childNodes = template.childNodes; } append(content, childNodes); return content; }; return function createContent(markup, type) { return (type === 'svg' ? createSVG : createHTML)(markup); }; function append(root, childNodes) { var length = childNodes.length; while (length--) root.appendChild(childNodes[0]); } function create(element) { return element === FRAGMENT ? document.createDocumentFragment() : document.createElementNS('http://www.w3.org/1999/xhtml', element); } // it could use createElementNS when hasNode is there // but this fallback is equally fast and easier to maintain // it is also battle tested already in all IE function createSVG(svg) { var content = create(FRAGMENT); var template = create('div'); template.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg">' + svg + '</svg>'; append(content, template.firstChild.childNodes); return content; } }(document)); /*! (c) Andrea Giammarchi - ISC */ var importNode = (function ( document, appendChild, cloneNode, createTextNode, importNode ) { var native = importNode in document; // IE 11 has problems with cloning templates: // it "forgets" empty childNodes. This feature-detects that. var fragment = document.createDocumentFragment(); fragment[appendChild](document[createTextNode]('g')); fragment[appendChild](document[createTextNode]('')); var content = native ? document[importNode](fragment, true) : fragment[cloneNode](true); return content.childNodes.length < 2 ? function importNode(node, deep) { var clone = node[cloneNode](); for (var childNodes = node.childNodes || [], length = childNodes.length, i = 0; deep && i < length; i++ ) { clone[appendChild](importNode(childNodes[i], deep)); } return clone; } : (native ? document[importNode] : function (node, deep) { return node[cloneNode](!!deep); } ); }( document, 'appendChild', 'cloneNode', 'createTextNode', 'importNode' )); var trim = ''.trim || function () { return String(this).replace(/^\s+|\s+/g, ''); }; /* istanbul ignore next */ var normalizeAttributes = UID_IE ? function (attributes, parts) { var html = parts.join(' '); return parts.slice.call(attributes, 0).sort(function (left, right) { return html.indexOf(left.name) <= html.indexOf(right.name) ? -1 : 1; }); } : function (attributes, parts) { return parts.slice.call(attributes, 0); } ; function find(node, path) { var length = path.length; var i = 0; while (i < length) node = node.childNodes[path[i++]]; return node; } function parse(node, holes, parts, path) { var childNodes = node.childNodes; var length = childNodes.length; var i = 0; while (i < length) { var child = childNodes[i]; switch (child.nodeType) { case ELEMENT_NODE: var childPath = path.concat(i); parseAttributes(child, holes, parts, childPath); parse(child, holes, parts, childPath); break; case COMMENT_NODE: var textContent = child.textContent; if (textContent === UID) { parts.shift(); holes.push( // basicHTML or other non standard engines // might end up having comments in nodes // where they shouldn't, hence this check. SHOULD_USE_TEXT_CONTENT.test(node.nodeName) ? Text(node, path) : Any(child, path.concat(i)) ); } else { switch (textContent.slice(0, 2)) { case '/*': if (textContent.slice(-2) !== '*/') break; case '\uD83D\uDC7B': // ghost node.removeChild(child); i--; length--; } } break; case TEXT_NODE: // the following ignore is actually covered by browsers // only basicHTML ends up on previous COMMENT_NODE case // instead of TEXT_NODE because it knows nothing about // special style or textarea behavior /* istanbul ignore if */ if ( SHOULD_USE_TEXT_CONTENT.test(node.nodeName) && trim.call(child.textContent) === UIDC ) { parts.shift(); holes.push(Text(node, path)); } break; } i++; } } function parseAttributes(node, holes, parts, path) { var attributes = node.attributes; var cache = []; var remove = []; var array = normalizeAttributes(attributes, parts); var length = array.length; var i = 0; while (i < length) { var attribute = array[i++]; var direct = attribute.value === UID; var sparse; if (direct || 1 < (sparse = attribute.value.split(UIDC)).length) { var name = attribute.name; // the following ignore is covered by IE // and the IE9 double viewBox test /* istanbul ignore else */ if (cache.indexOf(name) < 0) { cache.push(name); var realName = parts.shift().replace( direct ? /^(?:|[\S\s]*?\s)(\S+?)\s*=\s*('|")?$/ : new RegExp( '^(?:|[\\S\\s]*?\\s)(' + name + ')\\s*=\\s*(\'|")[\\S\\s]*', 'i' ), '$1' ); var value = attributes[realName] || // the following ignore is covered by browsers // while basicHTML is already case-sensitive /* istanbul ignore next */ attributes[realName.toLowerCase()]; if (direct) holes.push(Attr(value, path, realName, null)); else { var skip = sparse.length - 2; while (skip--) parts.shift(); holes.push(Attr(value, path, realName, sparse)); } } remove.push(attribute); } } length = remove.length; i = 0; /* istanbul ignore next */ var cleanValue = 0 < length && UID_IE && !('ownerSVGElement' in node); while (i < length) { // Edge HTML bug #16878726 var attr = remove[i++]; // IE/Edge bug lighterhtml#63 - clean the value or it'll persist /* istanbul ignore next */ if (cleanValue) attr.value = ''; // IE/Edge bug lighterhtml#64 - don't use removeAttributeNode node.removeAttribute(attr.name); } // This is a very specific Firefox/Safari issue // but since it should be a not so common pattern, // it's probably worth patching regardless. // Basically, scripts created through strings are death. // You need to create fresh new scripts instead. // TODO: is there any other node that needs such nonsense? var nodeName = node.nodeName; if (/^script$/i.test(nodeName)) { // this used to be like that // var script = createElement(node, nodeName); // then Edge arrived and decided that scripts created // through template documents aren't worth executing // so it became this ... hopefully it won't hurt in the wild var script = document.createElement(nodeName); length = attributes.length; i = 0; while (i < length) script.setAttributeNode(attributes[i++].cloneNode(true)); script.textContent = node.textContent; node.parentNode.replaceChild(script, node); } } function Any(node, path) { return { type: 'any', node: node, path: path }; } function Attr(node, path, name, sparse) { return { type: 'attr', node: node, path: path, name: name, sparse: sparse }; } function Text(node, path) { return { type: 'text', node: node, path: path }; } // globals var parsed = umap(new WeakMap$2); function createInfo(options, template) { var markup = (options.convert || domsanitizer)(template); var transform = options.transform; if (transform) markup = transform(markup); var content = createContent$1(markup, options.type); cleanContent(content); var holes = []; parse(content, holes, template.slice(0), []); return { content: content, updates: function (content) { var updates = []; var len = holes.length; var i = 0; var off = 0; while (i < len) { var info = holes[i++]; var node = find(content, info.path); switch (info.type) { case 'any': updates.push({fn: options.any(node, []), sparse: false}); break; case 'attr': var sparse = info.sparse; var fn = options.attribute(node, info.name, info.node); if (sparse === null) updates.push({fn: fn, sparse: false}); else { off += sparse.length - 2; updates.push({fn: fn, sparse: true, values: sparse}); } break; case 'text': updates.push({fn: options.text(node), sparse: false}); node.textContent = ''; break; } } len += off; return function () { var length = arguments.length; if (len !== (length - 1)) { throw new Error( (length - 1) + ' values instead of ' + len + '\n' + template.join('${value}') ); } var i = 1; var off = 1; while (i < length) { var update = updates[i - off]; if (update.sparse) { var values = update.values; var value = values[0]; var j = 1; var l = values.length; off += l - 2; while (j < l) value += arguments[i++] + values[j++]; update.fn(value); } else update.fn(arguments[i++]); } return content; }; } }; } function createDetails(options, template) { var info = parsed.get(template) || parsed.set(template, createInfo(options, template)); return info.updates(importNode.call(document, info.content, true)); } var empty = []; function domtagger(options) { var previous = empty; var updates = cleanContent; return function (template) { if (previous !== template) updates = createDetails(options, (previous = template)); return updates.apply(null, arguments); }; } function cleanContent(fragment) { var childNodes = fragment.childNodes; var i = childNodes.length; while (i--) { var child = childNodes[i]; if ( child.nodeType !== 1 && trim.call(child.textContent).length === 0 ) { fragment.removeChild(child); } } } /*! (c) Andrea Giammarchi - ISC */ var hyperStyle = (function (){ // from https://github.com/developit/preact/blob/33fc697ac11762a1cb6e71e9847670d047af7ce5/src/varants.js var IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i; var hyphen = /([^A-Z])([A-Z]+)/g; return function hyperStyle(node, original) { return 'ownerSVGElement' in node ? svg(node, original) : update(node.style, false); }; function ized($0, $1, $2) { return $1 + '-' + $2.toLowerCase(); } function svg(node, original) { var style; if (original) style = original.cloneNode(true); else { node.setAttribute('style', '--hyper:style;'); style = node.getAttributeNode('style'); } style.value = ''; node.setAttributeNode(style); return update(style, true); } function toStyle(object) { var key, css = []; for (key in object) css.push(key.replace(hyphen, ized), ':', object[key], ';'); return css.join(''); } function update(style, isSVG) { var oldType, oldValue; return function (newValue) { var info, key, styleValue, value; switch (typeof newValue) { case 'object': if (newValue) { if (oldType === 'object') { if (!isSVG) { if (oldValue !== newValue) { for (key in oldValue) { if (!(key in newValue)) { style[key] = ''; } } } } } else { if (isSVG) style.value = ''; else style.cssText = ''; } info = isSVG ? {} : style; for (key in newValue) { value = newValue[key]; styleValue = typeof value === 'number' && !IS_NON_DIMENSIONAL.test(key) ? (value + 'px') : value; if (!isSVG && /^--/.test(key)) info.setProperty(key, styleValue); else info[key] = styleValue; } oldType = 'object'; if (isSVG) style.value = toStyle((oldValue = info)); else oldValue = newValue; break; } default: if (oldValue != newValue) { oldType = 'string'; oldValue = newValue; if (isSVG) style.value = newValue || ''; else style.cssText = newValue || ''; } break; } }; } }()); const aria = node => value => { for (const key in value) node.setAttribute(key === 'role' ? key : `aria-${key}`, value[key]); }; const attribute = (node, name) => { let oldValue, orphan = true; const attributeNode = document.createAttributeNS(null, name); return newValue => { if (oldValue !== newValue) { oldValue = newValue; if (oldValue == null) { if (!orphan) { node.removeAttributeNode(attributeNode); orphan = true; } } else { attributeNode.value = newValue; if (orphan) { node.setAttributeNodeNS(attributeNode); orphan = false; } } } }; }; const data = ({dataset}) => value => { for (const key in value) dataset[key] = value[key]; }; const event = (node, name) => { let oldValue, type = name.slice(2); if (!(name in node) && name.toLowerCase() in node) type = type.toLowerCase(); return newValue => { const info = isArray(newValue) ? newValue : [newValue, false]; if (oldValue !== info[0]) { if (oldValue) node.removeEventListener(type, oldValue, info[1]); if (oldValue = info[0]) node.addEventListener(type, oldValue, info[1]); } }; }; const ref = node => value => { if (typeof value === 'function') value(node); else value.current = node; }; const setter = (node, key) => value => { node[key] = value; }; // special attributes helpers const hyperProperty = (node, name) => { let oldValue; return newValue => { if (oldValue !== newValue) { oldValue = newValue; if (node[name] !== newValue) { if (newValue == null) { // cleanup before dropping the attribute to fix IE/Edge gotcha node[name] = ''; node.removeAttribute(name); } else node[name] = newValue; } } }; }; // list of attributes that should not be directly assigned const readOnly = /^(?:form|list)$/i; // simplifies text node creation const text = (node, text) => node.ownerDocument.createTextNode(text); function Tagger(type) { this.type = type; return domtagger(this); } Tagger.prototype = { // there are four kind of attributes, and related behavior: // * events, with a name starting with `on`, to add/remove event listeners // * special, with a name present in their inherited prototype, accessed directly // * regular, accessed through get/setAttribute standard DOM methods // * style, the only regular attribute that also accepts an object as value // so that you can style=${{width: 120}}. In this case, the behavior has been // fully inspired by Preact library and its simplicity. attribute(node, name, original) { const isSVG = this.type === 'svg'; switch (name) { case 'class': if (isSVG) return attribute(node, name); name = 'className'; case 'props': return setter(node, name); case 'aria': return aria(node); case 'style': return hyperStyle(node, original, isSVG); case 'ref': return ref(node); case '.dataset': return data(node); default: if (name.slice(0, 1) === '.') return setter(node, name.slice(1)); if (name.slice(0, 2) === 'on') return event(node, name); if (name in node && !(isSVG || readOnly.test(name))) return hyperProperty(node, name); return attribute(node, name); } }, // in a hyper(node)`<div>${content}</div>` case // everything could happen: // * it's a JS primitive, stored as text // * it's null or undefined, the node should be cleaned // * it's a promise, update the content once resolved // * it's an explicit intent, perform the desired operation // * it's an Array, resolve all values if Promises and/or // update the node with the resulting list of content any(node, childNodes) { const {type} = this; let fastPath = false; let oldValue; const anyContent = value => { switch (typeof value) { case 'string': case 'number': case 'boolean': if (fastPath) { if (oldValue !== value) { oldValue = value; childNodes[0].textContent = value; } } else { fastPath = true; oldValue = value; childNodes = udomdiff( node.parentNode, childNodes, [text(node, value)], diffable, node ); } break; case 'function': anyContent(value(node)); break; case 'object': case 'undefined': if (value == null) { fastPath = false; childNodes = udomdiff( node.parentNode, childNodes, [], diffable, node ); break; } default: fastPath = false; oldValue = value; if (isArray(value)) { if (value.length === 0) { if (childNodes.length) { childNodes = udomdiff( node.parentNode, childNodes, [], diffable, node ); } } else { switch (typeof value[0]) { case 'string': case 'number': case 'boolean': anyContent(String(value)); break; case 'function': anyContent(value.map(invoke, node)); break; case 'object': if (isArray(value[0])) { value = value.concat.apply([], value); } default: childNodes = udomdiff( node.parentNode, childNodes, value, diffable, node ); break; } } } else if ('ELEMENT_NODE' in value) { childNodes = udomdiff( node.parentNode, childNodes, value.nodeType === 11 ? slice.call(value.childNodes) : [value], diffable, node ); } else if ('text' in value) { anyContent(String(value.text)); } else if ('any' in value) { anyContent(value.any); } else if ('html' in value) { childNodes = udomdiff( node.parentNode, childNodes, slice.call( createContent( [].concat(value.html).join(''), type ).childNodes ), diffable, node ); } else if ('length' in value) { anyContent(slice.call(value)); } break; } }; return anyContent; }, // style or textareas don't accept HTML as content // it's pointless to transform or analyze anything // different from text there but it's worth checking // for possible defined intents. text(node) { let oldValue; const textContent = value => { if (oldValue !== value) { oldValue = value; const type = typeof value; if (type === 'object' && value) { if ('text' in value) { textContent(String(value.text)); } else if ('any' in value) { textContent(value.any); } else if ('html' in value) { textContent([].concat(value.html).join('')); } else if ('length' in value) { textContent(slice.call(value).join('')); } } else if (type === 'function') { textContent(value(node)); } else { node.textContent = value == null ? '' : value; } } }; return textContent; } }; function invoke(callback) { return callback(this); } const {create, freeze, keys} = Object; const tProto = Tagger.prototype; const cache = umap(new WeakMap$1); const createRender = Tagger => ({ html: outer('html', Tagger), svg: outer('svg', Tagger), render(where, what) { const hole = typeof what === 'function' ? what() : what; const info = cache.get(where) || cache.set(where, createCache()); const wire = hole instanceof LighterHole ? unroll(Tagger, info, hole) : hole; if (wire !== info.wire) { info.wire = wire; where.textContent = ''; where.appendChild(wire.valueOf()); } return where; } }); const createCache = () => ({stack: [], entry: null, wire: null}); const outer = (type, Tagger) => { const cache = umap(new WeakMap$1); const fixed = info => function () { return unroll(Tagger, info, hole.apply(null, arguments)); }; hole.for = (ref, id) => { const memo = cache.get(ref) || cache.set(ref, create(null)); return memo[id] || (memo[id] = fixed(createCache())); }; hole.node = function () { return unroll( Tagger, createCache(), hole.apply(null, arguments) ).valueOf(); }; return hole; function hole() { return new LighterHole(type, tta.apply(null, arguments)); } }; const unroll = (Tagger, info, {type, template, values}) => { const {length} = values; unrollValues(Tagger, info, values, length); let {entry} = info; if (!entry || (entry.template !== template || entry.type !== type)) { const tag = new Tagger(type); info.entry = (entry = { type, template, tag, wire: persistent(tag(template, ...values)) }); } else entry.tag(template, ...values); return entry.wire; }; const unrollValues = (Tagger, {stack}, values, length) => { for (let i = 0; i < length; i++) { const hole = values[i]; if (hole instanceof Hole) values[i] = unroll( Tagger, stack[i] || (stack[i] = createCache()), hole ); else if (isArray(hole)) unrollValues( Tagger, stack[i] || (stack[i] = createCache()), hole, hole.length ); else stack[i] = null; } if (length < stack.length) stack.splice(length); }; freeze(LighterHole); function LighterHole(type, args) { this.type = type; this.template = args.shift(); this.values = args; }const Hole = LighterHole; const custom = overrides => { const prototype = create(tProto); keys(overrides).forEach(key => { prototype[key] = overrides[key]( prototype[key] || (key === 'convert' ? domsanitizer : String) ); }); CustomTagger.prototype = prototype; return createRender(CustomTagger); function CustomTagger() { return Tagger.apply(this, arguments); } }; const {render, html, svg} = createRender(Tagger); function tta() { let out = [], i = 0, {length} = arguments; while (i < length) out.push(arguments[i++]); return out; } var esm = /*#__PURE__*/Object.freeze({ __proto__: null, Hole: Hole, custom: custom, render: render, html: html, svg: svg }); const getSavedData = (savedData) => { if (savedData) { return savedData.data.text } }; var helper = { getSavedData }; function getCjsExportFromNamespace (n) { return n && n['default'] || n; } var require$$0 = getCjsExportFromNamespace(esm); const { render: render$1, html: html$1, svg: svg$1 } = require$$0; const { getSavedData: getSavedData$1 } = helper; class Paragraph { constructor (customOptions) { const defaultOptions = { i18n: { locale: 'en-US', rtl: 'ltr', translations: { } }, controllers: [ ] }; this.options = { ...defaultOptions, ...customOptions }; } render (i18n, savedData) { const textDirection = (i18n.rtl !== undefined) ? i18n.rtl : this.options.i18n.rtl; return html$1.node`<div class="freedom-editor-blocks ${this.constructor.name}-block" data-block-type="${this.constructor.name}"> <p contenteditable dir="${textDirection}">${getSavedData$1(savedData)}</p> </div>` } save (block) { const editableField = block.querySelector('p'); // Cannot pass type: this.constructor.name, or else the type of the block will be minifyed return { type: this.constructor.name, data: { text: editableField.textContent } } } } var src = { Paragraph }; module.exports = src;